diff options
177 files changed, 6286 insertions, 2834 deletions
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 5f3bb6bb..7caa5d1e 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -5,7 +5,7 @@ If you are using the nvidia proprietary driver for any reason, you have two choi If `lsmod | grep nvidia | wc -l` shows anything other than zero, your bug report is not welcome here. -Otherwise, please include the following four components in your bug report: sway version, debug log, configuration (if applicable), and an explanation of steps taken to reproduce the issue. +Otherwise, please include the following four components in your bug report: sway version, debug log, configuration (if applicable), and an explanation of steps taken to reproduce the issue. If sway crashes, also include a stack trace. Obtain your version like so: @@ -32,3 +32,11 @@ You should try to reproduce the issue with the default configuration. If you can * Configuration File: Finally, explain the steps you took in plain English to reproduce the problem below. + +* Stack Trace, if sway crashes: + +If you use systemd, you should be able to open the coredump of the most recent crash with GDB like so: + + coredumpctl gdb sway + +And then type `bt full` to obtain the stack trace. 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)](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)](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)](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)](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)](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)](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)](https://sr.ht/ICd5.png) diff --git a/common/ipc-client.c b/common/ipc-client.c index 496fd131..3515ef0a 100644 --- a/common/ipc-client.c +++ b/common/ipc-client.c @@ -7,7 +7,6 @@ #include <sys/un.h> #include <unistd.h> #include "ipc-client.h" -#include "readline.h" #include "log.h" static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; @@ -18,28 +17,30 @@ char *get_socketpath(void) { if (swaysock) { return strdup(swaysock); } + char *line = NULL; + size_t line_size = 0; FILE *fp = popen("sway --get-socketpath 2>/dev/null", "r"); if (fp) { - char *line = read_line(fp); + getline(&line, &line_size, fp); pclose(fp); if (line && *line) { return line; } - free(line); } const char *i3sock = getenv("I3SOCK"); if (i3sock) { + free(line); return strdup(i3sock); } fp = popen("i3 --get-socketpath 2>/dev/null", "r"); if (fp) { - char *line = read_line(fp); + getline(&line, &line_size, fp); pclose(fp); if (line && *line) { return line; } - free(line); } + free(line); return NULL; } diff --git a/common/list.c b/common/list.c index ee268c9a..c3e2fe20 100644 --- a/common/list.c +++ b/common/list.c @@ -17,7 +17,7 @@ list_t *create_list(void) { static void list_resize(list_t *list) { if (list->length == list->capacity) { - list->capacity += 10; + list->capacity *= 2; list->items = realloc(list->items, sizeof(void*) * list->capacity); } } @@ -30,15 +30,6 @@ void list_free(list_t *list) { free(list); } -void list_foreach(list_t *list, void (*callback)(void *item)) { - if (list == NULL || callback == NULL) { - return; - } - for (int i = 0; i < list->length; i++) { - callback(list->items[i]); - } -} - void list_add(list_t *list, void *item) { list_resize(list); list->items[list->length++] = item; @@ -57,8 +48,7 @@ void list_del(list_t *list, int index) { } void list_cat(list_t *list, list_t *source) { - int i; - for (i = 0; i < source->length; ++i) { + for (int i = 0; i < source->length; ++i) { list_add(list, source->items[i]); } } @@ -156,3 +146,15 @@ void list_stable_sort(list_t *list, int compare(const void *a, const void *b)) { list_inplace_sort(list, 0, list->length - 1, compare); } } + +void list_free_items_and_destroy(list_t *list) { + if (!list) { + return; + } + + for (int i = 0; i < list->length; ++i) { + free(list->items[i]); + } + list_free(list); +} + diff --git a/common/loop.c b/common/loop.c index 82b80017..295f3a30 100644 --- a/common/loop.c +++ b/common/loop.c @@ -45,10 +45,8 @@ struct loop *loop_create(void) { } void loop_destroy(struct loop *loop) { - list_foreach(loop->fd_events, free); - list_foreach(loop->timers, free); - list_free(loop->fd_events); - list_free(loop->timers); + list_free_items_and_destroy(loop->fd_events); + list_free_items_and_destroy(loop->timers); free(loop->fds); free(loop); } diff --git a/common/meson.build b/common/meson.build index 224a9c3f..4ad872d1 100644 --- a/common/meson.build +++ b/common/meson.build @@ -8,7 +8,6 @@ lib_sway_common = static_library( 'loop.c', 'list.c', 'pango.c', - 'readline.c', 'stringop.c', 'unicode.c', 'util.c' diff --git a/common/readline.c b/common/readline.c deleted file mode 100644 index 58652429..00000000 --- a/common/readline.c +++ /dev/null @@ -1,72 +0,0 @@ -#define _POSIX_C_SOURCE 200809L -#include "readline.h" -#include "log.h" -#include <stdlib.h> -#include <stdio.h> - -char *read_line(FILE *file) { - size_t length = 0, size = 128; - char *string = malloc(size); - char lastChar = '\0'; - if (!string) { - wlr_log(WLR_ERROR, "Unable to allocate memory for read_line"); - return NULL; - } - while (1) { - int c = getc(file); - if (c == '\n' && lastChar == '\\'){ - --length; // Ignore last character. - lastChar = '\0'; - continue; - } - if (c == EOF || c == '\n' || c == '\0') { - break; - } - if (c == '\r') { - continue; - } - lastChar = c; - if (length == size) { - char *new_string = realloc(string, size *= 2); - if (!new_string) { - free(string); - wlr_log(WLR_ERROR, "Unable to allocate memory for read_line"); - return NULL; - } - string = new_string; - } - string[length++] = c; - } - if (length + 1 == size) { - char *new_string = realloc(string, length + 1); - if (!new_string) { - free(string); - return NULL; - } - string = new_string; - } - string[length] = '\0'; - return string; -} - -char *peek_line(FILE *file, int line_offset, long *position) { - long pos = ftell(file); - size_t length = 0; - char *line = NULL; - for (int i = 0; i <= line_offset; i++) { - ssize_t read = getline(&line, &length, file); - if (read < 0) { - free(line); - line = NULL; - break; - } - if (read > 0 && line[read - 1] == '\n') { - line[read - 1] = '\0'; - } - } - if (position) { - *position = ftell(file); - } - fseek(file, pos, SEEK_SET); - return line; -} diff --git a/common/stringop.c b/common/stringop.c index d2c91c24..8af0d60f 100644 --- a/common/stringop.c +++ b/common/stringop.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -9,24 +9,17 @@ #include "string.h" #include "list.h" -const char whitespace[] = " \f\n\r\t\v"; +static const char whitespace[] = " \f\n\r\t\v"; -char *strip_whitespace(char *_str) { - if (*_str == '\0') - return _str; - char *strold = _str; - while (*_str == ' ' || *_str == '\t') { - _str++; +void strip_whitespace(char *str) { + size_t len = strlen(str); + size_t start = strspn(str, whitespace); + memmove(str, &str[start], len + 1 - start); + + if (*str) { + for (len -= start + 1; isspace(str[len]); --len) {} + str[len + 1] = '\0'; } - char *str = strdup(_str); - free(strold); - int i; - for (i = 0; str[i] != '\0'; ++i); - do { - i--; - } while (i >= 0 && (str[i] == ' ' || str[i] == '\t')); - str[i + 1] = '\0'; - return str; } void strip_quotes(char *str) { @@ -97,14 +90,6 @@ list_t *split_string(const char *str, const char *delims) { return res; } -void free_flat_list(list_t *list) { - int i; - for (i = 0; i < list->length; ++i) { - free(list->items[i]); - } - list_free(list); -} - char **split_args(const char *start, int *argc) { *argc = 0; int alloc = 2; diff --git a/common/util.c b/common/util.c index 0caafb39..d66058a6 100644 --- a/common/util.c +++ b/common/util.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <sys/types.h> #include <sys/stat.h> @@ -13,7 +13,6 @@ #include <xkbcommon/xkbcommon-names.h> #include <wlr/types/wlr_keyboard.h> #include "log.h" -#include "readline.h" #include "util.h" int wrap(int i, int max) { @@ -24,7 +23,8 @@ int numlen(int n) { if (n == 0) { return 1; } - return log10(n) + 1; + // Account for the '-' in negative numbers. + return log10(abs(n)) + (n > 0 ? 1 : 2); } static struct modifier_key { @@ -86,11 +86,12 @@ pid_t get_parent_pid(pid_t child) { char *token = NULL; const char *sep = " "; FILE *stat = NULL; + size_t buf_size = 0; sprintf(file_name, "/proc/%d/stat", child); if ((stat = fopen(file_name, "r"))) { - if ((buffer = read_line(stat))) { + if (getline(&buffer, &buf_size, stat) != -1) { token = strtok(buffer, sep); // pid token = strtok(NULL, sep); // executable name token = strtok(NULL, sep); // state 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/bash/swayidle b/completions/bash/swayidle deleted file mode 100644 index a0cdc8b2..00000000 --- a/completions/bash/swayidle +++ /dev/null @@ -1,48 +0,0 @@ -# swaymsg(1) completion - -_swayidle() -{ - local cur prev - _get_comp_words_by_ref -n : cur prev - local prev2=${COMP_WORDS[COMP_CWORD-2]} - local prev3=${COMP_WORDS[COMP_CWORD-3]} - - events=( - 'timeout' - 'before-sleep' - ) - - short=( - -h - -d - ) - - if [ "$prev" = timeout ]; then - # timeout <timeout> - return - elif [ "$prev2" = timeout ]; then - # timeout <timeout> <timeout command> - COMPREPLY=($(compgen -c -- "$cur")) - return - elif [ "$prev3" = timeout ]; then - # timeout <timeout> <timeout command> [resume <resume command>] - COMPREPLY=(resume) - # optional argument; no return here as user may skip 'resume' - fi - - case "$prev" in - resume) - COMPREPLY=($(compgen -c -- "$cur")) - return - ;; - before-sleep) - COMPREPLY=($(compgen -c -- "$cur")) - return - ;; - esac - - COMPREPLY+=($(compgen -W "${events[*]}" -- "$cur")) - COMPREPLY+=($(compgen -W "${short[*]}" -- "$cur")) - -} && -complete -F _swayidle swayidle diff --git a/completions/fish/swayidle.fish b/completions/fish/swayidle.fish deleted file mode 100644 index 71279925..00000000 --- a/completions/fish/swayidle.fish +++ /dev/null @@ -1,3 +0,0 @@ -# swayidle -complete -c swayidle -s h --description 'show help' -complete -c swayidle -s d --description 'debug' 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/_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}' @@ -41,7 +41,7 @@ output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill # before-sleep 'swaylock -c 000000' # # This will lock your screen after 300 seconds of inactivity, then turn off -# your displays after another 600 seconds, and turn your screens back on when +# your displays after another 300 seconds, and turn your screens back on when # resumed. It will also lock your screen before your computer goes to sleep. ### Input configuration diff --git a/include/ipc.h b/include/ipc.h index 9063b933..6063f69c 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -16,6 +16,7 @@ enum ipc_command_type { IPC_GET_BINDING_MODES = 8, IPC_GET_CONFIG = 9, IPC_SEND_TICK = 10, + IPC_SYNC = 11, // sway-specific command types IPC_GET_INPUTS = 100, diff --git a/include/list.h b/include/list.h index 03851a82..895f6cc0 100644 --- a/include/list.h +++ b/include/list.h @@ -9,7 +9,6 @@ typedef struct { list_t *create_list(void); void list_free(list_t *list); -void list_foreach(list_t *list, void (*callback)(void* item)); void list_add(list_t *list, void *item); void list_insert(list_t *list, int index, void *item); void list_del(list_t *list, int index); @@ -27,4 +26,10 @@ void list_stable_sort(list_t *list, int compare(const void *a, const void *b)); void list_swap(list_t *list, int src, int dest); // move item to end of list void list_move_to_end(list_t *list, void *item); + +/* Calls `free` for each item in the list, then frees the list. + * Do not use this to free lists of primitives or items that require more + * complicated deallocation code. + */ +void list_free_items_and_destroy(list_t *list); #endif diff --git a/include/readline.h b/include/readline.h deleted file mode 100644 index ee2eba5d..00000000 --- a/include/readline.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef _SWAY_READLINE_H -#define _SWAY_READLINE_H - -#include <stdio.h> - -char *read_line(FILE *file); -char *peek_line(FILE *file, int line_offset, long *position); -char *read_line_buffer(FILE *file, char *string, size_t string_len); - -#endif diff --git a/include/stringop.h b/include/stringop.h index 919e605c..f7ca60a5 100644 --- a/include/stringop.h +++ b/include/stringop.h @@ -3,10 +3,7 @@ #include "list.h" -// array of whitespace characters to use for delims -extern const char whitespace[]; - -char *strip_whitespace(char *str); +void strip_whitespace(char *str); char *strip_comments(char *str); void strip_quotes(char *str); @@ -17,9 +14,8 @@ char *lenient_strncat(char *dest, const char *src, size_t len); // strcmp that also handles null pointers. int lenient_strcmp(char *a, char *b); -// Simply split a string with delims, free with `free_flat_list` +// Simply split a string with delims, free with `list_free_items_and_destroy` list_t *split_string(const char *str, const char *delims); -void free_flat_list(list_t *list); // Splits an argument string, keeping quotes intact char **split_args(const char *str, int *argc); diff --git a/include/sway/commands.h b/include/sway/commands.h index 32925369..5d45d78b 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -56,7 +56,7 @@ struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers, * all matching containers. Otherwise, it'll run on the `con` container. If * `con` is NULL then it'll run on the currently focused container. */ -struct cmd_results *execute_command(char *command, struct sway_seat *seat, +list_t *execute_command(char *command, struct sway_seat *seat, struct sway_container *con); /** * Parse and handles a command during config file loading. @@ -82,11 +82,11 @@ struct cmd_results *cmd_results_new(enum cmd_status status, const char* input, c */ void free_cmd_results(struct cmd_results *results); /** - * Serializes cmd_results to a JSON string. + * Serializes a list of cmd_results to a JSON string. * * Free the JSON string later on. */ -char *cmd_results_to_json(struct cmd_results *results); +char *cmd_results_to_json(list_t *res_list); struct cmd_results *add_color(const char *name, char *buffer, const char *color); @@ -172,7 +172,11 @@ 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; +sway_cmd cmd_titlebar_padding; sway_cmd cmd_unmark; sway_cmd cmd_urgent; sway_cmd cmd_workspace; @@ -180,12 +184,12 @@ sway_cmd cmd_workspace_layout; sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_xwayland; -sway_cmd bar_cmd_activate_button; +sway_cmd bar_cmd_bindcode; 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; sway_cmd bar_cmd_modifier; sway_cmd bar_cmd_output; @@ -194,12 +198,15 @@ 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_status_edge_padding; +sway_cmd bar_cmd_status_padding; 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; @@ -255,8 +262,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 c44533ee..96fe899b 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" @@ -26,7 +27,8 @@ struct sway_variable { enum binding_input_type { BINDING_KEYCODE, BINDING_KEYSYM, - BINDING_MOUSE, + BINDING_MOUSECODE, + BINDING_MOUSESYM, }; enum binding_flags { @@ -140,6 +142,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 { @@ -224,9 +227,13 @@ struct bar_config { bool wrap_scroll; char *separator_symbol; bool strip_workspace_numbers; + bool strip_workspace_name; bool binding_mode_indicator; bool verbose; + struct side_gaps gaps; pid_t pid; + int status_padding; + int status_edge_padding; struct { char *background; char *statusline; @@ -250,6 +257,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 { @@ -356,6 +370,12 @@ enum mouse_warping_mode { WARP_CONTAINER }; +enum alignment { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT +}; + /** * The configuration struct. The result of loading a config file. */ @@ -390,6 +410,9 @@ struct sway_config { size_t font_height; size_t font_baseline; bool pango_markup; + int titlebar_border_thickness; + int titlebar_h_padding; + int titlebar_v_padding; size_t urgent_timeout; enum sway_fowa focus_on_window_activation; enum sway_popup_during_fullscreen popup_during_fullscreen; @@ -406,7 +429,10 @@ struct sway_config { bool validating; 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; @@ -415,6 +441,8 @@ struct sway_config { list_t *config_chain; const char *current_config_path; const char *current_config; + int current_config_line_number; + char *current_config_line; enum sway_container_border border; enum sway_container_border floating_border; @@ -480,6 +508,11 @@ bool read_config(FILE *file, struct sway_config *config, struct swaynag_instance *swaynag); /** + * Adds a warning entry to the swaynag instance used for errors. + */ +void config_add_swaynag_warning(char *fmt, ...); + +/** * Free config struct */ void free_config(struct sway_config *config); @@ -516,7 +549,7 @@ struct seat_attachment_config *seat_attachment_config_new(void); struct seat_attachment_config *seat_config_get_attachment( struct seat_config *seat_config, char *identifier); -void apply_seat_config(struct seat_config *seat); +struct seat_config *store_seat_config(struct seat_config *seat); int output_name_cmp(const void *item, const void *data); @@ -535,8 +568,6 @@ void apply_output_config_to_outputs(struct output_config *oc); void free_output_config(struct output_config *oc); -void create_default_output_configs(void); - int workspace_output_cmp_workspace(const void *a, const void *b); int sway_binding_cmp(const void *a, const void *b); diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h index 50ac453b..77aa0ea1 100644 --- a/include/sway/input/cursor.h +++ b/include/sway/input/cursor.h @@ -1,10 +1,17 @@ #ifndef _SWAY_INPUT_CURSOR_H #define _SWAY_INPUT_CURSOR_H +#include <stdbool.h> #include <stdint.h> +#include <wlr/types/wlr_surface.h> #include "sway/input/seat.h" #define SWAY_CURSOR_PRESSED_BUTTONS_CAP 32 +#define SWAY_SCROLL_UP KEY_MAX + 1 +#define SWAY_SCROLL_DOWN KEY_MAX + 2 +#define SWAY_SCROLL_LEFT KEY_MAX + 3 +#define SWAY_SCROLL_RIGHT KEY_MAX + 4 + struct sway_cursor { struct sway_seat *seat; struct wlr_cursor *cursor; @@ -16,6 +23,8 @@ struct sway_cursor { const char *image; struct wl_client *image_client; + struct wlr_surface *image_surface; + int hotspot_x, hotspot_y; struct wl_listener motion; struct wl_listener motion_absolute; @@ -33,11 +42,20 @@ struct sway_cursor { struct wl_listener request_set_cursor; + struct wl_event_source *hide_source; + bool hidden; + // Mouse binding state uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP]; size_t pressed_button_count; }; +struct sway_node; + +struct sway_node *node_at_coords( + struct sway_seat *seat, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy); + void sway_cursor_destroy(struct sway_cursor *cursor); struct sway_cursor *sway_cursor_create(struct sway_seat *seat); @@ -48,6 +66,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. @@ -58,12 +80,27 @@ void dispatch_cursor_button(struct sway_cursor *cursor, struct wlr_input_device *device, uint32_t time_msec, uint32_t button, enum wlr_button_state state); +void dispatch_cursor_axis(struct sway_cursor *cursor, + struct wlr_event_pointer_axis *event); + void cursor_set_image(struct sway_cursor *cursor, const char *image, struct wl_client *client); +void cursor_set_image_surface(struct sway_cursor *cursor, + struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y, + struct wl_client *client); + void cursor_warp_to_container(struct sway_cursor *cursor, struct sway_container *container); void cursor_warp_to_workspace(struct sway_cursor *cursor, struct sway_workspace *workspace); + +uint32_t get_mouse_bindsym(const char *name, char **error); + +uint32_t get_mouse_bindcode(const char *name, char **error); + +// Considers both bindsym and bindcode +uint32_t get_mouse_button(const char *name, char **error); + #endif diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index 219aa9ba..8e8bf1f2 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -37,6 +37,10 @@ void input_manager_configure_xcursor(void); void input_manager_apply_input_config(struct input_config *input_config); +void input_manager_reset_input(struct sway_input_device *input_device); + +void input_manager_reset_all_inputs(); + void input_manager_apply_seat_config(struct seat_config *seat_config); struct sway_seat *input_manager_get_default_seat(void); @@ -44,6 +48,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..d2f14895 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -6,6 +6,17 @@ #include <wlr/util/edges.h> #include "sway/input/input-manager.h" +struct sway_seat; + +struct sway_seatop_impl { + void (*motion)(struct sway_seat *seat, uint32_t time_msec); + void (*finish)(struct sway_seat *seat); + void (*abort)(struct sway_seat *seat); + void (*unref)(struct sway_seat *seat, struct sway_container *con); + void (*render)(struct sway_seat *seat, struct sway_output *output, + pixman_region32_t *damage); +}; + struct sway_seat_device { struct sway_seat *sway_seat; struct sway_input_device *input_device; @@ -35,15 +46,6 @@ struct sway_drag_icon { struct wl_listener destroy; }; -enum sway_seat_operation { - OP_NONE, - OP_DOWN, - OP_MOVE_FLOATING, - OP_MOVE_TILING, - OP_RESIZE_FLOATING, - OP_RESIZE_TILING, -}; - struct sway_seat { struct wlr_seat *wlr_seat; struct sway_cursor *cursor; @@ -63,19 +65,10 @@ struct sway_seat { int32_t touch_id; double touch_x, touch_y; - // Operations (drag and resize) - enum sway_seat_operation operation; - struct sway_container *op_container; - struct sway_node *op_target_node; // target for tiling move - enum wlr_edges op_target_edge; - struct wlr_box op_drop_box; - enum wlr_edges op_resize_edge; - uint32_t op_button; - bool op_resize_preserve_ratio; - double op_ref_lx, op_ref_ly; // cursor's x/y at start of op - double op_ref_width, op_ref_height; // container's size at start of op - double op_ref_con_lx, op_ref_con_ly; // container's x/y at start of op - bool op_moved; // if the mouse moved during a down op + // Seat operations (drag and resize) + const struct sway_seatop_impl *seatop_impl; + void *seatop_data; + uint32_t seatop_button; uint32_t last_button; uint32_t last_button_serial; @@ -99,6 +92,9 @@ void seat_add_device(struct sway_seat *seat, void seat_configure_device(struct sway_seat *seat, struct sway_input_device *device); +void seat_reset_device(struct sway_seat *seat, + struct sway_input_device *input_device); + void seat_remove_device(struct sway_seat *seat, struct sway_input_device *device); @@ -174,33 +170,65 @@ 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); -void seat_begin_down(struct sway_seat *seat, struct sway_container *con, - uint32_t button, double sx, double sy); +void seatop_begin_down(struct sway_seat *seat, + struct sway_container *con, uint32_t button, int sx, int sy); -void seat_begin_move_floating(struct sway_seat *seat, +void seatop_begin_move_floating(struct sway_seat *seat, struct sway_container *con, uint32_t button); -void seat_begin_move_tiling(struct sway_seat *seat, +void seatop_begin_move_tiling_threshold(struct sway_seat *seat, struct sway_container *con, uint32_t button); -void seat_begin_resize_floating(struct sway_seat *seat, +void seatop_begin_move_tiling(struct sway_seat *seat, + struct sway_container *con, uint32_t button); + +void seatop_begin_resize_floating(struct sway_seat *seat, struct sway_container *con, uint32_t button, enum wlr_edges edge); -void seat_begin_resize_tiling(struct sway_seat *seat, +void seatop_begin_resize_tiling(struct sway_seat *seat, struct sway_container *con, uint32_t button, enum wlr_edges edge); struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat, struct sway_workspace *workspace); -void seat_end_mouse_operation(struct sway_seat *seat); - void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, uint32_t button, enum wlr_button_state state); void seat_consider_warp_to_focus(struct sway_seat *seat); +bool seat_doing_seatop(struct sway_seat *seat); + +void seatop_motion(struct sway_seat *seat, uint32_t time_msec); + +/** + * End a seatop and apply the affects. + */ +void seatop_finish(struct sway_seat *seat); + +/** + * End a seatop without applying the affects. + */ +void seatop_abort(struct sway_seat *seat); + +/** + * Instructs the seatop implementation to drop any references to the given + * container (eg. because the container is destroying). + * The seatop may choose to abort itself in response to this. + */ +void seatop_unref(struct sway_seat *seat, struct sway_container *con); + +/** + * Instructs a seatop to render anything that it needs to render + * (eg. dropzone for move-tiling) + */ +void seatop_render(struct sway_seat *seat, struct sway_output *output, + pixman_region32_t *damage); + + #endif diff --git a/include/sway/output.h b/include/sway/output.h index 43c1ab96..bdf9614d 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -84,9 +84,7 @@ void output_damage_box(struct sway_output *output, struct wlr_box *box); void output_damage_whole_container(struct sway_output *output, struct sway_container *con); -struct sway_output *output_by_name(const char *name); - -struct sway_output *output_by_identifier(const char *identifier); +struct sway_output *output_by_name_or_id(const char *name_or_id); void output_sort_workspaces(struct sway_output *output); @@ -148,4 +146,12 @@ enum sway_container_layout output_get_default_layout( void output_add_listeners(struct sway_output *output); +void render_rect(struct wlr_output *wlr_output, + pixman_region32_t *output_damage, const struct wlr_box *_box, + float color[static 4]); + +void premultiply_alpha(float color[4], float opacity); + +void scale_box(struct wlr_box *box, float scale); + #endif diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index f907aad2..9a432cb2 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -10,12 +10,6 @@ struct sway_view; struct sway_seat; -#define TITLEBAR_BORDER_THICKNESS 1 - -// Padding includes titlebar border -#define TITLEBAR_H_PADDING 3 -#define TITLEBAR_V_PADDING 4 - enum sway_container_layout { L_NONE, L_HORIZ, @@ -339,4 +333,6 @@ void container_add_mark(struct sway_container *container, char *mark); void container_update_marks_textures(struct sway_container *container); +void container_raise_floating(struct sway_container *con); + #endif diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 4716c688..5cc9777b 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -195,6 +195,7 @@ struct sway_view_child { struct sway_view *view; struct wlr_surface *surface; + bool mapped; struct wl_listener surface_commit; struct wl_listener surface_new_subsurface; @@ -203,6 +204,12 @@ struct sway_view_child { struct wl_listener surface_destroy; }; +struct sway_subsurface { + struct sway_view_child child; + + struct wl_listener destroy; +}; + struct sway_xdg_popup_v6 { struct sway_view_child child; diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h index 95b20510..2d9ba0d9 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 { @@ -53,6 +61,7 @@ struct swaybar_output { struct wl_list hotspots; // swaybar_hotspot::link char *name; + char *identifier; bool focused; uint32_t width, height; @@ -62,12 +71,15 @@ 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 { struct wl_list link; // swaybar_output::workspaces int num; char *name; + char *label; bool focused; bool visible; bool urgent; @@ -77,6 +89,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 5d40790a..add0a1cf 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" @@ -34,6 +35,7 @@ struct swaybar_config { char *hidden_state; char *modifier; bool strip_workspace_numbers; + bool strip_workspace_name; bool binding_mode_indicator; bool wrap_scroll; bool workspace_buttons; @@ -41,6 +43,14 @@ struct swaybar_config { struct wl_list outputs; // config_output::link bool all_outputs; int height; + int status_padding; + int status_edge_padding; + struct { + int top; + int right; + int bottom; + int left; + } gaps; struct { uint32_t background; @@ -57,6 +67,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/i3bar.h b/include/swaybar/i3bar.h index 3f1ecc25..aa4415ff 100644 --- a/include/swaybar/i3bar.h +++ b/include/swaybar/i3bar.h @@ -27,6 +27,7 @@ struct i3bar_block { void i3bar_block_unref(struct i3bar_block *block); bool i3bar_handle_readable(struct status_line *status); enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, - struct i3bar_block *block, int x, int y, enum x11_button button); + struct i3bar_block *block, int x, int y, int rx, int ry, int w, int h, + uint32_t button); #endif diff --git a/include/swaybar/input.h b/include/swaybar/input.h index a552e7ac..4b46b0de 100644 --- a/include/swaybar/input.h +++ b/include/swaybar/input.h @@ -4,6 +4,12 @@ #include <wayland-client.h> #include "list.h" +#define SWAY_SCROLL_UP KEY_MAX + 1 +#define SWAY_SCROLL_DOWN KEY_MAX + 2 +#define SWAY_SCROLL_LEFT KEY_MAX + 3 +#define SWAY_SCROLL_RIGHT KEY_MAX + 4 + +struct swaybar; struct swaybar_output; struct swaybar_pointer { @@ -13,6 +19,7 @@ struct swaybar_pointer { struct wl_surface *cursor_surface; struct swaybar_output *current; int x, y; + uint32_t serial; }; enum x11_button { @@ -37,13 +44,16 @@ struct swaybar_hotspot { struct wl_list link; // swaybar_output::hotspots int x, y, width, height; enum hotspot_event_handling (*callback)(struct swaybar_output *output, - int x, int y, enum x11_button button, void *data); + struct swaybar_hotspot *hotspot, int x, int y, uint32_t button, + void *data); void (*destroy)(void *data); void *data; }; extern const struct wl_seat_listener seat_listener; +void update_cursor(struct swaybar *bar); + void free_hotspots(struct wl_list *list); #endif 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..4238bdb8 --- /dev/null +++ b/include/swaybar/tray/item.h @@ -0,0 +1,49 @@ +#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 + + sd_bus_slot *new_icon_slot; + sd_bus_slot *new_attention_icon_slot; + sd_bus_slot *new_status_slot; +}; + +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/include/swaylock/swaylock.h b/include/swaylock/swaylock.h index 18af7ab4..516a56f4 100644 --- a/include/swaylock/swaylock.h +++ b/include/swaylock/swaylock.h @@ -22,6 +22,7 @@ enum auth_state { struct swaylock_colorset { uint32_t input; uint32_t cleared; + uint32_t caps_lock; uint32_t verifying; uint32_t wrong; }; @@ -30,6 +31,8 @@ struct swaylock_colors { uint32_t background; uint32_t bs_highlight; uint32_t key_highlight; + uint32_t caps_lock_bs_highlight; + uint32_t caps_lock_key_highlight; uint32_t separator; struct swaylock_colorset inside; struct swaylock_colorset line; @@ -45,6 +48,8 @@ struct swaylock_args { uint32_t thickness; bool ignore_empty; bool show_indicator; + bool show_caps_lock_text; + bool show_caps_lock_indicator; bool daemonize; }; diff --git a/include/swaynag/swaynag.h b/include/swaynag/swaynag.h index a32d1503..0fd1eb50 100644 --- a/include/swaynag/swaynag.h +++ b/include/swaynag/swaynag.h @@ -44,6 +44,7 @@ struct swaynag_button { int y; int width; int height; + bool terminal; }; struct swaynag_details { diff --git a/meson.build b/meson.build index bb60bc89..bffbe312 100644 --- a/meson.build +++ b/meson.build @@ -32,8 +32,6 @@ if is_freebsd add_project_arguments('-D_C11_SOURCE', language: 'c') endif -swayidle_deps = [] - jsonc = dependency('json-c', version: '>=0.13') pcre = dependency('libpcre') wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots']) @@ -48,6 +46,7 @@ pango = dependency('pango') pangocairo = dependency('pangocairo') gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: false) pixman = dependency('pixman-1') +libevdev = dependency('libevdev') libinput = dependency('libinput', version: '>=1.6.0') libpam = cc.find_library('pam', required: false) crypt = cc.find_library('crypt', required: false) @@ -67,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') @@ -86,7 +86,6 @@ if scdoc.found() 'sway/sway-output.5.scd', 'swaylock/swaylock.1.scd', 'swaymsg/swaymsg.1.scd', - 'swayidle/swayidle.1.scd', 'swaynag/swaynag.1.scd', 'swaynag/swaynag.5.scd', ] @@ -108,13 +107,7 @@ if scdoc.found() endforeach endif -# If prefix is '/usr', sysconfdir will be explicitly set to '/etc' by Meson to -# enforce FHS compliance, so we should look for configs there as well. -if prefix == '/usr' - add_project_arguments('-DSYSCONFDIR="/@0@"'.format(sysconfdir), language : 'c') -else - add_project_arguments('-DSYSCONFDIR="/@0@/@1@"'.format(prefix, sysconfdir), language : 'c') -endif +add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c') version = get_option('sway-version') if version != '' @@ -152,21 +145,13 @@ subdir('swaymsg') subdir('client') subdir('swaybg') subdir('swaybar') -subdir('swayidle') subdir('swaynag') subdir('swaylock') config = configuration_data() config.set('datadir', join_paths(prefix, datadir)) config.set('prefix', prefix) - -# If prefix is '/usr', sysconfdir will be explicitly set to '/etc' by Meson to -# enforce FHS compliance, so we should look for configs there as well. -if prefix == '/usr' - config.set('sysconfdir', sysconfdir) -else - config.set('sysconfdir', join_paths(prefix, sysconfdir)) -endif +config.set('sysconfdir', join_paths(prefix, sysconfdir)) configure_file( configuration: config, @@ -226,7 +211,7 @@ endif if (get_option('bash-completions')) bash_files = files( 'completions/bash/sway', - 'completions/bash/swayidle', + 'completions/bash/swaybar', 'completions/bash/swaylock', 'completions/bash/swaymsg', ) @@ -238,7 +223,6 @@ endif if (get_option('fish-completions')) fish_files = files( 'completions/fish/sway.fish', - 'completions/fish/swayidle.fish', 'completions/fish/swaylock.fish', 'completions/fish/swaymsg.fish', 'completions/fish/swaynag.fish', 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 a68c724a..1d190e0b 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -42,22 +42,6 @@ struct cmd_results *checkarg(int argc, const char *name, enum expected_args type : NULL; } -void apply_seat_config(struct seat_config *seat_config) { - int i; - i = list_seq_find(config->seat_configs, seat_name_cmp, seat_config->name); - if (i >= 0) { - // merge existing config - struct seat_config *sc = config->seat_configs->items[i]; - merge_seat_config(sc, seat_config); - free_seat_config(seat_config); - seat_config = sc; - } else { - list_add(config->seat_configs, seat_config); - } - - input_manager_apply_seat_config(seat_config); -} - /* Keep alphabetized */ static struct cmd_handler handlers[] = { { "assign", cmd_assign }, @@ -103,6 +87,10 @@ 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 }, { "workspace", cmd_workspace }, { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, }; @@ -213,12 +201,9 @@ static void set_config_node(struct sway_node *node) { } } -struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, +list_t *execute_command(char *_exec, struct sway_seat *seat, struct sway_container *con) { - // Even though this function will process multiple commands we will only - // return the last error, if any (for now). (Since we have access to an - // error string we could e.g. concatenate all errors there.) - struct cmd_results *results = NULL; + list_t *res_list = create_list(); char *exec = strdup(_exec); char *head = exec; char *cmdlist; @@ -233,15 +218,6 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, } } - // This is the container or workspace which this command will run on. - // Ignored if the command string contains criteria. - struct sway_node *node; - if (con) { - node = &con->node; - } else { - node = seat_get_focus_inactive(seat, &root->node); - } - config->handler_context.seat = seat; head = exec; @@ -252,8 +228,8 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, char *error = NULL; struct criteria *criteria = criteria_parse(head, &error); if (!criteria) { - results = cmd_results_new(CMD_INVALID, head, - "%s", error); + list_add(res_list, cmd_results_new(CMD_INVALID, head, + "%s", error)); free(error); goto cleanup; } @@ -262,15 +238,15 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, criteria_destroy(criteria); config->handler_context.using_criteria = true; // Skip leading whitespace - head += strspn(head, whitespace); + for (; isspace(*head); ++head) {} } // Split command list cmdlist = argsep(&head, ";"); - cmdlist += strspn(cmdlist, whitespace); + for (; isspace(*cmdlist); ++cmdlist) {} do { // Split commands cmd = argsep(&cmdlist, ","); - cmd += strspn(cmd, whitespace); + for (; isspace(*cmd); ++cmd) {} if (strcmp(cmd, "") == 0) { wlr_log(WLR_INFO, "Ignoring empty command."); continue; @@ -289,10 +265,8 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, } struct cmd_handler *handler = find_handler(argv[0], NULL, 0); if (!handler) { - if (results) { - free_cmd_results(results); - } - results = cmd_results_new(CMD_INVALID, cmd, "Unknown/invalid command"); + list_add(res_list, cmd_results_new(CMD_INVALID, cmd, + "Unknown/invalid command")); free_argv(argc, argv); goto cleanup; } @@ -304,31 +278,26 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, } if (!config->handler_context.using_criteria) { + // The container or workspace which this command will run on. + struct sway_node *node = con ? &con->node : + seat_get_focus_inactive(seat, &root->node); set_config_node(node); struct cmd_results *res = handler->handle(argc-1, argv+1); - if (res->status != CMD_SUCCESS) { + list_add(res_list, res); + if (res->status == CMD_INVALID) { free_argv(argc, argv); - if (results) { - free_cmd_results(results); - } - results = res; goto cleanup; } - free_cmd_results(res); } else { for (int i = 0; i < views->length; ++i) { struct sway_view *view = views->items[i]; set_config_node(&view->container->node); struct cmd_results *res = handler->handle(argc-1, argv+1); - if (res->status != CMD_SUCCESS) { + list_add(res_list, res); + if (res->status == CMD_INVALID) { free_argv(argc, argv); - if (results) { - free_cmd_results(results); - } - results = res; goto cleanup; } - free_cmd_results(res); } } free_argv(argc, argv); @@ -337,10 +306,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat, cleanup: free(exec); list_free(views); - if (!results) { - results = cmd_results_new(CMD_SUCCESS, NULL, NULL); - } - return results; + return res_list; } // this is like execute_command above, except: @@ -418,6 +384,7 @@ struct cmd_results *config_command(char *exec) { // Strip quotes and unescape the string for (int i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) { if (handler->handle != cmd_exec && handler->handle != cmd_exec_always + && handler->handle != cmd_mode && handler->handle != cmd_bindsym && handler->handle != cmd_bindcode && handler->handle != cmd_set @@ -572,20 +539,25 @@ void free_cmd_results(struct cmd_results *results) { free(results); } -char *cmd_results_to_json(struct cmd_results *results) { +char *cmd_results_to_json(list_t *res_list) { json_object *result_array = json_object_new_array(); - json_object *root = json_object_new_object(); - json_object_object_add(root, "success", - json_object_new_boolean(results->status == CMD_SUCCESS)); - if (results->input) { - json_object_object_add( - root, "input", json_object_new_string(results->input)); - } - if (results->error) { - json_object_object_add( - root, "error", json_object_new_string(results->error)); + for (int i = 0; i < res_list->length; ++i) { + struct cmd_results *results = res_list->items[i]; + json_object *root = json_object_new_object(); + json_object_object_add(root, "success", + json_object_new_boolean(results->status == CMD_SUCCESS)); + if (results->error) { + json_object_object_add(root, "parse_error", + json_object_new_boolean(results->status == CMD_INVALID)); + json_object_object_add( + root, "error", json_object_new_string(results->error)); + } + if (results->input) { + json_object_object_add( + root, "input", json_object_new_string(results->input)); + } + json_object_array_add(result_array, root); } - json_object_array_add(result_array, root); const char *json = json_object_to_json_string(result_array); char *res = strdup(json); json_object_put(result_array); diff --git a/sway/commands/assign.c b/sway/commands/assign.c index 04582e88..716d70cf 100644 --- a/sway/commands/assign.c +++ b/sway/commands/assign.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdio.h> #include <string.h> #include "sway/commands.h" diff --git a/sway/commands/bar.c b/sway/commands/bar.c index c808aef2..2a82d508 100644 --- a/sway/commands/bar.c +++ b/sway/commands/bar.c @@ -8,12 +8,12 @@ // Must be in alphabetical order for bsearch static struct cmd_handler bar_handlers[] = { - { "activate_button", bar_cmd_activate_button }, + { "bindcode", bar_cmd_bindcode }, { "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 }, { "hidden_state", bar_cmd_hidden_state }, { "icon_theme", bar_cmd_icon_theme }, @@ -22,10 +22,13 @@ 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 }, + { "status_edge_padding", bar_cmd_status_edge_padding }, + { "status_padding", bar_cmd_status_padding }, + { "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/bind.c b/sway/commands/bar/bind.c new file mode 100644 index 00000000..a4c65ec4 --- /dev/null +++ b/sway/commands/bar/bind.c @@ -0,0 +1,106 @@ +#include <libevdev/libevdev.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/input/cursor.h" +#include "list.h" +#include "log.h" +#include "stringop.h" + +static struct cmd_results *bar_cmd_bind(int argc, char **argv, bool code) { + const char *command = code ? "bar bindcode" : "bar bindsym"; + struct cmd_results *error = NULL; + if ((error = checkarg(argc, command, EXPECTED_AT_LEAST, 2))) { + return error; + } + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, command, "No bar defined."); + } + + struct bar_binding *binding = calloc(1, sizeof(struct bar_binding)); + if (!binding) { + return cmd_results_new(CMD_FAILURE, command, + "Unable to allocate bar binding"); + } + + binding->release = false; + if (strcmp("--release", argv[0]) == 0) { + binding->release = true; + argv++; + argc--; + } + + char *message = NULL; + if (code) { + binding->button = get_mouse_bindcode(argv[0], &message); + } else { + binding->button = get_mouse_bindsym(argv[0], &message); + } + if (message) { + free_bar_binding(binding); + error = cmd_results_new(CMD_INVALID, command, message); + free(message); + return error; + } else if (!binding->button) { + free_bar_binding(binding); + return cmd_results_new(CMD_INVALID, command, + "Unknown button %s", argv[0]); + } + + const char *name = libevdev_event_code_get_name(EV_KEY, binding->button); + if (!name) { + switch (binding->button) { + case SWAY_SCROLL_UP: + name = "SWAY_SCROLL_UP"; + break; + case SWAY_SCROLL_DOWN: + name = "SWAY_SCROLL_DOWN"; + break; + case SWAY_SCROLL_LEFT: + name = "SWAY_SCROLL_LEFT"; + break; + case SWAY_SCROLL_RIGHT: + name = "SWAY_SCROLL_RIGHT"; + break; + default: + // Unreachable + break; + } + } + + binding->command = join_args(argv + 1, argc - 1); + + list_t *bindings = config->current_bar->bindings; + bool overwritten = false; + for (int i = 0; i < bindings->length; i++) { + struct bar_binding *other = bindings->items[i]; + if (other->button == binding->button && + other->release == binding->release) { + overwritten = true; + bindings->items[i] = binding; + free_bar_binding(other); + wlr_log(WLR_DEBUG, "[bar %s] Updated binding for %u (%s)%s", + config->current_bar->id, binding->button, name, + binding->release ? " - release" : ""); + break; + } + } + if (!overwritten) { + list_add(bindings, binding); + wlr_log(WLR_DEBUG, "[bar %s] Added binding for %u (%s)%s", + config->current_bar->id, binding->button, name, + binding->release ? " - release" : ""); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + +struct cmd_results *bar_cmd_bindcode(int argc, char **argv) { + return bar_cmd_bind(argc, argv, true); +} + +struct cmd_results *bar_cmd_bindsym(int argc, char **argv) { + return bar_cmd_bind(argc, argv, false); +} diff --git a/sway/commands/bar/bindsym.c b/sway/commands/bar/bindsym.c deleted file mode 100644 index 965c8903..00000000 --- a/sway/commands/bar/bindsym.c +++ /dev/null @@ -1,69 +0,0 @@ -#define _XOPEN_SOURCE 500 -#include <stdlib.h> -#include <string.h> -#include <strings.h> -#include "sway/commands.h" -#include "sway/config.h" -#include "list.h" -#include "log.h" -#include "stringop.h" - -struct cmd_results *bar_cmd_bindsym(int argc, char **argv) { - struct cmd_results *error = NULL; - if ((error = checkarg(argc, "bar bindsym", EXPECTED_AT_LEAST, 2))) { - return error; - } - if (!config->current_bar) { - return cmd_results_new(CMD_FAILURE, "bar bindsym", "No bar defined."); - } - - struct bar_binding *binding = calloc(1, sizeof(struct bar_binding)); - if (!binding) { - return cmd_results_new(CMD_FAILURE, "bar bindsym", - "Unable to allocate bar binding"); - } - - binding->release = false; - if (strcmp("--release", argv[0]) == 0) { - binding->release = true; - argv++; - argc--; - } - - binding->button = 0; - if (strncasecmp(argv[0], "button", strlen("button")) == 0 && - strlen(argv[0]) == strlen("button0")) { - binding->button = argv[0][strlen("button")] - '0'; - } - if (binding->button < 1 || binding->button > 9) { - free_bar_binding(binding); - return cmd_results_new(CMD_FAILURE, "bar bindsym", - "Only button<1-9> is supported"); - } - - binding->command = join_args(argv + 1, argc - 1); - - list_t *bindings = config->current_bar->bindings; - bool overwritten = false; - for (int i = 0; i < bindings->length; i++) { - struct bar_binding *other = bindings->items[i]; - if (other->button == binding->button && - other->release == binding->release) { - overwritten = true; - bindings->items[i] = binding; - free_bar_binding(other); - wlr_log(WLR_DEBUG, "[bar %s] Updated binding for button%u%s", - config->current_bar->id, binding->button, - binding->release ? " (release)" : ""); - break; - } - } - if (!overwritten) { - list_add(bindings, binding); - wlr_log(WLR_DEBUG, "[bar %s] Added binding for button%u%s", - config->current_bar->id, binding->button, - binding->release ? " (release)" : ""); - } - - return cmd_results_new(CMD_SUCCESS, NULL, NULL); -} diff --git a/sway/commands/bar/colors.c b/sway/commands/bar/colors.c index 8c862ca9..ebf1e3e1 100644 --- a/sway/commands/bar/colors.c +++ b/sway/commands/bar/colors.c @@ -118,8 +118,8 @@ struct cmd_results *bar_colors_cmd_statusline(int argc, char **argv) { } struct cmd_results *bar_colors_cmd_focused_statusline(int argc, char **argv) { - return parse_single_color(&(config->current_bar->colors.focused_separator), - "focused_separator", argc, argv); + return parse_single_color(&(config->current_bar->colors.focused_statusline), + "focused_statusline", argc, argv); } struct cmd_results *bar_colors_cmd_urgent_workspace(int argc, char **argv) { 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/gaps.c b/sway/commands/bar/gaps.c new file mode 100644 index 00000000..f78f3742 --- /dev/null +++ b/sway/commands/bar/gaps.c @@ -0,0 +1,60 @@ +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include "sway/commands.h" +#include "sway/ipc-server.h" +#include "log.h" + +struct cmd_results *bar_cmd_gaps(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 1))) { + return error; + } + if ((error = checkarg(argc, "gaps", EXPECTED_AT_MOST, 4))) { + return error; + } + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, "bar gaps", "No bar defined."); + } + + int top = 0, right = 0, bottom = 0, left = 0; + + for (int i = 0; i < argc; i++) { + char *end; + int amount = strtol(argv[i], &end, 10); + if (strlen(end) && strcasecmp(end, "px") != 0) { + return cmd_results_new(CMD_INVALID, "bar gaps", + "Expected 'bar [<bar-id>] gaps <all> | <horizonal> " + "<vertical> | <top> <right> <bottom> <left>'"); + } + + if (i == 0) { + top = amount; + } + if (i == 0 || i == 1) { + right = amount; + } + if (i == 0 || i == 2) { + bottom = amount; + } + if (i == 0 || i == 1 || i == 3) { + left = amount; + } + } + + config->current_bar->gaps.top = top; + config->current_bar->gaps.right = right; + config->current_bar->gaps.bottom = bottom; + config->current_bar->gaps.left = left; + + wlr_log(WLR_DEBUG, "Setting bar gaps to %d %d %d %d on bar: %s", + config->current_bar->gaps.top, config->current_bar->gaps.right, + config->current_bar->gaps.bottom, config->current_bar->gaps.left, + config->current_bar->id); + + if (!config->reading) { + ipc_event_barconfig_update(config->current_bar); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/bar/hidden_state.c b/sway/commands/bar/hidden_state.c index 5be6c2dc..79eaf01c 100644 --- a/sway/commands/bar/hidden_state.c +++ b/sway/commands/bar/hidden_state.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <string.h> #include <strings.h> #include "sway/commands.h" diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c index 44cd3076..9d3b6040 100644 --- a/sway/commands/bar/icon_theme.c +++ b/sway/commands/bar/icon_theme.c @@ -1,8 +1,28 @@ -#define _XOPEN_SOURCE 500 +#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/id.c b/sway/commands/bar/id.c index 7690a852..35509459 100644 --- a/sway/commands/bar/id.c +++ b/sway/commands/bar/id.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <string.h> #include "sway/commands.h" #include "log.h" diff --git a/sway/commands/bar/mode.c b/sway/commands/bar/mode.c index 2cba785e..dcaf6da9 100644 --- a/sway/commands/bar/mode.c +++ b/sway/commands/bar/mode.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <string.h> #include <strings.h> #include "sway/commands.h" diff --git a/sway/commands/bar/modifier.c b/sway/commands/bar/modifier.c index 09025fff..b5a16f45 100644 --- a/sway/commands/bar/modifier.c +++ b/sway/commands/bar/modifier.c @@ -20,15 +20,14 @@ struct cmd_results *bar_cmd_modifier(int argc, char **argv) { uint32_t tmp_mod; if ((tmp_mod = get_modifier_mask_by_name(split->items[i])) > 0) { mod |= tmp_mod; - continue; } else { error = cmd_results_new(CMD_INVALID, "modifier", "Unknown modifier '%s'", split->items[i]); - free_flat_list(split); + list_free_items_and_destroy(split); return error; } } - free_flat_list(split); + list_free_items_and_destroy(split); config->current_bar->modifier = mod; wlr_log(WLR_DEBUG, "Show/Hide the bar when pressing '%s' in hide mode.", argv[0]); diff --git a/sway/commands/bar/output.c b/sway/commands/bar/output.c index 72754e05..930d779d 100644 --- a/sway/commands/bar/output.c +++ b/sway/commands/bar/output.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdbool.h> #include <string.h> #include "sway/commands.h" 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/separator_symbol.c b/sway/commands/bar/separator_symbol.c index 392ab730..060b8f52 100644 --- a/sway/commands/bar/separator_symbol.c +++ b/sway/commands/bar/separator_symbol.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <string.h> #include "sway/commands.h" #include "log.h" diff --git a/sway/commands/bar/status_edge_padding.c b/sway/commands/bar/status_edge_padding.c new file mode 100644 index 00000000..f3b10631 --- /dev/null +++ b/sway/commands/bar/status_edge_padding.c @@ -0,0 +1,21 @@ +#include <stdlib.h> +#include <string.h> +#include "sway/commands.h" +#include "log.h" + +struct cmd_results *bar_cmd_status_edge_padding(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "status_edge_padding", EXPECTED_EQUAL_TO, 1))) { + return error; + } + char *end; + int padding = strtol(argv[0], &end, 10); + if (strlen(end) || padding < 0) { + return cmd_results_new(CMD_INVALID, "status_edge_padding", + "Padding must be a positive integer"); + } + config->current_bar->status_edge_padding = padding; + wlr_log(WLR_DEBUG, "Status edge padding on bar %s: %d", + config->current_bar->id, config->current_bar->status_edge_padding); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/bar/status_padding.c b/sway/commands/bar/status_padding.c new file mode 100644 index 00000000..13b8eb6b --- /dev/null +++ b/sway/commands/bar/status_padding.c @@ -0,0 +1,21 @@ +#include <stdlib.h> +#include <string.h> +#include "sway/commands.h" +#include "log.h" + +struct cmd_results *bar_cmd_status_padding(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "status_padding", EXPECTED_EQUAL_TO, 1))) { + return error; + } + char *end; + int padding = strtol(argv[0], &end, 10); + if (strlen(end) || padding < 0) { + return cmd_results_new(CMD_INVALID, "status_padding", + "Padding must be a positive integer"); + } + config->current_bar->status_padding = padding; + wlr_log(WLR_DEBUG, "Status padding on bar %s: %d", + config->current_bar->id, config->current_bar->status_padding); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/bar/strip_workspace_name.c b/sway/commands/bar/strip_workspace_name.c new file mode 100644 index 00000000..79692f6e --- /dev/null +++ b/sway/commands/bar/strip_workspace_name.c @@ -0,0 +1,32 @@ +#include <string.h> +#include <strings.h> +#include "sway/commands.h" +#include "log.h" +#include "util.h" + +struct cmd_results *bar_cmd_strip_workspace_name(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, + "strip_workspace_name", EXPECTED_EQUAL_TO, 1))) { + return error; + } + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, + "strip_workspace_name", "No bar defined."); + } + + config->current_bar->strip_workspace_name = + parse_boolean(argv[0], config->current_bar->strip_workspace_name); + + if (config->current_bar->strip_workspace_name) { + config->current_bar->strip_workspace_numbers = false; + + wlr_log(WLR_DEBUG, "Stripping workspace name on bar: %s", + config->current_bar->id); + } else { + wlr_log(WLR_DEBUG, "Enabling workspace name on bar: %s", + config->current_bar->id); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/bar/strip_workspace_numbers.c b/sway/commands/bar/strip_workspace_numbers.c index 4e47d047..b33d01e5 100644 --- a/sway/commands/bar/strip_workspace_numbers.c +++ b/sway/commands/bar/strip_workspace_numbers.c @@ -2,6 +2,7 @@ #include <strings.h> #include "sway/commands.h" #include "log.h" +#include "util.h" struct cmd_results *bar_cmd_strip_workspace_numbers(int argc, char **argv) { struct cmd_results *error = NULL; @@ -13,17 +14,19 @@ struct cmd_results *bar_cmd_strip_workspace_numbers(int argc, char **argv) { return cmd_results_new(CMD_FAILURE, "strip_workspace_numbers", "No bar defined."); } - if (strcasecmp("yes", argv[0]) == 0) { - config->current_bar->strip_workspace_numbers = true; + + config->current_bar->strip_workspace_numbers = + parse_boolean(argv[0], config->current_bar->strip_workspace_numbers); + + if (config->current_bar->strip_workspace_numbers) { + config->current_bar->strip_workspace_name = false; + wlr_log(WLR_DEBUG, "Stripping workspace numbers on bar: %s", config->current_bar->id); - } else if (strcasecmp("no", argv[0]) == 0) { - config->current_bar->strip_workspace_numbers = false; + } else { wlr_log(WLR_DEBUG, "Enabling workspace numbers on bar: %s", config->current_bar->id); - } else { - return cmd_results_new(CMD_INVALID, - "strip_workspace_numbers", "Invalid value %s", argv[0]); } + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } 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 6ab16731..a1169c20 100644 --- a/sway/commands/bar/tray_output.c +++ b/sway/commands/bar/tray_output.c @@ -1,8 +1,42 @@ -#define _XOPEN_SOURCE 500 +#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/bind.c b/sway/commands/bind.c index a9de227f..be47d412 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -1,15 +1,13 @@ -#define _XOPEN_SOURCE 500 -#ifdef __linux__ +#define _POSIX_C_SOURCE 200809L +#include <libevdev/libevdev.h> #include <linux/input-event-codes.h> -#elif __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#endif #include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon-names.h> #include <string.h> #include <strings.h> #include "sway/commands.h" #include "sway/config.h" +#include "sway/input/cursor.h" #include "sway/ipc-server.h" #include "list.h" #include "log.h" @@ -23,9 +21,7 @@ void free_sway_binding(struct sway_binding *binding) { return; } - if (binding->keys) { - free_flat_list(binding->keys); - } + list_free_items_and_destroy(binding->keys); free(binding->input); free(binding->command); free(binding); @@ -80,7 +76,6 @@ static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) { return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0); } - /** * From a keycode, bindcode, or bindsym name and the most likely binding type, * identify the appropriate numeric value corresponding to the key. Return NULL @@ -90,52 +85,91 @@ static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) { */ static struct cmd_results *identify_key(const char* name, bool first_key, uint32_t* key_val, enum binding_input_type* type) { - if (*type == BINDING_KEYCODE) { - // check for keycode + if (*type == BINDING_MOUSECODE) { + // check for mouse bindcodes + char *message = NULL; + uint32_t button = get_mouse_bindcode(name, &message); + if (!button) { + if (message) { + struct cmd_results *error = + cmd_results_new(CMD_INVALID, "bindcode", message); + free(message); + return error; + } else { + return cmd_results_new(CMD_INVALID, "bindcode", + "Unknown button code %s", name); + } + } + *key_val = button; + } else if (*type == BINDING_MOUSESYM) { + // check for mouse bindsyms (x11 buttons or event names) + char *message = NULL; + uint32_t button = get_mouse_bindsym(name, &message); + if (!button) { + if (message) { + struct cmd_results *error = + cmd_results_new(CMD_INVALID, "bindsym", message); + free(message); + return error; + } else if (!button) { + return cmd_results_new(CMD_INVALID, "bindsym", + "Unknown button %s", name); + } + } + *key_val = button; + } else if (*type == BINDING_KEYCODE) { + // check for keycode. If it is the first key, allow mouse bindcodes + if (first_key) { + char *message = NULL; + uint32_t button = get_mouse_bindcode(name, &message); + free(message); + if (button) { + *type = BINDING_MOUSECODE; + *key_val = button; + return NULL; + } + } + xkb_keycode_t keycode = strtol(name, NULL, 10); if (!xkb_keycode_is_legal_ext(keycode)) { - return cmd_results_new(CMD_INVALID, "bindcode", - "Invalid keycode '%s'", name); + if (first_key) { + return cmd_results_new(CMD_INVALID, "bindcode", + "Invalid keycode or button code '%s'", name); + } else { + return cmd_results_new(CMD_INVALID, "bindcode", + "Invalid keycode '%s'", name); + } } *key_val = keycode; } else { - // check for keysym - xkb_keysym_t keysym = xkb_keysym_from_name(name, - XKB_KEYSYM_CASE_INSENSITIVE); - - // Check for mouse binding - uint32_t button = 0; - if (strncasecmp(name, "button", strlen("button")) == 0 && - strlen(name) == strlen("button0")) { - button = name[strlen("button")] - '1' + BTN_LEFT; + // check for keysym. If it is the first key, allow mouse bindsyms + if (first_key) { + char *message = NULL; + uint32_t button = get_mouse_bindsym(name, &message); + if (message) { + struct cmd_results *error = + cmd_results_new(CMD_INVALID, "bindsym", message); + free(message); + return error; + } else if (button) { + *type = BINDING_MOUSESYM; + *key_val = button; + return NULL; + } } - if (*type == BINDING_KEYSYM) { - if (button) { - if (first_key) { - *type = BINDING_MOUSE; - *key_val = button; - } else { - return cmd_results_new(CMD_INVALID, "bindsym", - "Mixed button '%s' into key sequence", name); - } - } else if (keysym) { - *key_val = keysym; - } else { - return cmd_results_new(CMD_INVALID, "bindsym", - "Unknown key '%s'", name); - } - } else { - if (button) { - *key_val = button; - } else if (keysym) { + xkb_keysym_t keysym = xkb_keysym_from_name(name, + XKB_KEYSYM_CASE_INSENSITIVE); + if (!keysym) { + if (first_key) { return cmd_results_new(CMD_INVALID, "bindsym", - "Mixed keysym '%s' into button sequence", name); + "Unknown key or button '%s'", name); } else { return cmd_results_new(CMD_INVALID, "bindsym", - "Unknown button '%s'", name); + "Unknown key '%s'", name); } } + *key_val = keysym; } return NULL; } @@ -161,6 +195,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM; bool exclude_titlebar = false; + bool warn = true; // Handle --release and --locked while (argc > 0) { @@ -178,6 +213,8 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, strlen("--input-device=")) == 0) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); + } else if (strcmp("--no-warn", argv[0]) == 0) { + warn = false; } else { break; } @@ -186,7 +223,8 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, } if (binding->flags & (BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR) || exclude_titlebar) { - binding->type = BINDING_MOUSE; + binding->type = binding->type == BINDING_KEYCODE ? + BINDING_MOUSECODE : BINDING_MOUSESYM; } if (argc < 2) { @@ -220,21 +258,22 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, uint32_t *key = calloc(1, sizeof(uint32_t)); if (!key) { free_sway_binding(binding); - free_flat_list(split); + list_free_items_and_destroy(split); return cmd_results_new(CMD_FAILURE, bindtype, "Unable to allocate binding key"); } *key = key_val; list_add(binding->keys, key); } - free_flat_list(split); + list_free_items_and_destroy(split); binding->order = binding_order++; // refine region of interest for mouse binding once we are certain // that this is one if (exclude_titlebar) { binding->flags &= ~BINDING_TITLEBAR; - } else if (binding->type == BINDING_MOUSE) { + } else if (binding->type == BINDING_MOUSECODE + || binding->type == BINDING_MOUSESYM) { binding->flags |= BINDING_TITLEBAR; } @@ -255,8 +294,15 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, for (int i = 0; i < mode_bindings->length; ++i) { struct sway_binding *config_binding = mode_bindings->items[i]; if (binding_key_compare(binding, config_binding)) { - wlr_log(WLR_DEBUG, "overwriting old binding with command '%s'", - config_binding->command); + wlr_log(WLR_INFO, "Overwriting binding '%s' for device '%s' " + "from `%s` to `%s`", argv[0], binding->input, + binding->command, config_binding->command); + if (warn) { + config_add_swaynag_warning("Overwriting binding" + "'%s' for device '%s' to `%s` from `%s`", + argv[0], binding->input, binding->command, + config_binding->command); + } free_sway_binding(config_binding); mode_bindings->items[i] = binding; overwritten = true; @@ -270,7 +316,6 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, wlr_log(WLR_DEBUG, "%s - Bound %s to command `%s` for device '%s'", bindtype, argv[0], binding->command, binding->input); return cmd_results_new(CMD_SUCCESS, NULL, NULL); - } struct cmd_results *cmd_bindsym(int argc, char **argv) { @@ -281,21 +326,25 @@ struct cmd_results *cmd_bindcode(int argc, char **argv) { return cmd_bindsym_or_bindcode(argc, argv, true); } - /** * Execute the command associated to a binding */ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding) { wlr_log(WLR_DEBUG, "running command for binding: %s", binding->command); - config->handler_context.seat = seat; - struct cmd_results *results = execute_command(binding->command, NULL, NULL); - if (results->status == CMD_SUCCESS) { + list_t *res_list = execute_command(binding->command, seat, NULL); + bool success = true; + for (int i = 0; i < res_list->length; ++i) { + struct cmd_results *results = res_list->items[i]; + if (results->status != CMD_SUCCESS) { + wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)", + binding->command, results->error); + success = false; + } + free_cmd_results(results); + } + list_free(res_list); + if (success) { ipc_event_binding(binding); - } else { - wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)", - binding->command, results->error); } - - free_cmd_results(results); } diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c index 7a15709b..9ec28d81 100644 --- a/sway/commands/exec_always.c +++ b/sway/commands/exec_always.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <stdint.h> #include <string.h> diff --git a/sway/commands/focus.c b/sway/commands/focus.c index f6338c55..97ffe91c 100644 --- a/sway/commands/focus.c +++ b/sway/commands/focus.c @@ -193,7 +193,7 @@ static struct cmd_results *focus_output(struct sway_seat *seat, "Expected 'focus output <direction|name>'"); } char *identifier = join_args(argv, argc); - struct sway_output *output = output_by_name(identifier); + struct sway_output *output = output_by_name_or_id(identifier); if (!output) { enum wlr_direction direction; @@ -269,6 +269,9 @@ struct cmd_results *cmd_focus(int argc, char **argv) { } if (argc == 0 && container) { + if (container->scratchpad && !container->workspace) { + root_scratchpad_show(container); + } seat_set_focus_container(seat, container); seat_consider_warp_to_focus(seat); return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c index ac4d6563..7c0f7d7f 100644 --- a/sway/commands/for_window.c +++ b/sway/commands/for_window.c @@ -1,4 +1,3 @@ -#define _XOPEN_SOURCE 500 #include <string.h> #include "sway/commands.h" #include "sway/criteria.h" diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c index ff7cbba7..b78187d9 100644 --- a/sway/commands/fullscreen.c +++ b/sway/commands/fullscreen.c @@ -13,14 +13,14 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) { return error; } if (!root->outputs->length) { - return cmd_results_new(CMD_INVALID, "fullscreen", + return cmd_results_new(CMD_FAILURE, "fullscreen", "Can't run this command while there's no outputs connected."); } struct sway_node *node = config->handler_context.node; struct sway_container *container = config->handler_context.container; struct sway_workspace *workspace = config->handler_context.workspace; if (node->type == N_WORKSPACE && workspace->tiling->length == 0) { - return cmd_results_new(CMD_INVALID, "fullscreen", + return cmd_results_new(CMD_FAILURE, "fullscreen", "Can't fullscreen an empty workspace"); } if (node->type == N_WORKSPACE) { diff --git a/sway/commands/input/events.c b/sway/commands/input/events.c index e7ed69c6..69f46269 100644 --- a/sway/commands/input/events.c +++ b/sway/commands/input/events.c @@ -1,10 +1,69 @@ +#include <limits.h> #include <string.h> #include <strings.h> +#include <wlr/backend/libinput.h> #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "log.h" +static void toggle_send_events_for_device(struct input_config *ic, + struct sway_input_device *input_device) { + struct wlr_input_device *wlr_device = input_device->wlr_device; + if (!wlr_input_device_is_libinput(wlr_device)) { + return; + } + struct libinput_device *libinput_dev + = wlr_libinput_get_device_handle(wlr_device); + + enum libinput_config_send_events_mode mode = + libinput_device_config_send_events_get_mode(libinput_dev); + uint32_t possible = + libinput_device_config_send_events_get_modes(libinput_dev); + + switch (mode) { + case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED: + mode = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + if (possible & mode) { + break; + } + // fall through + case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE: + mode = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; + if (possible & mode) { + break; + } + // fall through + case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED: + default: + mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + break; + } + + ic->send_events = mode; +} + +static void toggle_send_events(struct input_config *ic) { + struct sway_input_device *input_device = NULL; + wl_list_for_each(input_device, &server.input->devices, link) { + if (strcmp(input_device->identifier, ic->identifier) == 0) { + toggle_send_events_for_device(ic, input_device); + } + } +} + +static void toggle_wildcard_send_events() { + struct sway_input_device *input_device = NULL; + wl_list_for_each(input_device, &server.input->devices, link) { + struct input_config *ic = new_input_config(input_device->identifier); + if (!ic) { + break; + } + toggle_send_events_for_device(ic, input_device); + store_input_config(ic); + } +} + struct cmd_results *input_cmd_events(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "events", EXPECTED_AT_LEAST, 1))) { @@ -23,9 +82,24 @@ struct cmd_results *input_cmd_events(int argc, char **argv) { } else if (strcasecmp(argv[0], "disabled_on_external_mouse") == 0) { ic->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; - } else { + } else if (config->reading) { return cmd_results_new(CMD_INVALID, "events", "Expected 'events <enabled|disabled|disabled_on_external_mouse>'"); + } else if (strcasecmp(argv[0], "toggle") == 0) { + if (strcmp(ic->identifier, "*") == 0) { + // Update the device input configs and then reset the wildcard + // config send events mode so that is does not override the device + // ones. The device ones will be applied when attempting to apply + // the wildcard config + toggle_wildcard_send_events(); + ic->send_events = INT_MIN; + } else { + toggle_send_events(ic); + } + } else { + return cmd_results_new(CMD_INVALID, "events", + "Expected 'events <enabled|disabled|disabled_on_external_mouse|" + "toggle>'"); } return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/input/scroll_button.c b/sway/commands/input/scroll_button.c index 1958f23c..d82a1fe1 100644 --- a/sway/commands/input/scroll_button.c +++ b/sway/commands/input/scroll_button.c @@ -1,9 +1,7 @@ -#include <string.h> -#include <strings.h> -#include <errno.h> +#include <libevdev/libevdev.h> #include "sway/config.h" #include "sway/commands.h" -#include "sway/input/input-manager.h" +#include "sway/input/cursor.h" struct cmd_results *input_cmd_scroll_button(int argc, char **argv) { struct cmd_results *error = NULL; @@ -16,22 +14,26 @@ struct cmd_results *input_cmd_scroll_button(int argc, char **argv) { "No input device defined."); } - errno = 0; - char *endptr; - int scroll_button = strtol(*argv, &endptr, 10); - if (endptr == *argv && scroll_button == 0) { - return cmd_results_new(CMD_INVALID, "scroll_button", - "Scroll button identifier must be an integer."); + if (strcmp(*argv, "disable") == 0) { + ic->scroll_button = 0; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } - if (errno == ERANGE) { + + char *message = NULL; + uint32_t button = get_mouse_button(*argv, &message); + if (message) { + error = cmd_results_new(CMD_INVALID, "scroll_button", message); + free(message); + return error; + } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN + || button == SWAY_SCROLL_LEFT || button == SWAY_SCROLL_RIGHT) { return cmd_results_new(CMD_INVALID, "scroll_button", - "Scroll button identifier out of range."); - } - if (scroll_button < 0) { + "X11 axis buttons are not supported for scroll_button"); + } else if (!button) { return cmd_results_new(CMD_INVALID, "scroll_button", - "Scroll button identifier cannot be negative."); + "Unknown button %s", *argv); } - ic->scroll_button = scroll_button; + ic->scroll_button = button; return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/input/xkb_layout.c b/sway/commands/input/xkb_layout.c index 5fccd4a3..43166401 100644 --- a/sway/commands/input/xkb_layout.c +++ b/sway/commands/input/xkb_layout.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" diff --git a/sway/commands/input/xkb_model.c b/sway/commands/input/xkb_model.c index c4d04638..066f632b 100644 --- a/sway/commands/input/xkb_model.c +++ b/sway/commands/input/xkb_model.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" diff --git a/sway/commands/input/xkb_options.c b/sway/commands/input/xkb_options.c index 794ab6e9..09dc4a5c 100644 --- a/sway/commands/input/xkb_options.c +++ b/sway/commands/input/xkb_options.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" diff --git a/sway/commands/input/xkb_rules.c b/sway/commands/input/xkb_rules.c index 257c3288..d3e576e6 100644 --- a/sway/commands/input/xkb_rules.c +++ b/sway/commands/input/xkb_rules.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" diff --git a/sway/commands/input/xkb_variant.c b/sway/commands/input/xkb_variant.c index 3832dc8e..2d7581d1 100644 --- a/sway/commands/input/xkb_variant.c +++ b/sway/commands/input/xkb_variant.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" diff --git a/sway/commands/mode.c b/sway/commands/mode.c index 637ca45e..189e3c1a 100644 --- a/sway/commands/mode.c +++ b/sway/commands/mode.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdbool.h> #include <string.h> #include <strings.h> diff --git a/sway/commands/move.c b/sway/commands/move.c index 7d8c1f1a..72e177e8 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <ctype.h> #include <stdbool.h> #include <string.h> @@ -64,7 +64,7 @@ static struct sway_output *output_in_direction(const char *direction_string, } } - return output_by_name(direction_string); + return output_by_name_or_id(direction_string); } static bool is_parallel(enum sway_container_layout layout, @@ -154,6 +154,8 @@ static void container_move_to_container_from_direction( static void container_move_to_workspace_from_direction( struct sway_container *container, struct sway_workspace *workspace, enum wlr_direction move_dir) { + container->width = container->height = 0; + if (is_parallel(workspace->layout, move_dir)) { wlr_log(WLR_DEBUG, "Reparenting container (parallel)"); int index = @@ -216,6 +218,7 @@ static void container_move_to_container(struct sway_container *container, return; } if (container_is_floating(container)) { + container_move_to_workspace(container, destination->workspace); return; } struct sway_workspace *old_workspace = container->workspace; diff --git a/sway/commands/no_focus.c b/sway/commands/no_focus.c index 61a8de7e..cb81a445 100644 --- a/sway/commands/no_focus.c +++ b/sway/commands/no_focus.c @@ -1,4 +1,3 @@ -#define _XOPEN_SOURCE 500 #include <string.h> #include "sway/commands.h" #include "sway/criteria.h" diff --git a/sway/commands/output/background.c b/sway/commands/output/background.c index 30fb47c4..2cd1b76a 100644 --- a/sway/commands/output/background.c +++ b/sway/commands/output/background.c @@ -116,11 +116,8 @@ struct cmd_results *output_cmd_background(int argc, char **argv) { if (!can_access) { wlr_log(WLR_ERROR, "Unable to access background file '%s': %s", src, strerror(errno)); - if (config->reading && !config->validating) { - swaynag_log(config->swaynag_command, - &config->swaynag_config_errors, - "Unable to access background file '%s'", src); - } + config_add_swaynag_warning("Unable to access background file '%s'", + src); free(src); } else { // Escape double quotes in the final path for swaybg diff --git a/sway/commands/output/transform.c b/sway/commands/output/transform.c index c1555323..ca6f73a4 100644 --- a/sway/commands/output/transform.c +++ b/sway/commands/output/transform.c @@ -45,7 +45,7 @@ struct cmd_results *output_cmd_transform(int argc, char **argv) { return cmd_results_new(CMD_INVALID, "output", "Cannot apply relative transform to all outputs."); } - struct sway_output *s_output = output_by_name(output->name); + struct sway_output *s_output = output_by_name_or_id(output->name); if (s_output == NULL) { return cmd_results_new(CMD_INVALID, "output", "Cannot apply relative transform to unknown output %s", output->name); diff --git a/sway/commands/reload.c b/sway/commands/reload.c index 62105cdc..3ccbbf34 100644 --- a/sway/commands/reload.c +++ b/sway/commands/reload.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <string.h> #include "sway/commands.h" #include "sway/config.h" @@ -24,8 +24,7 @@ static void do_reload(void *data) { if (!load_main_config(config->current_config_path, true, false)) { wlr_log(WLR_ERROR, "Error(s) reloading config"); - list_foreach(bar_ids, free); - list_free(bar_ids); + list_free_items_and_destroy(bar_ids); return; } @@ -42,9 +41,7 @@ static void do_reload(void *data) { } } } - - list_foreach(bar_ids, free); - list_free(bar_ids); + list_free_items_and_destroy(bar_ids); config_update_font_height(true); root_for_each_container(rebuild_textures_iterator, NULL); diff --git a/sway/commands/rename.c b/sway/commands/rename.c index 0cee9293..491dbab0 100644 --- a/sway/commands/rename.c +++ b/sway/commands/rename.c @@ -1,4 +1,3 @@ -#define _XOPEN_SOURCE 500 #include <ctype.h> #include <string.h> #include <strings.h> @@ -82,8 +81,12 @@ struct cmd_results *cmd_rename(int argc, char **argv) { struct sway_workspace *tmp_workspace = workspace_by_name(new_name); if (tmp_workspace) { free(new_name); - return cmd_results_new(CMD_INVALID, "rename", - "Workspace already exists"); + if (tmp_workspace == workspace) { + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } else { + return cmd_results_new(CMD_INVALID, "rename", + "Workspace already exists"); + } } wlr_log(WLR_DEBUG, "renaming workspace '%s' to '%s'", workspace->name, new_name); diff --git a/sway/commands/resize.c b/sway/commands/resize.c index a90d578e..cf5dea02 100644 --- a/sway/commands/resize.c +++ b/sway/commands/resize.c @@ -512,34 +512,42 @@ static struct cmd_results *resize_set_floating(struct sway_container *con, calculate_constraints(&min_width, &max_width, &min_height, &max_height); if (width->amount) { - if (width->unit == RESIZE_UNIT_PPT || - width->unit == RESIZE_UNIT_DEFAULT) { + switch (width->unit) { + case RESIZE_UNIT_PPT: // Convert to px width->amount = con->workspace->width * width->amount / 100; width->unit = RESIZE_UNIT_PX; - } - if (width->unit == RESIZE_UNIT_PX) { + // Falls through + case RESIZE_UNIT_PX: + case RESIZE_UNIT_DEFAULT: width->amount = fmax(min_width, fmin(width->amount, max_width)); grow_width = width->amount - con->width; - con->x -= grow_width / 2; con->width = width->amount; + break; + case RESIZE_UNIT_INVALID: + sway_assert(false, "invalid width unit"); + break; } } if (height->amount) { - if (height->unit == RESIZE_UNIT_PPT || - height->unit == RESIZE_UNIT_DEFAULT) { + switch (height->unit) { + case RESIZE_UNIT_PPT: // Convert to px height->amount = con->workspace->height * height->amount / 100; height->unit = RESIZE_UNIT_PX; - } - if (height->unit == RESIZE_UNIT_PX) { + // Falls through + case RESIZE_UNIT_PX: + case RESIZE_UNIT_DEFAULT: height->amount = fmax(min_height, fmin(height->amount, max_height)); grow_height = height->amount - con->height; - con->y -= grow_height / 2; con->height = height->amount; + break; + case RESIZE_UNIT_INVALID: + sway_assert(false, "invalid height unit"); + break; } } diff --git a/sway/commands/seat.c b/sway/commands/seat.c index 5abb19b0..b8db862b 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) { @@ -26,9 +27,18 @@ struct cmd_results *cmd_seat(int argc, char **argv) { struct cmd_results *res = config_subcommand(argv + 1, argc - 1, seat_handlers, sizeof(seat_handlers)); + if (res && res->status != CMD_SUCCESS) { + free_seat_config(config->handler_context.seat_config); + config->handler_context.seat_config = NULL; + return res; + } - free_seat_config(config->handler_context.seat_config); - config->handler_context.seat_config = NULL; + struct seat_config *sc = + store_seat_config(config->handler_context.seat_config); + if (!config->reading) { + input_manager_apply_seat_config(sc); + } - return res; + config->handler_context.seat_config = NULL; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/seat/attach.c b/sway/commands/seat/attach.c index 6b4bcf1f..0fb17f1d 100644 --- a/sway/commands/seat/attach.c +++ b/sway/commands/seat/attach.c @@ -1,10 +1,7 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <string.h> -#include <strings.h> -#include "sway/input/input-manager.h" #include "sway/commands.h" #include "sway/config.h" -#include "log.h" #include "stringop.h" struct cmd_results *seat_cmd_attach(int argc, char **argv) { @@ -12,19 +9,17 @@ struct cmd_results *seat_cmd_attach(int argc, char **argv) { if ((error = checkarg(argc, "attach", EXPECTED_AT_LEAST, 1))) { return error; } - struct seat_config *current_seat_config = - config->handler_context.seat_config; - if (!current_seat_config) { + if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "attach", "No seat defined"); } - struct seat_config *new_config = new_seat_config(current_seat_config->name); - struct seat_attachment_config *new_attachment = seat_attachment_config_new(); - new_attachment->identifier = strdup(argv[0]); - list_add(new_config->attachments, new_attachment); - - if (!config->validating) { - apply_seat_config(new_config); + struct seat_attachment_config *attachment = seat_attachment_config_new(); + if (!attachment) { + return cmd_results_new(CMD_FAILURE, "attach", + "Failed to allocate seat attachment config"); } + attachment->identifier = strdup(argv[0]); + list_add(config->handler_context.seat_config->attachments, attachment); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/seat/cursor.c b/sway/commands/seat/cursor.c index 1d41a94e..8d9e426a 100644 --- a/sway/commands/seat/cursor.c +++ b/sway/commands/seat/cursor.c @@ -1,12 +1,9 @@ -#define _XOPEN_SOURCE 700 -#ifdef __linux__ +#define _POSIX_C_SOURCE 200809L #include <linux/input-event-codes.h> -#elif __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#endif #include <strings.h> #include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_pointer.h> #include "sway/commands.h" #include "sway/input/cursor.h" @@ -15,20 +12,10 @@ static struct cmd_results *press_or_release(struct sway_cursor *cursor, static const char *expected_syntax = "Expected 'cursor <move> <x> <y>' or " "'cursor <set> <x> <y>' or " - "'curor <press|release> <left|right|1|2|3...>'"; - -struct cmd_results *seat_cmd_cursor(int argc, char **argv) { - struct cmd_results *error = NULL; - if ((error = checkarg(argc, "cursor", EXPECTED_AT_LEAST, 2))) { - return error; - } - struct sway_seat *seat = config->handler_context.seat; - if (!seat) { - return cmd_results_new(CMD_FAILURE, "cursor", "No seat defined"); - } - - struct sway_cursor *cursor = seat->cursor; + "'curor <press|release> <button[1-9]|event-name-or-code>'"; +static struct cmd_results *handle_command(struct sway_cursor *cursor, + int argc, char **argv) { if (strcasecmp(argv[0], "move") == 0) { if (argc < 3) { return cmd_results_new(CMD_INVALID, "cursor", expected_syntax); @@ -50,6 +37,7 @@ struct cmd_results *seat_cmd_cursor(int argc, char **argv) { if (argc < 2) { return cmd_results_new(CMD_INVALID, "cursor", expected_syntax); } + struct cmd_results *error = NULL; if ((error = press_or_release(cursor, argv[0], argv[1]))) { return error; } @@ -58,6 +46,40 @@ struct cmd_results *seat_cmd_cursor(int argc, char **argv) { return cmd_results_new(CMD_SUCCESS, NULL, NULL); } +struct cmd_results *seat_cmd_cursor(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "cursor", EXPECTED_AT_LEAST, 2))) { + return error; + } + struct seat_config *sc = config->handler_context.seat_config; + if (!sc) { + return cmd_results_new(CMD_FAILURE, "cursor", "No seat defined"); + } + + if (config->reading || !config->active) { + return cmd_results_new(CMD_DEFER, NULL, NULL); + } + + if (strcmp(sc->name, "*") != 0) { + struct sway_seat *seat = input_manager_get_seat(sc->name); + if (!seat) { + return cmd_results_new(CMD_FAILURE, "cursor", + "Failed to get seat"); + } + error = handle_command(seat->cursor, argc, argv); + } else { + struct sway_seat *seat = NULL; + wl_list_for_each(seat, &server.input->seats, link) { + error = handle_command(seat->cursor, argc, argv); + if ((error && error->status != CMD_SUCCESS)) { + break; + } + } + } + + return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + static struct cmd_results *press_or_release(struct sway_cursor *cursor, char *action, char *button_str) { enum wlr_button_state state; @@ -70,15 +92,35 @@ static struct cmd_results *press_or_release(struct sway_cursor *cursor, return cmd_results_new(CMD_INVALID, "cursor", expected_syntax); } - if (strcasecmp(button_str, "left") == 0) { - button = BTN_LEFT; - } else if (strcasecmp(button_str, "right") == 0) { - button = BTN_RIGHT; - } else { - button = strtol(button_str, NULL, 10); - if (button == 0) { - return cmd_results_new(CMD_INVALID, "cursor", expected_syntax); - } + char *message = NULL; + button = get_mouse_button(button_str, &message); + if (message) { + struct cmd_results *error = + cmd_results_new(CMD_INVALID, "cursor", message); + free(message); + return error; + } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN + || button == SWAY_SCROLL_LEFT || button == SWAY_SCROLL_RIGHT) { + // Dispatch axis event + enum wlr_axis_orientation orientation = + (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN) + ? WLR_AXIS_ORIENTATION_VERTICAL + : WLR_AXIS_ORIENTATION_HORIZONTAL; + double delta = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_LEFT) + ? -1 : 1; + struct wlr_event_pointer_axis event = { + .device = NULL, + .time_msec = 0, + .source = WLR_AXIS_SOURCE_WHEEL, + .orientation = orientation, + .delta = delta * 15, + .delta_discrete = delta + }; + dispatch_cursor_axis(cursor, &event); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } else if (!button) { + return cmd_results_new(CMD_INVALID, "curor", + "Unknown button %s", button_str); } dispatch_cursor_button(cursor, NULL, 0, button, state); return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/seat/fallback.c b/sway/commands/seat/fallback.c index a0ddf3ef..8f1ab12c 100644 --- a/sway/commands/seat/fallback.c +++ b/sway/commands/seat/fallback.c @@ -1,27 +1,18 @@ -#include <string.h> -#include <strings.h> #include "sway/config.h" #include "sway/commands.h" -#include "sway/input/input-manager.h" #include "util.h" struct cmd_results *seat_cmd_fallback(int argc, char **argv) { struct cmd_results *error = NULL; - if ((error = checkarg(argc, "fallback", EXPECTED_AT_LEAST, 1))) { + if ((error = checkarg(argc, "fallback", EXPECTED_EQUAL_TO, 1))) { return error; } - struct seat_config *current_seat_config = - config->handler_context.seat_config; - if (!current_seat_config) { + if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "fallback", "No seat defined"); } - struct seat_config *new_config = - new_seat_config(current_seat_config->name); - - new_config->fallback = parse_boolean(argv[0], false); - if (!config->validating) { - apply_seat_config(new_config); - } + config->handler_context.seat_config->fallback = + parse_boolean(argv[0], false); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/seat/hide_cursor.c b/sway/commands/seat/hide_cursor.c new file mode 100644 index 00000000..343573b5 --- /dev/null +++ b/sway/commands/seat/hide_cursor.c @@ -0,0 +1,29 @@ +#define _POSIX_C_SOURCE 200809L +#include <string.h> +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/input/seat.h" +#include "stringop.h" + +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); + if (*end) { + return cmd_results_new(CMD_INVALID, "hide_cursor", + "Expected an integer timeout"); + } + if (timeout < 100 && timeout != 0) { + timeout = 100; + } + config->handler_context.seat_config->hide_cursor_timeout = timeout; + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/set.c b/sway/commands/set.c index be51230b..d912e4fd 100644 --- a/sway/commands/set.c +++ b/sway/commands/set.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdio.h> #include <string.h> #include <strings.h> diff --git a/sway/commands/split.c b/sway/commands/split.c index ed106a86..84385fa9 100644 --- a/sway/commands/split.c +++ b/sway/commands/split.c @@ -18,6 +18,10 @@ static struct cmd_results *do_split(int layout) { workspace_split(ws, layout); } + if (con && con->parent && con->parent->parent) { + container_flatten(con->parent->parent); + } + arrange_workspace(ws); return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/swap.c b/sway/commands/swap.c index 08860264..670d6bca 100644 --- a/sway/commands/swap.c +++ b/sway/commands/swap.c @@ -108,7 +108,7 @@ static void container_swap(struct sway_container *con1, container_set_fullscreen(con2, false); } - struct sway_seat *seat = input_manager_get_default_seat(); + struct sway_seat *seat = config->handler_context.seat; struct sway_container *focus = seat_get_focused_container(seat); struct sway_workspace *vis1 = output_get_active_workspace(con1->workspace->output); 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/commands/title_align.c b/sway/commands/title_align.c new file mode 100644 index 00000000..82578186 --- /dev/null +++ b/sway/commands/title_align.c @@ -0,0 +1,30 @@ +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" +#include "sway/tree/container.h" +#include "sway/tree/root.h" + +struct cmd_results *cmd_title_align(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "title_align", EXPECTED_AT_LEAST, 1))) { + return error; + } + + if (strcmp(argv[0], "left") == 0) { + config->title_align = ALIGN_LEFT; + } else if (strcmp(argv[0], "center") == 0) { + config->title_align = ALIGN_CENTER; + } else if (strcmp(argv[0], "right") == 0) { + config->title_align = ALIGN_RIGHT; + } else { + return cmd_results_new(CMD_INVALID, "title_align", + "Expected 'title_align left|center|right'"); + } + + for (int i = 0; i < root->outputs->length; ++i) { + struct sway_output *output = root->outputs->items[i]; + output_damage_whole(output); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/titlebar_border_thickness.c b/sway/commands/titlebar_border_thickness.c new file mode 100644 index 00000000..c1e9bb52 --- /dev/null +++ b/sway/commands/titlebar_border_thickness.c @@ -0,0 +1,30 @@ +#include <string.h> +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" +#include "sway/tree/arrange.h" +#include "log.h" + +struct cmd_results *cmd_titlebar_border_thickness(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "titlebar_border_thickness", EXPECTED_EQUAL_TO, 1))) { + return error; + } + + char *inv; + int value = strtol(argv[0], &inv, 10); + if (*inv != '\0' || value < 0 || value > config->titlebar_v_padding) { + return cmd_results_new(CMD_FAILURE, "titlebar_border_thickness", + "Invalid size specified"); + } + + config->titlebar_border_thickness = value; + + for (int i = 0; i < root->outputs->length; ++i) { + struct sway_output *output = root->outputs->items[i]; + arrange_workspace(output_get_active_workspace(output)); + output_damage_whole(output); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/titlebar_padding.c b/sway/commands/titlebar_padding.c new file mode 100644 index 00000000..a642e945 --- /dev/null +++ b/sway/commands/titlebar_padding.c @@ -0,0 +1,42 @@ +#include <string.h> +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" +#include "sway/tree/arrange.h" +#include "log.h" + +struct cmd_results *cmd_titlebar_padding(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "titlebar_padding", EXPECTED_AT_LEAST, 1))) { + return error; + } + + char *inv; + int h_value = strtol(argv[0], &inv, 10); + if (*inv != '\0' || h_value < 0 || h_value < config->titlebar_border_thickness) { + return cmd_results_new(CMD_FAILURE, "titlebar_padding", + "Invalid size specified"); + } + + int v_value; + if (argc == 1) { + v_value = h_value; + } else { + v_value = strtol(argv[1], &inv, 10); + if (*inv != '\0' || v_value < 0 || v_value < config->titlebar_border_thickness) { + return cmd_results_new(CMD_FAILURE, "titlebar_padding", + "Invalid size specified"); + } + } + + config->titlebar_v_padding = v_value; + config->titlebar_h_padding = h_value; + + for (int i = 0; i < root->outputs->length; ++i) { + struct sway_output *output = root->outputs->items[i]; + arrange_workspace(output_get_active_workspace(output)); + output_damage_whole(output); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c index 92118ecf..211b344d 100644 --- a/sway/commands/workspace.c +++ b/sway/commands/workspace.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <ctype.h> #include <limits.h> #include <string.h> @@ -33,12 +33,12 @@ static struct workspace_config *workspace_config_find_or_create(char *ws_name) { void free_workspace_config(struct workspace_config *wsc) { free(wsc->workspace); - free_flat_list(wsc->outputs); + list_free_items_and_destroy(wsc->outputs); free(wsc); } static void prevent_invalid_outer_gaps(struct workspace_config *wsc) { - if (wsc->gaps_outer.top != INT_MIN && + if (wsc->gaps_outer.top != INT_MIN && wsc->gaps_outer.top < -wsc->gaps_inner) { wsc->gaps_outer.top = -wsc->gaps_inner; } diff --git a/sway/config.c b/sway/config.c index 6f65d0c2..18fee404 100644 --- a/sway/config.c +++ b/sway/config.c @@ -1,5 +1,4 @@ -#define _POSIX_C_SOURCE 200809L -#define _XOPEN_SOURCE 700 +#define _XOPEN_SOURCE 700 // for realpath #include <stdio.h> #include <stdbool.h> #include <stdlib.h> @@ -14,11 +13,7 @@ #include <limits.h> #include <dirent.h> #include <strings.h> -#ifdef __linux__ #include <linux/input-event-codes.h> -#elif __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#endif #include <wlr/types/wlr_output.h> #include "sway/input/input-manager.h" #include "sway/input/seat.h" @@ -31,7 +26,6 @@ #include "sway/tree/workspace.h" #include "cairo.h" #include "pango.h" -#include "readline.h" #include "stringop.h" #include "list.h" #include "log.h" @@ -39,26 +33,24 @@ struct sway_config *config = NULL; static void free_mode(struct sway_mode *mode) { - int i; - if (!mode) { return; } free(mode->name); if (mode->keysym_bindings) { - for (i = 0; i < mode->keysym_bindings->length; i++) { + for (int i = 0; i < mode->keysym_bindings->length; i++) { free_sway_binding(mode->keysym_bindings->items[i]); } list_free(mode->keysym_bindings); } if (mode->keycode_bindings) { - for (i = 0; i < mode->keycode_bindings->length; i++) { + for (int i = 0; i < mode->keycode_bindings->length; i++) { free_sway_binding(mode->keycode_bindings->items[i]); } list_free(mode->keycode_bindings); } if (mode->mouse_bindings) { - for (i = 0; i < mode->mouse_bindings->length; i++) { + for (int i = 0; i < mode->mouse_bindings->length; i++) { free_sway_binding(mode->mouse_bindings->items[i]); } list_free(mode->mouse_bindings); @@ -214,6 +206,10 @@ static void config_defaults(struct sway_config *config) { config->popup_during_fullscreen = POPUP_SMART; config->xwayland = true; + config->titlebar_border_thickness = 1; + config->titlebar_h_padding = 5; + config->titlebar_v_padding = 4; + // floating view config->floating_maximum_width = 0; config->floating_maximum_height = 0; @@ -231,7 +227,9 @@ static void config_defaults(struct sway_config *config) { config->auto_back_and_forth = false; config->reading = false; 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; @@ -313,27 +311,16 @@ static char *get_config_path(void) { SYSCONFDIR "/i3/config", }; - if (!getenv("XDG_CONFIG_HOME")) { - char *home = getenv("HOME"); - char *config_home = malloc(strlen(home) + strlen("/.config") + 1); - if (!config_home) { - wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config"); - } else { - strcpy(config_home, home); - strcat(config_home, "/.config"); - setenv("XDG_CONFIG_HOME", config_home, 1); - wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home); - free(config_home); - } + char *config_home = getenv("XDG_CONFIG_HOME"); + if (!config_home || !*config_home) { + config_paths[1] = "$HOME/.config/sway/config"; + config_paths[3] = "$HOME/.config/i3/config"; } - wordexp_t p; - char *path; - - int i; - for (i = 0; i < (int)(sizeof(config_paths) / sizeof(char *)); ++i) { - if (wordexp(config_paths[i], &p, 0) == 0) { - path = strdup(p.we_wordv[0]); + for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { + wordexp_t p; + if (wordexp(config_paths[i], &p, WRDE_UNDEF) == 0) { + char *path = strdup(p.we_wordv[0]); wordfree(&p); if (file_exists(path)) { return path; @@ -342,7 +329,7 @@ static char *get_config_path(void) { } } - return NULL; // Not reached + return NULL; } static bool load_config(const char *path, struct sway_config *config, @@ -402,7 +389,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) { &old_config->swaynag_config_errors, sizeof(struct swaynag_instance)); - create_default_output_configs(); + input_manager_reset_all_inputs(); } config->current_config_path = path; @@ -454,7 +441,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) { } } - free_flat_list(secconfigs); + list_free_items_and_destroy(secconfigs); } */ @@ -475,6 +462,11 @@ 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(); + for (int i = 0; i < config->seat_configs->length; i++) { + input_manager_apply_seat_config(config->seat_configs->items[i]); + } } if (old_config) { @@ -581,29 +573,56 @@ bool load_include_configs(const char *path, struct sway_config *config, return true; } -static int detect_brace_on_following_line(FILE *file, char *line, - int line_number) { - int lines = 0; - if (line[strlen(line) - 1] != '{' && line[strlen(line) - 1] != '}') { - char *peeked = NULL; - long position = 0; - do { - free(peeked); - peeked = peek_line(file, lines, &position); - if (peeked) { - peeked = strip_whitespace(peeked); +// get line, with backslash continuation +static ssize_t getline_with_cont(char **lineptr, size_t *line_size, FILE *file, + int *nlines) { + char *next_line = NULL; + size_t next_line_size = 0; + ssize_t nread = getline(lineptr, line_size, file); + *nlines = nread == -1 ? 0 : 1; + while (nread >= 2 && strcmp(&(*lineptr)[nread - 2], "\\\n") == 0) { + ssize_t next_nread = getline(&next_line, &next_line_size, file); + if (next_nread == -1) { + break; + } + (*nlines)++; + + nread += next_nread - 2; + if ((ssize_t) *line_size < nread + 1) { + *line_size = nread + 1; + *lineptr = realloc(*lineptr, *line_size); + if (!*lineptr) { + nread = -1; + break; } - lines++; - } while (peeked && strlen(peeked) == 0); + } + strcpy(&(*lineptr)[nread - next_nread], next_line); + } + free(next_line); + return nread; +} - if (peeked && strlen(peeked) == 1 && peeked[0] == '{') { - fseek(file, position, SEEK_SET); - } else { - lines = 0; +static int detect_brace(FILE *file) { + int ret = 0; + int lines = 0; + long pos = ftell(file); + char *line = NULL; + size_t line_size = 0; + while ((getline(&line, &line_size, file)) != -1) { + lines++; + strip_whitespace(line); + if (*line) { + if (strcmp(line, "{") == 0) { + ret = lines; + } + break; } - free(peeked); } - return lines; + free(line); + if (ret == 0) { + fseek(file, pos, SEEK_SET); + } + return ret; } static char *expand_line(const char *block, const char *line, bool add_brace) { @@ -645,58 +664,51 @@ bool read_config(FILE *file, struct sway_config *config, bool success = true; int line_number = 0; - char *line; + char *line = NULL; + size_t line_size = 0; + ssize_t nread; list_t *stack = create_list(); size_t read = 0; - while (!feof(file)) { - char *block = stack->length ? stack->items[0] : NULL; - line = read_line(file); - if (!line) { - continue; - } - line_number++; - wlr_log(WLR_DEBUG, "Read line %d: %s", line_number, line); - + int nlines = 0; + while ((nread = getline_with_cont(&line, &line_size, file, &nlines)) != -1) { if (reading_main_config) { - size_t length = strlen(line); - - if (read + length > config_size) { + if (read + nread > config_size) { wlr_log(WLR_ERROR, "Config file changed during reading"); - list_foreach(stack, free); - list_free(stack); - free(line); - return false; + success = false; + break; } - strcpy(this_config + read, line); - if (line_number != 1) { - this_config[read - 1] = '\n'; - } - read += length + 1; + strcpy(&this_config[read], line); + read += nread; } - line = strip_whitespace(line); - if (line[0] == '#') { - free(line); - continue; + if (line[nread - 1] == '\n') { + line[nread - 1] = '\0'; } - if (strlen(line) == 0) { - free(line); + + line_number += nlines; + wlr_log(WLR_DEBUG, "Read line %d: %s", line_number, line); + + strip_whitespace(line); + if (!*line || line[0] == '#') { continue; } - int brace_detected = detect_brace_on_following_line(file, line, - line_number); - if (brace_detected > 0) { - line_number += brace_detected; - wlr_log(WLR_DEBUG, "Detected open brace on line %d", line_number); + int brace_detected = 0; + if (line[strlen(line) - 1] != '{' && line[strlen(line) - 1] != '}') { + brace_detected = detect_brace(file); + if (brace_detected > 0) { + line_number += brace_detected; + wlr_log(WLR_DEBUG, "Detected open brace on line %d", line_number); + } } + char *block = stack->length ? stack->items[0] : NULL; char *expanded = expand_line(block, line, brace_detected > 0); if (!expanded) { - list_foreach(stack, free); - list_free(stack); - free(line); - return false; + success = false; + break; } + config->current_config_line_number = line_number; + config->current_config_line = line; struct cmd_results *res; if (block && strcmp(block, "<commands>") == 0) { // Special case @@ -753,15 +765,40 @@ bool read_config(FILE *file, struct sway_config *config, default:; } free(expanded); - free(line); free_cmd_results(res); } - list_foreach(stack, free); - list_free(stack); + free(line); + list_free_items_and_destroy(stack); + config->current_config_line_number = 0; + config->current_config_line = NULL; return success; } +void config_add_swaynag_warning(char *fmt, ...) { + if (config->reading && !config->validating) { + va_list args; + va_start(args, fmt); + size_t length = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + char *temp = malloc(length + 1); + if (!temp) { + wlr_log(WLR_ERROR, "Failed to allocate buffer for warning."); + return; + } + + va_start(args, fmt); + vsnprintf(temp, length, fmt, args); + va_end(args); + + swaynag_log(config->swaynag_command, &config->swaynag_config_errors, + "Warning on line %i (%s) '%s': %s", + config->current_config_line_number, config->current_config_path, + config->current_config_line, temp); + } +} + char *do_var_replacement(char *str) { int i; char *find = str; diff --git a/sway/config/bar.c b/sway/config/bar.c index 7bca5f49..701bf051 100644 --- a/sway/config/bar.c +++ b/sway/config/bar.c @@ -1,5 +1,4 @@ #define _POSIX_C_SOURCE 200809L -#define _XOPEN_SOURCE 700 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> @@ -13,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" @@ -50,13 +50,10 @@ void free_bar_config(struct bar_config *bar) { free(bar->font); free(bar->separator_symbol); for (int i = 0; i < bar->bindings->length; i++) { - struct bar_binding *binding = bar->bindings->items[i]; - free_bar_binding(binding); + free_bar_binding(bar->bindings->items[i]); } list_free(bar->bindings); - if (bar->outputs) { - free_flat_list(bar->outputs); - } + list_free_items_and_destroy(bar->outputs); if (bar->pid != 0) { terminate_swaybar(bar->pid); } @@ -81,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); } @@ -95,15 +96,18 @@ struct bar_config *default_bar_config(void) { bar->pango_markup = false; bar->swaybar_command = NULL; bar->font = NULL; - bar->height = -1; + bar->height = 0; bar->workspace_buttons = true; bar->wrap_scroll = false; bar->separator_symbol = NULL; bar->strip_workspace_numbers = false; + bar->strip_workspace_name = false; bar->binding_mode_indicator = true; bar->verbose = false; bar->pid = 0; bar->modifier = get_modifier_mask_by_name("Mod4"); + bar->status_padding = 1; + bar->status_edge_padding = 3; if (!(bar->mode = strdup("dock"))) { goto cleanup; } @@ -168,6 +172,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/input.c b/sway/config/input.c index d5d2d90b..d649d34d 100644 --- a/sway/config/input.c +++ b/sway/config/input.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <limits.h> #include <float.h> diff --git a/sway/config/output.c b/sway/config/output.c index 07543e3c..f24e7d66 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdbool.h> #include <string.h> @@ -179,10 +179,6 @@ void apply_output_config(struct output_config *oc, struct sway_output *output) { if (oc && oc->enabled == 0) { if (output->enabled) { - if (output->bg_pid != 0) { - terminate_swaybg(output->bg_pid); - output->bg_pid = 0; - } output_disable(output); wlr_output_layout_remove(root->output_layout, wlr_output); } @@ -276,18 +272,86 @@ void apply_output_config(struct output_config *oc, struct sway_output *output) { } } -static struct output_config *get_output_config(char *name, char *identifier) { +static void default_output_config(struct output_config *oc, + struct wlr_output *wlr_output) { + oc->enabled = 1; + if (!wl_list_empty(&wlr_output->modes)) { + struct wlr_output_mode *mode = + wl_container_of(wlr_output->modes.prev, mode, link); + oc->width = mode->width; + oc->height = mode->height; + oc->refresh_rate = mode->refresh; + } + oc->x = oc->y = -1; + oc->scale = 1; + oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; +} + +static struct output_config *get_output_config(char *identifier, + struct sway_output *sway_output) { + const char *name = sway_output->wlr_output->name; + struct output_config *oc_name = NULL; int i = list_seq_find(config->output_configs, output_name_cmp, name); if (i >= 0) { - return config->output_configs->items[i]; + oc_name = config->output_configs->items[i]; } + struct output_config *oc_id = NULL; i = list_seq_find(config->output_configs, output_name_cmp, identifier); if (i >= 0) { - return config->output_configs->items[i]; + oc_id = config->output_configs->items[i]; + } + + struct output_config *result = result = new_output_config("temp"); + if (config->reloading) { + default_output_config(result, sway_output->wlr_output); + } + if (oc_name && oc_id) { + // Generate a config named `<identifier> on <name>` which contains a + // merged copy of the identifier on name. This will make sure that both + // identifier and name configs are respected, with identifier getting + // priority + size_t length = snprintf(NULL, 0, "%s on %s", identifier, name) + 1; + char *temp = malloc(length); + snprintf(temp, length, "%s on %s", identifier, name); + + free(result->name); + result->name = temp; + merge_output_config(result, oc_name); + merge_output_config(result, oc_id); + + wlr_log(WLR_DEBUG, "Generated output config \"%s\" (enabled: %d)" + " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" + " (dpms %d)", result->name, result->enabled, result->width, + result->height, result->refresh_rate, result->x, result->y, + result->scale, result->transform, result->background, + result->background_option, result->dpms_state); + } else if (oc_name) { + // No identifier config, just return a copy of the name config + free(result->name); + result->name = strdup(name); + merge_output_config(result, oc_name); + } else if (oc_id) { + // No name config, just return a copy of the identifier config + free(result->name); + result->name = strdup(identifier); + merge_output_config(result, oc_id); + } else if (config->reloading) { + // Neither config exists, but we need to reset the output so create a + // default config for the output and if a wildcard config exists, merge + // that on top + free(result->name); + result->name = strdup("*"); + i = list_seq_find(config->output_configs, output_name_cmp, "*"); + if (i >= 0) { + merge_output_config(result, config->output_configs->items[i]); + } + } else { + free_output_config(result); + result = NULL; } - return NULL; + return result; } void apply_output_config_to_outputs(struct output_config *oc) { @@ -301,14 +365,17 @@ void apply_output_config_to_outputs(struct output_config *oc) { char *name = sway_output->wlr_output->name; output_get_identifier(id, sizeof(id), sway_output); if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) { - struct output_config *current = oc; + struct output_config *current = new_output_config(oc->name); + merge_output_config(current, oc); if (wildcard) { - struct output_config *tmp = get_output_config(name, id); + struct output_config *tmp = get_output_config(id, sway_output); if (tmp) { + free_output_config(current); current = tmp; } } apply_output_config(current, sway_output); + free_output_config(current); if (!wildcard) { // Stop looking if the output config isn't applicable to all @@ -329,28 +396,3 @@ void free_output_config(struct output_config *oc) { free(oc->background_fallback); free(oc); } - -static void default_output_config(struct output_config *oc, - struct wlr_output *wlr_output) { - oc->enabled = 1; - if (!wl_list_empty(&wlr_output->modes)) { - struct wlr_output_mode *mode = - wl_container_of(wlr_output->modes.prev, mode, link); - oc->width = mode->width; - oc->height = mode->height; - oc->refresh_rate = mode->refresh; - } - oc->x = oc->y = -1; - oc->scale = 1; - oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; -} - -void create_default_output_configs(void) { - struct sway_output *sway_output; - wl_list_for_each(sway_output, &root->all_outputs, link) { - char *name = sway_output->wlr_output->name; - struct output_config *oc = new_output_config(name); - default_output_config(oc, sway_output->wlr_output); - list_add(config->output_configs, oc); - } -} diff --git a/sway/config/seat.c b/sway/config/seat.c index 46456caf..d7316c68 100644 --- a/sway/config/seat.c +++ b/sway/config/seat.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <string.h> #include "sway/config.h" @@ -11,7 +11,6 @@ struct seat_config *new_seat_config(const char* name) { return NULL; } - wlr_log(WLR_DEBUG, "new_seat_config(%s)", name); seat->name = strdup(name); if (!sway_assert(seat->name, "could not allocate name for seat")) { free(seat); @@ -26,10 +25,57 @@ struct seat_config *new_seat_config(const char* name) { free(seat); return NULL; } + seat->hide_cursor_timeout = -1; return seat; } +static void merge_wildcard_on_all(struct seat_config *wildcard) { + for (int i = 0; i < config->seat_configs->length; i++) { + struct seat_config *sc = config->seat_configs->items[i]; + if (strcmp(wildcard->name, sc->name) != 0) { + wlr_log(WLR_DEBUG, "Merging seat * config on %s", sc->name); + merge_seat_config(sc, wildcard); + } + } +} + +struct seat_config *store_seat_config(struct seat_config *sc) { + bool wildcard = strcmp(sc->name, "*") == 0; + if (wildcard) { + merge_wildcard_on_all(sc); + } + + int i = list_seq_find(config->seat_configs, seat_name_cmp, sc->name); + if (i >= 0) { + wlr_log(WLR_DEBUG, "Merging on top of existing seat config"); + struct seat_config *current = config->seat_configs->items[i]; + merge_seat_config(current, sc); + free_seat_config(sc); + sc = current; + } else if (!wildcard) { + wlr_log(WLR_DEBUG, "Adding non-wildcard seat config"); + i = list_seq_find(config->seat_configs, seat_name_cmp, "*"); + if (i >= 0) { + wlr_log(WLR_DEBUG, "Merging on top of seat * config"); + struct seat_config *current = new_seat_config(sc->name); + merge_seat_config(current, config->seat_configs->items[i]); + merge_seat_config(current, sc); + free_seat_config(sc); + sc = current; + } + list_add(config->seat_configs, sc); + } else { + // New wildcard config. Just add it + wlr_log(WLR_DEBUG, "Adding seat * config"); + list_add(config->seat_configs, sc); + } + + wlr_log(WLR_DEBUG, "Config stored for seat %s", sc->name); + + return sc; +} + struct seat_attachment_config *seat_attachment_config_new(void) { struct seat_attachment_config *attachment = calloc(1, sizeof(struct seat_attachment_config)); @@ -65,11 +111,6 @@ static void merge_seat_attachment_config(struct seat_attachment_config *dest, } void merge_seat_config(struct seat_config *dest, struct seat_config *source) { - if (source->name) { - free(dest->name); - dest->name = strdup(source->name); - } - if (source->fallback != -1) { dest->fallback = source->fallback; } @@ -97,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) { @@ -117,11 +162,8 @@ void free_seat_config(struct seat_config *seat) { free(seat->name); for (int i = 0; i < seat->attachments->length; ++i) { - struct seat_attachment_config *attachment = - seat->attachments->items[i]; - seat_attachment_config_free(attachment); + seat_attachment_config_free(seat->attachments->items[i]); } - list_free(seat->attachments); free(seat); } diff --git a/sway/criteria.c b/sway/criteria.c index 3393852c..54583b04 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <stdio.h> #include <stdbool.h> diff --git a/sway/desktop/output.c b/sway/desktop/output.c index d649100f..04c9b4f6 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -29,23 +29,13 @@ #include "sway/tree/view.h" #include "sway/tree/workspace.h" -struct sway_output *output_by_name(const char *name) { +struct sway_output *output_by_name_or_id(const char *name_or_id) { for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; - if (strcasecmp(output->wlr_output->name, name) == 0) { - return output; - } - } - return NULL; -} - -struct sway_output *output_by_identifier(const char *identifier) { - for (int i = 0; i < root->outputs->length; ++i) { - struct sway_output *output = root->outputs->items[i]; - char output_identifier[128]; - snprintf(output_identifier, sizeof(output_identifier), "%s %s %s", output->wlr_output->make, - output->wlr_output->model, output->wlr_output->serial); - if (strcasecmp(output_identifier, identifier) == 0) { + char identifier[128]; + output_get_identifier(identifier, sizeof(identifier), output); + if (strcasecmp(identifier, name_or_id) == 0 + || strcasecmp(output->wlr_output->name, name_or_id) == 0) { return output; } } @@ -110,7 +100,7 @@ static bool get_surface_box(struct surface_iterator_data *data, } struct wlr_box rotated_box; - wlr_box_rotated_bounds(&box, data->rotation, &rotated_box); + wlr_box_rotated_bounds(&rotated_box, &box, data->rotation); struct wlr_box output_box = { .width = output->width, @@ -118,7 +108,7 @@ static bool get_surface_box(struct surface_iterator_data *data, }; struct wlr_box intersection; - return wlr_box_intersection(&output_box, &rotated_box, &intersection); + return wlr_box_intersection(&intersection, &output_box, &rotated_box); } static void output_for_each_surface_iterator(struct wlr_surface *surface, @@ -310,7 +300,7 @@ static int scale_length(int length, int offset, float scale) { return round((offset + length) * scale) - round(offset * scale); } -static void scale_box(struct wlr_box *box, float scale) { +void scale_box(struct wlr_box *box, float scale) { box->width = scale_length(box->width, box->x, scale); box->height = scale_length(box->height, box->y, scale); box->x = round(box->x * scale); @@ -433,7 +423,7 @@ static void damage_surface_iterator(struct sway_output *output, } if (whole) { - wlr_box_rotated_bounds(&box, rotation, &box); + wlr_box_rotated_bounds(&box, &box, rotation); wlr_output_damage_add_box(output->damage, &box); } @@ -568,6 +558,8 @@ void handle_new_output(struct wl_listener *listener, void *data) { if (!oc || oc->enabled) { output_enable(output, oc); + } else { + wlr_output_enable(output->wlr_output, false); } transaction_commit_dirty(); diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 8d4a701b..a38c6a07 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -49,13 +49,6 @@ static int scale_length(int length, int offset, float scale) { return round((offset + length) * scale) - round(offset * scale); } -static void scale_box(struct wlr_box *box, float scale) { - box->width = scale_length(box->width, box->x, scale); - box->height = scale_length(box->height, box->y, scale); - box->x = round(box->x * scale); - box->y = round(box->y * scale); -} - static void scissor_output(struct wlr_output *wlr_output, pixman_box32_t *rect) { struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); @@ -73,7 +66,7 @@ static void scissor_output(struct wlr_output *wlr_output, enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); - wlr_box_transform(&box, transform, ow, oh, &box); + wlr_box_transform(&box, &box, transform, ow, oh); wlr_renderer_scissor(renderer, &box); } @@ -164,7 +157,7 @@ static void render_drag_icons(struct sway_output *output, // _box.x and .y are expected to be layout-local // _box.width and .height are expected to be output-buffer-local -static void render_rect(struct wlr_output *wlr_output, +void render_rect(struct wlr_output *wlr_output, pixman_region32_t *output_damage, const struct wlr_box *_box, float color[static 4]) { struct wlr_renderer *renderer = @@ -197,7 +190,7 @@ damage_finish: pixman_region32_fini(&damage); } -static void premultiply_alpha(float color[4], float opacity) { +void premultiply_alpha(float color[4], float opacity) { color[3] *= opacity; color[0] *= color[3]; color[1] *= color[3]; @@ -261,7 +254,7 @@ static void render_saved_view(struct sway_view *view, }; struct wlr_box intersection; - bool intersects = wlr_box_intersection(&output_box, &box, &intersection); + bool intersects = wlr_box_intersection(&intersection, &output_box, &box); if (!intersects) { return; } @@ -368,6 +361,10 @@ static void render_titlebar(struct sway_output *output, children->items[children->length - 1] == con; double output_x = output->wlr_output->lx; double output_y = output->wlr_output->ly; + int titlebar_border_thickness = config->titlebar_border_thickness; + int titlebar_h_padding = config->titlebar_h_padding; + int titlebar_v_padding = config->titlebar_v_padding; + enum alignment title_align = config->title_align; // Single pixel bar above title memcpy(&color, colors->border, sizeof(float) * 4); @@ -375,7 +372,7 @@ static void render_titlebar(struct sway_output *output, box.x = x; box.y = y; box.width = width; - box.height = TITLEBAR_BORDER_THICKNESS; + box.height = titlebar_border_thickness; scale_box(&box, output_scale); render_rect(output->wlr_output, output_damage, &box, color); @@ -391,45 +388,51 @@ static void render_titlebar(struct sway_output *output, } } box.x = x + left_offset; - box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS; + box.y = y + container_titlebar_height() - titlebar_border_thickness; box.width = width - left_offset - right_offset; - box.height = TITLEBAR_BORDER_THICKNESS; + box.height = titlebar_border_thickness; scale_box(&box, output_scale); render_rect(output->wlr_output, output_damage, &box, color); if (layout == L_TABBED) { // Single pixel left edge box.x = x; - box.y = y + TITLEBAR_BORDER_THICKNESS; - box.width = TITLEBAR_BORDER_THICKNESS; + box.y = y + titlebar_border_thickness; + box.width = titlebar_border_thickness; box.height = - container_titlebar_height() - TITLEBAR_BORDER_THICKNESS * 2; + container_titlebar_height() - titlebar_border_thickness * 2; scale_box(&box, output_scale); render_rect(output->wlr_output, output_damage, &box, color); // Single pixel right edge - box.x = x + width - TITLEBAR_BORDER_THICKNESS; - box.y = y + TITLEBAR_BORDER_THICKNESS; - box.width = TITLEBAR_BORDER_THICKNESS; + box.x = x + width - titlebar_border_thickness; + box.y = y + titlebar_border_thickness; + box.width = titlebar_border_thickness; box.height = - container_titlebar_height() - TITLEBAR_BORDER_THICKNESS * 2; + container_titlebar_height() - titlebar_border_thickness * 2; scale_box(&box, output_scale); render_rect(output->wlr_output, output_damage, &box, color); } - size_t inner_width = width - TITLEBAR_H_PADDING * 2; - int bg_y = y + TITLEBAR_BORDER_THICKNESS; + int inner_x = x - output_x + titlebar_h_padding; + int bg_y = y + titlebar_border_thickness; + size_t inner_width = width - titlebar_h_padding * 2; + + // output-buffer local + int ob_inner_x = round(inner_x * output_scale); + int ob_inner_width = scale_length(inner_width, inner_x, output_scale); int ob_bg_height = scale_length( - (TITLEBAR_V_PADDING - TITLEBAR_BORDER_THICKNESS) * 2 + + (titlebar_v_padding - titlebar_border_thickness) * 2 + config->font_height, bg_y, output_scale); // Marks - int marks_ob_width = 0; // output-buffer-local + int ob_marks_x = 0; // output-buffer-local + int ob_marks_width = 0; // output-buffer-local if (config->show_marks && marks_texture) { struct wlr_box texture_box; wlr_texture_get_size(marks_texture, &texture_box.width, &texture_box.height); - marks_ob_width = texture_box.width; + ob_marks_width = texture_box.width; // The marks texture might be shorter than the config->font_height, in // which case we need to pad it as evenly as possible above and below. @@ -437,9 +440,15 @@ static void render_titlebar(struct sway_output *output, int ob_padding_above = floor(ob_padding_total / 2.0); int ob_padding_below = ceil(ob_padding_total / 2.0); - // Render texture - texture_box.x = round((x - output_x + width - TITLEBAR_H_PADDING) - * output_scale) - texture_box.width; + // Render texture. If the title is on the right, the marks will be on + // the left. Otherwise, they will be on the right. + if (title_align == ALIGN_RIGHT || texture_box.width > ob_inner_width) { + texture_box.x = ob_inner_x; + } else { + texture_box.x = ob_inner_x + ob_inner_width - texture_box.width; + } + ob_marks_x = texture_box.x; + texture_box.y = round((bg_y - output_y) * output_scale) + ob_padding_above; @@ -448,8 +457,8 @@ static void render_titlebar(struct sway_output *output, WL_OUTPUT_TRANSFORM_NORMAL, 0.0, output->wlr_output->transform_matrix); - if (inner_width * output_scale < texture_box.width) { - texture_box.width = inner_width * output_scale; + if (ob_inner_width < texture_box.width) { + texture_box.width = ob_inner_width; } render_texture(output->wlr_output, output_damage, marks_texture, &texture_box, matrix, con->alpha); @@ -458,7 +467,7 @@ static void render_titlebar(struct sway_output *output, memcpy(&color, colors->background, sizeof(float) * 4); premultiply_alpha(color, con->alpha); box.x = texture_box.x + round(output_x * output_scale); - box.y = round((y + TITLEBAR_BORDER_THICKNESS) * output_scale); + box.y = round((y + titlebar_border_thickness) * output_scale); box.width = texture_box.width; box.height = ob_padding_above; render_rect(output->wlr_output, output_damage, &box, color); @@ -470,24 +479,43 @@ static void render_titlebar(struct sway_output *output, } // Title text - size_t title_ob_width = 0; // output-buffer-local + int ob_title_x = 0; // output-buffer-local + int ob_title_width = 0; // output-buffer-local if (title_texture) { struct wlr_box texture_box; wlr_texture_get_size(title_texture, &texture_box.width, &texture_box.height); - title_ob_width = texture_box.width; + ob_title_width = texture_box.width; // The title texture might be shorter than the config->font_height, // in which case we need to pad it above and below. int ob_padding_above = round((config->font_baseline - - con->title_baseline + TITLEBAR_V_PADDING - - TITLEBAR_BORDER_THICKNESS) * output_scale); + con->title_baseline + titlebar_v_padding - + titlebar_border_thickness) * output_scale); int ob_padding_below = ob_bg_height - ob_padding_above - texture_box.height; // Render texture - texture_box.x = - round((x - output_x + TITLEBAR_H_PADDING) * output_scale); + if (texture_box.width > ob_inner_width - ob_marks_width) { + texture_box.x = (title_align == ALIGN_RIGHT && ob_marks_width) + ? ob_marks_x + ob_marks_width : ob_inner_x; + } else if (title_align == ALIGN_LEFT) { + texture_box.x = ob_inner_x; + } else if (title_align == ALIGN_CENTER) { + // If there are marks visible, center between the edge and marks. + // Otherwise, center in the inner area. + if (ob_marks_width) { + texture_box.x = (ob_inner_x + ob_marks_x) / 2 + - texture_box.width / 2; + } else { + texture_box.x = ob_inner_x + ob_inner_width / 2 + - texture_box.width / 2; + } + } else { + texture_box.x = ob_inner_x + ob_inner_width - texture_box.width; + } + ob_title_x = texture_box.x; + texture_box.y = round((bg_y - output_y) * output_scale) + ob_padding_above; @@ -496,11 +524,10 @@ static void render_titlebar(struct sway_output *output, WL_OUTPUT_TRANSFORM_NORMAL, 0.0, output->wlr_output->transform_matrix); - int inner_x = x - output_x + TITLEBAR_H_PADDING; - int ob_inner_width = scale_length(inner_width, inner_x, output_scale); - if (ob_inner_width - marks_ob_width < texture_box.width) { - texture_box.width = ob_inner_width - marks_ob_width; + if (ob_inner_width - ob_marks_width < texture_box.width) { + texture_box.width = ob_inner_width - ob_marks_width; } + render_texture(output->wlr_output, output_damage, title_texture, &texture_box, matrix, con->alpha); @@ -508,7 +535,7 @@ static void render_titlebar(struct sway_output *output, memcpy(&color, colors->background, sizeof(float) * 4); premultiply_alpha(color, con->alpha); box.x = texture_box.x + round(output_x * output_scale); - box.y = round((y + TITLEBAR_BORDER_THICKNESS) * output_scale); + box.y = round((y + titlebar_border_thickness) * output_scale); box.width = texture_box.width; box.height = ob_padding_above; render_rect(output->wlr_output, output_damage, &box, color); @@ -519,50 +546,83 @@ static void render_titlebar(struct sway_output *output, render_rect(output->wlr_output, output_damage, &box, color); } + // Determine the left + right extends of the textures (output-buffer local) + int ob_left_x, ob_left_width, ob_right_x, ob_right_width; + if (ob_title_width == 0 && ob_marks_width == 0) { + ob_left_x = ob_inner_x; + ob_left_width = 0; + ob_right_x = ob_inner_x; + ob_right_width = 0; + } else if (ob_title_x < ob_marks_x) { + ob_left_x = ob_title_x; + ob_left_width = ob_title_width; + ob_right_x = ob_marks_x; + ob_right_width = ob_marks_width; + } else { + ob_left_x = ob_marks_x; + ob_left_width = ob_marks_width; + ob_right_x = ob_title_x; + ob_right_width = ob_title_width; + } + if (ob_left_x < ob_inner_x) { + ob_left_x = ob_inner_x; + } else if (ob_left_x + ob_left_width > ob_right_x + ob_right_width) { + ob_right_x = ob_left_x; + ob_right_width = ob_left_width; + } + // Filler between title and marks - box.width = - round(inner_width * output_scale) - title_ob_width - marks_ob_width; + box.width = ob_right_x - ob_left_x - ob_left_width; if (box.width > 0) { - box.x = round((x + TITLEBAR_H_PADDING) * output_scale) + title_ob_width; + box.x = ob_left_x + ob_left_width + round(output_x * output_scale); box.y = round(bg_y * output_scale); box.height = ob_bg_height; render_rect(output->wlr_output, output_damage, &box, color); } - // Padding left of title - left_offset = (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS; + // Padding on left side + left_offset = (layout == L_TABBED) * titlebar_border_thickness; box.x = x + left_offset; - box.y = y + TITLEBAR_BORDER_THICKNESS; - box.width = TITLEBAR_H_PADDING - left_offset; - box.height = (TITLEBAR_V_PADDING - TITLEBAR_BORDER_THICKNESS) * 2 + + box.y = y + titlebar_border_thickness; + box.width = titlebar_h_padding - left_offset; + box.height = (titlebar_v_padding - titlebar_border_thickness) * 2 + config->font_height; scale_box(&box, output_scale); + int left_x = ob_left_x + round(output_x * output_scale); + if (box.x + box.width < left_x) { + box.width += left_x - box.x - box.width; + } render_rect(output->wlr_output, output_damage, &box, color); - // Padding right of marks - right_offset = (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS; - box.x = x + width - TITLEBAR_H_PADDING; - box.y = y + TITLEBAR_BORDER_THICKNESS; - box.width = TITLEBAR_H_PADDING - right_offset; - box.height = (TITLEBAR_V_PADDING - TITLEBAR_BORDER_THICKNESS) * 2 + + // Padding on right side + right_offset = (layout == L_TABBED) * titlebar_border_thickness; + box.x = x + width - titlebar_h_padding; + box.y = y + titlebar_border_thickness; + box.width = titlebar_h_padding - right_offset; + box.height = (titlebar_v_padding - titlebar_border_thickness) * 2 + config->font_height; scale_box(&box, output_scale); + int right_rx = ob_right_x + ob_right_width + round(output_x * output_scale); + if (right_rx < box.x) { + box.width += box.x - right_rx; + box.x = right_rx; + } render_rect(output->wlr_output, output_damage, &box, color); if (connects_sides) { // Left pixel in line with bottom bar box.x = x; - box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS; + box.y = y + container_titlebar_height() - titlebar_border_thickness; box.width = state->border_thickness * state->border_left; - box.height = TITLEBAR_BORDER_THICKNESS; + box.height = titlebar_border_thickness; scale_box(&box, output_scale); render_rect(output->wlr_output, output_damage, &box, color); // Right pixel in line with bottom bar box.x = x + width - state->border_thickness * state->border_right; - box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS; + box.y = y + container_titlebar_height() - titlebar_border_thickness; box.width = state->border_thickness * state->border_right; - box.height = TITLEBAR_BORDER_THICKNESS; + box.height = titlebar_border_thickness; scale_box(&box, output_scale); render_rect(output->wlr_output, output_damage, &box, color); } @@ -882,21 +942,11 @@ static void render_floating(struct sway_output *soutput, } } -static void render_dropzones(struct sway_output *output, +static void render_seatops(struct sway_output *output, pixman_region32_t *damage) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { - if (seat->operation == OP_MOVE_TILING && seat->op_target_node - && node_get_output(seat->op_target_node) == output) { - float color[4]; - memcpy(&color, config->border_colors.focused.indicator, - sizeof(float) * 4); - premultiply_alpha(color, 0.5); - struct wlr_box box; - memcpy(&box, &seat->op_drop_box, sizeof(struct wlr_box)); - scale_box(&box, output->wlr_output->scale); - render_rect(output->wlr_output, damage, &box, color); - } + seatop_render(seat, output, damage); } } @@ -950,7 +1000,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); } @@ -993,7 +1043,7 @@ void output_render(struct sway_output *output, struct timespec *when, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); } - render_dropzones(output, damage); + render_seatops(output, damage); struct sway_seat *seat = input_manager_current_seat(); struct sway_container *focus = seat_get_focused_container(seat); @@ -1012,15 +1062,22 @@ renderer_end: wlr_render_texture(renderer, root->debug_tree, wlr_output->transform_matrix, 0, 40, 1); } - if (debug.damage == DAMAGE_HIGHLIGHT) { - int width, height; - wlr_output_transformed_resolution(wlr_output, &width, &height); - pixman_region32_union_rect(damage, damage, 0, 0, width, height); - } wlr_renderer_scissor(renderer, NULL); wlr_output_render_software_cursors(wlr_output, damage); wlr_renderer_end(renderer); + + int width, height; + wlr_output_transformed_resolution(wlr_output, &width, &height); + + if (debug.damage == DAMAGE_HIGHLIGHT) { + pixman_region32_union_rect(damage, damage, 0, 0, width, height); + } + + enum wl_output_transform transform = + wlr_output_transform_invert(wlr_output->transform); + wlr_region_transform(damage, damage, transform, width, height); + if (!wlr_output_damage_swap_buffers(output->damage, when, damage)) { return; } diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index bf0038b4..1cdd7c6d 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -299,7 +299,7 @@ static void transaction_apply(struct sway_transaction *transaction) { if (root->outputs->length) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { - if (seat->operation == OP_NONE) { + if (!seat_doing_seatop(seat)) { cursor_rebase(seat->cursor); } } @@ -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/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 801dcee0..f05e156f 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -360,7 +360,7 @@ static void handle_request_move(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_move_event *e = data; struct sway_seat *seat = e->seat->seat->data; if (e->serial == seat->last_button_serial) { - seat_begin_move_floating(seat, view->container, seat->last_button); + seatop_begin_move_floating(seat, view->container, seat->last_button); } } @@ -374,7 +374,7 @@ static void handle_request_resize(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_resize_event *e = data; struct sway_seat *seat = e->seat->seat->data; if (e->serial == seat->last_button_serial) { - seat_begin_resize_floating(seat, view->container, + seatop_begin_resize_floating(seat, view->container, seat->last_button, e->edges); } } diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c index 4bc83b8e..9f6741c8 100644 --- a/sway/desktop/xdg_shell_v6.c +++ b/sway/desktop/xdg_shell_v6.c @@ -357,7 +357,7 @@ static void handle_request_move(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_v6_move_event *e = data; struct sway_seat *seat = e->seat->seat->data; if (e->serial == seat->last_button_serial) { - seat_begin_move_floating(seat, view->container, seat->last_button); + seatop_begin_move_floating(seat, view->container, seat->last_button); } } @@ -371,7 +371,7 @@ static void handle_request_resize(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_v6_resize_event *e = data; struct sway_seat *seat = e->seat->seat->data; if (e->serial == seat->last_button_serial) { - seat_begin_resize_floating(seat, view->container, + seatop_begin_resize_floating(seat, view->container, seat->last_button, e->edges); } } diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c index 1838ad32..080f6c41 100644 --- a/sway/desktop/xwayland.c +++ b/sway/desktop/xwayland.c @@ -470,7 +470,7 @@ static void handle_request_move(struct wl_listener *listener, void *data) { return; } struct sway_seat *seat = input_manager_current_seat(); - seat_begin_move_floating(seat, view->container, seat->last_button); + seatop_begin_move_floating(seat, view->container, seat->last_button); } static void handle_request_resize(struct wl_listener *listener, void *data) { @@ -486,7 +486,7 @@ static void handle_request_resize(struct wl_listener *listener, void *data) { } struct wlr_xwayland_resize_event *e = data; struct sway_seat *seat = input_manager_current_seat(); - seat_begin_resize_floating(seat, view->container, + seatop_begin_resize_floating(seat, view->container, seat->last_button, e->edges); } diff --git a/sway/input/cursor.c b/sway/input/cursor.c index d89f64d8..08222494 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -1,12 +1,11 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <math.h> -#ifdef __linux__ +#include <libevdev/libevdev.h> #include <linux/input-event-codes.h> -#elif __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#endif +#include <errno.h> #include <float.h> #include <limits.h> +#include <strings.h> #include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_idle.h> @@ -28,10 +27,6 @@ #include "sway/tree/workspace.h" #include "wlr-layer-shell-unstable-v1-protocol.h" -// When doing a tiling drag, this is the thickness of the dropzone -// when dragging to the edge of a layout container. -#define DROP_LAYOUT_BORDER 30 - static uint32_t get_current_time_msec(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -60,7 +55,7 @@ static struct wlr_surface *layer_surface_at(struct sway_output *output, * Returns the node at the cursor's position. If there is a surface at that * location, it is stored in **surface (it may not be a view). */ -static struct sway_node *node_at_coords( +struct sway_node *node_at_coords( struct sway_seat *seat, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { // check for unmanaged views first @@ -88,6 +83,10 @@ static struct sway_node *node_at_coords( return NULL; } struct sway_output *output = wlr_output->data; + if (!output) { + // output is being destroyed + return NULL; + } double ox = lx, oy = ly; wlr_output_layout_output_coords(root->output_layout, wlr_output, &ox, &oy); @@ -223,323 +222,6 @@ static enum wlr_edges find_resize_edge(struct sway_container *cont, return edge; } -static void handle_down_motion(struct sway_seat *seat, - struct sway_cursor *cursor, uint32_t time_msec) { - struct sway_container *con = seat->op_container; - if (seat_is_input_allowed(seat, con->view->surface)) { - double moved_x = cursor->cursor->x - seat->op_ref_lx; - double moved_y = cursor->cursor->y - seat->op_ref_ly; - double sx = seat->op_ref_con_lx + moved_x; - double sy = seat->op_ref_con_ly + moved_y; - wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); - } - seat->op_moved = true; -} - -static void handle_move_floating_motion(struct sway_seat *seat, - struct sway_cursor *cursor) { - struct sway_container *con = seat->op_container; - desktop_damage_whole_container(con); - container_floating_translate(con, - cursor->cursor->x - cursor->previous.x, - cursor->cursor->y - cursor->previous.y); - desktop_damage_whole_container(con); -} - -static void resize_box(struct wlr_box *box, enum wlr_edges edge, - int thickness) { - switch (edge) { - case WLR_EDGE_TOP: - box->height = thickness; - break; - case WLR_EDGE_LEFT: - box->width = thickness; - break; - case WLR_EDGE_RIGHT: - box->x = box->x + box->width - thickness; - box->width = thickness; - break; - case WLR_EDGE_BOTTOM: - box->y = box->y + box->height - thickness; - box->height = thickness; - break; - case WLR_EDGE_NONE: - box->x += thickness; - box->y += thickness; - box->width -= thickness * 2; - box->height -= thickness * 2; - break; - } -} - -static void handle_move_tiling_motion(struct sway_seat *seat, - struct sway_cursor *cursor) { - struct wlr_surface *surface = NULL; - double sx, sy; - struct sway_node *node = node_at_coords(seat, - cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); - // Damage the old location - desktop_damage_box(&seat->op_drop_box); - - if (!node) { - // Eg. hovered over a layer surface such as swaybar - seat->op_target_node = NULL; - seat->op_target_edge = WLR_EDGE_NONE; - return; - } - - if (node->type == N_WORKSPACE) { - // Emtpy workspace - seat->op_target_node = node; - seat->op_target_edge = WLR_EDGE_NONE; - workspace_get_box(node->sway_workspace, &seat->op_drop_box); - desktop_damage_box(&seat->op_drop_box); - return; - } - - // Deny moving within own workspace if this is the only child - struct sway_container *con = node->sway_container; - if (workspace_num_tiling_views(seat->op_container->workspace) == 1 && - con->workspace == seat->op_container->workspace) { - seat->op_target_node = NULL; - seat->op_target_edge = WLR_EDGE_NONE; - return; - } - - // Traverse the ancestors, trying to find a layout container perpendicular - // to the edge. Eg. close to the top or bottom of a horiz layout. - while (con) { - enum wlr_edges edge = WLR_EDGE_NONE; - enum sway_container_layout layout = container_parent_layout(con); - struct wlr_box parent; - con->parent ? container_get_box(con->parent, &parent) : - workspace_get_box(con->workspace, &parent); - if (layout == L_HORIZ || layout == L_TABBED) { - if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) { - edge = WLR_EDGE_TOP; - } else if (cursor->cursor->y > parent.y + parent.height - - DROP_LAYOUT_BORDER) { - edge = WLR_EDGE_BOTTOM; - } - } else if (layout == L_VERT || layout == L_STACKED) { - if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) { - edge = WLR_EDGE_LEFT; - } else if (cursor->cursor->x > parent.x + parent.width - - DROP_LAYOUT_BORDER) { - edge = WLR_EDGE_RIGHT; - } - } - if (edge) { - seat->op_target_node = node_get_parent(&con->node); - seat->op_target_edge = edge; - node_get_box(seat->op_target_node, &seat->op_drop_box); - resize_box(&seat->op_drop_box, edge, DROP_LAYOUT_BORDER); - desktop_damage_box(&seat->op_drop_box); - return; - } - con = con->parent; - } - - // Use the hovered view - but we must be over the actual surface - con = node->sway_container; - if (!con->view->surface || node == &seat->op_container->node) { - seat->op_target_node = NULL; - seat->op_target_edge = WLR_EDGE_NONE; - return; - } - - // Find the closest edge - size_t thickness = fmin(con->content_width, con->content_height) * 0.3; - size_t closest_dist = INT_MAX; - size_t dist; - seat->op_target_edge = WLR_EDGE_NONE; - if ((dist = cursor->cursor->y - con->y) < closest_dist) { - closest_dist = dist; - seat->op_target_edge = WLR_EDGE_TOP; - } - if ((dist = cursor->cursor->x - con->x) < closest_dist) { - closest_dist = dist; - seat->op_target_edge = WLR_EDGE_LEFT; - } - if ((dist = con->x + con->width - cursor->cursor->x) < closest_dist) { - closest_dist = dist; - seat->op_target_edge = WLR_EDGE_RIGHT; - } - if ((dist = con->y + con->height - cursor->cursor->y) < closest_dist) { - closest_dist = dist; - seat->op_target_edge = WLR_EDGE_BOTTOM; - } - - if (closest_dist > thickness) { - seat->op_target_edge = WLR_EDGE_NONE; - } - - seat->op_target_node = node; - seat->op_drop_box.x = con->content_x; - seat->op_drop_box.y = con->content_y; - seat->op_drop_box.width = con->content_width; - seat->op_drop_box.height = con->content_height; - resize_box(&seat->op_drop_box, seat->op_target_edge, thickness); - desktop_damage_box(&seat->op_drop_box); -} - -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 - *min_width = 0; - } else if (config->floating_minimum_width == 0) { // automatic - *min_width = 75; - } else { - *min_width = config->floating_minimum_width; - } - - if (config->floating_minimum_height == -1) { // no minimum - *min_height = 0; - } else if (config->floating_minimum_height == 0) { // automatic - *min_height = 50; - } else { - *min_height = config->floating_minimum_height; - } - - if (config->floating_maximum_width == -1) { // no maximum - *max_width = INT_MAX; - } else if (config->floating_maximum_width == 0) { // automatic - *max_width = con->workspace->width; - } else { - *max_width = config->floating_maximum_width; - } - - if (config->floating_maximum_height == -1) { // no maximum - *max_height = INT_MAX; - } else if (config->floating_maximum_height == 0) { // automatic - *max_height = con->workspace->height; - } else { - *max_height = config->floating_maximum_height; - } -} - -static void handle_resize_floating_motion(struct sway_seat *seat, - struct sway_cursor *cursor) { - struct sway_container *con = seat->op_container; - enum wlr_edges edge = seat->op_resize_edge; - - // The amount the mouse has moved since the start of the resize operation - // Positive is down/right - double mouse_move_x = cursor->cursor->x - seat->op_ref_lx; - double mouse_move_y = cursor->cursor->y - seat->op_ref_ly; - - if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) { - mouse_move_x = 0; - } - if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) { - mouse_move_y = 0; - } - - double grow_width = edge & WLR_EDGE_LEFT ? -mouse_move_x : mouse_move_x; - double grow_height = edge & WLR_EDGE_TOP ? -mouse_move_y : mouse_move_y; - - if (seat->op_resize_preserve_ratio) { - double x_multiplier = grow_width / seat->op_ref_width; - double y_multiplier = grow_height / seat->op_ref_height; - double max_multiplier = fmax(x_multiplier, y_multiplier); - grow_width = seat->op_ref_width * max_multiplier; - grow_height = seat->op_ref_height * max_multiplier; - } - - // Determine new width/height, and accommodate for floating min/max values - double width = seat->op_ref_width + grow_width; - double height = seat->op_ref_height + grow_height; - int min_width, max_width, min_height, max_height; - calculate_floating_constraints(con, &min_width, &max_width, - &min_height, &max_height); - width = fmax(min_width, fmin(width, max_width)); - height = fmax(min_height, fmin(height, max_height)); - - // Apply the view's min/max size - if (con->view) { - double view_min_width, view_max_width, view_min_height, view_max_height; - view_get_constraints(con->view, &view_min_width, &view_max_width, - &view_min_height, &view_max_height); - width = fmax(view_min_width, fmin(width, view_max_width)); - height = fmax(view_min_height, fmin(height, view_max_height)); - } - - // Recalculate these, in case we hit a min/max limit - grow_width = width - seat->op_ref_width; - grow_height = height - seat->op_ref_height; - - // Determine grow x/y values - these are relative to the container's x/y at - // the start of the resize operation. - double grow_x = 0, grow_y = 0; - if (edge & WLR_EDGE_LEFT) { - grow_x = -grow_width; - } else if (edge & WLR_EDGE_RIGHT) { - grow_x = 0; - } else { - grow_x = -grow_width / 2; - } - if (edge & WLR_EDGE_TOP) { - grow_y = -grow_height; - } else if (edge & WLR_EDGE_BOTTOM) { - grow_y = 0; - } else { - grow_y = -grow_height / 2; - } - - // Determine the amounts we need to bump everything relative to the current - // size. - int relative_grow_width = width - con->width; - int relative_grow_height = height - con->height; - int relative_grow_x = (seat->op_ref_con_lx + grow_x) - con->x; - int relative_grow_y = (seat->op_ref_con_ly + grow_y) - con->y; - - // Actually resize stuff - con->x += relative_grow_x; - con->y += relative_grow_y; - con->width += relative_grow_width; - con->height += relative_grow_height; - - con->content_x += relative_grow_x; - con->content_y += relative_grow_y; - con->content_width += relative_grow_width; - con->content_height += relative_grow_height; - - arrange_container(con); -} - -static void handle_resize_tiling_motion(struct sway_seat *seat, - struct sway_cursor *cursor) { - int amount_x = 0; - int amount_y = 0; - int moved_x = cursor->cursor->x - seat->op_ref_lx; - int moved_y = cursor->cursor->y - seat->op_ref_ly; - enum wlr_edges edge_x = WLR_EDGE_NONE; - enum wlr_edges edge_y = WLR_EDGE_NONE; - struct sway_container *con = seat->op_container; - - if (seat->op_resize_edge & WLR_EDGE_TOP) { - amount_y = (seat->op_ref_height - moved_y) - con->height; - edge_y = WLR_EDGE_TOP; - } else if (seat->op_resize_edge & WLR_EDGE_BOTTOM) { - amount_y = (seat->op_ref_height + moved_y) - con->height; - edge_y = WLR_EDGE_BOTTOM; - } - if (seat->op_resize_edge & WLR_EDGE_LEFT) { - amount_x = (seat->op_ref_width - moved_x) - con->width; - edge_x = WLR_EDGE_LEFT; - } else if (seat->op_resize_edge & WLR_EDGE_RIGHT) { - amount_x = (seat->op_ref_width + moved_x) - con->width; - edge_x = WLR_EDGE_RIGHT; - } - - if (amount_x != 0) { - container_resize_tiled(seat->op_container, edge_x, amount_x); - } - if (amount_y != 0) { - container_resize_tiled(seat->op_container, edge_y, amount_y); - } -} - static void cursor_do_rebase(struct sway_cursor *cursor, uint32_t time_msec, struct sway_node *node, struct wlr_surface *surface, double sx, double sy) { @@ -589,6 +271,50 @@ void cursor_rebase(struct sway_cursor *cursor) { cursor_do_rebase(cursor, time_msec, cursor->previous.node, surface, sx, sy); } +static int hide_notify(void *data) { + struct sway_cursor *cursor = data; + wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); + cursor->hidden = true; + return 1; +} + +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_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); + } +} + void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec) { if (time_msec == 0) { @@ -598,26 +324,8 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, struct sway_seat *seat = cursor->seat; struct wlr_seat *wlr_seat = seat->wlr_seat; - if (seat->operation != OP_NONE) { - switch (seat->operation) { - case OP_DOWN: - handle_down_motion(seat, cursor, time_msec); - break; - case OP_MOVE_FLOATING: - handle_move_floating_motion(seat, cursor); - break; - case OP_MOVE_TILING: - handle_move_tiling_motion(seat, cursor); - break; - case OP_RESIZE_FLOATING: - handle_resize_floating_motion(seat, cursor); - break; - case OP_RESIZE_TILING: - handle_resize_tiling_motion(seat, cursor); - break; - case OP_NONE: - break; - } + if (seat_doing_seatop(seat)) { + seatop_motion(seat, time_msec); cursor->previous.x = cursor->cursor->x; cursor->previous.y = cursor->cursor->y; return; @@ -679,11 +387,11 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, motion); - wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); struct wlr_event_pointer_motion *event = data; wlr_cursor_move(cursor->cursor, event->device, event->delta_x, event->delta_y); cursor_send_pointer_motion(cursor, event->time_msec); + cursor_handle_activity(cursor); transaction_commit_dirty(); } @@ -691,10 +399,10 @@ static void handle_cursor_motion_absolute( struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, motion_absolute); - wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); 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); + cursor_handle_activity(cursor); transaction_commit_dirty(); } @@ -794,11 +502,16 @@ void dispatch_cursor_button(struct sway_cursor *cursor, struct sway_seat *seat = cursor->seat; // Handle existing seat operation - if (cursor->seat->operation != OP_NONE) { - if (button == cursor->seat->op_button && state == WLR_BUTTON_RELEASED) { - seat_end_mouse_operation(seat); + if (seat_doing_seatop(seat)) { + if (button == seat->seatop_button && state == WLR_BUTTON_RELEASED) { + seatop_finish(seat); seat_pointer_notify_button(seat, time_msec, button, state); } + if (state == WLR_BUTTON_PRESSED) { + state_add_button(cursor, button); + } else { + state_erase_button(cursor, button); + } return; } @@ -864,7 +577,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor, if (cont && resize_edge && button == BTN_LEFT && state == WLR_BUTTON_PRESSED && !is_floating) { seat_set_focus_container(seat, cont); - seat_begin_resize_tiling(seat, cont, button, edge); + seatop_begin_resize_tiling(seat, cont, button, edge); return; } @@ -894,7 +607,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor, } cursor_set_image(seat->cursor, image, NULL); seat_set_focus_container(seat, cont); - seat_begin_resize_tiling(seat, cont, button, edge); + seatop_begin_resize_tiling(seat, cont, button, edge); return; } } @@ -909,7 +622,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor, cont = cont->parent; } seat_set_focus_container(seat, cont); - seat_begin_move_floating(seat, cont, button); + seatop_begin_move_floating(seat, cont, button); return; } } @@ -919,7 +632,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor, state == WLR_BUTTON_PRESSED) { // Via border if (button == BTN_LEFT && resize_edge != WLR_EDGE_NONE) { - seat_begin_resize_floating(seat, cont, button, resize_edge); + seatop_begin_resize_floating(seat, cont, button, resize_edge); return; } @@ -936,16 +649,30 @@ void dispatch_cursor_button(struct sway_cursor *cursor, WLR_EDGE_RIGHT : WLR_EDGE_LEFT; edge |= cursor->cursor->y > floater->y + floater->height / 2 ? WLR_EDGE_BOTTOM : WLR_EDGE_TOP; - seat_begin_resize_floating(seat, floater, button, edge); + seatop_begin_resize_floating(seat, floater, button, edge); return; } } // Handle moving a tiling container - if (config->tiling_drag && mod_pressed && state == WLR_BUTTON_PRESSED && - !is_floating_or_child && cont && !cont->is_fullscreen) { + if (config->tiling_drag && (mod_pressed || on_titlebar) && + state == WLR_BUTTON_PRESSED && !is_floating_or_child && + cont && !cont->is_fullscreen) { + 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) { + seatop_begin_move_tiling_threshold(seat, cont, button); + } else { + seatop_begin_move_tiling(seat, cont, button); + } return; } @@ -953,7 +680,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor, if (surface && cont && state == WLR_BUTTON_PRESSED) { seat_set_focus_container(seat, cont); seat_pointer_notify_button(seat, time_msec, button, state); - seat_begin_down(seat, cont, button, sx, sy); + seatop_begin_down(seat, cont, button, sx, sy); return; } @@ -970,18 +697,32 @@ void dispatch_cursor_button(struct sway_cursor *cursor, static void handle_cursor_button(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, button); - wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); struct wlr_event_pointer_button *event = data; dispatch_cursor_button(cursor, event->device, event->time_msec, event->button, event->state); + cursor_handle_activity(cursor); transaction_commit_dirty(); } -static void dispatch_cursor_axis(struct sway_cursor *cursor, +static uint32_t wl_axis_to_button(struct wlr_event_pointer_axis *event) { + switch (event->orientation) { + case WLR_AXIS_ORIENTATION_VERTICAL: + return event->delta < 0 ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN; + case WLR_AXIS_ORIENTATION_HORIZONTAL: + return event->delta < 0 ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT; + default: + wlr_log(WLR_DEBUG, "Unknown axis orientation"); + return 0; + } +} + +void dispatch_cursor_axis(struct sway_cursor *cursor, struct wlr_event_pointer_axis *event) { struct sway_seat *seat = cursor->seat; - struct sway_input_device *input_device = event->device->data; - struct input_config *ic = input_device_get_config(input_device); + struct sway_input_device *input_device = + event->device ? event->device->data : NULL; + struct input_config *ic = + input_device ? input_device_get_config(input_device) : NULL; // Determine what's under the cursor struct wlr_surface *surface = NULL; @@ -993,11 +734,35 @@ static void dispatch_cursor_axis(struct sway_cursor *cursor, enum wlr_edges edge = cont ? find_edge(cont, cursor) : WLR_EDGE_NONE; bool on_border = edge != WLR_EDGE_NONE; bool on_titlebar = cont && !on_border && !surface; + bool on_titlebar_border = cont && on_border && + cursor->cursor->y < cont->content_y; + bool on_contents = cont && !on_border && surface; float scroll_factor = (ic == NULL || ic->scroll_factor == FLT_MIN) ? 1.0f : ic->scroll_factor; - // Scrolling on a tabbed or stacked title bar - if (on_titlebar) { + bool handled = false; + + // Gather information needed for mouse bindings + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); + uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + struct wlr_input_device *device = + input_device ? input_device->wlr_device : NULL; + char *dev_id = device ? input_device_get_identifier(device) : strdup("*"); + uint32_t button = wl_axis_to_button(event); + + // Handle mouse bindings - x11 mouse buttons 4-7 - press event + struct sway_binding *binding = NULL; + state_add_button(cursor, button); + binding = get_active_mouse_binding(cursor, + config->current_mode->mouse_bindings, modifiers, false, + on_titlebar, on_border, on_contents, dev_id); + if (binding) { + seat_execute_command(seat, binding); + handled = true; + } + + // Scrolling on a tabbed or stacked title bar (handled as press event) + if (!handled && (on_titlebar || on_titlebar_border)) { enum sway_container_layout layout = container_parent_layout(cont); if (layout == L_TABBED || layout == L_STACKED) { struct sway_node *tabcontainer = node_get_parent(node); @@ -1024,20 +789,33 @@ static void dispatch_cursor_axis(struct sway_cursor *cursor, seat_set_raw_focus(seat, new_focus); seat_set_raw_focus(seat, old_focus); } - return; + handled = true; } } - wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec, - event->orientation, scroll_factor * event->delta, - round(scroll_factor * event->delta_discrete), event->source); + // Handle mouse bindings - x11 mouse buttons 4-7 - release event + binding = get_active_mouse_binding(cursor, + config->current_mode->mouse_bindings, modifiers, true, + on_titlebar, on_border, on_contents, dev_id); + state_erase_button(cursor, button); + if (binding) { + seat_execute_command(seat, binding); + handled = true; + } + free(dev_id); + + if (!handled) { + wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec, + event->orientation, scroll_factor * event->delta, + round(scroll_factor * event->delta_discrete), event->source); + } } static void handle_cursor_axis(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, axis); - wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); struct wlr_event_pointer_axis *event = data; dispatch_cursor_axis(cursor, event); + cursor_handle_activity(cursor); transaction_commit_dirty(); } @@ -1209,7 +987,7 @@ static void handle_request_set_cursor(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, request_set_cursor); - if (cursor->seat->operation != OP_NONE) { + if (seat_doing_seatop(cursor->seat)) { return; } struct wlr_seat_pointer_request_set_cursor_event *event = data; @@ -1228,10 +1006,8 @@ static void handle_request_set_cursor(struct wl_listener *listener, return; } - wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x, - event->hotspot_y); - cursor->image = NULL; - cursor->image_client = focused_client; + cursor_set_image_surface(cursor, event->surface, event->hotspot_x, + event->hotspot_y, focused_client); } void cursor_set_image(struct sway_cursor *cursor, const char *image, @@ -1239,14 +1015,43 @@ void cursor_set_image(struct sway_cursor *cursor, const char *image, if (!(cursor->seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { return; } + + const char *current_image = cursor->image; + cursor->image = image; + cursor->image_surface = NULL; + cursor->hotspot_x = cursor->hotspot_y = 0; + cursor->image_client = client; + + if (cursor->hidden) { + return; + } + if (!image) { wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); - } else if (!cursor->image || strcmp(cursor->image, image) != 0) { + } else if (!current_image || strcmp(current_image, image) != 0) { wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, image, cursor->cursor); } - cursor->image = image; +} + +void cursor_set_image_surface(struct sway_cursor *cursor, + struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y, + struct wl_client *client) { + if (!(cursor->seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { + return; + } + + cursor->image = NULL; + cursor->image_surface = surface; + cursor->hotspot_x = hotspot_x; + cursor->hotspot_y = hotspot_y; cursor->image_client = client; + + if (cursor->hidden) { + return; + } + + wlr_cursor_set_surface(cursor->cursor, surface, hotspot_x, hotspot_y); } void sway_cursor_destroy(struct sway_cursor *cursor) { @@ -1254,6 +1059,8 @@ void sway_cursor_destroy(struct sway_cursor *cursor) { return; } + wl_event_source_remove(cursor->hide_source); + wlr_xcursor_manager_destroy(cursor->xcursor_manager); wlr_cursor_destroy(cursor->cursor); free(cursor); @@ -1277,6 +1084,9 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) { cursor->seat = seat; wlr_cursor_attach_output_layout(wlr_cursor, root->output_layout); + cursor->hide_source = wl_event_loop_add_timer(server.wl_event_loop, + hide_notify, cursor); + // input events wl_signal_add(&wlr_cursor->events.motion, &cursor->motion); cursor->motion.notify = handle_cursor_motion; @@ -1362,3 +1172,66 @@ void cursor_warp_to_workspace(struct sway_cursor *cursor, wlr_cursor_warp(cursor->cursor, NULL, x, y); } + +uint32_t get_mouse_bindsym(const char *name, char **error) { + if (strncasecmp(name, "button", strlen("button")) == 0) { + // Map to x11 mouse buttons + int number = name[strlen("button")] - '0'; + if (number < 1 || number > 9 || strlen(name) > strlen("button0")) { + *error = strdup("Only buttons 1-9 are supported. For other mouse " + "buttons, use the name of the event code."); + return 0; + } + static const uint32_t buttons[] = {BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, + SWAY_SCROLL_UP, SWAY_SCROLL_DOWN, SWAY_SCROLL_LEFT, + SWAY_SCROLL_RIGHT, BTN_SIDE, BTN_EXTRA}; + return buttons[number - 1]; + } else if (strncmp(name, "BTN_", strlen("BTN_")) == 0) { + // Get event code from name + int code = libevdev_event_code_from_name(EV_KEY, name); + if (code == -1) { + size_t len = snprintf(NULL, 0, "Unknown event %s", name) + 1; + *error = malloc(len); + if (*error) { + snprintf(*error, len, "Unknown event %s", name); + } + return 0; + } + return code; + } + return 0; +} + +uint32_t get_mouse_bindcode(const char *name, char **error) { + // Validate event code + errno = 0; + char *endptr; + int code = strtol(name, &endptr, 10); + if (endptr == name && code <= 0) { + *error = strdup("Button event code must be a positive integer."); + return 0; + } else if (errno == ERANGE) { + *error = strdup("Button event code out of range."); + return 0; + } + const char *event = libevdev_event_code_get_name(EV_KEY, code); + if (!event || strncmp(event, "BTN_", strlen("BTN_")) != 0) { + size_t len = snprintf(NULL, 0, "Event code %d (%s) is not a button", + code, event) + 1; + *error = malloc(len); + if (*error) { + snprintf(*error, len, "Event code %d (%s) is not a button", + code, event); + } + return 0; + } + return code; +} + +uint32_t get_mouse_button(const char *name, char **error) { + uint32_t button = get_mouse_bindsym(name, error); + if (!button && !*error) { + button = get_mouse_bindcode(name, error); + } + return button; +} diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 68445d68..d90803f6 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <ctype.h> #include <float.h> #include <limits.h> @@ -49,7 +49,7 @@ char *input_device_get_identifier(struct wlr_input_device *device) { int vendor = device->vendor; int product = device->product; char *name = strdup(device->name); - name = strip_whitespace(name); + strip_whitespace(name); char *p = name; for (; *p; ++p) { @@ -82,11 +82,12 @@ static struct sway_input_device *input_sway_device_from_wlr( return NULL; } -static bool input_has_seat_configuration(void) { +static bool input_has_seat_fallback_configuration(void) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); - if (seat_config) { + if (seat_config && strcmp(seat_config->name, "*") != 0 + && seat_config->fallback != -1) { return true; } } @@ -94,6 +95,18 @@ static bool input_has_seat_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; @@ -116,6 +129,24 @@ static void input_manager_libinput_config_keyboard( } } +static void input_manager_libinput_reset_keyboard( + struct sway_input_device *input_device) { + struct wlr_input_device *wlr_device = input_device->wlr_device; + struct libinput_device *libinput_device; + + if (!wlr_input_device_is_libinput(wlr_device)) { + return; + } + + libinput_device = wlr_libinput_get_device_handle(wlr_device); + + uint32_t send_events = + libinput_device_config_send_events_get_default_mode(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_keyboard(%s) send_events_set_mode(%d)", + input_device->identifier, send_events); + libinput_device_config_send_events_set_mode(libinput_device, send_events); +} + static void input_manager_libinput_config_touch( struct sway_input_device *input_device) { struct wlr_input_device *wlr_device = input_device->wlr_device; @@ -138,6 +169,24 @@ static void input_manager_libinput_config_touch( } } +static void input_manager_libinput_reset_touch( + struct sway_input_device *input_device) { + struct wlr_input_device *wlr_device = input_device->wlr_device; + struct libinput_device *libinput_device; + + if (!wlr_input_device_is_libinput(wlr_device)) { + return; + } + + libinput_device = wlr_libinput_get_device_handle(wlr_device); + + uint32_t send_events = + libinput_device_config_send_events_get_default_mode(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_touch(%s) send_events_set_mode(%d)", + input_device->identifier, send_events); + libinput_device_config_send_events_set_mode(libinput_device, send_events); +} + static void input_manager_libinput_config_pointer( struct sway_input_device *input_device) { struct wlr_input_device *wlr_device = input_device->wlr_device; @@ -167,14 +216,14 @@ static void input_manager_libinput_config_pointer( if (ic->drag != INT_MIN) { wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) tap_set_drag_enabled(%d)", - ic->identifier, ic->click_method); + ic->identifier, ic->drag); libinput_device_config_tap_set_drag_enabled(libinput_device, ic->drag); } if (ic->drag_lock != INT_MIN) { wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) tap_set_drag_lock_enabled(%d)", - ic->identifier, ic->click_method); + ic->identifier, ic->drag_lock); libinput_device_config_tap_set_drag_lock_enabled(libinput_device, ic->drag_lock); } @@ -235,12 +284,118 @@ static void input_manager_libinput_config_pointer( } if (ic->tap_button_map != INT_MIN) { wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) tap_set_button_map(%d)", - ic->identifier, ic->tap); + ic->identifier, ic->tap_button_map); libinput_device_config_tap_set_button_map(libinput_device, ic->tap_button_map); } } +static void input_manager_libinput_reset_pointer( + struct sway_input_device *input_device) { + struct wlr_input_device *wlr_device = input_device->wlr_device; + + if (!wlr_input_device_is_libinput(wlr_device)) { + return; + } + + struct libinput_device *libinput_device = + wlr_libinput_get_device_handle(wlr_device); + + enum libinput_config_accel_profile accel_profile = + libinput_device_config_accel_get_default_profile(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) accel_set_profile(%d)", + input_device->identifier, accel_profile); + libinput_device_config_accel_set_profile(libinput_device, accel_profile); + + enum libinput_config_click_method click_method = + libinput_device_config_click_get_default_method(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) click_set_method(%d)", + input_device->identifier, click_method); + libinput_device_config_click_set_method(libinput_device, click_method); + + enum libinput_config_drag_state drag = + libinput_device_config_tap_get_default_drag_enabled(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) tap_set_drag_enabled(%d)", + input_device->identifier, drag); + libinput_device_config_tap_set_drag_enabled(libinput_device, drag); + + enum libinput_config_drag_lock_state drag_lock = + libinput_device_config_tap_get_default_drag_lock_enabled( + libinput_device); + wlr_log(WLR_DEBUG, + "libinput_reset_pointer(%s) tap_set_drag_lock_enabled(%d)", + input_device->identifier, drag_lock); + libinput_device_config_tap_set_drag_lock_enabled(libinput_device, + drag_lock); + + enum libinput_config_dwt_state dwt = + libinput_device_config_dwt_get_default_enabled(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) dwt_set_enabled(%d)", + input_device->identifier, dwt); + libinput_device_config_dwt_set_enabled(libinput_device, dwt); + + int left_handed = + libinput_device_config_left_handed_get_default(libinput_device); + wlr_log(WLR_DEBUG, + "libinput_reset_pointer(%s) left_handed_set_enabled(%d)", + input_device->identifier, left_handed); + libinput_device_config_left_handed_set(libinput_device, left_handed); + + enum libinput_config_middle_emulation_state middle_emulation = + libinput_device_config_middle_emulation_get_default_enabled( + libinput_device); + wlr_log(WLR_DEBUG, + "libinput_reset_pointer(%s) middle_emulation_set_enabled(%d)", + input_device->identifier, middle_emulation); + libinput_device_config_middle_emulation_set_enabled(libinput_device, + middle_emulation); + + int natural_scroll = + libinput_device_config_scroll_get_default_natural_scroll_enabled( + libinput_device); + wlr_log(WLR_DEBUG, + "libinput_reset_pointer(%s) natural_scroll_set_enabled(%d)", + input_device->identifier, natural_scroll); + libinput_device_config_scroll_set_natural_scroll_enabled( + libinput_device, natural_scroll); + + double pointer_accel = + libinput_device_config_accel_get_default_speed(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) accel_set_speed(%f)", + input_device->identifier, pointer_accel); + libinput_device_config_accel_set_speed(libinput_device, pointer_accel); + + uint32_t scroll_button = + libinput_device_config_scroll_get_default_button(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) scroll_set_button(%d)", + input_device->identifier, scroll_button); + libinput_device_config_scroll_set_button(libinput_device, scroll_button); + + enum libinput_config_scroll_method scroll_method = + libinput_device_config_scroll_get_default_method(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) scroll_set_method(%d)", + input_device->identifier, scroll_method); + libinput_device_config_scroll_set_method(libinput_device, scroll_method); + + uint32_t send_events = + libinput_device_config_send_events_get_default_mode(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) send_events_set_mode(%d)", + input_device->identifier, send_events); + libinput_device_config_send_events_set_mode(libinput_device, send_events); + + enum libinput_config_tap_state tap = + libinput_device_config_tap_get_default_enabled(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) tap_set_enabled(%d)", + input_device->identifier, tap); + libinput_device_config_tap_set_enabled(libinput_device, tap); + + enum libinput_config_tap_button_map tap_button_map = + libinput_device_config_tap_get_button_map(libinput_device); + wlr_log(WLR_DEBUG, "libinput_reset_pointer(%s) tap_set_button_map(%d)", + input_device->identifier, tap_button_map); + libinput_device_config_tap_set_button_map(libinput_device, tap_button_map); +} + static void handle_device_destroy(struct wl_listener *listener, void *data) { struct wlr_input_device *device = data; @@ -295,15 +450,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_configuration()) { - wlr_log(WLR_DEBUG, "no seat configuration, using default seat"); - seat = input_manager_get_default_seat(); - seat_add_device(seat, input_device); - return; - } + 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 && @@ -458,15 +608,50 @@ void input_manager_apply_input_config(struct input_config *input_config) { } } -void input_manager_apply_seat_config(struct seat_config *seat_config) { - wlr_log(WLR_DEBUG, "applying new seat config for seat %s", - seat_config->name); - struct sway_seat *seat = input_manager_get_seat(seat_config->name); - if (!seat) { - return; +void input_manager_reset_input(struct sway_input_device *input_device) { + if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER || + input_device->wlr_device->type == WLR_INPUT_DEVICE_TABLET_TOOL) { + input_manager_libinput_reset_pointer(input_device); + } else if (input_device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) { + input_manager_libinput_reset_keyboard(input_device); + } else if (input_device->wlr_device->type == WLR_INPUT_DEVICE_TOUCH) { + input_manager_libinput_reset_touch(input_device); + } + + struct sway_seat *seat = NULL; + wl_list_for_each(seat, &server.input->seats, link) { + seat_reset_device(seat, input_device); } +} + +void input_manager_reset_all_inputs() { + struct sway_input_device *input_device = NULL; + wl_list_for_each(input_device, &server.input->devices, link) { + input_manager_reset_input(input_device); + } +} - seat_apply_config(seat, seat_config); + +void input_manager_apply_seat_config(struct seat_config *seat_config) { + wlr_log(WLR_DEBUG, "applying seat config for seat %s", seat_config->name); + if (strcmp(seat_config->name, "*") == 0) { + struct sway_seat *seat = NULL; + wl_list_for_each(seat, &server.input->seats, link) { + // Only apply the wildcard config directly if there is no seat + // specific config + struct seat_config *sc = seat_get_config(seat); + if (!sc) { + sc = seat_config; + } + seat_apply_config(seat, sc); + } + } else { + struct sway_seat *seat = input_manager_get_seat(seat_config->name); + if (!seat) { + return; + } + seat_apply_config(seat, seat_config); + } // for every device, try to add it to a seat and if no seat has it // attached, add it to the fallback seats. diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index c1b53237..46286410 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -295,14 +295,10 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { get_active_binding(&keyboard->state_keysyms_raw, config->current_mode->keysym_bindings, &binding, raw_modifiers, false, input_inhibited, device_identifier); - - if (binding) { - seat_execute_command(seat, binding); - handled = true; - } } - // Set up (or clear) keyboard repeat for a pressed binding + // Set up (or clear) keyboard repeat for a pressed binding. Since the + // binding may remove the keyboard, the timer needs to be updated first if (binding && wlr_device->keyboard->repeat_info.delay > 0) { keyboard->repeat_binding = binding; if (wl_event_source_timer_update(keyboard->key_repeat_source, @@ -316,6 +312,11 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { } } + if (binding) { + seat_execute_command(seat, binding); + handled = true; + } + // Compositor bindings if (!handled && event->state == WLR_KEY_PRESSED) { handled = keyboard_execute_compositor_binding( diff --git a/sway/input/seat.c b/sway/input/seat.c index 663c5140..a63999b6 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1,12 +1,7 @@ -#define _XOPEN_SOURCE 700 -#define _POSIX_C_SOURCE 199309L +#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <errno.h> -#ifdef __linux__ #include <linux/input-event-codes.h> -#elif __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#endif #include <strings.h> #include <time.h> #include <wlr/types/wlr_cursor.h> @@ -175,6 +170,12 @@ static void handle_seat_node_destroy(struct wl_listener *listener, void *data) { parent = node_get_parent(parent); } + if (next_focus->type == N_WORKSPACE && + !workspace_is_visible(next_focus->sway_workspace)) { + // Do not change focus to a non-visible workspace + return; + } + if (needs_new_focus) { // The structure change might have caused it to move up to the top of // the focus stack without sending focus notifications to the view @@ -307,7 +308,7 @@ static void handle_new_drag_icon(struct wl_listener *listener, void *data) { wl_list_insert(&root->drag_icons, &icon->link); drag_icon_update_position(icon); - seat_end_mouse_operation(seat); + seatop_abort(seat); } static void collect_focus_iter(struct sway_node *node, void *data) { @@ -387,6 +388,7 @@ static void seat_update_capabilities(struct sway_seat *seat) { caps |= WL_SEAT_CAPABILITY_POINTER; break; case WLR_INPUT_DEVICE_TABLET_PAD: + case WLR_INPUT_DEVICE_SWITCH: break; } } @@ -403,6 +405,14 @@ static void seat_update_capabilities(struct sway_seat *seat) { } } +static void seat_reset_input_config(struct sway_seat *seat, + struct sway_seat_device *sway_device) { + wlr_log(WLR_DEBUG, "Resetting output mapping for input device %s", + sway_device->input_device->identifier); + wlr_cursor_map_input_to_output(seat->cursor->cursor, + sway_device->input_device->wlr_device, NULL); +} + static void seat_apply_input_config(struct sway_seat *seat, struct sway_seat_device *sway_device) { const char *mapped_to_output = NULL; @@ -422,7 +432,13 @@ static void seat_apply_input_config(struct sway_seat *seat, if (mapped_to_output != NULL) { wlr_log(WLR_DEBUG, "Mapping input device %s to output %s", sway_device->input_device->identifier, mapped_to_output); - struct sway_output *output = output_by_name(mapped_to_output); + if (strcmp("*", mapped_to_output) == 0) { + wlr_cursor_map_input_to_output(seat->cursor->cursor, + sway_device->input_device->wlr_device, NULL); + wlr_log(WLR_DEBUG, "Reset output mapping"); + return; + } + struct sway_output *output = output_by_name_or_id(mapped_to_output); if (output) { wlr_cursor_map_input_to_output(seat->cursor->cursor, sway_device->input_device->wlr_device, output->wlr_output); @@ -508,6 +524,38 @@ void seat_configure_device(struct sway_seat *seat, case WLR_INPUT_DEVICE_TABLET_PAD: wlr_log(WLR_DEBUG, "TODO: configure tablet pad"); break; + case WLR_INPUT_DEVICE_SWITCH: + wlr_log(WLR_DEBUG, "TODO: configure switch device"); + break; + } +} + +void seat_reset_device(struct sway_seat *seat, + struct sway_input_device *input_device) { + struct sway_seat_device *seat_device = seat_get_device(seat, input_device); + if (!seat_device) { + return; + } + + switch (input_device->wlr_device->type) { + case WLR_INPUT_DEVICE_POINTER: + seat_reset_input_config(seat, seat_device); + break; + case WLR_INPUT_DEVICE_KEYBOARD: + sway_keyboard_configure(seat_device->keyboard); + break; + case WLR_INPUT_DEVICE_TOUCH: + seat_reset_input_config(seat, seat_device); + break; + case WLR_INPUT_DEVICE_TABLET_TOOL: + seat_reset_input_config(seat, seat_device); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + wlr_log(WLR_DEBUG, "TODO: reset tablet pad"); + break; + case WLR_INPUT_DEVICE_SWITCH: + wlr_log(WLR_DEBUG, "TODO: reset switch device"); + break; } } @@ -614,18 +662,6 @@ static int handle_urgent_timeout(void *data) { return 0; } -static void container_raise_floating(struct sway_container *con) { - // Bring container to front by putting it at the end of the floating list. - struct sway_container *floater = con; - while (floater->parent) { - floater = floater->parent; - } - if (container_is_floating(floater)) { - list_move_to_end(floater->workspace->floating, floater); - node_set_dirty(&floater->workspace->node); - } -} - static void set_workspace(struct sway_seat *seat, struct sway_workspace *new_ws) { if (seat->workspace == new_ws) { @@ -986,6 +1022,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) { @@ -1000,174 +1038,16 @@ struct seat_config *seat_get_config(struct sway_seat *seat) { 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; - seat->op_container = con; - seat->op_button = button; - seat->op_ref_lx = seat->cursor->cursor->x; - seat->op_ref_ly = seat->cursor->cursor->y; - seat->op_ref_con_lx = sx; - seat->op_ref_con_ly = sy; - seat->op_moved = false; - - container_raise_floating(con); -} - -void seat_begin_move_floating(struct sway_seat *seat, - struct sway_container *con, uint32_t button) { - if (!seat->cursor) { - wlr_log(WLR_DEBUG, "Ignoring move request due to no cursor device"); - return; - } - seat->operation = OP_MOVE_FLOATING; - seat->op_container = con; - seat->op_button = button; - - container_raise_floating(con); - - cursor_set_image(seat->cursor, "grab", NULL); -} - -void seat_begin_move_tiling(struct sway_seat *seat, - struct sway_container *con, uint32_t button) { - seat->operation = OP_MOVE_TILING; - seat->op_container = con; - seat->op_button = button; - seat->op_target_node = NULL; - seat->op_target_edge = 0; - cursor_set_image(seat->cursor, "grab", NULL); -} - -void seat_begin_resize_floating(struct sway_seat *seat, - struct sway_container *con, uint32_t button, enum wlr_edges edge) { - if (!seat->cursor) { - wlr_log(WLR_DEBUG, "Ignoring resize request due to no cursor device"); - return; - } - struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); - seat->operation = OP_RESIZE_FLOATING; - seat->op_container = con; - seat->op_resize_preserve_ratio = keyboard && - (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT); - seat->op_resize_edge = edge == WLR_EDGE_NONE ? - WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge; - seat->op_button = button; - seat->op_ref_lx = seat->cursor->cursor->x; - seat->op_ref_ly = seat->cursor->cursor->y; - seat->op_ref_con_lx = con->x; - seat->op_ref_con_ly = con->y; - seat->op_ref_width = con->width; - seat->op_ref_height = con->height; - - container_raise_floating(con); - - const char *image = edge == WLR_EDGE_NONE ? - "se-resize" : wlr_xcursor_get_resize_name(edge); - cursor_set_image(seat->cursor, image, NULL); -} - -void seat_begin_resize_tiling(struct sway_seat *seat, - struct sway_container *con, uint32_t button, enum wlr_edges edge) { - seat->operation = OP_RESIZE_TILING; - seat->op_container = con; - seat->op_resize_edge = edge; - seat->op_button = button; - seat->op_ref_lx = seat->cursor->cursor->x; - seat->op_ref_ly = seat->cursor->cursor->y; - seat->op_ref_con_lx = con->x; - seat->op_ref_con_ly = con->y; - seat->op_ref_width = con->width; - seat->op_ref_height = con->height; -} - -static bool is_parallel(enum sway_container_layout layout, - enum wlr_edges edge) { - bool layout_is_horiz = layout == L_HORIZ || layout == L_TABBED; - bool edge_is_horiz = edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT; - return layout_is_horiz == edge_is_horiz; -} - -static void seat_end_move_tiling(struct sway_seat *seat) { - struct sway_container *con = seat->op_container; - struct sway_container *old_parent = con->parent; - struct sway_workspace *old_ws = con->workspace; - struct sway_node *target_node = seat->op_target_node; - struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ? - target_node->sway_workspace : target_node->sway_container->workspace; - enum wlr_edges edge = seat->op_target_edge; - int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; - - container_detach(con); - - // Moving container into empty workspace - if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { - workspace_add_tiling(new_ws, con); - } else if (target_node->type == N_CONTAINER) { - // Moving container before/after another - struct sway_container *target = target_node->sway_container; - enum sway_container_layout layout = container_parent_layout(target); - if (edge && !is_parallel(layout, edge)) { - enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || - edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; - container_split(target, new_layout); - } - container_add_sibling(target, con, after); - } else { - // Target is a workspace which requires splitting - enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || - edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; - workspace_split(new_ws, new_layout); - workspace_insert_tiling(new_ws, con, after); - } - - if (old_parent) { - container_reap_empty(old_parent); - } - - // This is a bit dirty, but we'll set the dimensions to that of a sibling. - // I don't think there's any other way to make it consistent without - // changing how we auto-size containers. - list_t *siblings = container_get_siblings(con); - if (siblings->length > 1) { - int index = list_find(siblings, con); - struct sway_container *sibling = index == 0 ? - siblings->items[1] : siblings->items[index - 1]; - con->width = sibling->width; - con->height = sibling->height; - } - - arrange_workspace(old_ws); - if (new_ws != old_ws) { - arrange_workspace(new_ws); - } -} - -void seat_end_mouse_operation(struct sway_seat *seat) { - enum sway_seat_operation operation = seat->operation; - if (seat->operation == OP_MOVE_FLOATING) { - // We "move" the container to its own location so it discovers its - // output again. - struct sway_container *con = seat->op_container; - container_floating_move_to(con, con->x, con->y); - } else if (seat->operation == OP_MOVE_TILING && seat->op_target_node) { - seat_end_move_tiling(seat); - } - seat->operation = OP_NONE; - seat->op_container = NULL; - if (operation == OP_DOWN) { - // Set the cursor's previous coords to the x/y at the start of the - // operation, so the container change will be detected if using - // focus_follows_mouse and the cursor moved off the original container - // during the operation. - seat->cursor->previous.x = seat->op_ref_lx; - seat->cursor->previous.y = seat->op_ref_ly; - if (seat->op_moved) { - cursor_send_pointer_motion(seat->cursor, 0); +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; } - } else { - cursor_set_image(seat->cursor, "left_ptr", NULL); } + + return NULL; } void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, @@ -1197,4 +1077,49 @@ 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)); + } +} + +bool seat_doing_seatop(struct sway_seat *seat) { + return seat->seatop_impl != NULL; +} + +void seatop_unref(struct sway_seat *seat, struct sway_container *con) { + if (seat->seatop_impl && seat->seatop_impl->unref) { + seat->seatop_impl->unref(seat, con); + } +} + +void seatop_motion(struct sway_seat *seat, uint32_t time_msec) { + if (seat->seatop_impl && seat->seatop_impl->motion) { + seat->seatop_impl->motion(seat, time_msec); + } +} + +void seatop_finish(struct sway_seat *seat) { + if (seat->seatop_impl && seat->seatop_impl->finish) { + seat->seatop_impl->finish(seat); + } + free(seat->seatop_data); + seat->seatop_data = NULL; + seat->seatop_impl = NULL; +} + +void seatop_abort(struct sway_seat *seat) { + if (seat->seatop_impl && seat->seatop_impl->abort) { + seat->seatop_impl->abort(seat); + } + free(seat->seatop_data); + seat->seatop_data = NULL; + seat->seatop_impl = NULL; +} + +void seatop_render(struct sway_seat *seat, struct sway_output *output, + pixman_region32_t *damage) { + if (seat->seatop_impl && seat->seatop_impl->render) { + seat->seatop_impl->render(seat, output, damage); + } } diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c new file mode 100644 index 00000000..ad11c5ca --- /dev/null +++ b/sway/input/seatop_down.c @@ -0,0 +1,77 @@ +#define _POSIX_C_SOURCE 200809L +#include <wlr/types/wlr_cursor.h> +#include "sway/input/cursor.h" +#include "sway/input/seat.h" +#include "sway/tree/view.h" + +struct seatop_down_event { + struct sway_container *con; + double ref_lx, ref_ly; // cursor's x/y at start of op + double ref_con_lx, ref_con_ly; // container's x/y at start of op + bool moved; +}; + +static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { + struct seatop_down_event *e = seat->seatop_data; + struct sway_container *con = e->con; + if (seat_is_input_allowed(seat, con->view->surface)) { + double moved_x = seat->cursor->cursor->x - e->ref_lx; + double moved_y = seat->cursor->cursor->y - e->ref_ly; + double sx = e->ref_con_lx + moved_x; + double sy = e->ref_con_ly + moved_y; + wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); + } + e->moved = true; +} + +static void handle_finish(struct sway_seat *seat) { + struct seatop_down_event *e = seat->seatop_data; + // Set the cursor's previous coords to the x/y at the start of the + // operation, so the container change will be detected if using + // focus_follows_mouse and the cursor moved off the original container + // during the operation. + seat->cursor->previous.x = e->ref_lx; + seat->cursor->previous.y = e->ref_ly; + if (e->moved) { + cursor_send_pointer_motion(seat->cursor, 0); + } +} + +static void handle_abort(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_unref(struct sway_seat *seat, struct sway_container *con) { + struct seatop_down_event *e = seat->seatop_data; + if (e->con == con) { + seatop_abort(seat); + } +} + +static const struct sway_seatop_impl seatop_impl = { + .motion = handle_motion, + .finish = handle_finish, + .abort = handle_abort, + .unref = handle_unref, +}; + +void seatop_begin_down(struct sway_seat *seat, + struct sway_container *con, uint32_t button, int sx, int sy) { + seatop_abort(seat); + + struct seatop_down_event *e = + calloc(1, sizeof(struct seatop_down_event)); + if (!e) { + return; + } + e->con = con; + e->ref_lx = seat->cursor->cursor->x; + e->ref_ly = seat->cursor->cursor->y; + e->ref_con_lx = sx; + e->ref_con_ly = sy; + e->moved = false; + + seat->seatop_impl = &seatop_impl; + seat->seatop_data = e; + seat->seatop_button = button; +} diff --git a/sway/input/seatop_move_floating.c b/sway/input/seatop_move_floating.c new file mode 100644 index 00000000..08e3a5a4 --- /dev/null +++ b/sway/input/seatop_move_floating.c @@ -0,0 +1,65 @@ +#define _POSIX_C_SOURCE 200809L +#include <wlr/types/wlr_cursor.h> +#include "sway/desktop.h" +#include "sway/input/cursor.h" +#include "sway/input/seat.h" + +struct seatop_move_floating_event { + struct sway_container *con; +}; + +static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { + struct seatop_move_floating_event *e = seat->seatop_data; + desktop_damage_whole_container(e->con); + container_floating_translate(e->con, + seat->cursor->cursor->x - seat->cursor->previous.x, + seat->cursor->cursor->y - seat->cursor->previous.y); + desktop_damage_whole_container(e->con); +} + +static void handle_finish(struct sway_seat *seat) { + struct seatop_move_floating_event *e = seat->seatop_data; + + // We "move" the container to its own location + // so it discovers its output again. + container_floating_move_to(e->con, e->con->x, e->con->y); + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_abort(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_unref(struct sway_seat *seat, struct sway_container *con) { + struct seatop_move_floating_event *e = seat->seatop_data; + if (e->con == con) { + seatop_abort(seat); + } +} + +static const struct sway_seatop_impl seatop_impl = { + .motion = handle_motion, + .finish = handle_finish, + .abort = handle_abort, + .unref = handle_unref, +}; + +void seatop_begin_move_floating(struct sway_seat *seat, + struct sway_container *con, uint32_t button) { + seatop_abort(seat); + + struct seatop_move_floating_event *e = + calloc(1, sizeof(struct seatop_move_floating_event)); + if (!e) { + return; + } + e->con = con; + + seat->seatop_impl = &seatop_impl; + seat->seatop_data = e; + seat->seatop_button = button; + + container_raise_floating(con); + + cursor_set_image(seat->cursor, "grab", NULL); +} diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c new file mode 100644 index 00000000..8b541f80 --- /dev/null +++ b/sway/input/seatop_move_tiling.c @@ -0,0 +1,335 @@ +#define _POSIX_C_SOURCE 200809L +#include <limits.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/util/edges.h> +#include "sway/desktop.h" +#include "sway/input/cursor.h" +#include "sway/input/seat.h" +#include "sway/output.h" +#include "sway/tree/arrange.h" +#include "sway/tree/node.h" +#include "sway/tree/view.h" +#include "sway/tree/workspace.h" + +// Thickness of the dropzone when dragging to the edge of a layout container +#define DROP_LAYOUT_BORDER 30 + +struct seatop_move_tiling_event { + struct sway_container *con; + struct sway_node *target_node; + enum wlr_edges target_edge; + struct wlr_box drop_box; + double ref_lx, ref_ly; // cursor's x/y at start of op + bool threshold_reached; +}; + +static void handle_render(struct sway_seat *seat, + struct sway_output *output, pixman_region32_t *damage) { + struct seatop_move_tiling_event *e = seat->seatop_data; + if (!e->threshold_reached) { + return; + } + if (e->target_node && node_get_output(e->target_node) == output) { + float color[4]; + memcpy(&color, config->border_colors.focused.indicator, + sizeof(float) * 4); + premultiply_alpha(color, 0.5); + struct wlr_box box; + memcpy(&box, &e->drop_box, sizeof(struct wlr_box)); + scale_box(&box, output->wlr_output->scale); + render_rect(output->wlr_output, damage, &box, color); + } +} + +static void handle_motion_prethreshold(struct sway_seat *seat) { + struct seatop_move_tiling_event *e = seat->seatop_data; + double cx = seat->cursor->cursor->x; + double cy = seat->cursor->cursor->y; + double sx = e->ref_lx; + double sy = e->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) { + e->threshold_reached = true; + cursor_set_image(seat->cursor, "grab", NULL); + } +} + +static void resize_box(struct wlr_box *box, enum wlr_edges edge, + int thickness) { + switch (edge) { + case WLR_EDGE_TOP: + box->height = thickness; + break; + case WLR_EDGE_LEFT: + box->width = thickness; + break; + case WLR_EDGE_RIGHT: + box->x = box->x + box->width - thickness; + box->width = thickness; + break; + case WLR_EDGE_BOTTOM: + box->y = box->y + box->height - thickness; + box->height = thickness; + break; + case WLR_EDGE_NONE: + box->x += thickness; + box->y += thickness; + box->width -= thickness * 2; + box->height -= thickness * 2; + break; + } +} + +static void handle_motion_postthreshold(struct sway_seat *seat) { + struct seatop_move_tiling_event *e = seat->seatop_data; + struct wlr_surface *surface = NULL; + double sx, sy; + struct sway_cursor *cursor = seat->cursor; + struct sway_node *node = node_at_coords(seat, + cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); + // Damage the old location + desktop_damage_box(&e->drop_box); + + if (!node) { + // Eg. hovered over a layer surface such as swaybar + e->target_node = NULL; + e->target_edge = WLR_EDGE_NONE; + return; + } + + if (node->type == N_WORKSPACE) { + // Emtpy workspace + e->target_node = node; + e->target_edge = WLR_EDGE_NONE; + workspace_get_box(node->sway_workspace, &e->drop_box); + desktop_damage_box(&e->drop_box); + return; + } + + // Deny moving within own workspace if this is the only child + struct sway_container *con = node->sway_container; + if (workspace_num_tiling_views(e->con->workspace) == 1 && + con->workspace == e->con->workspace) { + e->target_node = NULL; + e->target_edge = WLR_EDGE_NONE; + return; + } + + // Traverse the ancestors, trying to find a layout container perpendicular + // to the edge. Eg. close to the top or bottom of a horiz layout. + while (con) { + enum wlr_edges edge = WLR_EDGE_NONE; + enum sway_container_layout layout = container_parent_layout(con); + struct wlr_box parent; + con->parent ? container_get_box(con->parent, &parent) : + workspace_get_box(con->workspace, &parent); + if (layout == L_HORIZ || layout == L_TABBED) { + if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) { + edge = WLR_EDGE_TOP; + } else if (cursor->cursor->y > parent.y + parent.height + - DROP_LAYOUT_BORDER) { + edge = WLR_EDGE_BOTTOM; + } + } else if (layout == L_VERT || layout == L_STACKED) { + if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) { + edge = WLR_EDGE_LEFT; + } else if (cursor->cursor->x > parent.x + parent.width + - DROP_LAYOUT_BORDER) { + edge = WLR_EDGE_RIGHT; + } + } + if (edge) { + e->target_node = node_get_parent(&con->node); + e->target_edge = edge; + node_get_box(e->target_node, &e->drop_box); + resize_box(&e->drop_box, edge, DROP_LAYOUT_BORDER); + desktop_damage_box(&e->drop_box); + return; + } + con = con->parent; + } + + // Use the hovered view - but we must be over the actual surface + con = node->sway_container; + if (!con->view->surface || node == &e->con->node) { + e->target_node = NULL; + e->target_edge = WLR_EDGE_NONE; + return; + } + + // Find the closest edge + size_t thickness = fmin(con->content_width, con->content_height) * 0.3; + size_t closest_dist = INT_MAX; + size_t dist; + e->target_edge = WLR_EDGE_NONE; + if ((dist = cursor->cursor->y - con->y) < closest_dist) { + closest_dist = dist; + e->target_edge = WLR_EDGE_TOP; + } + if ((dist = cursor->cursor->x - con->x) < closest_dist) { + closest_dist = dist; + e->target_edge = WLR_EDGE_LEFT; + } + if ((dist = con->x + con->width - cursor->cursor->x) < closest_dist) { + closest_dist = dist; + e->target_edge = WLR_EDGE_RIGHT; + } + if ((dist = con->y + con->height - cursor->cursor->y) < closest_dist) { + closest_dist = dist; + e->target_edge = WLR_EDGE_BOTTOM; + } + + if (closest_dist > thickness) { + e->target_edge = WLR_EDGE_NONE; + } + + e->target_node = node; + e->drop_box.x = con->content_x; + e->drop_box.y = con->content_y; + e->drop_box.width = con->content_width; + e->drop_box.height = con->content_height; + resize_box(&e->drop_box, e->target_edge, thickness); + desktop_damage_box(&e->drop_box); +} + +static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { + struct seatop_move_tiling_event *e = seat->seatop_data; + if (e->threshold_reached) { + handle_motion_postthreshold(seat); + } else { + handle_motion_prethreshold(seat); + } +} + +static void handle_abort(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static bool is_parallel(enum sway_container_layout layout, + enum wlr_edges edge) { + bool layout_is_horiz = layout == L_HORIZ || layout == L_TABBED; + bool edge_is_horiz = edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT; + return layout_is_horiz == edge_is_horiz; +} + +static void handle_finish(struct sway_seat *seat) { + struct seatop_move_tiling_event *e = seat->seatop_data; + + if (!e->target_node) { + handle_abort(seat); + return; + } + + struct sway_container *con = e->con; + struct sway_container *old_parent = con->parent; + struct sway_workspace *old_ws = con->workspace; + struct sway_node *target_node = e->target_node; + struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ? + target_node->sway_workspace : target_node->sway_container->workspace; + enum wlr_edges edge = e->target_edge; + int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; + + container_detach(con); + + // Moving container into empty workspace + if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { + workspace_add_tiling(new_ws, con); + } else if (target_node->type == N_CONTAINER) { + // Moving container before/after another + struct sway_container *target = target_node->sway_container; + enum sway_container_layout layout = container_parent_layout(target); + if (edge && !is_parallel(layout, edge)) { + enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || + edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; + container_split(target, new_layout); + } + container_add_sibling(target, con, after); + } else { + // Target is a workspace which requires splitting + enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || + edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; + workspace_split(new_ws, new_layout); + workspace_insert_tiling(new_ws, con, after); + } + + if (old_parent) { + container_reap_empty(old_parent); + } + + // This is a bit dirty, but we'll set the dimensions to that of a sibling. + // I don't think there's any other way to make it consistent without + // changing how we auto-size containers. + list_t *siblings = container_get_siblings(con); + if (siblings->length > 1) { + int index = list_find(siblings, con); + struct sway_container *sibling = index == 0 ? + siblings->items[1] : siblings->items[index - 1]; + con->width = sibling->width; + con->height = sibling->height; + } + + arrange_workspace(old_ws); + if (new_ws != old_ws) { + arrange_workspace(new_ws); + } + + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_unref(struct sway_seat *seat, struct sway_container *con) { + struct seatop_move_tiling_event *e = seat->seatop_data; + if (e->target_node == &con->node) { // Drop target + e->target_node = NULL; + } + if (e->con == con) { // The container being moved + seatop_abort(seat); + } +} + +static const struct sway_seatop_impl seatop_impl = { + .motion = handle_motion, + .finish = handle_finish, + .abort = handle_abort, + .unref = handle_unref, + .render = handle_render, +}; + +void seatop_begin_move_tiling_threshold(struct sway_seat *seat, + struct sway_container *con, uint32_t button) { + seatop_abort(seat); + + struct seatop_move_tiling_event *e = + calloc(1, sizeof(struct seatop_move_tiling_event)); + if (!e) { + return; + } + e->con = con; + e->ref_lx = seat->cursor->cursor->x; + e->ref_ly = seat->cursor->cursor->y; + + seat->seatop_impl = &seatop_impl; + seat->seatop_data = e; + seat->seatop_button = button; + + container_raise_floating(con); +} + +void seatop_begin_move_tiling(struct sway_seat *seat, + struct sway_container *con, uint32_t button) { + seatop_begin_move_tiling_threshold(seat, con, button); + struct seatop_move_tiling_event *e = seat->seatop_data; + if (e) { + e->threshold_reached = true; + cursor_set_image(seat->cursor, "grab", NULL); + } +} diff --git a/sway/input/seatop_resize_floating.c b/sway/input/seatop_resize_floating.c new file mode 100644 index 00000000..12851b40 --- /dev/null +++ b/sway/input/seatop_resize_floating.c @@ -0,0 +1,199 @@ +#define _POSIX_C_SOURCE 200809L +#include <limits.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_xcursor_manager.h> +#include "sway/input/cursor.h" +#include "sway/input/seat.h" +#include "sway/tree/arrange.h" +#include "sway/tree/view.h" +#include "sway/tree/workspace.h" + +struct seatop_resize_floating_event { + struct sway_container *con; + enum wlr_edges edge; + bool preserve_ratio; + double ref_lx, ref_ly; // cursor's x/y at start of op + double ref_width, ref_height; // container's size at start of op + double ref_con_lx, ref_con_ly; // container's x/y at start of op +}; + +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 + *min_width = 0; + } else if (config->floating_minimum_width == 0) { // automatic + *min_width = 75; + } else { + *min_width = config->floating_minimum_width; + } + + if (config->floating_minimum_height == -1) { // no minimum + *min_height = 0; + } else if (config->floating_minimum_height == 0) { // automatic + *min_height = 50; + } else { + *min_height = config->floating_minimum_height; + } + + if (config->floating_maximum_width == -1) { // no maximum + *max_width = INT_MAX; + } else if (config->floating_maximum_width == 0) { // automatic + *max_width = con->workspace->width; + } else { + *max_width = config->floating_maximum_width; + } + + if (config->floating_maximum_height == -1) { // no maximum + *max_height = INT_MAX; + } else if (config->floating_maximum_height == 0) { // automatic + *max_height = con->workspace->height; + } else { + *max_height = config->floating_maximum_height; + } +} + +static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { + struct seatop_resize_floating_event *e = seat->seatop_data; + struct sway_container *con = e->con; + enum wlr_edges edge = e->edge; + struct sway_cursor *cursor = seat->cursor; + + // The amount the mouse has moved since the start of the resize operation + // Positive is down/right + double mouse_move_x = cursor->cursor->x - e->ref_lx; + double mouse_move_y = cursor->cursor->y - e->ref_ly; + + if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) { + mouse_move_x = 0; + } + if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) { + mouse_move_y = 0; + } + + double grow_width = edge & WLR_EDGE_LEFT ? -mouse_move_x : mouse_move_x; + double grow_height = edge & WLR_EDGE_TOP ? -mouse_move_y : mouse_move_y; + + if (e->preserve_ratio) { + double x_multiplier = grow_width / e->ref_width; + double y_multiplier = grow_height / e->ref_height; + double max_multiplier = fmax(x_multiplier, y_multiplier); + grow_width = e->ref_width * max_multiplier; + grow_height = e->ref_height * max_multiplier; + } + + // Determine new width/height, and accommodate for floating min/max values + double width = e->ref_width + grow_width; + double height = e->ref_height + grow_height; + int min_width, max_width, min_height, max_height; + calculate_floating_constraints(con, &min_width, &max_width, + &min_height, &max_height); + width = fmax(min_width, fmin(width, max_width)); + height = fmax(min_height, fmin(height, max_height)); + + // Apply the view's min/max size + if (con->view) { + double view_min_width, view_max_width, view_min_height, view_max_height; + view_get_constraints(con->view, &view_min_width, &view_max_width, + &view_min_height, &view_max_height); + width = fmax(view_min_width, fmin(width, view_max_width)); + height = fmax(view_min_height, fmin(height, view_max_height)); + } + + // Recalculate these, in case we hit a min/max limit + grow_width = width - e->ref_width; + grow_height = height - e->ref_height; + + // Determine grow x/y values - these are relative to the container's x/y at + // the start of the resize operation. + double grow_x = 0, grow_y = 0; + if (edge & WLR_EDGE_LEFT) { + grow_x = -grow_width; + } else if (edge & WLR_EDGE_RIGHT) { + grow_x = 0; + } else { + grow_x = -grow_width / 2; + } + if (edge & WLR_EDGE_TOP) { + grow_y = -grow_height; + } else if (edge & WLR_EDGE_BOTTOM) { + grow_y = 0; + } else { + grow_y = -grow_height / 2; + } + + // Determine the amounts we need to bump everything relative to the current + // size. + int relative_grow_width = width - con->width; + int relative_grow_height = height - con->height; + int relative_grow_x = (e->ref_con_lx + grow_x) - con->x; + int relative_grow_y = (e->ref_con_ly + grow_y) - con->y; + + // Actually resize stuff + con->x += relative_grow_x; + con->y += relative_grow_y; + con->width += relative_grow_width; + con->height += relative_grow_height; + + con->content_x += relative_grow_x; + con->content_y += relative_grow_y; + con->content_width += relative_grow_width; + con->content_height += relative_grow_height; + + arrange_container(con); +} + +static void handle_finish(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_abort(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_unref(struct sway_seat *seat, struct sway_container *con) { + struct seatop_resize_floating_event *e = seat->seatop_data; + if (e->con == con) { + seatop_abort(seat); + } +} + +static const struct sway_seatop_impl seatop_impl = { + .motion = handle_motion, + .finish = handle_finish, + .abort = handle_abort, + .unref = handle_unref, +}; + +void seatop_begin_resize_floating(struct sway_seat *seat, + struct sway_container *con, uint32_t button, enum wlr_edges edge) { + seatop_abort(seat); + + struct seatop_resize_floating_event *e = + calloc(1, sizeof(struct seatop_resize_floating_event)); + if (!e) { + return; + } + e->con = con; + + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); + e->preserve_ratio = keyboard && + (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT); + + e->edge = edge == WLR_EDGE_NONE ? WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge; + e->ref_lx = seat->cursor->cursor->x; + e->ref_ly = seat->cursor->cursor->y; + e->ref_con_lx = con->x; + e->ref_con_ly = con->y; + e->ref_width = con->width; + e->ref_height = con->height; + + seat->seatop_impl = &seatop_impl; + seat->seatop_data = e; + seat->seatop_button = button; + + container_raise_floating(con); + + const char *image = edge == WLR_EDGE_NONE ? + "se-resize" : wlr_xcursor_get_resize_name(edge); + cursor_set_image(seat->cursor, image, NULL); +} diff --git a/sway/input/seatop_resize_tiling.c b/sway/input/seatop_resize_tiling.c new file mode 100644 index 00000000..30431f04 --- /dev/null +++ b/sway/input/seatop_resize_tiling.c @@ -0,0 +1,92 @@ +#define _POSIX_C_SOURCE 200809L +#include <wlr/types/wlr_cursor.h> +#include "sway/commands.h" +#include "sway/input/cursor.h" +#include "sway/input/seat.h" + +struct seatop_resize_tiling_event { + struct sway_container *con; + enum wlr_edges edge; + double ref_lx, ref_ly; // cursor's x/y at start of op + double ref_width, ref_height; // container's size at start of op + double ref_con_lx, ref_con_ly; // container's x/y at start of op +}; + +static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { + struct seatop_resize_tiling_event *e = seat->seatop_data; + int amount_x = 0; + int amount_y = 0; + int moved_x = seat->cursor->cursor->x - e->ref_lx; + int moved_y = seat->cursor->cursor->y - e->ref_ly; + enum wlr_edges edge_x = WLR_EDGE_NONE; + enum wlr_edges edge_y = WLR_EDGE_NONE; + struct sway_container *con = e->con; + + if (e->edge & WLR_EDGE_TOP) { + amount_y = (e->ref_height - moved_y) - con->height; + edge_y = WLR_EDGE_TOP; + } else if (e->edge & WLR_EDGE_BOTTOM) { + amount_y = (e->ref_height + moved_y) - con->height; + edge_y = WLR_EDGE_BOTTOM; + } + if (e->edge & WLR_EDGE_LEFT) { + amount_x = (e->ref_width - moved_x) - con->width; + edge_x = WLR_EDGE_LEFT; + } else if (e->edge & WLR_EDGE_RIGHT) { + amount_x = (e->ref_width + moved_x) - con->width; + edge_x = WLR_EDGE_RIGHT; + } + + if (amount_x != 0) { + container_resize_tiled(e->con, edge_x, amount_x); + } + if (amount_y != 0) { + container_resize_tiled(e->con, edge_y, amount_y); + } +} + +static void handle_finish(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_abort(struct sway_seat *seat) { + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +static void handle_unref(struct sway_seat *seat, struct sway_container *con) { + struct seatop_resize_tiling_event *e = seat->seatop_data; + if (e->con == con) { + seatop_abort(seat); + } +} + +static const struct sway_seatop_impl seatop_impl = { + .motion = handle_motion, + .finish = handle_finish, + .abort = handle_abort, + .unref = handle_unref, +}; + +void seatop_begin_resize_tiling(struct sway_seat *seat, + struct sway_container *con, uint32_t button, enum wlr_edges edge) { + seatop_abort(seat); + + struct seatop_resize_tiling_event *e = + calloc(1, sizeof(struct seatop_resize_tiling_event)); + if (!e) { + return; + } + e->con = con; + e->edge = edge; + + e->ref_lx = seat->cursor->cursor->x; + e->ref_ly = seat->cursor->cursor->y; + e->ref_con_lx = con->x; + e->ref_con_ly = con->y; + e->ref_width = con->width; + e->ref_height = con->height; + + seat->seatop_impl = &seatop_impl; + seat->seatop_data = e; + seat->seatop_button = button; +} diff --git a/sway/ipc-json.c b/sway/ipc-json.c index e3450df1..6e5ba4fd 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -1,6 +1,8 @@ #include <json-c/json.h> +#include <libevdev/libevdev.h> #include <stdio.h> #include <ctype.h> +#include "config.h" #include "log.h" #include "sway/config.h" #include "sway/ipc-json.h" @@ -9,12 +11,17 @@ #include "sway/tree/workspace.h" #include "sway/output.h" #include "sway/input/input-manager.h" +#include "sway/input/cursor.h" #include "sway/input/seat.h" +#include <wlr/backend/libinput.h> #include <wlr/types/wlr_box.h> #include <wlr/types/wlr_output.h> #include <xkbcommon/xkbcommon.h> #include "wlr-layer-shell-unstable-v1-protocol.h" +static const int i3_output_id = INT32_MAX; +static const int i3_scratch_id = INT32_MAX - 1; + static const char *ipc_json_layout_description(enum sway_container_layout l) { switch (l) { case L_VERT: @@ -32,15 +39,68 @@ static const char *ipc_json_layout_description(enum sway_container_layout l) { } static const char *ipc_json_orientation_description(enum sway_container_layout l) { - if (l == L_VERT) { + switch (l) { + case L_VERT: return "vertical"; + case L_HORIZ: + return "horizontal"; + default: + return "none"; } +} - if (l == L_HORIZ) { - return "horizontal"; +static const char *ipc_json_border_description(enum sway_container_border border) { + switch (border) { + case B_NONE: + return "none"; + case B_PIXEL: + return "pixel"; + case B_NORMAL: + return "normal"; + case B_CSD: + return "csd"; } + return "unknown"; +} - return "none"; +static const char *ipc_json_output_transform_description(enum wl_output_transform transform) { + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + return "normal"; + case WL_OUTPUT_TRANSFORM_90: + return "90"; + case WL_OUTPUT_TRANSFORM_180: + return "180"; + case WL_OUTPUT_TRANSFORM_270: + return "270"; + case WL_OUTPUT_TRANSFORM_FLIPPED: + return "flipped"; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + return "flipped-90"; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return "flipped-180"; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return "flipped-270"; + } + return NULL; +} + +static const char *ipc_json_device_type_description(struct sway_input_device *device) { + switch (device->wlr_device->type) { + case WLR_INPUT_DEVICE_POINTER: + return "pointer"; + case WLR_INPUT_DEVICE_KEYBOARD: + return "keyboard"; + case WLR_INPUT_DEVICE_TOUCH: + return "touch"; + case WLR_INPUT_DEVICE_TABLET_TOOL: + return "tablet_tool"; + case WLR_INPUT_DEVICE_TABLET_PAD: + return "tablet_pad"; + case WLR_INPUT_DEVICE_SWITCH: + return "switch"; + } + return "unknown"; } json_object *ipc_json_get_version(void) { @@ -76,30 +136,43 @@ static json_object *ipc_json_create_empty_rect(void) { return ipc_json_create_rect(&empty); } -static void ipc_json_describe_root(struct sway_root *root, json_object *object) { - json_object_object_add(object, "type", json_object_new_string("root")); +static json_object *ipc_json_create_node(int id, char *name, + bool focused, json_object *focus, struct wlr_box *box) { + json_object *object = json_object_new_object(); + + json_object_object_add(object, "id", json_object_new_int(id)); + json_object_object_add(object, "name", + name ? json_object_new_string(name) : NULL); + json_object_object_add(object, "rect", ipc_json_create_rect(box)); + json_object_object_add(object, "focused", json_object_new_boolean(focused)); + json_object_object_add(object, "focus", focus); + + // set default values to be compatible with i3 + json_object_object_add(object, "border", + json_object_new_string( + ipc_json_border_description(B_NONE))); + json_object_object_add(object, "current_border_width", + json_object_new_int(0)); + json_object_object_add(object, "layout", + json_object_new_string( + ipc_json_layout_description(L_HORIZ))); + json_object_object_add(object, "orientation", + json_object_new_string( + ipc_json_orientation_description(L_HORIZ))); + json_object_object_add(object, "percent", NULL); + json_object_object_add(object, "window_rect", ipc_json_create_empty_rect()); + json_object_object_add(object, "deco_rect", ipc_json_create_empty_rect()); + json_object_object_add(object, "geometry", ipc_json_create_empty_rect()); + json_object_object_add(object, "window", NULL); + json_object_object_add(object, "urgent", json_object_new_boolean(false)); + json_object_object_add(object, "floating_nodes", json_object_new_array()); + json_object_object_add(object, "sticky", json_object_new_boolean(false)); + + return object; } -static const char *ipc_json_get_output_transform(enum wl_output_transform transform) { - switch (transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - return "normal"; - case WL_OUTPUT_TRANSFORM_90: - return "90"; - case WL_OUTPUT_TRANSFORM_180: - return "180"; - case WL_OUTPUT_TRANSFORM_270: - return "270"; - case WL_OUTPUT_TRANSFORM_FLIPPED: - return "flipped"; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - return "flipped-90"; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - return "flipped-180"; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - return "flipped-270"; - } - return NULL; +static void ipc_json_describe_root(struct sway_root *root, json_object *object) { + json_object_object_add(object, "type", json_object_new_string("root")); } static void ipc_json_describe_output(struct sway_output *output, @@ -110,7 +183,8 @@ static void ipc_json_describe_output(struct sway_output *output, json_object_object_add(object, "primary", json_object_new_boolean(false)); json_object_object_add(object, "layout", json_object_new_string("output")); json_object_object_add(object, "orientation", - json_object_new_string(ipc_json_orientation_description(L_NONE))); + json_object_new_string( + ipc_json_orientation_description(L_NONE))); json_object_object_add(object, "make", json_object_new_string(wlr_output->make)); json_object_object_add(object, "model", @@ -121,7 +195,7 @@ static void ipc_json_describe_output(struct sway_output *output, json_object_new_double(wlr_output->scale)); json_object_object_add(object, "transform", json_object_new_string( - ipc_json_get_output_transform(wlr_output->transform))); + ipc_json_output_transform_description(wlr_output->transform))); struct sway_workspace *ws = output_get_active_workspace(output); json_object_object_add(object, "current_workspace", @@ -187,6 +261,52 @@ json_object *ipc_json_describe_disabled_output(struct sway_output *output) { return object; } +static json_object *ipc_json_describe_scratchpad_output(void) { + struct wlr_box box; + root_get_box(root, &box); + + // Create focus stack for __i3_scratch workspace + json_object *workspace_focus = json_object_new_array(); + for (int i = root->scratchpad->length - 1; i >= 0; --i) { + struct sway_container *container = root->scratchpad->items[i]; + json_object_array_add(workspace_focus, + json_object_new_int(container->node.id)); + } + + json_object *workspace = ipc_json_create_node(i3_scratch_id, + "__i3_scratch", false, workspace_focus, &box); + json_object_object_add(workspace, "type", + json_object_new_string("workspace")); + + // List all hidden scratchpad containers as floating nodes + json_object *floating_array = json_object_new_array(); + for (int i = 0; i < root->scratchpad->length; ++i) { + struct sway_container *container = root->scratchpad->items[i]; + if (!container->workspace) { + json_object_array_add(floating_array, + ipc_json_describe_node_recursive(&container->node)); + } + } + json_object_object_add(workspace, "floating_nodes", floating_array); + + // Create focus stack for __i3 output + json_object *output_focus = json_object_new_array(); + json_object_array_add(output_focus, json_object_new_int(i3_scratch_id)); + + json_object *output = ipc_json_create_node(i3_output_id, + "__i3", false, output_focus, &box); + json_object_object_add(output, "type", + json_object_new_string("output")); + json_object_object_add(output, "layout", + json_object_new_string("output")); + + json_object *nodes = json_object_new_array(); + json_object_array_add(nodes, workspace); + json_object_object_add(output, "nodes", nodes); + + return output; +} + static void ipc_json_describe_workspace(struct sway_workspace *workspace, json_object *object) { int num = isdigit(workspace->name[0]) ? atoi(workspace->name) : -1; @@ -200,11 +320,12 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace, json_object_object_add(object, "representation", workspace->representation ? json_object_new_string(workspace->representation) : NULL); - const char *layout = ipc_json_layout_description(workspace->layout); - json_object_object_add(object, "layout", json_object_new_string(layout)); - - const char *orientation = ipc_json_orientation_description(workspace->layout); - json_object_object_add(object, "orientation", json_object_new_string(orientation)); + json_object_object_add(object, "layout", + json_object_new_string( + ipc_json_layout_description(workspace->layout))); + json_object_object_add(object, "orientation", + json_object_new_string( + ipc_json_orientation_description(workspace->layout))); // Floating json_object *floating_array = json_object_new_array(); @@ -216,20 +337,6 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace, json_object_object_add(object, "floating_nodes", floating_array); } -static const char *describe_container_border(enum sway_container_border border) { - switch (border) { - case B_NONE: - return "none"; - case B_PIXEL: - return "pixel"; - case B_NORMAL: - return "normal"; - case B_CSD: - return "csd"; - } - return "unknown"; -} - static void ipc_json_describe_view(struct sway_container *c, json_object *object) { json_object_object_add(object, "pid", json_object_new_int(c->view->pid)); @@ -307,15 +414,18 @@ static void ipc_json_describe_container(struct sway_container *c, json_object *o json_object_new_string(container_is_floating(c) ? "floating_con" : "con")); json_object_object_add(object, "layout", - json_object_new_string(ipc_json_layout_description(c->layout))); + json_object_new_string( + ipc_json_layout_description(c->layout))); json_object_object_add(object, "orientation", - json_object_new_string(ipc_json_orientation_description(c->layout))); + json_object_new_string( + ipc_json_orientation_description(c->layout))); bool urgent = c->view ? view_is_urgent(c->view) : container_has_urgent_child(c); json_object_object_add(object, "urgent", json_object_new_boolean(urgent)); json_object_object_add(object, "sticky", json_object_new_boolean(c->is_sticky)); + json_object_object_add(object, "fullscreen_mode", json_object_new_int(c->is_fullscreen)); struct sway_node *parent = node_get_parent(&c->node); struct wlr_box parent_box = {0, 0, 0, 0}; @@ -331,7 +441,8 @@ static void ipc_json_describe_container(struct sway_container *c, json_object *o } json_object_object_add(object, "border", - json_object_new_string(describe_container_border(c->current.border))); + json_object_new_string( + ipc_json_border_description(c->current.border))); json_object_object_add(object, "current_border_width", json_object_new_int(c->current.border_thickness)); json_object_object_add(object, "floating_nodes", json_object_new_array()); @@ -372,17 +483,10 @@ static void focus_inactive_children_iterator(struct sway_node *node, json_object *ipc_json_describe_node(struct sway_node *node) { struct sway_seat *seat = input_manager_get_default_seat(); bool focused = seat_get_focus(seat) == node; - - json_object *object = json_object_new_object(); char *name = node_get_name(node); struct wlr_box box; node_get_box(node, &box); - json_object_object_add(object, "id", json_object_new_int((int)node->id)); - json_object_object_add(object, "name", - name ? json_object_new_string(name) : NULL); - json_object_object_add(object, "rect", ipc_json_create_rect(&box)); - json_object_object_add(object, "focused", json_object_new_boolean(focused)); json_object *focus = json_object_new_array(); struct focus_inactive_data data = { @@ -390,24 +494,9 @@ json_object *ipc_json_describe_node(struct sway_node *node) { .object = focus, }; seat_for_each_node(seat, focus_inactive_children_iterator, &data); - json_object_object_add(object, "focus", focus); - // set default values to be compatible with i3 - json_object_object_add(object, "border", - json_object_new_string(describe_container_border(B_NONE))); - json_object_object_add(object, "current_border_width", json_object_new_int(0)); - json_object_object_add(object, "layout", - json_object_new_string(ipc_json_layout_description(L_HORIZ))); - json_object_object_add(object, "orientation", - json_object_new_string(ipc_json_orientation_description(L_HORIZ))); - json_object_object_add(object, "percent", NULL); - json_object_object_add(object, "window_rect", ipc_json_create_empty_rect()); - json_object_object_add(object, "deco_rect", ipc_json_create_empty_rect()); - json_object_object_add(object, "geometry", ipc_json_create_empty_rect()); - json_object_object_add(object, "window", NULL); - json_object_object_add(object, "urgent", json_object_new_boolean(false)); - json_object_object_add(object, "floating_nodes", json_object_new_array()); - json_object_object_add(object, "sticky", json_object_new_boolean(false)); + json_object *object = ipc_json_create_node( + (int)node->id, name, focused, focus, &box); switch (node->type) { case N_ROOT: @@ -434,6 +523,8 @@ json_object *ipc_json_describe_node_recursive(struct sway_node *node) { json_object *children = json_object_new_array(); switch (node->type) { case N_ROOT: + json_object_array_add(children, + ipc_json_describe_scratchpad_output()); for (i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; json_object_array_add(children, @@ -470,22 +561,6 @@ json_object *ipc_json_describe_node_recursive(struct sway_node *node) { return object; } -static const char *describe_device_type(struct sway_input_device *device) { - switch (device->wlr_device->type) { - case WLR_INPUT_DEVICE_POINTER: - return "pointer"; - case WLR_INPUT_DEVICE_KEYBOARD: - return "keyboard"; - case WLR_INPUT_DEVICE_TOUCH: - return "touch"; - case WLR_INPUT_DEVICE_TABLET_TOOL: - return "tablet_tool"; - case WLR_INPUT_DEVICE_TABLET_PAD: - return "tablet_pad"; - } - return "unknown"; -} - json_object *ipc_json_describe_input(struct sway_input_device *device) { if (!(sway_assert(device, "Device must not be null"))) { return NULL; @@ -502,7 +577,8 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) { json_object_object_add(object, "product", json_object_new_int(device->wlr_device->product)); json_object_object_add(object, "type", - json_object_new_string(describe_device_type(device))); + json_object_new_string( + ipc_json_device_type_description(device))); if (device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *keyboard = device->wlr_device->keyboard; @@ -525,6 +601,26 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) { } } + if (wlr_input_device_is_libinput(device->wlr_device)) { + struct libinput_device *libinput_dev; + libinput_dev = wlr_libinput_get_device_handle(device->wlr_device); + + const char *events = "unknown"; + switch (libinput_device_config_send_events_get_mode(libinput_dev)) { + case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED: + events = "enabled"; + break; + case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE: + events = "disabled_on_external_mouse"; + break; + case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED: + events = "disabled"; + break; + } + json_object_object_add(object, "libinput_send_events", + json_object_new_string(events)); + } + return object; } @@ -553,6 +649,31 @@ json_object *ipc_json_describe_seat(struct sway_seat *seat) { return object; } +static uint32_t event_to_x11_button(uint32_t event) { + switch (event) { + case BTN_LEFT: + return 1; + case BTN_MIDDLE: + return 2; + case BTN_RIGHT: + return 3; + case SWAY_SCROLL_UP: + return 4; + case SWAY_SCROLL_DOWN: + return 5; + case SWAY_SCROLL_LEFT: + return 6; + case SWAY_SCROLL_RIGHT: + return 7; + case BTN_SIDE: + return 8; + case BTN_EXTRA: + return 9; + default: + return 0; + } +} + json_object *ipc_json_describe_bar_config(struct bar_config *bar) { if (!sway_assert(bar, "Bar must not be NULL")) { return NULL; @@ -569,18 +690,36 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) { json_object_new_string(bar->status_command) : NULL); json_object_object_add(json, "font", json_object_new_string((bar->font) ? bar->font : config->font)); + + json_object *gaps = json_object_new_object(); + json_object_object_add(gaps, "top", + json_object_new_int(bar->gaps.top)); + json_object_object_add(gaps, "right", + json_object_new_int(bar->gaps.right)); + json_object_object_add(gaps, "bottom", + json_object_new_int(bar->gaps.bottom)); + json_object_object_add(gaps, "left", + json_object_new_int(bar->gaps.left)); + json_object_object_add(json, "gaps", gaps); + if (bar->separator_symbol) { json_object_object_add(json, "separator_symbol", json_object_new_string(bar->separator_symbol)); } json_object_object_add(json, "bar_height", json_object_new_int(bar->height)); + json_object_object_add(json, "status_padding", + json_object_new_int(bar->status_padding)); + json_object_object_add(json, "status_edge_padding", + json_object_new_int(bar->status_edge_padding)); json_object_object_add(json, "wrap_scroll", json_object_new_boolean(bar->wrap_scroll)); json_object_object_add(json, "workspace_buttons", json_object_new_boolean(bar->workspace_buttons)); json_object_object_add(json, "strip_workspace_numbers", json_object_new_boolean(bar->strip_workspace_numbers)); + json_object_object_add(json, "strip_workspace_name", + json_object_new_boolean(bar->strip_workspace_name)); json_object_object_add(json, "binding_mode_indicator", json_object_new_boolean(bar->binding_mode_indicator)); json_object_object_add(json, "verbose", @@ -680,6 +819,8 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) { struct bar_binding *binding = bar->bindings->items[i]; json_object *bind = json_object_new_object(); json_object_object_add(bind, "input_code", + json_object_new_int(event_to_x11_button(binding->button))); + json_object_object_add(bind, "event_code", json_object_new_int(binding->button)); json_object_object_add(bind, "command", json_object_new_string(binding->command)); @@ -699,5 +840,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/ipc-server.c b/sway/ipc-server.c index 6466d263..ff1bc89f 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -1,10 +1,6 @@ // See https://i3wm.org/docs/ipc.html for protocol information #define _POSIX_C_SOURCE 200112L -#ifdef __linux__ #include <linux/input-event-codes.h> -#elif __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#endif #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -73,14 +69,11 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { unlink(ipc_sockaddr->sun_path); while (ipc_client_list->length) { - struct ipc_client *client = ipc_client_list->items[0]; - ipc_client_disconnect(client); + ipc_client_disconnect(ipc_client_list->items[ipc_client_list->length-1]); } list_free(ipc_client_list); - if (ipc_sockaddr) { - free(ipc_sockaddr); - } + free(ipc_sockaddr); wl_list_remove(&ipc_display_destroy.link); } @@ -452,8 +445,12 @@ void ipc_event_binding(struct sway_binding *binding) { json_object_object_add(json_binding, "input_code", json_object_new_int(input_code)); json_object_object_add(json_binding, "symbols", symbols); json_object_object_add(json_binding, "symbol", symbol); - json_object_object_add(json_binding, "input_type", binding->type == BINDING_MOUSE ? - json_object_new_string("mouse") : json_object_new_string("keyboard")); + + bool mouse = binding->type == BINDING_MOUSECODE || + binding->type == BINDING_MOUSESYM; + json_object_object_add(json_binding, "input_type", mouse + ? json_object_new_string("mouse") + : json_object_new_string("keyboard")); json_object *json = json_object_new_object(); json_object_object_add(json, "change", json_object_new_string("run")); @@ -597,13 +594,18 @@ void ipc_client_handle_command(struct ipc_client *client) { switch (client->current_command) { case IPC_COMMAND: { - struct cmd_results *results = execute_command(buf, NULL, NULL); + list_t *res_list = execute_command(buf, NULL, NULL); transaction_commit_dirty(); - char *json = cmd_results_to_json(results); + char *json = cmd_results_to_json(res_list); int length = strlen(json); client_valid = ipc_send_reply(client, json, (uint32_t)length); free(json); - free_cmd_results(results); + while (res_list->length) { + struct cmd_results *results = res_list->items[0]; + free_cmd_results(results); + list_del(res_list, 0); + } + list_free(res_list); goto exit_cleanup; } @@ -619,8 +621,19 @@ void ipc_client_handle_command(struct ipc_client *client) { json_object *outputs = json_object_new_array(); for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; - json_object_array_add(outputs, - ipc_json_describe_node(&output->node)); + json_object *output_json = ipc_json_describe_node(&output->node); + + // override the default focused indicator because it's set + // differently for the get_outputs reply + struct sway_seat *seat = input_manager_get_default_seat(); + struct sway_workspace *focused_ws = + seat_get_focused_workspace(seat); + bool focused = focused_ws && output == focused_ws->output; + json_object_object_del(output_json, "focused"); + json_object_object_add(output_json, "focused", + json_object_new_boolean(focused)); + + json_object_array_add(outputs, output_json); } struct sway_output *output; wl_list_for_each(output, &root->all_outputs, link) { @@ -651,8 +664,9 @@ void ipc_client_handle_command(struct ipc_client *client) { { // TODO: Check if they're permitted to use these events struct json_object *request = json_tokener_parse(buf); - if (request == NULL) { - client_valid = ipc_send_reply(client, "{\"success\": false}", 18); + if (request == NULL || !json_object_is_type(request, json_type_array)) { + const char msg[] = "{\"success\": false}"; + client_valid = ipc_send_reply(client, msg, strlen(msg)); wlr_log(WLR_INFO, "Failed to parse subscribe request"); goto exit_cleanup; } @@ -679,8 +693,8 @@ void ipc_client_handle_command(struct ipc_client *client) { client->subscribed_events |= event_mask(IPC_EVENT_TICK); is_tick = true; } else { - client_valid = - ipc_send_reply(client, "{\"success\": false}", 18); + const char msg[] = "{\"success\": false}"; + client_valid = ipc_send_reply(client, msg, strlen(msg)); json_object_put(request); wlr_log(WLR_INFO, "Unsupported event type in subscribe request"); goto exit_cleanup; @@ -688,10 +702,12 @@ void ipc_client_handle_command(struct ipc_client *client) { } json_object_put(request); - client_valid = ipc_send_reply(client, "{\"success\": true}", 17); + const char msg[] = "{\"success\": true}"; + client_valid = ipc_send_reply(client, msg, strlen(msg)); if (is_tick) { client->current_command = IPC_EVENT_TICK; - ipc_send_reply(client, "{\"first\": true, \"payload\": \"\"}", 30); + const char tickmsg[] = "{\"first\": true, \"payload\": \"\"}"; + ipc_send_reply(client, tickmsg, strlen(tickmsg)); } goto exit_cleanup; } @@ -820,6 +836,14 @@ void ipc_client_handle_command(struct ipc_client *client) { goto exit_cleanup; } + case IPC_SYNC: + { + // It was decided sway will not support this, just return success:false + const char msg[] = "{\"success\": false}"; + ipc_send_reply(client, msg, strlen(msg)); + goto exit_cleanup; + } + default: wlr_log(WLR_INFO, "Unknown IPC command type %i", client->current_command); goto exit_cleanup; diff --git a/sway/main.c b/sway/main.c index 86374163..6e3f6b67 100644 --- a/sway/main.c +++ b/sway/main.c @@ -1,9 +1,9 @@ -#define _XOPEN_SOURCE 700 -#define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200809L #include <getopt.h> #include <pango/pangocairo.h> #include <signal.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -23,7 +23,6 @@ #include "sway/ipc-server.h" #include "ipc-client.h" #include "log.h" -#include "readline.h" #include "stringop.h" #include "util.h" @@ -48,31 +47,28 @@ void detect_raspi(void) { if (!f) { return; } - char *line; - while(!feof(f)) { - if (!(line = read_line(f))) { - break; - } + char *line = NULL; + size_t line_size = 0; + while (getline(&line, &line_size, f) != -1) { if (strstr(line, "Raspberry Pi")) { raspi = true; + break; } - free(line); } fclose(f); FILE *g = fopen("/proc/modules", "r"); if (!g) { + free(line); return; } bool vc4 = false; - while (!feof(g)) { - if (!(line = read_line(g))) { - break; - } + while (getline(&line, &line_size, g) != -1) { if (strstr(line, "vc4")) { vc4 = true; + break; } - free(line); } + free(line); fclose(g); if (!vc4 && raspi) { fprintf(stderr, "\x1B[1;31mWarning: You have a " @@ -87,13 +83,10 @@ void detect_proprietary(int allow_unsupported_gpu) { if (!f) { return; } - while (!feof(f)) { - char *line; - if (!(line = read_line(f))) { - break; - } + char *line = NULL; + size_t line_size = 0; + while (getline(&line, &line_size, f) != -1) { if (strstr(line, "nvidia")) { - free(line); if (allow_unsupported_gpu) { wlr_log(WLR_ERROR, "!!! Proprietary Nvidia drivers are in use !!!"); @@ -107,7 +100,6 @@ void detect_proprietary(int allow_unsupported_gpu) { break; } if (strstr(line, "fglrx")) { - free(line); if (allow_unsupported_gpu) { wlr_log(WLR_ERROR, "!!! Proprietary AMD drivers are in use !!!"); @@ -119,8 +111,8 @@ void detect_proprietary(int allow_unsupported_gpu) { } break; } - free(line); } + free(line); fclose(f); } @@ -147,6 +139,19 @@ static void log_env(void) { } } +static void log_file(FILE *f) { + char *line = NULL; + size_t line_size = 0; + ssize_t nread; + while ((nread = getline(&line, &line_size, f)) != -1) { + if (line[nread - 1] == '\n') { + line[nread - 1] = '\0'; + } + wlr_log(WLR_INFO, "%s", line); + } + free(line); +} + static void log_distro(void) { const char *paths[] = { "/etc/lsb-release", @@ -159,16 +164,7 @@ static void log_distro(void) { FILE *f = fopen(paths[i], "r"); if (f) { wlr_log(WLR_INFO, "Contents of %s:", paths[i]); - while (!feof(f)) { - char *line; - if (!(line = read_line(f))) { - break; - } - if (*line) { - wlr_log(WLR_INFO, "%s", line); - } - free(line); - } + log_file(f); fclose(f); } } @@ -180,16 +176,7 @@ static void log_kernel(void) { wlr_log(WLR_INFO, "Unable to determine kernel version"); return; } - while (!feof(f)) { - char *line; - if (!(line = read_line(f))) { - break; - } - if (*line) { - wlr_log(WLR_INFO, "%s", line); - } - free(line); - } + log_file(f); pclose(f); } @@ -393,11 +380,15 @@ int main(int argc, char **argv) { wlr_log(WLR_DEBUG, "Running deferred commands"); while (config->cmd_queue->length) { char *line = config->cmd_queue->items[0]; - struct cmd_results *res = execute_command(line, NULL, NULL); - if (res->status != CMD_SUCCESS) { - wlr_log(WLR_ERROR, "Error on line '%s': %s", line, res->error); + list_t *res_list = execute_command(line, NULL, NULL); + for (int i = 0; i < res_list->length; ++i) { + struct cmd_results *res = res_list->items[i]; + if (res->status != CMD_SUCCESS) { + wlr_log(WLR_ERROR, "Error on line '%s': %s", line, res->error); + } + free_cmd_results(res); } - free_cmd_results(res); + list_free(res_list); free(line); list_del(config->cmd_queue, 0); } diff --git a/sway/meson.build b/sway/meson.build index 4524bac9..c2ed6298 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -23,6 +23,11 @@ sway_sources = files( 'input/input-manager.c', 'input/seat.c', + 'input/seatop_down.c', + 'input/seatop_move_floating.c', + 'input/seatop_move_tiling.c', + 'input/seatop_resize_floating.c', + 'input/seatop_resize_tiling.c', 'input/cursor.c', 'input/keyboard.c', @@ -78,6 +83,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', @@ -88,7 +94,11 @@ 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', + 'commands/titlebar_padding.c', 'commands/unmark.c', 'commands/urgent.c', 'commands/workspace.c', @@ -96,12 +106,11 @@ sway_sources = files( 'commands/ws_auto_back_and_forth.c', 'commands/xwayland.c', - 'commands/bar/activate_button.c', + 'commands/bar/bind.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', 'commands/bar/hidden_state.c', 'commands/bar/icon_theme.c', @@ -111,11 +120,14 @@ 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/status_edge_padding.c', + 'commands/bar/status_padding.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', @@ -170,6 +182,7 @@ sway_deps = [ cairo, gdk_pixbuf, jsonc, + libevdev, libinput, math, pango, diff --git a/sway/security.c b/sway/security.c index cc0d3f66..6a00229e 100644 --- a/sway/security.c +++ b/sway/security.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <string.h> #include "sway/security.h" diff --git a/sway/server.c b/sway/server.c index 68142e87..0529cab1 100644 --- a/sway/server.c +++ b/sway/server.c @@ -7,25 +7,26 @@ #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.h> #include <wlr/types/wlr_gamma_control_v1.h> +#include <wlr/types/wlr_gamma_control.h> +#include <wlr/types/wlr_gtk_primary_selection.h> #include <wlr/types/wlr_idle.h> #include <wlr/types/wlr_layer_shell_v1.h> -#include <wlr/types/wlr_primary_selection.h> #include <wlr/types/wlr_screencopy_v1.h> #include <wlr/types/wlr_server_decoration.h> #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_xdg_decoration_v1.h> #include <wlr/types/wlr_xdg_output_v1.h> #include <wlr/util/log.h> +#include "config.h" #include "list.h" #include "sway/config.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/input/input-manager.h" #include "sway/server.h" #include "sway/tree/root.h" -#include "config.h" #if HAVE_XWAYLAND #include "sway/xwayland.h" #endif @@ -57,7 +58,7 @@ bool server_init(struct sway_server *server) { wlr_gamma_control_manager_create(server->wl_display); wlr_gamma_control_manager_v1_create(server->wl_display); - wlr_primary_selection_device_manager_create(server->wl_display); + wlr_gtk_primary_selection_device_manager_create(server->wl_display); server->new_output.notify = handle_new_output; wl_signal_add(&server->backend->events.new_output, &server->new_output); @@ -106,6 +107,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 873741c0..3f6b4298 100644 --- a/sway/sway-bar.5.scd +++ b/sway/sway-bar.5.scd @@ -50,6 +50,10 @@ Sway allows configuring swaybar in the sway configuration file. *workspace\_buttons* yes|no Enables or disables workspace buttons on the bar. Default is _yes_. +*strip\_workspace\_name* yes|no + If set to _yes_, then workspace names will be omitted from the workspace + button and only the custom number will be shown. Default is _no_. + *strip\_workspace\_numbers* yes|no If set to _yes_, then workspace numbers will be omitted from the workspace button and only the custom name will be shown. Default is _no_. @@ -57,13 +61,28 @@ Sway allows configuring swaybar in the sway configuration file. *binding\_mode\_indicator* yes|no Enable or disable binding mode indicator. Default is _yes_. +*gaps* <all> | <horizontal> <vertical> | <top> <right> <bottom> <left> + Sets the gaps from the edge of the screen for the bar. Gaps can either be + set all at once, per direction, or per side. Note that only sides that + touch an edge of the screen can have gaps. For the side that does not + touch an edge of the screen, per-side outer gaps for workspaces may be of + use. + *height* <height> - Sets the height of the bar. Default height will match the font size. + Sets the height of the bar. Default height (0) will match the font size. + +*bindcode* [--release] <event-code> <command> + Executes _command_ when the mouse button has been pressed (or if _released_ + is given, when the button has been released). The buttons can be given as + an event code, which can be obtaining from `libinput debug-events`. To + disable the default behavior for a button, use the command _nop_. -*bindsym* [--release] button<n> <command> - Executes _command_ when mouse button _n_ has been pressed (or if _released_ - is given, when mouse button _n_ has been released). To disable the default - behavior for a button, use the command _nop_. +*bindsym* [--release] button[1-9]|<event-name> <command> + Executes _command_ when the mouse button has been pressed (or if _released_ + is given, when the button has been released). The buttons can be given as a + x11 button number or an event name, which can be obtained from `libinput + debug-events`. To disable the default behavior for a button, use the + command _nop_. *mode* dock|hide|invisible Specifies the visibility of the bar. In _dock_ mode, it is permanently @@ -81,6 +100,16 @@ Sway allows configuring swaybar in the sway configuration file. *modifier* <Modifier>|none Specifies the modifier key that shows a hidden bar. Default is _Mod4_. +*status\_padding* <padding> + Sets the vertical padding that is used for the status line. The default is + _1_. If _padding_ is _0_, blocks will be able to take up the full height of + the bar. This value will be multiplied by the output scale. + +*status\_edge\_padding* <padding> + Sets the padding that is used when the status line is at the right edge of + the bar. This value will be multiplied by the output scale. The default is + _3_. + ## TRAY Swaybar provides a system tray where third-party applications may place icons. @@ -89,27 +118,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..c2673f2a 100644 --- a/sway/sway-input.5.scd +++ b/sway/sway-input.5.scd @@ -82,9 +82,12 @@ The following commands may only be used in the configuration file. *input* <identifier> dwt enabled|disabled Enables or disables disable-while-typing for the specified input device. -*input* <identifier> events enabled|disabled|disabled\_on\_external\_mouse - Enables or disables send\_events for specified input device. (Disabling - send\_events disables the input device) +*input* <identifier> events enabled|disabled|disabled\_on\_external\_mouse|toggle + Enables or disables send\_events for specified input device. Disabling + send\_events disables the input device. The _toggle_ option cannot be used + in the config. The order is enabled, disabled\_on\_external\_mouse, + disabled, (loop back to enabled). Any mode which is not supported by the + device will be skipped during the toggle. *input* <identifier> left\_handed enabled|disabled Enables or disables left handed mode for specified input device. @@ -105,10 +108,11 @@ The following commands may only be used in the configuration file. *input* <identifier> repeat\_rate <characters per second> Sets the frequency of key repeats once the repeat\_delay has passed. -*input* <identifier> scroll\_button <button\_identifier> - Sets button used for scroll\_method on\_button\_down. The button identifier - can be obtained from `libinput debug-events`. - If set to 0, it disables the scroll\_button on\_button\_down. +*input* <identifier> scroll\_button disable|button[1-3,8,9]|<event-code-or-name> + Sets the button used for scroll\_method on\_button\_down. The button can + be given as an event name or code, which can be obtained from `libinput + debug-events`, or as a x11 mouse button (button[1-3,8,9]). If set to + _disable_, it disables the scroll\_method on\_button\_down. *input* <identifier> scroll\_factor <floating point value> Changes the scroll factor for the specified input device. Scroll speed will @@ -141,10 +145,29 @@ in their own "seat"). Attach an input device to this seat by its input identifier. A special value of "\*" will attach all devices to the seat. +*seat* <seat> cursor move|set <x> <y> + Move specified seat's cursor relative to current position or wrap to + absolute coordinates (with respect to the global coordinate space). + Specifying either value as 0 will not update that coordinate. + +*seat* <seat> cursor press|release button[1-9]|<event-name-or-code> + Simulate pressing (or releasing) the specified mouse button on the + specified seat. The button can either be provided as a button event name or + event code, which can be obtained from `libinput debug-events`, or as an x11 + mouse button (button[1-9]). If using button[4-7], which map to axes, an axis + event will be simulated, however _press_ and _release_ will be ignored and + both will occur. + *seat* <name> fallback true|false 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 37b7108b..28524478 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -36,7 +36,22 @@ must be separated by one space. For example: *output* <name> position|pos <X> <Y> Places the specified output at the specific position in the global - coordinate space. + 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 + 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 1020 res 3200x1800 + + output eDP1 pos 1600 0 res 1920x1080 + + 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 @@ -45,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 @@ -65,7 +81,7 @@ must be separated by one space. For example: to apply a rotation and flip, or "normal" to apply no transform. If a single output is chosen and a rotation direction is specified (_clockwise_ or _anticlockwise_) then the transform is added or - subtracted from the current tranform. + subtracted from the current transform. *output* <name> disable|enable Enables or disables the specified output (all outputs are enabled by 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 95376ccc..06bc0dbf 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -134,8 +134,9 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). *focus* mode\_toggle Moves focus between the floating and tiled layers. -*fullscreen* - Toggles fullscreen for the focused view. +*fullscreen* [enable|disable|toggle] + Makes focused view fullscreen, non-fullscreen, or the opposite of what it + is now. If no argument is given, it does the same as _toggle_. *gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current set|plus|minus <amount> @@ -280,14 +281,23 @@ runtime. for\_window <criteria> move container to output <output> -*bindsym* [--release|--locked] [--input-device=<device>] <key combo> <command> +*bindsym* [--release|--locked] [--input-device=<device>] [--no-warn] <key combo> <command> Binds _key combo_ to execute the sway command _command_ when pressed. You may use XKB key names here (*xev*(1) is a good tool for discovering these). With the flag _--release_, the command is executed when the key combo is released. Unless the flag _--locked_ is set, the command will not be run when a screen locking program is active. If _input-device_ is given, the binding will only be executed for that input device and will be executed - instead of any binding that is generic to all devices. + instead of any binding that is generic to all devices. By default, if you + overwrite a binding, swaynag will give you a warning. To silence this, use + the _--no-warn_ flag. + + Mouse buttons can either be specified in the form _button[1-9]_ or by using + the name of the event code (ex _BTN\_LEFT_ or _BTN\_RIGHT_). For the former + option, the buttons will be mapped to their values in X11 (1=left, 2=middle, + 3=right, 4=scroll up, 5=scroll down, 6=scroll left, 7=scroll right, 8=back, + 9=forward). For the latter option, you can find the event names using + _libinput debug-events_. Example: ``` @@ -295,8 +305,8 @@ runtime. bindsym Mod1+Shift+f exec firefox ``` - *bindcode* [--release|--locked] [--input-device=<device>] <code> <command> - is also available for binding with key codes instead of key names. + *bindcode* [--release|--locked] [--input-device=<device>] [--no-warn] <code> <command> + is also available for binding with key/button codes instead of key/button names. *client.<class>* <border> <background> <text> <indicator> <child\_border> Configures the color of window borders and title bars. All 5 colors are @@ -321,7 +331,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: @@ -440,6 +451,16 @@ The default colors are: *font* <font> Sets font for use in title bars in Pango format. +*titlebar\_border\_thickness* <thickness> + 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 + should be greater than titlebar\_border\_thickness. If _vertical_ value is + not specified it is set to the _horizontal_ value. + *for\_window* <criteria> <command> Whenever a window that matches _criteria_ appears, run list of commands. See *CRITERIA* for more details. @@ -474,15 +495,6 @@ The default colors are: *seat* <seat> <seat-subcommands...> For details on seat subcommands, see *sway-input*(5). -*seat* <seat> cursor move|set <x> <y> - Move specified seat's cursor relative to current position or wrap to - absolute coordinates (with respect to the global coordinate space). - Specifying either value as 0 will not update that coordinate. - -*seat* <seat> cursor press|release left|right|1|2|3... - Simulate pressing (or releasing) the specified mouse button on the - specified seat. - *kill* Kills (closes) the currently focused container and all of its children. @@ -549,6 +561,26 @@ The default colors are: Set the opacity of the window between 0 (completely transparent) and 1 (completely opaque). +*tiling\_drag* enable|disable|toggle + Sets whether or not tiling containers can be dragged with the mouse. If + enabled (default), the _floating\_mod_ can be used to drag tiling, as well + as floating, containers. Using the left mouse button on title bars without + 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 + _right_ side. + *unmark* [<identifier>] *unmark* will remove _identifier_ from the list of current marks on a window. If _identifier_ is omitted, all marks are removed. diff --git a/sway/tree/container.c b/sway/tree/container.c index cf6f5b54..d9c721f5 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -21,6 +21,7 @@ #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" +#include "list.h" #include "log.h" #include "stringop.h" @@ -67,8 +68,7 @@ void container_destroy(struct sway_container *con) { list_free(con->current.children); list_free(con->outputs); - list_foreach(con->marks, free); - list_free(con->marks); + list_free_items_and_destroy(con->marks); wlr_texture_destroy(con->marks_focused); wlr_texture_destroy(con->marks_focused_inactive); wlr_texture_destroy(con->marks_unfocused); @@ -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], @@ -594,7 +601,7 @@ void container_update_representation(struct sway_container *con) { } size_t container_titlebar_height(void) { - return config->font_height + TITLEBAR_V_PADDING * 2; + return config->font_height + config->titlebar_v_padding * 2; } void container_init_floating(struct sway_container *con) { @@ -857,15 +864,7 @@ bool container_has_urgent_child(struct sway_container *container) { void container_end_mouse_operation(struct sway_container *container) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { - if (seat->op_container == container) { - seat->op_target_node = NULL; // ensure tiling move doesn't apply - seat_end_mouse_operation(seat); - } - // If the user is doing a tiling drag over this container, - // keep the operation active but unset the target container. - if (seat->op_target_node == &container->node) { - seat->op_target_node = NULL; - } + seatop_unref(seat, container); } } @@ -979,7 +978,7 @@ void container_discover_outputs(struct sway_container *con) { output_get_box(output, &output_box); struct wlr_box intersection; bool intersects = - wlr_box_intersection(&con_box, &output_box, &intersection); + wlr_box_intersection(&intersection, &con_box, &output_box); int index = list_find(con->outputs, output); if (intersects && index == -1) { @@ -1267,7 +1266,9 @@ bool container_find_and_unmark(char *mark) { } void container_clear_marks(struct sway_container *con) { - list_foreach(con->marks, free); + for (int i = 0; i < con->marks->length; ++i) { + free(con->marks->items[i]); + } con->marks->length = 0; ipc_event_window(con, "mark"); } @@ -1375,3 +1376,16 @@ void container_update_marks_textures(struct sway_container *con) { &config->border_colors.urgent); container_damage_whole(con); } + +void container_raise_floating(struct sway_container *con) { + // Bring container to front by putting it at the end of the floating list. + struct sway_container *floater = con; + while (floater->parent) { + floater = floater->parent; + } + if (container_is_floating(floater)) { + list_move_to_end(floater->workspace->floating, floater); + node_set_dirty(&floater->workspace->node); + } +} + diff --git a/sway/tree/output.c b/sway/tree/output.c index 3c4614a8..f24be010 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -58,6 +58,7 @@ struct sway_output *output_create(struct wlr_output *wlr_output) { wlr_output->data = output; wl_signal_add(&wlr_output->events.destroy, &output->destroy); + wl_signal_init(&output->events.destroy); wl_list_insert(&root->all_outputs, &output->link); @@ -76,7 +77,6 @@ void output_enable(struct sway_output *output, struct output_config *oc) { for (size_t i = 0; i < len; ++i) { wl_list_init(&output->layers[i]); } - wl_signal_init(&output->events.destroy); output->enabled = true; list_add(root->outputs, output); @@ -88,11 +88,12 @@ void output_enable(struct sway_output *output, struct output_config *oc) { restore_workspaces(output); + struct sway_workspace *ws = NULL; if (!output->workspaces->length) { // Create workspace char *ws_name = workspace_next_name(wlr_output->name); wlr_log(WLR_DEBUG, "Creating default workspace %s", ws_name); - struct sway_workspace *ws = workspace_create(output, ws_name); + ws = workspace_create(output, ws_name); // Set each seat's focus if not already set struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { @@ -104,9 +105,15 @@ void output_enable(struct sway_output *output, struct output_config *oc) { ipc_event_workspace(NULL, ws, "init"); } - apply_output_config(oc, output); + if (ws && config->default_orientation == L_NONE) { + // Since the output transformation and resolution could have changed + // due to applying the output config, the previously set layout for the + // created workspace may not be correct for `default_orientation auto` + ws->layout = output_get_default_layout(output); + } + input_manager_configure_xcursor(); wl_signal_add(&wlr_output->events.mode, &output->mode); @@ -218,6 +225,11 @@ void output_disable(struct sway_output *output) { root_for_each_container(untrack_output, output); + if (output->bg_pid) { + terminate_swaybg(output->bg_pid); + output->bg_pid = 0; + } + int index = list_find(root->outputs, output); list_del(root->outputs, index); diff --git a/sway/tree/root.c b/sway/tree/root.c index 544d666a..e1624863 100644 --- a/sway/tree/root.c +++ b/sway/tree/root.c @@ -5,6 +5,7 @@ #include <wlr/types/wlr_output_layout.h> #include "sway/desktop/transaction.h" #include "sway/input/seat.h" +#include "sway/ipc-server.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" @@ -68,13 +69,18 @@ void root_scratchpad_add_container(struct sway_container *con) { list_add(root->scratchpad, con); struct sway_seat *seat = input_manager_current_seat(); + struct sway_node *new_focus = NULL; if (parent) { arrange_container(parent); - seat_set_focus(seat, seat_get_focus_inactive(seat, &parent->node)); - } else { + new_focus = seat_get_focus_inactive(seat, &parent->node); + } + if (!new_focus) { arrange_workspace(workspace); - seat_set_focus(seat, seat_get_focus_inactive(seat, &workspace->node)); + new_focus = seat_get_focus_inactive(seat, &workspace->node); } + seat_set_focus(seat, new_focus); + + ipc_event_window(con, "move"); } void root_scratchpad_remove_container(struct sway_container *con) { @@ -85,45 +91,51 @@ void root_scratchpad_remove_container(struct sway_container *con) { int index = list_find(root->scratchpad, con); if (index != -1) { list_del(root->scratchpad, index); + ipc_event_window(con, "move"); } } void root_scratchpad_show(struct sway_container *con) { struct sway_seat *seat = input_manager_current_seat(); - struct sway_workspace *ws = seat_get_focused_workspace(seat); + struct sway_workspace *new_ws = seat_get_focused_workspace(seat); + struct sway_workspace *old_ws = con->workspace; - // If the current con or any of its parents are in fullscreen mode, we - // first need to disable it before showing the scratchpad con. - if (ws->fullscreen) { - container_set_fullscreen(ws->fullscreen, false); + // If the current con or any of its parents are in fullscreen mode, we + // first need to disable it before showing the scratchpad con. + if (new_ws->fullscreen) { + container_set_fullscreen(new_ws->fullscreen, false); } // Show the container - if (con->workspace) { + if (old_ws) { container_detach(con); } - workspace_add_floating(ws, con); + workspace_add_floating(new_ws, con); // Make sure the container's center point overlaps this workspace double center_lx = con->x + con->width / 2; double center_ly = con->y + con->height / 2; struct wlr_box workspace_box; - workspace_get_box(ws, &workspace_box); + workspace_get_box(new_ws, &workspace_box); if (!wlr_box_contains_point(&workspace_box, center_lx, center_ly)) { // Maybe resize it - if (con->width > ws->width || con->height > ws->height) { + if (con->width > new_ws->width || con->height > new_ws->height) { container_init_floating(con); } // Center it - double new_lx = ws->x + (ws->width - con->width) / 2; - double new_ly = ws->y + (ws->height - con->height) / 2; + double new_lx = new_ws->x + (new_ws->width - con->width) / 2; + double new_ly = new_ws->y + (new_ws->height - con->height) / 2; container_floating_move_to(con, new_lx, new_ly); } - arrange_workspace(ws); + arrange_workspace(new_ws); seat_set_focus(seat, seat_get_focus_inactive(seat, &con->node)); + + if (new_ws != old_ws) { + ipc_event_window(con, "move"); + } } void root_scratchpad_hide(struct sway_container *con) { @@ -133,10 +145,12 @@ void root_scratchpad_hide(struct sway_container *con) { container_detach(con); arrange_workspace(ws); - if (&con->node == focus) { + if (&con->node == focus || node_has_ancestor(focus, &con->node)) { seat_set_focus(seat, seat_get_focus_inactive(seat, &ws->node)); } list_move_to_end(root->scratchpad, con); + + ipc_event_window(con, "move"); } struct pid_workspace { diff --git a/sway/tree/view.c b/sway/tree/view.c index d7110619..5371ee20 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -437,9 +437,14 @@ void view_execute_criteria(struct sway_view *view) { wlr_log(WLR_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", criteria->raw, view, criteria->cmdlist); list_add(view->executed_criteria, criteria); - struct cmd_results *res = execute_command( + list_t *res_list = execute_command( criteria->cmdlist, NULL, view->container); - free_cmd_results(res); + while (res_list->length) { + struct cmd_results *res = res_list->items[0]; + free_cmd_results(res); + list_del(res_list, 0); + } + list_free(res_list); } list_free(criterias); } @@ -454,7 +459,7 @@ static struct sway_workspace *select_workspace(struct sway_view *view) { for (int i = 0; i < criterias->length; ++i) { struct criteria *criteria = criterias->items[i]; if (criteria->type == CT_ASSIGN_OUTPUT) { - struct sway_output *output = output_by_name(criteria->target); + struct sway_output *output = output_by_name_or_id(criteria->target); if (output) { ws = output_get_active_workspace(output); break; @@ -600,7 +605,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, view_update_title(view, false); container_update_representation(view->container); - view_execute_criteria(view); if (decoration) { view_update_csd_from_client(view, decoration); @@ -617,6 +621,8 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, } } + view_execute_criteria(view); + if (should_focus(view)) { input_manager_set_focus(&view->container->node); } @@ -648,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(); @@ -674,6 +674,8 @@ void view_update_size(struct sway_view *view, int width, int height) { container_set_geometry_from_content(view->container); } +static const struct sway_view_child_impl subsurface_impl; + static void subsurface_get_root_coords(struct sway_view_child *child, int *root_sx, int *root_sy) { struct wlr_surface *surface = child->surface; @@ -689,18 +691,47 @@ static void subsurface_get_root_coords(struct sway_view_child *child, } } +static void subsurface_destroy(struct sway_view_child *child) { + if (!sway_assert(child->impl == &subsurface_impl, + "Expected a subsurface")) { + return; + } + struct sway_subsurface *subsurface = (struct sway_subsurface *)child; + wl_list_remove(&subsurface->destroy.link); + free(subsurface); +} + static const struct sway_view_child_impl subsurface_impl = { .get_root_coords = subsurface_get_root_coords, + .destroy = subsurface_destroy, }; +static void subsurface_handle_destroy(struct wl_listener *listener, + void *data) { + struct sway_subsurface *subsurface = + wl_container_of(listener, subsurface, destroy); + struct sway_view_child *child = &subsurface->child; + view_child_destroy(child); +} + +static void view_child_damage(struct sway_view_child *child, bool whole); + static void view_subsurface_create(struct sway_view *view, - struct wlr_subsurface *subsurface) { - struct sway_view_child *child = calloc(1, sizeof(struct sway_view_child)); - if (child == NULL) { + struct wlr_subsurface *wlr_subsurface) { + struct sway_subsurface *subsurface = + calloc(1, sizeof(struct sway_subsurface)); + if (subsurface == NULL) { wlr_log(WLR_ERROR, "Allocation failed"); return; } - view_child_init(child, &subsurface_impl, view, subsurface->surface); + view_child_init(&subsurface->child, &subsurface_impl, view, + wlr_subsurface->surface); + + wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); + subsurface->destroy.notify = subsurface_handle_destroy; + + subsurface->child.mapped = true; + view_child_damage(&subsurface->child, true); } static void view_child_damage(struct sway_view_child *child, bool whole) { @@ -745,6 +776,7 @@ static void view_child_handle_surface_map(struct wl_listener *listener, void *data) { struct sway_view_child *child = wl_container_of(listener, child, surface_map); + child->mapped = true; view_child_damage(child, true); } @@ -753,6 +785,7 @@ static void view_child_handle_surface_unmap(struct wl_listener *listener, struct sway_view_child *child = wl_container_of(listener, child, surface_unmap); view_child_damage(child, true); + child->mapped = false; } void view_child_init(struct sway_view_child *child, @@ -771,6 +804,7 @@ void view_child_init(struct sway_view_child *child, wl_signal_add(&surface->events.destroy, &child->surface_destroy); child->surface_destroy.notify = view_child_handle_surface_destroy; + // Not all child views have a map/unmap event child->surface_map.notify = view_child_handle_surface_map; child->surface_unmap.notify = view_child_handle_surface_unmap; @@ -781,6 +815,10 @@ void view_child_init(struct sway_view_child *child, } void view_child_destroy(struct sway_view_child *child) { + if (child->mapped && child->view->container != NULL) { + view_child_damage(child, true); + } + wl_list_remove(&child->surface_commit.link); wl_list_remove(&child->surface_destroy.link); @@ -824,12 +862,29 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { return NULL; } +static char *escape_pango_markup(const char *buffer) { + size_t length = escape_markup_text(buffer, NULL); + char *escaped_title = calloc(length + 1, sizeof(char)); + escape_markup_text(buffer, escaped_title); + return escaped_title; +} + static size_t append_prop(char *buffer, const char *value) { if (!value) { return 0; } - lenient_strcat(buffer, value); - return strlen(value); + // If using pango_markup in font, we need to escape all markup chars + // from values to make sure tags are not inserted by clients + if (config->pango_markup) { + char *escaped_value = escape_pango_markup(value); + lenient_strcat(buffer, escaped_value); + size_t len = strlen(escaped_value); + free(escaped_value); + return len; + } else { + lenient_strcat(buffer, value); + return strlen(value); + } } /** @@ -838,11 +893,7 @@ static size_t append_prop(char *buffer, const char *value) { */ static size_t parse_title_format(struct sway_view *view, char *buffer) { if (!view->title_format || strcmp(view->title_format, "%title") == 0) { - const char *title = view_get_title(view); - if (buffer && title) { - strcpy(buffer, title); - } - return title ? strlen(title) : 0; + return append_prop(buffer, view_get_title(view)); } size_t len = 0; @@ -882,14 +933,6 @@ static size_t parse_title_format(struct sway_view *view, char *buffer) { return len; } -static char *escape_title(char *buffer) { - size_t length = escape_markup_text(buffer, NULL); - char *escaped_title = calloc(length + 1, sizeof(char)); - escape_markup_text(buffer, escaped_title); - free(buffer); - return escaped_title; -} - void view_update_title(struct sway_view *view, bool force) { const char *title = view_get_title(view); @@ -912,10 +955,6 @@ void view_update_title(struct sway_view *view, bool force) { return; } parse_title_format(view, buffer); - // now we have the title, but needs to be escaped when using pango markup - if (config->pango_markup) { - buffer = escape_title(buffer); - } view->container->title = strdup(title); view->container->formatted_title = buffer; diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 4be63311..7f18046d 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -35,10 +35,8 @@ struct sway_output *workspace_get_initial_output(const char *name) { struct workspace_config *wsc = workspace_find_config(name); if (wsc) { for (int i = 0; i < wsc->outputs->length; i++) { - struct sway_output *output = output_by_name(wsc->outputs->items[i]); - if (!output) { - output = output_by_identifier(wsc->outputs->items[i]); - } + struct sway_output *output = + output_by_name_or_id(wsc->outputs->items[i]); if (output) { return output; } @@ -113,7 +111,10 @@ struct sway_workspace *workspace_create(struct sway_output *output, // Add output priorities for (int i = 0; i < wsc->outputs->length; ++i) { - list_add(ws->output_priority, strdup(wsc->outputs->items[i])); + char *name = wsc->outputs->items[i]; + if (strcmp(name, "*") != 0) { + list_add(ws->output_priority, strdup(name)); + } } } } @@ -142,7 +143,7 @@ void workspace_destroy(struct sway_workspace *workspace) { free(workspace->name); free(workspace->representation); - free_flat_list(workspace->output_priority); + list_free_items_and_destroy(workspace->output_priority); list_free(workspace->floating); list_free(workspace->tiling); list_free(workspace->current.floating); @@ -182,7 +183,11 @@ static bool workspace_valid_on_output(const char *output_name, const char *ws_name) { struct workspace_config *wsc = workspace_find_config(ws_name); char identifier[128]; - struct sway_output *output = output_by_name(output_name); + struct sway_output *output = output_by_name_or_id(output_name); + if (!output) { + return false; + } + output_name = output->wlr_output->name; output_get_identifier(identifier, sizeof(identifier), output); if (!wsc) { @@ -190,7 +195,8 @@ static bool workspace_valid_on_output(const char *output_name, } for (int i = 0; i < wsc->outputs->length; i++) { - if (strcmp(wsc->outputs->items[i], output_name) == 0 || + if (strcmp(wsc->outputs->items[i], "*") == 0 || + strcmp(wsc->outputs->items[i], output_name) == 0 || strcmp(wsc->outputs->items[i], identifier) == 0) { return true; } @@ -286,6 +292,14 @@ char *workspace_next_name(const char *output_name) { // assignments primarily, falling back to bindings and numbers. struct sway_mode *mode = config->current_mode; + char identifier[128]; + struct sway_output *output = output_by_name_or_id(output_name); + if (!output) { + return NULL; + } + output_name = output->wlr_output->name; + output_get_identifier(identifier, sizeof(identifier), output); + int order = INT_MAX; char *target = NULL; for (int i = 0; i < mode->keysym_bindings->length; ++i) { @@ -304,7 +318,9 @@ char *workspace_next_name(const char *output_name) { } bool found = false; for (int j = 0; j < wsc->outputs->length; ++j) { - if (strcmp(wsc->outputs->items[j], output_name) == 0) { + if (strcmp(wsc->outputs->items[j], "*") == 0 || + strcmp(wsc->outputs->items[j], output_name) == 0 || + strcmp(wsc->outputs->items[j], identifier) == 0) { found = true; free(target); target = strdup(wsc->workspace); @@ -525,13 +541,19 @@ void workspace_output_add_priority(struct sway_workspace *workspace, struct sway_output *workspace_output_get_highest_available( struct sway_workspace *ws, struct sway_output *exclude) { + char exclude_id[128] = {'\0'}; + if (exclude) { + output_get_identifier(exclude_id, sizeof(exclude_id), exclude); + } + for (int i = 0; i < ws->output_priority->length; i++) { char *name = ws->output_priority->items[i]; - if (exclude && strcasecmp(name, exclude->wlr_output->name) == 0) { + if (exclude && (strcmp(name, exclude->wlr_output->name) == 0 + || strcmp(name, exclude_id) == 0)) { continue; } - struct sway_output *output = output_by_name(name); + struct sway_output *output = output_by_name_or_id(name); if (output) { return output; } diff --git a/swaybar/bar.c b/swaybar/bar.c index 08c386a7..d36367fc 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -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" @@ -31,6 +35,7 @@ void free_workspaces(struct wl_list *list) { wl_list_for_each_safe(ws, tmp, list, link) { wl_list_remove(&ws->link); free(ws->name); + free(ws->label); free(ws); } } @@ -54,6 +59,7 @@ static void swaybar_output_free(struct swaybar_output *output) { free_workspaces(&output->workspaces); wl_list_remove(&output->link); free(output->name); + free(output->identifier); free(output); } @@ -119,7 +125,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); @@ -161,13 +167,15 @@ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) { return visible; } -static bool bar_uses_output(struct swaybar *bar, const char *name) { - if (bar->config->all_outputs) { +static bool bar_uses_output(struct swaybar_output *output) { + if (output->bar->config->all_outputs) { return true; } + char *identifier = output->identifier; struct config_output *coutput; - wl_list_for_each(coutput, &bar->config->outputs, link) { - if (strcmp(coutput->name, name) == 0) { + wl_list_for_each(coutput, &output->bar->config->outputs, link) { + if (strcmp(coutput->name, output->name) == 0 || + (identifier && strcmp(coutput->name, identifier) == 0)) { return true; } } @@ -195,6 +203,10 @@ static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct swaybar_output *output = data; output->scale = factor; + if (output == output->bar->pointer.current) { + update_cursor(output->bar); + render_frame(output); + } } struct wl_output_listener output_listener = { @@ -206,12 +218,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, @@ -220,7 +236,7 @@ static void xdg_output_handle_done(void *data, struct swaybar *bar = output->bar; assert(output->name != NULL); - if (!bar_uses_output(bar, output->name)) { + if (!bar_uses_output(output)) { swaybar_output_free(output); return; } @@ -245,7 +261,22 @@ static void xdg_output_handle_name(void *data, static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { - // Who cares + // wlroots currently sets the description to `make model serial (name)` + // If this changes in the future, this will need to be modified. + struct swaybar_output *output = data; + free(output->identifier); + output->identifier = NULL; + char *paren = strrchr(description, '('); + if (paren) { + size_t length = paren - description; + output->identifier = malloc(length); + if (!output->identifier) { + wlr_log(WLR_ERROR, "Failed to allocate output identifier"); + return; + } + strncpy(output->identifier, description, length); + output->identifier[length - 1] = '\0'; + } } struct zxdg_output_v1_listener xdg_output_listener = { @@ -272,7 +303,7 @@ static void handle_global(void *data, struct wl_registry *registry, struct swaybar *bar = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { bar->compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 3); + &wl_compositor_interface, 4); } else if (strcmp(interface, wl_seat_interface.name) == 0) { bar->seat = wl_registry_bind(registry, name, &wl_seat_interface, 3); @@ -354,25 +385,15 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) { wl_display_roundtrip(bar->display); struct swaybar_pointer *pointer = &bar->pointer; - - int max_scale = 1; - struct swaybar_output *output; - wl_list_for_each(output, &bar->outputs, link) { - if (output->scale > max_scale) { - max_scale = output->scale; - } - } - - pointer->cursor_theme = - wl_cursor_theme_load(NULL, 24 * max_scale, bar->shm); - assert(pointer->cursor_theme); - struct wl_cursor *cursor; - cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); - assert(cursor); - pointer->cursor_image = cursor->images[0]; 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); } @@ -414,6 +435,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) { @@ -431,6 +457,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 0fd1f02e..d4cc9b1a 100644 --- a/swaybar/config.c +++ b/swaybar/config.c @@ -1,9 +1,10 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <string.h> #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" @@ -30,15 +31,24 @@ struct swaybar_config *init_config(void) { config->hidden_state = strdup("hide"); config->sep_symbol = NULL; config->strip_workspace_numbers = false; + config->strip_workspace_name = false; config->binding_mode_indicator = true; config->wrap_scroll = false; config->workspace_buttons = true; config->bindings = create_list(); wl_list_init(&config->outputs); + config->status_padding = 1; + config->status_edge_padding = 3; /* height */ config->height = 0; + /* gaps */ + config->gaps.top = 0; + config->gaps.right = 0; + config->gaps.bottom = 0; + config->gaps.left = 0; + /* colors */ config->colors.background = 0x000000FF; config->colors.focused_background = 0x000000FF; @@ -66,6 +76,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; } @@ -95,5 +109,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/i3bar.c b/swaybar/i3bar.c index 3ea74e13..116c8f6e 100644 --- a/swaybar/i3bar.c +++ b/swaybar/i3bar.c @@ -259,8 +259,34 @@ bool i3bar_handle_readable(struct status_line *status) { } } +static uint32_t event_to_x11_button(uint32_t event) { + switch (event) { + case BTN_LEFT: + return 1; + case BTN_MIDDLE: + return 2; + case BTN_RIGHT: + return 3; + case SWAY_SCROLL_UP: + return 4; + case SWAY_SCROLL_DOWN: + return 5; + case SWAY_SCROLL_LEFT: + return 6; + case SWAY_SCROLL_RIGHT: + return 7; + case BTN_SIDE: + return 8; + case BTN_EXTRA: + return 9; + default: + return 0; + } +} + enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, - struct i3bar_block *block, int x, int y, enum x11_button button) { + struct i3bar_block *block, int x, int y, int rx, int ry, int w, int h, + uint32_t button) { wlr_log(WLR_DEBUG, "block %s clicked", block->name); if (!block->name || !status->click_events) { return HOTSPOT_PROCESS; @@ -274,9 +300,15 @@ enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, json_object_new_string(block->instance)); } - json_object_object_add(event_json, "button", json_object_new_int(button)); + json_object_object_add(event_json, "button", + json_object_new_int(event_to_x11_button(button))); + json_object_object_add(event_json, "event", json_object_new_int(button)); json_object_object_add(event_json, "x", json_object_new_int(x)); json_object_object_add(event_json, "y", json_object_new_int(y)); + json_object_object_add(event_json, "relative_x", json_object_new_int(rx)); + json_object_object_add(event_json, "relative_y", json_object_new_int(ry)); + json_object_object_add(event_json, "width", json_object_new_int(w)); + json_object_object_add(event_json, "height", json_object_new_int(h)); if (dprintf(status->write_fd, "%s%s\n", status->clicked ? "," : "", json_object_to_json_string(event_json)) < 0) { status_error(status, "[failed to write click event]"); diff --git a/swaybar/input.c b/swaybar/input.c index 263d0253..bdd55e58 100644 --- a/swaybar/input.c +++ b/swaybar/input.c @@ -1,9 +1,5 @@ #include <assert.h> -#ifdef __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#else #include <linux/input-event-codes.h> -#endif #include <stdlib.h> #include <wayland-client.h> #include <wayland-cursor.h> @@ -26,33 +22,39 @@ void free_hotspots(struct wl_list *list) { } } -static enum x11_button wl_button_to_x11_button(uint32_t button) { - switch (button) { - case BTN_LEFT: - return LEFT; - case BTN_MIDDLE: - return MIDDLE; - case BTN_RIGHT: - return RIGHT; - case BTN_SIDE: - return BACK; - case BTN_EXTRA: - return FORWARD; - default: - return NONE; - } -} - -static enum x11_button wl_axis_to_x11_button(uint32_t axis, wl_fixed_t value) { +static uint32_t wl_axis_to_button(uint32_t axis, wl_fixed_t value) { + bool negative = wl_fixed_to_double(value) < 0; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: - return wl_fixed_to_double(value) < 0 ? SCROLL_UP : SCROLL_DOWN; + return negative ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: - return wl_fixed_to_double(value) < 0 ? SCROLL_LEFT : SCROLL_RIGHT; + return negative ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT; default: wlr_log(WLR_DEBUG, "Unexpected axis value on mouse scroll"); - return NONE; + return 0; + } +} + +void update_cursor(struct swaybar *bar) { + struct swaybar_pointer *pointer = &bar->pointer; + if (pointer->cursor_theme) { + wl_cursor_theme_destroy(pointer->cursor_theme); } + int scale = pointer->current ? pointer->current->scale : 1; + pointer->cursor_theme = wl_cursor_theme_load(NULL, 24 * scale, bar->shm); + struct wl_cursor *cursor; + cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); + pointer->cursor_image = cursor->images[0]; + wl_surface_set_buffer_scale(pointer->cursor_surface, scale); + wl_surface_attach(pointer->cursor_surface, + wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0); + wl_pointer_set_cursor(pointer->pointer, pointer->serial, + pointer->cursor_surface, + pointer->cursor_image->hotspot_x / scale, + pointer->cursor_image->hotspot_y / scale); + wl_surface_damage_buffer(pointer->cursor_surface, 0, 0, + INT32_MAX, INT32_MAX); + wl_surface_commit(pointer->cursor_surface); } static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, @@ -60,6 +62,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct swaybar *bar = data; struct swaybar_pointer *pointer = &bar->pointer; + pointer->serial = serial; struct swaybar_output *output; wl_list_for_each(output, &bar->outputs, link) { if (output->surface == surface) { @@ -67,20 +70,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, break; } } - int max_scale = 1; - struct swaybar_output *_output; - wl_list_for_each(_output, &bar->outputs, link) { - if (_output->scale > max_scale) { - max_scale = _output->scale; - } - } - wl_surface_set_buffer_scale(pointer->cursor_surface, max_scale); - wl_surface_attach(pointer->cursor_surface, - wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0); - wl_pointer_set_cursor(wl_pointer, serial, pointer->cursor_surface, - pointer->cursor_image->hotspot_x / max_scale, - pointer->cursor_image->hotspot_y / max_scale); - wl_surface_commit(pointer->cursor_surface); + update_cursor(bar); } static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, @@ -96,12 +86,12 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, bar->pointer.y = wl_fixed_to_int(surface_y); } -static bool check_bindings(struct swaybar *bar, uint32_t x11_button, +static bool check_bindings(struct swaybar *bar, uint32_t button, uint32_t state) { bool released = state == WL_POINTER_BUTTON_STATE_RELEASED; for (int i = 0; i < bar->config->bindings->length; i++) { struct swaybar_binding *binding = bar->config->bindings->items[i]; - if (binding->button == x11_button && binding->release == released) { + if (binding->button == button && binding->release == released) { ipc_execute_binding(bar, binding); return true; } @@ -118,7 +108,7 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, return; } - if (check_bindings(bar, wl_button_to_x11_button(button), state)) { + if (check_bindings(bar, button, state)) { return; } @@ -133,8 +123,8 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, && y >= hotspot->y && x < hotspot->x + hotspot->width && y < hotspot->y + hotspot->height) { - if (HOTSPOT_IGNORE == hotspot->callback(output, pointer->x, pointer->y, - wl_button_to_x11_button(button), hotspot->data)) { + if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, + pointer->x, pointer->y, button, hotspot->data)) { return; } } @@ -152,7 +142,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, // If there is a button press binding, execute it, skip default behavior, // and check button release bindings - enum x11_button button = wl_axis_to_x11_button(axis, value); + uint32_t button = wl_axis_to_button(axis, value); if (check_bindings(bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) { check_bindings(bar, button, WL_POINTER_BUTTON_STATE_RELEASED); return; @@ -166,8 +156,8 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, && y >= hotspot->y && x < hotspot->x + hotspot->width && y < hotspot->y + hotspot->height) { - if (HOTSPOT_IGNORE == hotspot->callback( - output, pointer->x, pointer->y, button, hotspot->data)) { + if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, + pointer->x, pointer->y, button, hotspot->data)) { return; } } diff --git a/swaybar/ipc.c b/swaybar/ipc.c index 706f968d..097f9161 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" @@ -153,18 +154,21 @@ static bool ipc_parse_config( return false; } json_object *markup, *mode, *hidden_state, *position, *status_command; - json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; - json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; - json_object *bindings; + json_object *font, *gaps, *bar_height, *wrap_scroll, *workspace_buttons; + json_object *strip_workspace_numbers, *strip_workspace_name; + json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol; + json_object *outputs, *bindings, *status_padding, *status_edge_padding; json_object_object_get_ex(bar_config, "mode", &mode); json_object_object_get_ex(bar_config, "hidden_state", &hidden_state); json_object_object_get_ex(bar_config, "position", &position); json_object_object_get_ex(bar_config, "status_command", &status_command); json_object_object_get_ex(bar_config, "font", &font); + json_object_object_get_ex(bar_config, "gaps", &gaps); json_object_object_get_ex(bar_config, "bar_height", &bar_height); json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll); json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons); json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers); + json_object_object_get_ex(bar_config, "strip_workspace_name", &strip_workspace_name); json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator); json_object_object_get_ex(bar_config, "verbose", &verbose); json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol); @@ -172,6 +176,9 @@ static bool ipc_parse_config( json_object_object_get_ex(bar_config, "outputs", &outputs); json_object_object_get_ex(bar_config, "pango_markup", &markup); json_object_object_get_ex(bar_config, "bindings", &bindings); + json_object_object_get_ex(bar_config, "status_padding", &status_padding); + json_object_object_get_ex(bar_config, "status_edge_padding", + &status_edge_padding); if (status_command) { free(config->status_command); config->status_command = strdup(json_object_get_string(status_command)); @@ -190,6 +197,9 @@ static bool ipc_parse_config( if (strip_workspace_numbers) { config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers); } + if (strip_workspace_name) { + config->strip_workspace_name = json_object_get_boolean(strip_workspace_name); + } if (binding_mode_indicator) { config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator); } @@ -202,6 +212,30 @@ static bool ipc_parse_config( if (bar_height) { config->height = json_object_get_int(bar_height); } + if (status_padding) { + config->status_padding = json_object_get_int(status_padding); + } + if (status_edge_padding) { + config->status_edge_padding = json_object_get_int(status_edge_padding); + } + if (gaps) { + json_object *top = json_object_object_get(gaps, "top"); + if (top) { + config->gaps.top = json_object_get_int(top); + } + json_object *right = json_object_object_get(gaps, "right"); + if (right) { + config->gaps.right = json_object_get_int(right); + } + json_object *bottom = json_object_object_get(gaps, "bottom"); + if (bottom) { + config->gaps.bottom = json_object_get_int(bottom); + } + json_object *left = json_object_object_get(gaps, "left"); + if (left) { + config->gaps.left = json_object_get_int(left); + } + } if (markup) { config->pango_markup = json_object_get_boolean(markup); } @@ -212,7 +246,7 @@ static bool ipc_parse_config( struct swaybar_binding *binding = calloc(1, sizeof(struct swaybar_binding)); binding->button = json_object_get_int( - json_object_object_get(bindobj, "input_code")); + json_object_object_get(bindobj, "event_code")); binding->command = strdup(json_object_get_string( json_object_object_get(bindobj, "command"))); binding->release = json_object_get_boolean( @@ -258,6 +292,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; } @@ -298,6 +366,24 @@ bool ipc_get_workspaces(struct swaybar *bar) { calloc(1, sizeof(struct swaybar_workspace)); ws->num = json_object_get_int(num); ws->name = strdup(json_object_get_string(name)); + ws->label = strdup(ws->name); + // ws->num will be -1 if workspace name doesn't begin with int. + if (ws->num != -1) { + size_t len_offset = numlen(ws->num); + if (bar->config->strip_workspace_name) { + free(ws->label); + ws->label = malloc(len_offset + 1 * sizeof(char)); + ws->label[len_offset] = '\0'; + strncpy(ws->label, ws->name, len_offset); + } else if (bar->config->strip_workspace_numbers) { + len_offset += ws->label[len_offset] == ':'; + if (strlen(ws->name) > len_offset) { + free(ws->label); + // Strip number prefix [1-?:] using len_offset. + ws->label = strdup(ws->name + len_offset); + } + } + } ws->visible = json_object_get_boolean(visible); ws->focused = json_object_get_boolean(focused); if (ws->focused) { @@ -423,6 +509,27 @@ static bool handle_barconfig_update(struct swaybar *bar, config->mode = strdup(json_object_get_string(json_mode)); wlr_log(WLR_DEBUG, "Changing bar mode to %s", config->mode); + json_object *gaps; + json_object_object_get_ex(json_config, "gaps", &gaps); + if (gaps) { + json_object *top = json_object_object_get(gaps, "top"); + if (top) { + config->gaps.top = json_object_get_int(top); + } + json_object *right = json_object_object_get(gaps, "right"); + if (right) { + config->gaps.right = json_object_get_int(right); + } + json_object *bottom = json_object_object_get(gaps, "bottom"); + if (bottom) { + config->gaps.bottom = json_object_get_int(bottom); + } + json_object *left = json_object_object_get(gaps, "left"); + if (left) { + config->gaps.left = json_object_get_int(left); + } + } + return determine_bar_visibility(bar, true); } diff --git a/swaybar/main.c b/swaybar/main.c index 2672abef..fa99b1ba 100644 --- a/swaybar/main.c +++ b/swaybar/main.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -76,7 +76,7 @@ int main(int argc, char **argv) { if (debug) { wlr_log_init(WLR_DEBUG, NULL); } else { - wlr_log_init(WLR_ERROR, NULL); + wlr_log_init(WLR_INFO, NULL); } if (!swaybar.id) { 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 4ebf922e..55f680ed 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include <assert.h> +#include <linux/input-event-codes.h> #include <limits.h> #include <stdlib.h> #include <stdint.h> @@ -14,6 +15,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; @@ -32,7 +36,8 @@ static uint32_t render_status_line_error(cairo_t *cairo, cairo_set_source_u32(cairo, 0xFF0000FF); int margin = 3 * output->scale; - int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; + double ws_vertical_padding = + output->bar->config->status_padding * output->scale; char *font = output->bar->config->font; int text_width, text_height; @@ -41,7 +46,8 @@ static uint32_t render_status_line_error(cairo_t *cairo, uint32_t ideal_height = text_height + ws_vertical_padding * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } *x -= text_width + margin; @@ -68,12 +74,13 @@ static uint32_t render_status_line_text(cairo_t *cairo, get_text_size(cairo, config->font, &text_width, &text_height, NULL, output->scale, config->pango_markup, "%s", text); - int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; + double ws_vertical_padding = config->status_padding * output->scale; int margin = 3 * output->scale; uint32_t ideal_height = text_height + ws_vertical_padding * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } @@ -87,13 +94,24 @@ static uint32_t render_status_line_text(cairo_t *cairo, return output->height; } -static void render_sharp_line(cairo_t *cairo, uint32_t color, +static void render_sharp_rectangle(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) { + cairo_save(cairo); cairo_set_source_u32(cairo, color); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); + cairo_rectangle(cairo, x, y, width, height); + cairo_fill(cairo); + cairo_restore(cairo); +} + +static void render_sharp_line(cairo_t *cairo, uint32_t color, + double x, double y, double width, double height) { if (width > 1 && height > 1) { - cairo_rectangle(cairo, x, y, width, height); - cairo_fill(cairo); + render_sharp_rectangle(cairo, color, x, y, width, height); } else { + cairo_save(cairo); + cairo_set_source_u32(cairo, color); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); if (width == 1) { x += 0.5; height += y; @@ -108,14 +126,17 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color, cairo_set_line_width(cairo, 1.0); cairo_line_to(cairo, width, height); cairo_stroke(cairo); + cairo_restore(cairo); } } -static enum hotspot_event_handling block_hotspot_callback(struct swaybar_output *output, - int x, int y, enum x11_button button, void *data) { +static enum hotspot_event_handling block_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, + int x, int y, uint32_t button, void *data) { struct i3bar_block *block = data; struct status_line *status = output->bar->status; - return i3bar_block_send_click(status, block, x, y, button); + return i3bar_block_send_click(status, block, x, y, x - hotspot->x, + y - hotspot->y, hotspot->width, hotspot->height, button); } static void i3bar_block_unref_callback(void *data) { @@ -136,7 +157,7 @@ static uint32_t render_status_block(cairo_t *cairo, output->scale, block->markup, "%s", block->full_text); int margin = 3 * output->scale; - int ws_vertical_padding = WS_VERTICAL_PADDING * 2; + double ws_vertical_padding = config->status_padding * output->scale; int width = text_width; if (width < block->min_width) { @@ -146,37 +167,40 @@ static uint32_t render_status_block(cairo_t *cairo, double block_width = width; uint32_t ideal_height = text_height + ws_vertical_padding * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } *x -= width; - if (block->border && block->border_left > 0) { - *x -= (block->border_left + margin); - block_width += block->border_left + margin; + if ((block->border || block->urgent) && block->border_left > 0) { + *x -= (block->border_left * output->scale + margin); + block_width += block->border_left * output->scale + margin; } - if (block->border && block->border_right > 0) { - *x -= (block->border_right + margin); - block_width += block->border_right + margin; + if ((block->border || block->urgent) && block->border_right > 0) { + *x -= (block->border_right * output->scale + margin); + block_width += block->border_right * output->scale + margin; } int sep_width, sep_height; + int sep_block_width = block->separator_block_width; if (!edge) { if (config->sep_symbol) { get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, output->scale, false, "%s", config->sep_symbol); uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; uint32_t _ideal_surface_height = _ideal_height / output->scale; - if (output->height < _ideal_surface_height) { + if (!output->bar->config->height && + output->height < _ideal_surface_height) { return _ideal_surface_height; } - if (sep_width > block->separator_block_width) { - block->separator_block_width = sep_width + margin * 2; + if (sep_width > sep_block_width) { + sep_block_width = sep_width + margin * 2; } } - *x -= block->separator_block_width; - } else { - *x -= margin; + *x -= sep_block_width; + } else if (config->status_edge_padding) { + *x -= config->status_edge_padding * output->scale; } uint32_t height = output->height * output->scale; @@ -193,53 +217,55 @@ static uint32_t render_status_block(cairo_t *cairo, wl_list_insert(&output->hotspots, &hotspot->link); } - double pos = *x; - if (block->background) { - cairo_set_source_u32(cairo, block->background); - cairo_rectangle(cairo, pos - 0.5 * output->scale, - output->scale, width, height); - cairo_fill(cairo); + double x_pos = *x; + double y_pos = ws_vertical_padding; + double render_height = height - ws_vertical_padding * 2; + + uint32_t bg_color = block->urgent + ? config->colors.urgent_workspace.background : block->background; + if (bg_color) { + render_sharp_rectangle(cairo, bg_color, x_pos, y_pos, + block_width, render_height); } - if (block->border && block->border_top > 0) { - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, output->scale, - block_width, block->border_top); + uint32_t border_color = block->urgent + ? config->colors.urgent_workspace.border : block->border; + if (border_color && block->border_top > 0) { + render_sharp_line(cairo, border_color, x_pos, y_pos, + block_width, block->border_top * output->scale); } - if (block->border && block->border_bottom > 0) { - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, - height - output->scale - block->border_bottom, - block_width, block->border_bottom); + if (border_color && block->border_bottom > 0) { + render_sharp_line(cairo, border_color, x_pos, + y_pos + render_height - block->border_bottom * output->scale, + block_width, block->border_bottom * output->scale); } - if (block->border != 0 && block->border_left > 0) { - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, output->scale, - block->border_left, height); - pos += block->border_left + margin; + if (border_color && block->border_left > 0) { + render_sharp_line(cairo, border_color, x_pos, y_pos, + block->border_left * output->scale, render_height); + x_pos += block->border_left * output->scale + margin; } double offset = 0; if (strncmp(block->align, "left", 5) == 0) { - offset = pos; + offset = x_pos; } else if (strncmp(block->align, "right", 5) == 0) { - offset = pos + width - text_width; + offset = x_pos + width - text_width; } else if (strncmp(block->align, "center", 6) == 0) { - offset = pos + (width - text_width) / 2; + offset = x_pos + (width - text_width) / 2; } cairo_move_to(cairo, offset, height / 2.0 - text_height / 2.0); uint32_t color = block->color ? *block->color : config->colors.statusline; + color = block->urgent ? config->colors.urgent_workspace.text : color; cairo_set_source_u32(cairo, color); pango_printf(cairo, config->font, output->scale, block->markup, "%s", block->full_text); - pos += width; + x_pos += width; if (block->border && block->border_right > 0) { - pos += margin; - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, output->scale, - block->border_right, height); - pos += block->border_right; + x_pos += margin; + render_sharp_line(cairo, border_color, x_pos, y_pos, + block->border_right * output->scale, render_height); + x_pos += block->border_right * output->scale; } if (!edge && block->separator) { @@ -249,16 +275,14 @@ static uint32_t render_status_block(cairo_t *cairo, cairo_set_source_u32(cairo, config->colors.separator); } if (config->sep_symbol) { - offset = pos + (block->separator_block_width - sep_width) / 2; + offset = x_pos + (sep_block_width - sep_width) / 2; cairo_move_to(cairo, offset, height / 2.0 - sep_height / 2.0); pango_printf(cairo, config->font, output->scale, false, "%s", config->sep_symbol); } else { cairo_set_line_width(cairo, 1); - cairo_move_to(cairo, - pos + block->separator_block_width / 2, margin); - cairo_line_to(cairo, - pos + block->separator_block_width / 2, height - margin); + cairo_move_to(cairo, x_pos + sep_block_width / 2, margin); + cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin); cairo_stroke(cairo); } } @@ -268,7 +292,7 @@ static uint32_t render_status_block(cairo_t *cairo, static uint32_t render_status_line_i3bar(cairo_t *cairo, struct swaybar_output *output, double *x) { uint32_t max_height = 0; - bool edge = true; + bool edge = *x == output->width * output->scale; struct i3bar_block *block; wl_list_for_each(block, &output->bar->status->blocks, link) { uint32_t h = render_status_block(cairo, output, block, x, edge); @@ -314,7 +338,8 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo, uint32_t ideal_height = text_height + ws_vertical_padding * 2 + border_width * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; @@ -342,22 +367,10 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo, return output->height; } -static const char *strip_workspace_number(const char *ws_name) { - size_t len = strlen(ws_name); - for (size_t i = 0; i < len; ++i) { - if (ws_name[i] < '0' || ws_name[i] > '9') { - if (':' == ws_name[i] && i < len - 1 && i > 0) { - return ws_name + i + 1; - } - return ws_name; - } - } - return ws_name; -} - -static enum hotspot_event_handling workspace_hotspot_callback(struct swaybar_output *output, - int x, int y, enum x11_button button, void *data) { - if (button != LEFT) { +static enum hotspot_event_handling workspace_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, + int x, int y, uint32_t button, void *data) { + if (button != BTN_LEFT) { return HOTSPOT_PROCESS; } ipc_send_workspace_command(output->bar, (const char *)data); @@ -368,11 +381,6 @@ static uint32_t render_workspace_button(cairo_t *cairo, struct swaybar_output *output, struct swaybar_workspace *ws, double *x) { struct swaybar_config *config = output->bar->config; - const char *name = ws->name; - if (config->strip_workspace_numbers) { - name = strip_workspace_number(ws->name); - } - struct box_colors box_colors; if (ws->urgent) { box_colors = config->colors.urgent_workspace; @@ -388,7 +396,7 @@ static uint32_t render_workspace_button(cairo_t *cairo, int text_width, text_height; get_text_size(cairo, config->font, &text_width, &text_height, NULL, - output->scale, config->pango_markup, "%s", name); + output->scale, config->pango_markup, "%s", ws->label); int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; @@ -397,7 +405,8 @@ static uint32_t render_workspace_button(cairo_t *cairo, uint32_t ideal_height = ws_vertical_padding * 2 + text_height + border_width * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } @@ -421,7 +430,7 @@ static uint32_t render_workspace_button(cairo_t *cairo, cairo_set_source_u32(cairo, box_colors.text); cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); pango_printf(cairo, config->font, output->scale, config->pango_markup, - "%s", name); + "%s", ws->label); struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); hotspot->x = *x; @@ -459,6 +468,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; @@ -518,12 +533,17 @@ void render_frame(struct swaybar_output *output) { cairo_restore(cairo); uint32_t height = render_to_cairo(cairo, output); int config_height = output->bar->config->height; - if (config_height >= 0 && height < (uint32_t)config_height) { + if (config_height > 0) { height = config_height; } if (height != output->height || output->width == 0) { // Reconfigure surface zwlr_layer_surface_v1_set_size(output->layer_surface, 0, height); + zwlr_layer_surface_v1_set_margin(output->layer_surface, + output->bar->config->gaps.top, + output->bar->config->gaps.right, + output->bar->config->gaps.bottom, + output->bar->config->gaps.left); if (strcmp(output->bar->config->mode, "dock") == 0) { zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, height); } diff --git a/swaybar/status_line.c b/swaybar/status_line.c index 744d2785..f0e2c300 100644 --- a/swaybar/status_line.c +++ b/swaybar/status_line.c @@ -12,7 +12,6 @@ #include "swaybar/config.h" #include "swaybar/i3bar.h" #include "swaybar/status_line.h" -#include "readline.h" static void status_line_close_fds(struct status_line *status) { if (status->read_fd != -1) { @@ -185,7 +184,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..cc8dd188 --- /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_INFO, "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_INFO, "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..0833dcb9 --- /dev/null +++ b/swaybar/tray/item.c @@ -0,0 +1,472 @@ +#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' ? // NeedsAttention + 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_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + return ret; + } + + if (sd_bus_message_at_end(msg, 0)) { + wlr_log(WLR_DEBUG, "%s %s no. of icons = 0", sni->watcher_id, prop); + 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_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto error; + } + + int size; + ret = sd_bus_message_read(msg, "ii", NULL, &size); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, 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_ERROR, "%s %s: %s", sni->watcher_id, 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); + } + list_free_items_and_destroy(*dest); + *dest = pixmaps; + wlr_log(WLR_DEBUG, "%s %s no. of icons = %d", sni->watcher_id, prop, + pixmaps->length); + + 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)) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, + sd_bus_message_get_error(msg)->message); + ret = sd_bus_message_get_errno(msg); + goto cleanup; + } + + ret = sd_bus_message_enter_container(msg, 'v', type); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto cleanup; + } + + if (!type) { + ret = read_pixmap(msg, sni, prop, dest); + if (ret < 0) { + goto cleanup; + } + } else { + if (*type == 's' || *type == 'o') { + free(*(char **)dest); + } + + ret = sd_bus_message_read(msg, type, dest); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto cleanup; + } + + if (*type == 's' || *type == 'o') { + char **str = dest; + *str = strdup(*str); + wlr_log(WLR_DEBUG, "%s %s = '%s'", sni->watcher_id, prop, *str); + } else if (*type == 'b') { + wlr_log(WLR_DEBUG, "%s %s = %s", sni->watcher_id, prop, + *(bool *)dest ? "true" : "false"); + } + } + + 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_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + } +} + +/* + * There is a quirk in sd-bus that in some systems, it is unable to get the + * well-known names on the bus, so it cannot identify if an incoming signal, + * which uses the sender's unique name, actually matches the callback's matching + * sender if the callback uses a well-known name, in which case it just calls + * the callback and hopes for the best, resulting in false positives. In the + * case of NewIcon & NewAttentionIcon, this doesn't affect anything, but it + * means that for NewStatus, if the SNI does not definitely match the sender, + * then the safe thing to do is to query the status independently. + * This function returns 1 if the SNI definitely matches the signal sender, + * which is returned by the calling function to indicate that signal matching + * can stop since it has already found the required callback, otherwise, it + * returns 0, which allows matching to continue. + */ +static int sni_check_msg_sender(struct swaybar_sni *sni, sd_bus_message *msg, + const char *signal) { + bool has_well_known_names = + sd_bus_creds_get_mask(sd_bus_message_get_creds(msg)) & SD_BUS_CREDS_WELL_KNOWN_NAMES; + if (sni->service[0] == ':' || has_well_known_names) { + wlr_log(WLR_DEBUG, "%s has new %s", sni->watcher_id, signal); + return 1; + } else { + wlr_log(WLR_DEBUG, "%s may have new %s", sni->watcher_id, signal); + return 0; + } +} + +static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { + struct swaybar_sni *sni = data; + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + return sni_check_msg_sender(sni, msg, "icon"); +} + +static int handle_new_attention_icon(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct swaybar_sni *sni = data; + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + return sni_check_msg_sender(sni, msg, "attention icon"); +} + +static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) { + struct swaybar_sni *sni = data; + int ret = sni_check_msg_sender(sni, msg, "status"); + if (ret == 1) { + char *status; + int r = sd_bus_message_read(msg, "s", &status); + if (r < 0) { + wlr_log(WLR_ERROR, "%s new status error: %s", sni->watcher_id, strerror(-ret)); + ret = r; + } else { + free(sni->status); + sni->status = strdup(status); + wlr_log(WLR_DEBUG, "%s has new status = '%s'", sni->watcher_id, status); + set_sni_dirty(sni); + } + } else { + sni_get_property_async(sni, "Status", "s", &sni->status); + } + + return ret; +} + +static void sni_match_signal(struct swaybar_sni *sni, sd_bus_slot **slot, + char *signal, sd_bus_message_handler_t callback) { + int ret = sd_bus_match_signal(sni->tray->bus, slot, sni->service, sni->path, + sni->interface, signal, callback, sni); + if (ret < 0) { + wlr_log(WLR_ERROR, "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, &sni->new_icon_slot, "NewIcon", handle_new_icon); + sni_match_signal(sni, &sni->new_attention_icon_slot, "NewAttentionIcon", + handle_new_attention_icon); + sni_match_signal(sni, &sni->new_status_slot, "NewStatus", handle_new_status); + + return sni; +} + +void destroy_sni(struct swaybar_sni *sni) { + if (!sni) { + return; + } + + sd_bus_slot_unref(sni->new_icon_slot); + sd_bus_slot_unref(sni->new_attention_icon_slot); + sd_bus_slot_unref(sni->new_status_slot); + + 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; + + sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path, + sni->interface, "Scroll", NULL, NULL, "is", delta*sign, orientation); + } else { + sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path, + sni->interface, method, NULL, NULL, "ii", x, y); + } +} + +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 %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 position 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 :( + 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..d5d0c84e --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,131 @@ +#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[i]); + } + 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) { + const struct swaybar_output *output = cmp_to; + if (output->identifier && strcmp(item, output->identifier) == 0) { + return 0; + } + return strcmp(item, output->name); +} + +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) == -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 deleted file mode 100644 index 2b185949..00000000 --- a/swayidle/main.c +++ /dev/null @@ -1,442 +0,0 @@ -#define _POSIX_C_SOURCE 200809L -#include <assert.h> -#include <errno.h> -#include <getopt.h> -#include <pthread.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/wait.h> -#include <unistd.h> -#include <wayland-client-protocol.h> -#include <wayland-client.h> -#include <wayland-server.h> -#include <wayland-util.h> -#include <wlr/config.h> -#include <wlr/util/log.h> -#include "config.h" -#include "idle-client-protocol.h" -#include "list.h" -#if HAVE_SYSTEMD -#include <systemd/sd-bus.h> -#include <systemd/sd-login.h> -#elif HAVE_ELOGIND -#include <elogind/sd-bus.h> -#include <elogind/sd-login.h> -#endif - -static struct org_kde_kwin_idle *idle_manager = NULL; -static struct wl_seat *seat = NULL; - -struct swayidle_state { - struct wl_display *display; - struct wl_event_loop *event_loop; - list_t *timeout_cmds; // struct swayidle_timeout_cmd * - char *lock_cmd; -} state; - -struct swayidle_timeout_cmd { - int timeout, registered_timeout; - struct org_kde_kwin_idle_timeout *idle_timer; - char *idle_cmd; - char *resume_cmd; -}; - -static void cmd_exec(char *param) { - wlr_log(WLR_DEBUG, "Cmd exec %s", param); - pid_t pid = fork(); - if (pid == 0) { - pid = fork(); - if (pid == 0) { - char *const cmd[] = { "sh", "-c", param, NULL, }; - execvp(cmd[0], cmd); - wlr_log_errno(WLR_ERROR, "execve failed!"); - exit(1); - } else if (pid < 0) { - wlr_log_errno(WLR_ERROR, "fork failed"); - exit(1); - } - exit(0); - } else if (pid < 0) { - wlr_log_errno(WLR_ERROR, "fork failed"); - } else { - wlr_log(WLR_DEBUG, "Spawned process %s", param); - waitpid(pid, NULL, 0); - } -} - -#if HAVE_SYSTEMD || HAVE_ELOGIND -static int lock_fd = -1; -static int ongoing_fd = -1; -static struct sd_bus *bus = NULL; - -static int release_lock(void *data) { - wlr_log(WLR_INFO, "Releasing sleep lock %d", ongoing_fd); - if (ongoing_fd >= 0) { - close(ongoing_fd); - } - ongoing_fd = -1; - return 0; -} - -static void acquire_sleep_lock(void) { - sd_bus_message *msg; - sd_bus_error error; - int ret = sd_bus_call_method(bus, "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", "Inhibit", - &error, &msg, "ssss", "sleep", "swayidle", - "Setup Up Lock Screen", "delay"); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to send Inhibit signal: %s", - strerror(-ret)); - return; - } - - ret = sd_bus_message_read(msg, "h", &lock_fd); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to parse D-Bus response for Inhibit: %s", - strerror(-ret)); - return; - } - - wlr_log(WLR_INFO, "Got sleep lock: %d", lock_fd); -} - -static int prepare_for_sleep(sd_bus_message *msg, void *userdata, - sd_bus_error *ret_error) { - /* "b" apparently reads into an int, not a bool */ - int going_down = 1; - int ret = sd_bus_message_read(msg, "b", &going_down); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to parse D-Bus response for Inhibit: %s", - strerror(-ret)); - } - wlr_log(WLR_DEBUG, "PrepareForSleep signal received %d", going_down); - if (!going_down) { - acquire_sleep_lock(); - return 0; - } - - ongoing_fd = lock_fd; - - if (state.lock_cmd) { - cmd_exec(state.lock_cmd); - } - - if (ongoing_fd >= 0) { - struct wl_event_source *source = - wl_event_loop_add_timer(state.event_loop, release_lock, NULL); - wl_event_source_timer_update(source, 1000); - } - - wlr_log(WLR_DEBUG, "Prepare for sleep done"); - return 0; -} - -static int dbus_event(int fd, uint32_t mask, void *data) { - sd_bus *bus = data; - while (sd_bus_process(bus, NULL) > 0) { - // Do nothing. - } - return 1; -} - -static void setup_sleep_listener(void) { - int ret = sd_bus_default_system(&bus); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", - strerror(-ret)); - return; - } - - char str[256]; - const char *fmt = "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.%s'," - "member='%s'," "path='%s'"; - - snprintf(str, sizeof(str), fmt, "Manager", "PrepareForSleep", - "/org/freedesktop/login1"); - ret = sd_bus_add_match(bus, NULL, str, prepare_for_sleep, NULL); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); - return; - } - acquire_sleep_lock(); - - wl_event_loop_add_fd(state.event_loop, sd_bus_get_fd(bus), - WL_EVENT_READABLE, dbus_event, bus); -} -#endif - -static void handle_global(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) { - if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0) { - idle_manager = - wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); - } -} - -static void handle_global_remove(void *data, struct wl_registry *registry, - uint32_t name) { - // Who cares -} - -static const struct wl_registry_listener registry_listener = { - .global = handle_global, - .global_remove = handle_global_remove, -}; - -static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener; - -static void register_timeout(struct swayidle_timeout_cmd *cmd, - int timeout) { - if (cmd->idle_timer != NULL) { - org_kde_kwin_idle_timeout_destroy(cmd->idle_timer); - cmd->idle_timer = NULL; - } - if (timeout < 0) { - wlr_log(WLR_DEBUG, "Not registering idle timeout"); - return; - } - wlr_log(WLR_DEBUG, "Register with timeout: %d", timeout); - cmd->idle_timer = - org_kde_kwin_idle_get_idle_timeout(idle_manager, seat, timeout); - org_kde_kwin_idle_timeout_add_listener(cmd->idle_timer, - &idle_timer_listener, cmd); - cmd->registered_timeout = timeout; -} - -static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) { - struct swayidle_timeout_cmd *cmd = data; - wlr_log(WLR_DEBUG, "idle state"); - if (cmd->idle_cmd) { - cmd_exec(cmd->idle_cmd); - } -} - -static void handle_resume(void *data, struct org_kde_kwin_idle_timeout *timer) { - struct swayidle_timeout_cmd *cmd = data; - wlr_log(WLR_DEBUG, "active state"); - if (cmd->registered_timeout != cmd->timeout) { - register_timeout(cmd, cmd->timeout); - } - if (cmd->resume_cmd) { - cmd_exec(cmd->resume_cmd); - } -} - -static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = { - .idle = handle_idle, - .resumed = handle_resume, -}; - -static char *parse_command(int argc, char **argv) { - if (argc < 1) { - wlr_log(WLR_ERROR, "Missing command"); - return NULL; - } - - wlr_log(WLR_DEBUG, "Command: %s", argv[0]); - return strdup(argv[0]); -} - -static int parse_timeout(int argc, char **argv) { - if (argc < 3) { - wlr_log(WLR_ERROR, "Too few parameters to timeout command. " - "Usage: timeout <seconds> <command>"); - exit(-1); - } - errno = 0; - char *endptr; - int seconds = strtoul(argv[1], &endptr, 10); - if (errno != 0 || *endptr != '\0') { - wlr_log(WLR_ERROR, "Invalid timeout parameter '%s', it should be a " - "numeric value representing seconds", optarg); - exit(-1); - } - - struct swayidle_timeout_cmd *cmd = - calloc(1, sizeof(struct swayidle_timeout_cmd)); - - if (seconds > 0) { - cmd->timeout = seconds * 1000; - } else { - cmd->timeout = -1; - } - - wlr_log(WLR_DEBUG, "Register idle timeout at %d ms", cmd->timeout); - wlr_log(WLR_DEBUG, "Setup idle"); - cmd->idle_cmd = parse_command(argc - 2, &argv[2]); - - int result = 3; - if (argc >= 5 && !strcmp("resume", argv[3])) { - wlr_log(WLR_DEBUG, "Setup resume"); - cmd->resume_cmd = parse_command(argc - 4, &argv[4]); - result = 5; - } - list_add(state.timeout_cmds, cmd); - return result; -} - -static int parse_sleep(int argc, char **argv) { - if (argc < 2) { - wlr_log(WLR_ERROR, "Too few parameters to before-sleep command. " - "Usage: before-sleep <command>"); - exit(-1); - } - - state.lock_cmd = parse_command(argc - 1, &argv[1]); - if (state.lock_cmd) { - wlr_log(WLR_DEBUG, "Setup sleep lock: %s", state.lock_cmd); - } - - return 2; -} - -static int parse_args(int argc, char *argv[]) { - bool debug = false; - - int c; - while ((c = getopt(argc, argv, "hd")) != -1) { - switch (c) { - case 'd': - debug = true; - break; - case 'h': - case '?': - printf("Usage: %s [OPTIONS]\n", argv[0]); - printf(" -d\tdebug\n"); - printf(" -h\tthis help menu\n"); - return 1; - default: - return 1; - } - } - - wlr_log_init(debug ? WLR_DEBUG : WLR_INFO, NULL); - - state.timeout_cmds = create_list(); - - int i = optind; - while (i < argc) { - if (!strcmp("timeout", argv[i])) { - wlr_log(WLR_DEBUG, "Got timeout"); - i += parse_timeout(argc - i, &argv[i]); - } else if (!strcmp("before-sleep", argv[i])) { - wlr_log(WLR_DEBUG, "Got before-sleep"); - i += parse_sleep(argc - i, &argv[i]); - } else { - wlr_log(WLR_ERROR, "Unsupported command '%s'", argv[i]); - return 1; - } - } - - return 0; -} - -void sway_terminate(int exit_code) { - wl_display_disconnect(state.display); - wl_event_loop_destroy(state.event_loop); - exit(exit_code); -} - -static void register_zero_idle_timeout(void *item) { - struct swayidle_timeout_cmd *cmd = item; - register_timeout(cmd, 0); -} - -static int handle_signal(int sig, void *data) { - switch (sig) { - case SIGINT: - case SIGTERM: - sway_terminate(0); - return 0; - case SIGUSR1: - wlr_log(WLR_DEBUG, "Got SIGUSR1"); - list_foreach(state.timeout_cmds, register_zero_idle_timeout); - return 1; - } - assert(false); // not reached -} - -static int display_event(int fd, uint32_t mask, void *data) { - if (mask & WL_EVENT_HANGUP) { - sway_terminate(0); - } - if (wl_display_dispatch(state.display) < 0) { - wlr_log_errno(WLR_ERROR, "wl_display_dispatch failed, exiting"); - sway_terminate(0); - } - wl_display_flush(state.display); - return 0; -} - -static void register_idle_timeout(void *item) { - struct swayidle_timeout_cmd *cmd = item; - register_timeout(cmd, cmd->timeout); -} - -int main(int argc, char *argv[]) { - if (parse_args(argc, argv) != 0) { - return -1; - } - - state.event_loop = wl_event_loop_create(); - - wl_event_loop_add_signal(state.event_loop, SIGINT, handle_signal, NULL); - wl_event_loop_add_signal(state.event_loop, SIGTERM, handle_signal, NULL); - wl_event_loop_add_signal(state.event_loop, SIGUSR1, handle_signal, NULL); - - state.display = wl_display_connect(NULL); - if (state.display == NULL) { - wlr_log(WLR_ERROR, "Unable to connect to the compositor. " - "If your compositor is running, check or set the " - "WAYLAND_DISPLAY environment variable."); - return -3; - } - - struct wl_registry *registry = wl_display_get_registry(state.display); - wl_registry_add_listener(registry, ®istry_listener, NULL); - wl_display_roundtrip(state.display); - - if (idle_manager == NULL) { - wlr_log(WLR_ERROR, "Display doesn't support idle protocol"); - return -4; - } - if (seat == NULL) { - wlr_log(WLR_ERROR, "Seat error"); - return -5; - } - - bool should_run = state.timeout_cmds->length > 0; -#if HAVE_SYSTEMD || HAVE_ELOGIND - if (state.lock_cmd) { - should_run = true; - setup_sleep_listener(); - } -#endif - if (!should_run) { - wlr_log(WLR_INFO, "No command specified! Nothing to do, will exit"); - sway_terminate(0); - } - - list_foreach(state.timeout_cmds, register_idle_timeout); - - wl_display_roundtrip(state.display); - - struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop, - wl_display_get_fd(state.display), WL_EVENT_READABLE, - display_event, NULL); - wl_event_source_check(source); - - while (wl_event_loop_dispatch(state.event_loop, -1) != 1) { - // This space intentionally left blank - } - - sway_terminate(0); -} diff --git a/swayidle/meson.build b/swayidle/meson.build deleted file mode 100644 index 79d2c5c4..00000000 --- a/swayidle/meson.build +++ /dev/null @@ -1,27 +0,0 @@ -threads = dependency('threads') - -swayidle_deps = [ - client_protos, - pixman, - wayland_client, - wayland_server, - wlroots, -] - -if systemd.found() - swayidle_deps += systemd -endif -if elogind.found() - swayidle_deps += elogind -endif - -executable( - 'swayidle', [ - 'main.c', - ], - include_directories: [sway_inc], - dependencies: swayidle_deps, - link_with: [lib_sway_common, lib_sway_client], - install_rpath : rpathdir, - install: true -) diff --git a/swayidle/swayidle.1.scd b/swayidle/swayidle.1.scd deleted file mode 100644 index 3083163f..00000000 --- a/swayidle/swayidle.1.scd +++ /dev/null @@ -1,63 +0,0 @@ -swayidle (1) - -# NAME - -swayidle - Idle manager for Wayland - -# SYNOPSIS - -*swayidle* [options] [events...] - -# OPTIONS - -*-h* - Show help message and quit. - -*-d* - Enable debug output. - -# DESCRIPTION - -swayidle listens for idle activity on your Wayland compositor and executes tasks -on various idle-related events. You can specify any number of events at the -command line. - -Sending SIGUSR1 to swayidle will immediately enter idle state. - -# EVENTS - -*timeout* <timeout> <timeout command> [resume <resume command>] - Execute _timeout command_ if there is no activity for <timeout> seconds. - - If you specify "resume <resume command>", _resume command_ will be run when - there is activity again. - -*before-sleep* <command> - If built with systemd support, executes _command_ before systemd puts the - computer to sleep. - -All commands are executed in a shell. - -# EXAMPLE - -``` -swayidle \ - timeout 300 'swaylock -c 000000' \ - timeout 600 'swaymsg "output * dpms off"' \ - resume 'swaymsg "output * dpms on"' \ - before-sleep 'swaylock -c 000000' -``` - -This will lock your screen after 300 seconds of inactivity, then turn off your -displays after another 600 seconds, and turn your screens back on when resumed. -It will also lock your screen before your computer goes to sleep. - -# AUTHORS - -Maintained by Drew DeVault <sir@cmpwn.com>, who is assisted by other open -source contributors. For more information about sway development, see -https://github.com/swaywm/sway. - -# SEE ALSO - -*sway*(5) *swaymsg*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) diff --git a/swaylock/main.c b/swaylock/main.c index 9b74b671..0b167da1 100644 --- a/swaylock/main.c +++ b/swaylock/main.c @@ -1,10 +1,10 @@ -#define _XOPEN_SOURCE 700 -#define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> +#include <poll.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -23,7 +23,6 @@ #include "cairo.h" #include "log.h" #include "loop.h" -#include "readline.h" #include "stringop.h" #include "util.h" #include "wlr-input-inhibitor-unstable-v1-client-protocol.h" @@ -397,28 +396,34 @@ static void set_default_colors(struct swaylock_colors *colors) { colors->background = 0xFFFFFFFF; colors->bs_highlight = 0xDB3300FF; colors->key_highlight = 0x33DB00FF; + colors->caps_lock_bs_highlight = 0xDB3300FF; + colors->caps_lock_key_highlight = 0x33DB00FF; colors->separator = 0x000000FF; colors->inside = (struct swaylock_colorset){ .input = 0x000000C0, .cleared = 0xE5A445C0, + .caps_lock = 0x000000C0, .verifying = 0x0072FFC0, .wrong = 0xFA0000C0, }; colors->line = (struct swaylock_colorset){ .input = 0x000000FF, .cleared = 0x000000FF, + .caps_lock = 0x000000FF, .verifying = 0x000000FF, .wrong = 0x000000FF, }; colors->ring = (struct swaylock_colorset){ .input = 0x337D00FF, .cleared = 0xE5A445FF, + .caps_lock = 0xE5A445FF, .verifying = 0x3300FFFF, .wrong = 0x7D3300FF, }; colors->text = (struct swaylock_colorset){ .input = 0xE5A445FF, .cleared = 0x000000FF, + .caps_lock = 0xE5A445FF, .verifying = 0x000000FF, .wrong = 0x000000FF, }; @@ -434,25 +439,31 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, enum line_mode *line_mode, char **config_path) { enum long_option_codes { LO_BS_HL_COLOR = 256, + LO_CAPS_LOCK_BS_HL_COLOR, + LO_CAPS_LOCK_KEY_HL_COLOR, LO_FONT, LO_IND_RADIUS, LO_IND_THICKNESS, LO_INSIDE_COLOR, LO_INSIDE_CLEAR_COLOR, + LO_INSIDE_CAPS_LOCK_COLOR, LO_INSIDE_VER_COLOR, LO_INSIDE_WRONG_COLOR, LO_KEY_HL_COLOR, LO_LINE_COLOR, LO_LINE_CLEAR_COLOR, + LO_LINE_CAPS_LOCK_COLOR, LO_LINE_VER_COLOR, LO_LINE_WRONG_COLOR, LO_RING_COLOR, LO_RING_CLEAR_COLOR, + LO_RING_CAPS_LOCK_COLOR, LO_RING_VER_COLOR, LO_RING_WRONG_COLOR, LO_SEP_COLOR, LO_TEXT_COLOR, LO_TEXT_CLEAR_COLOR, + LO_TEXT_CAPS_LOCK_COLOR, LO_TEXT_VER_COLOR, LO_TEXT_WRONG_COLOR, }; @@ -464,6 +475,8 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, {"daemonize", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"image", required_argument, NULL, 'i'}, + {"disable-caps-lock-text", no_argument, NULL, 'L'}, + {"indicator-caps-lock", no_argument, NULL, 'l'}, {"line-uses-inside", no_argument, NULL, 'n'}, {"socket", required_argument, NULL, 'p'}, {"line-uses-ring", no_argument, NULL, 'r'}, @@ -472,25 +485,31 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, {"no-unlock-indicator", no_argument, NULL, 'u'}, {"version", no_argument, NULL, 'v'}, {"bs-hl-color", required_argument, NULL, LO_BS_HL_COLOR}, + {"caps-lock-bs-hl-color", required_argument, NULL, LO_CAPS_LOCK_BS_HL_COLOR}, + {"caps-lock-key-hl-color", required_argument, NULL, LO_CAPS_LOCK_KEY_HL_COLOR}, {"font", required_argument, NULL, LO_FONT}, {"indicator-radius", required_argument, NULL, LO_IND_RADIUS}, {"indicator-thickness", required_argument, NULL, LO_IND_THICKNESS}, {"inside-color", required_argument, NULL, LO_INSIDE_COLOR}, {"inside-clear-color", required_argument, NULL, LO_INSIDE_CLEAR_COLOR}, + {"inside-caps-lock-color", required_argument, NULL, LO_INSIDE_CAPS_LOCK_COLOR}, {"inside-ver-color", required_argument, NULL, LO_INSIDE_VER_COLOR}, {"inside-wrong-color", required_argument, NULL, LO_INSIDE_WRONG_COLOR}, {"key-hl-color", required_argument, NULL, LO_KEY_HL_COLOR}, {"line-color", required_argument, NULL, LO_LINE_COLOR}, {"line-clear-color", required_argument, NULL, LO_LINE_CLEAR_COLOR}, + {"line-caps-lock-color", required_argument, NULL, LO_LINE_CAPS_LOCK_COLOR}, {"line-ver-color", required_argument, NULL, LO_LINE_VER_COLOR}, {"line-wrong-color", required_argument, NULL, LO_LINE_WRONG_COLOR}, {"ring-color", required_argument, NULL, LO_RING_COLOR}, {"ring-clear-color", required_argument, NULL, LO_RING_CLEAR_COLOR}, + {"ring-caps-lock-color", required_argument, NULL, LO_RING_CAPS_LOCK_COLOR}, {"ring-ver-color", required_argument, NULL, LO_RING_VER_COLOR}, {"ring-wrong-color", required_argument, NULL, LO_RING_WRONG_COLOR}, {"separator-color", required_argument, NULL, LO_SEP_COLOR}, {"text-color", required_argument, NULL, LO_TEXT_COLOR}, {"text-clear-color", required_argument, NULL, LO_TEXT_CLEAR_COLOR}, + {"text-caps-lock-color", required_argument, NULL, LO_TEXT_CAPS_LOCK_COLOR}, {"text-ver-color", required_argument, NULL, LO_TEXT_VER_COLOR}, {"text-wrong-color", required_argument, NULL, LO_TEXT_WRONG_COLOR}, {0, 0, 0, 0} @@ -499,76 +518,97 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, const char usage[] = "Usage: swaylock [options...]\n" "\n" - " -C, --config <config_file> " + " -C, --config <config_file> " "Path to the config file.\n" - " -c, --color <color> " + " -c, --color <color> " "Turn the screen into the given color instead of white.\n" - " -e, --ignore-empty-password " + " -e, --ignore-empty-password " "When an empty password is provided, do not validate it.\n" - " -f, --daemonize " + " -f, --daemonize " "Detach from the controlling terminal after locking.\n" - " -h, --help " + " -h, --help " "Show help message and quit.\n" - " -i, --image [<output>:]<path> " + " -i, --image [<output>:]<path> " "Display the given image.\n" - " -s, --scaling <mode> " + " -L, --disable-caps-lock-text " + "Disable the Caps Lock text.\n" + " -l, --indicator-caps-lock " + "Show the current Caps Lock state also on the indicator.\n" + " -s, --scaling <mode> " "Scaling mode: stretch, fill, fit, center, tile.\n" - " -t, --tiling " + " -t, --tiling " "Same as --scaling=tile.\n" - " -u, --no-unlock-indicator " + " -u, --no-unlock-indicator " "Disable the unlock indicator.\n" - " -v, --version " + " -v, --version " "Show the version number and quit.\n" - " --bs-hl-color <color> " + " --bs-hl-color <color> " "Sets the color of backspace highlight segments.\n" - " --font <font> " + " --caps-lock-bs-hl-color <color> " + "Sets the color of backspace highlight segments when Caps Lock " + "is active.\n" + " --caps-lock-key-hl-color <color> " + "Sets the color of the key press highlight segments when " + "Caps Lock is active.\n" + " --font <font> " "Sets the font of the text.\n" - " --indicator-radius <radius> " + " --indicator-radius <radius> " "Sets the indicator radius.\n" - " --indicator-thickness <thick> " + " --indicator-thickness <thick> " "Sets the indicator thickness.\n" - " --inside-color <color> " + " --inside-color <color> " "Sets the color of the inside of the indicator.\n" - " --inside-clear-color <color> " + " --inside-clear-color <color> " "Sets the color of the inside of the indicator when cleared.\n" - " --inside-ver-color <color> " + " --inside-caps-lock-color <color> " + "Sets the color of the inside of the indicator when Caps Lock " + "is active.\n" + " --inside-ver-color <color> " "Sets the color of the inside of the indicator when verifying.\n" - " --inside-wrong-color <color> " + " --inside-wrong-color <color> " "Sets the color of the inside of the indicator when invalid.\n" - " --key-hl-color <color> " + " --key-hl-color <color> " "Sets the color of the key press highlight segments.\n" - " --line-color <color> " + " --line-color <color> " "Sets the color of the line between the inside and ring.\n" - " --line-clear-color <color> " + " --line-clear-color <color> " "Sets the color of the line between the inside and ring when " "cleared.\n" - " --line-ver-color <color> " + " --line-caps-lock-color <color> " + "Sets the color of the line between the inside and ring when " + "Caps Lock is active.\n" + " --line-ver-color <color> " "Sets the color of the line between the inside and ring when " "verifying.\n" - " --line-wrong-color <color> " + " --line-wrong-color <color> " "Sets the color of the line between the inside and ring when " "invalid.\n" - " -n, --line-uses-inside " + " -n, --line-uses-inside " "Use the inside color for the line between the inside and ring.\n" - " -r, --line-uses-ring " + " -r, --line-uses-ring " "Use the ring color for the line between the inside and ring.\n" - " --ring-color <color> " + " --ring-color <color> " "Sets the color of the ring of the indicator.\n" - " --ring-clear-color <color> " + " --ring-clear-color <color> " "Sets the color of the ring of the indicator when cleared.\n" - " --ring-ver-color <color> " + " --ring-caps-lock-color <color> " + "Sets the color of the ring of the indicator when Caps Lock " + "is active.\n" + " --ring-ver-color <color> " "Sets the color of the ring of the indicator when verifying.\n" - " --ring-wrong-color <color> " + " --ring-wrong-color <color> " "Sets the color of the ring of the indicator when invalid.\n" - " --separator-color <color> " + " --separator-color <color> " "Sets the color of the lines that separate highlight segments.\n" - " --text-color <color> " + " --text-color <color> " "Sets the color of the text.\n" - " --text-clear-color <color> " + " --text-clear-color <color> " "Sets the color of the text when cleared.\n" - " --text-ver-color <color> " + " --text-caps-lock-color <color> " + "Sets the color of the text when Caps Lock is active.\n" + " --text-ver-color <color> " "Sets the color of the text when verifying.\n" - " --text-wrong-color <color> " + " --text-wrong-color <color> " "Sets the color of the text when invalid.\n" "\n" "All <color> options are of the form <rrggbb[aa]>.\n"; @@ -577,7 +617,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, optind = 1; while (1) { int opt_idx = 0; - c = getopt_long(argc, argv, "c:efhi:nrs:tuvC:", long_options, &opt_idx); + c = getopt_long(argc, argv, "c:efhi:Llnrs:tuvC:", long_options, &opt_idx); if (c == -1) { break; } @@ -608,6 +648,16 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, load_image(optarg, state); } break; + case 'L': + if (state) { + state->args.show_caps_lock_text = false; + } + break; + case 'l': + if (state) { + state->args.show_caps_lock_indicator = true; + } + break; case 'n': if (line_mode) { *line_mode = LM_INSIDE; @@ -645,6 +695,16 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, state->args.colors.bs_highlight = parse_color(optarg); } break; + case LO_CAPS_LOCK_BS_HL_COLOR: + if (state) { + state->args.colors.caps_lock_bs_highlight = parse_color(optarg); + } + break; + case LO_CAPS_LOCK_KEY_HL_COLOR: + if (state) { + state->args.colors.caps_lock_key_highlight = parse_color(optarg); + } + break; case LO_FONT: if (state) { free(state->args.font); @@ -671,6 +731,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, state->args.colors.inside.cleared = parse_color(optarg); } break; + case LO_INSIDE_CAPS_LOCK_COLOR: + if (state) { + state->args.colors.inside.caps_lock = parse_color(optarg); + } + break; case LO_INSIDE_VER_COLOR: if (state) { state->args.colors.inside.verifying = parse_color(optarg); @@ -696,6 +761,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, state->args.colors.line.cleared = parse_color(optarg); } break; + case LO_LINE_CAPS_LOCK_COLOR: + if (state) { + state->args.colors.line.caps_lock = parse_color(optarg); + } + break; case LO_LINE_VER_COLOR: if (state) { state->args.colors.line.verifying = parse_color(optarg); @@ -716,6 +786,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, state->args.colors.ring.cleared = parse_color(optarg); } break; + case LO_RING_CAPS_LOCK_COLOR: + if (state) { + state->args.colors.ring.caps_lock = parse_color(optarg); + } + break; case LO_RING_VER_COLOR: if (state) { state->args.colors.ring.verifying = parse_color(optarg); @@ -741,6 +816,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, state->args.colors.text.cleared = parse_color(optarg); } break; + case LO_TEXT_CAPS_LOCK_COLOR: + if (state) { + state->args.colors.text.caps_lock = parse_color(optarg); + } + break; case LO_TEXT_VER_COLOR: if (state) { state->args.colors.text.verifying = parse_color(optarg); @@ -808,36 +888,32 @@ static int load_config(char *path, struct swaylock_state *state, wlr_log(WLR_ERROR, "Failed to read config. Running without it."); return 0; } - char *line; + char *line = NULL; + size_t line_size = 0; + ssize_t nread; int line_number = 0; - while (!feof(config)) { - line = read_line(config); - if (!line) { - continue; - } - + int result = 0; + while ((nread = getline(&line, &line_size, config)) != -1) { line_number++; - if (line[0] == '#') { - free(line); - continue; + + if (line[nread - 1] == '\n') { + line[--nread] = '\0'; } - if (strlen(line) == 0) { - free(line); + + if (!*line || line[0] == '#') { continue; } wlr_log(WLR_DEBUG, "Config Line #%d: %s", line_number, line); - char flag[strlen(line) + 3]; + char flag[nread + 3]; sprintf(flag, "--%s", line); char *argv[] = {"swaylock", flag}; - int result = parse_options(2, argv, state, line_mode, NULL); + result = parse_options(2, argv, state, line_mode, NULL); if (result != 0) { - free(line); - fclose(config); - return result; + break; } - free(line); } + free(line); fclose(config); return 0; } @@ -862,6 +938,8 @@ int main(int argc, char **argv) { .thickness = 10, .ignore_empty = false, .show_indicator = true, + .show_caps_lock_indicator = false, + .show_caps_lock_text = true }; wl_list_init(&state.images); set_default_colors(&state.args.colors); @@ -962,7 +1040,7 @@ int main(int argc, char **argv) { } state.eventloop = loop_create(); - loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLL_IN, + loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN, display_in, NULL); state.run_display = true; diff --git a/swaylock/pam.c b/swaylock/pam.c index cac95a85..b90d9e87 100644 --- a/swaylock/pam.c +++ b/swaylock/pam.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <pwd.h> #include <security/pam_appl.h> #include <stdbool.h> diff --git a/swaylock/password.c b/swaylock/password.c index 6138e1fe..3bd113ad 100644 --- a/swaylock/password.c +++ b/swaylock/password.c @@ -1,4 +1,3 @@ -#define _XOPEN_SOURCE 500 #include <assert.h> #include <errno.h> #include <pwd.h> @@ -78,52 +77,56 @@ static void handle_preverify_timeout(void *data) { state->verify_password_timer = NULL; } -void swaylock_handle_key(struct swaylock_state *state, - xkb_keysym_t keysym, uint32_t codepoint) { - switch (keysym) { - case XKB_KEY_KP_Enter: /* fallthrough */ - case XKB_KEY_Return: - if (state->args.ignore_empty && state->password.len == 0) { - break; - } +static void submit_password(struct swaylock_state *state) { + if (state->args.ignore_empty && state->password.len == 0) { + return; + } - state->auth_state = AUTH_STATE_VALIDATING; - damage_state(state); + state->auth_state = AUTH_STATE_VALIDATING; + damage_state(state); - // We generally want to wait until all surfaces are showing the - // "verifying" state before we go and verify the password, because - // verifying it is a blocking operation. However, if the surface is on - // an output with DPMS off then it won't update, so we set a timer. - state->verify_password_timer = loop_add_timer( - state->eventloop, 50, handle_preverify_timeout, state); + // We generally want to wait until all surfaces are showing the + // "verifying" state before we go and verify the password, because + // verifying it is a blocking operation. However, if the surface is on + // an output with DPMS off then it won't update, so we set a timer. + state->verify_password_timer = loop_add_timer( + state->eventloop, 50, handle_preverify_timeout, state); - while (state->run_display && state->verify_password_timer) { - errno = 0; - if (wl_display_flush(state->display) == -1 && errno != EAGAIN) { - break; - } - loop_poll(state->eventloop); + while (state->run_display && state->verify_password_timer) { + errno = 0; + if (wl_display_flush(state->display) == -1 && errno != EAGAIN) { + break; + } + loop_poll(state->eventloop); - bool ok = 1; - struct swaylock_surface *surface; - wl_list_for_each(surface, &state->surfaces, link) { - if (surface->dirty) { - ok = 0; - } - } - if (ok) { - break; + bool ok = 1; + struct swaylock_surface *surface; + wl_list_for_each(surface, &state->surfaces, link) { + if (surface->dirty) { + ok = 0; } } - wl_display_flush(state->display); - - if (attempt_password(&state->password)) { - state->run_display = false; + if (ok) { break; } - state->auth_state = AUTH_STATE_INVALID; - damage_state(state); - schedule_indicator_clear(state); + } + wl_display_flush(state->display); + + if (attempt_password(&state->password)) { + state->run_display = false; + return; + } + state->auth_state = AUTH_STATE_INVALID; + damage_state(state); + schedule_indicator_clear(state); +} + +void swaylock_handle_key(struct swaylock_state *state, + xkb_keysym_t keysym, uint32_t codepoint) { + switch (keysym) { + case XKB_KEY_KP_Enter: /* fallthrough */ + case XKB_KEY_Return: + submit_password(state); break; case XKB_KEY_Delete: case XKB_KEY_BackSpace: @@ -143,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: @@ -166,6 +161,13 @@ void swaylock_handle_key(struct swaylock_state *state, schedule_indicator_clear(state); schedule_password_clear(state); break; + case XKB_KEY_d: + if (state->xkb.control) { + submit_password(state); + break; + } + // fallthrough + case XKB_KEY_c: /* fallthrough */ case XKB_KEY_u: if (state->xkb.control) { clear_password_buffer(&state->password); diff --git a/swaylock/render.c b/swaylock/render.c index fa8832bd..5aedaad5 100644 --- a/swaylock/render.c +++ b/swaylock/render.c @@ -1,4 +1,3 @@ -#define _POSIX_C_SOURCE 199506L #include <math.h> #include <stdlib.h> #include <wayland-client.h> @@ -19,7 +18,17 @@ static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state, } else if (state->auth_state == AUTH_STATE_CLEAR) { cairo_set_source_u32(cairo, colorset->cleared); } else { - cairo_set_source_u32(cairo, colorset->input); + if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { + cairo_set_source_u32(cairo, colorset->caps_lock); + } else if (state->xkb.caps_lock && !state->args.show_caps_lock_indicator && + state->args.show_caps_lock_text) { + uint32_t inputtextcolor = state->args.colors.text.input; + state->args.colors.text.input = state->args.colors.text.caps_lock; + cairo_set_source_u32(cairo, colorset->input); + state->args.colors.text.input = inputtextcolor; + } else { + cairo_set_source_u32(cairo, colorset->input); + } } } @@ -93,7 +102,8 @@ void render_frame(struct swaylock_surface *surface) { break; case AUTH_STATE_INPUT: case AUTH_STATE_INPUT_NOP: - if (state->xkb.caps_lock) { + case AUTH_STATE_BACKSPACE: + if (state->xkb.caps_lock && state->args.show_caps_lock_text) { text = "Caps Lock"; } break; @@ -126,9 +136,17 @@ void render_frame(struct swaylock_surface *surface) { arc_radius, highlight_start, highlight_start + TYPE_INDICATOR_RANGE); if (state->auth_state == AUTH_STATE_INPUT) { - cairo_set_source_u32(cairo, state->args.colors.key_highlight); + if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { + cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight); + } else { + cairo_set_source_u32(cairo, state->args.colors.key_highlight); + } } else { - cairo_set_source_u32(cairo, state->args.colors.bs_highlight); + if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { + cairo_set_source_u32(cairo, state->args.colors.caps_lock_bs_highlight); + } else { + cairo_set_source_u32(cairo, state->args.colors.bs_highlight); + } } cairo_stroke(cairo); 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); diff --git a/swaylock/shadow.c b/swaylock/shadow.c index f928eaa3..b7b10a67 100644 --- a/swaylock/shadow.c +++ b/swaylock/shadow.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE +#define _XOPEN_SOURCE // for crypt #include <pwd.h> #include <shadow.h> #include <stdbool.h> diff --git a/swaylock/swaylock.1.scd b/swaylock/swaylock.1.scd index 8ddc7d3a..2c7979be 100644 --- a/swaylock/swaylock.1.scd +++ b/swaylock/swaylock.1.scd @@ -27,7 +27,7 @@ Locks your Wayland session. *-f, --daemonize* Detach from the controlling terminal after locking. - Note: this is the default bahavior of i3lock. + Note: this is the default behavior of i3lock. *-h, --help* Show help message and quit. @@ -44,6 +44,12 @@ Locks your Wayland session. Display the given image, optionally only on the given output. Use -c to set a background color. +*-L, --disable-caps-lock-text* + Disable the Caps Lock Text. + +*-l, --indicator-caps-lock* + Show the current Caps Lock state also on the indicator. + *-s, --scaling* Scaling mode for images: _stretch_, _fill_, _fit_, _center_, or _tile_. @@ -58,6 +64,12 @@ Locks your Wayland session. *--bs-hl-color* <rrggbb[aa]> Sets the color of backspace highlight segments. +*--caps-lock-bs-hl-color* <rrggbb[aa]> + Sets the color of backspace highlight segments when Caps Lock is active. + +*--caps-lock-bs-hl-color* <rrggbb[aa]> + Sets the color of the key press highlight segments when Caps Lock is active. + *--font* <font> Sets the font of the text inside the indicator. @@ -75,6 +87,9 @@ Locks your Wayland session. *--inside-clear-color* <rrggbb[aa]> Sets the color of the inside of the indicator when cleared. +*--inside-caps-lock-color* <rrggbb[aa]> + Sets the color of the inside of the indicator when Caps Lock is active. + *--inside-ver-color* <rrggbb[aa]> Sets the color of the inside of the indicator when verifying. @@ -92,6 +107,10 @@ Locks your Wayland session. Sets the color of the lines that separate the inside and outside of the indicator when cleared. +*--line-caps-lock-color* <rrggbb[aa]> + Sets the color of the line between the inside and ring when Caps Lock + is active. + *--line-ver-color* <rrggbb[aa]> Sets the color of the lines that separate the inside and outside of the indicator when verifying. @@ -114,6 +133,9 @@ Locks your Wayland session. *--ring-clear-color* <rrggbb[aa]> Sets the color of the outside of the indicator when cleared. +*--ring-caps-lock-color* <rrggbb[aa]> + Sets the color of the ring of the indicator when Caps Lock is active. + *--ring-ver-color* <rrggbb[aa]> Sets the color of the outside of the indicator when verifying. @@ -129,6 +151,9 @@ Locks your Wayland session. *--text-clear-color* <rrggbb[aa]> Sets the color of the text inside the indicator when cleared. +*--text-caps-lock-color* <rrggbb[aa]> + Sets the color of the text when Caps Lock is active. + *--text-ver-color* <rrggbb[aa]> Sets the color of the text inside the indicator when verifying. diff --git a/swaymsg/main.c b/swaymsg/main.c index 243b5fdc..e5eee631 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -12,7 +12,6 @@ #include <json-c/json.h> #include "stringop.h" #include "ipc-client.h" -#include "readline.h" #include "log.h" void sway_terminate(int exit_code) { @@ -32,6 +31,9 @@ static bool success_object(json_object *result) { // Iterate results array and return false if any of them failed static bool success(json_object *r, bool fallback) { if (!json_object_is_type(r, json_type_array)) { + if (json_object_is_type(r, json_type_object)) { + return success_object(r); + } return fallback; } @@ -111,7 +113,7 @@ static const char *pretty_type_name(const char *name) { } static void pretty_print_input(json_object *i) { - json_object *id, *name, *type, *product, *vendor, *kbdlayout; + json_object *id, *name, *type, *product, *vendor, *kbdlayout, *events; json_object_object_get_ex(i, "identifier", &id); json_object_object_get_ex(i, "name", &name); json_object_object_get_ex(i, "type", &type); @@ -137,6 +139,10 @@ static void pretty_print_input(json_object *i) { json_object_get_string(kbdlayout)); } + if (json_object_object_get_ex(i, "libinput_send_events", &events)) { + printf(" Libinput Send Events: %s\n", json_object_get_string(events)); + } + printf("\n"); } @@ -305,8 +311,9 @@ static void pretty_print(int type, json_object *resp) { } int main(int argc, char **argv) { - static int quiet = 0; - static int raw = 0; + static bool quiet = false; + static bool raw = false; + static bool monitor = false; char *socket_path = NULL; char *cmdtype = NULL; @@ -314,6 +321,7 @@ int main(int argc, char **argv) { static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, + {"monitor", no_argument, NULL, 'm'}, {"quiet", no_argument, NULL, 'q'}, {"raw", no_argument, NULL, 'r'}, {"socket", required_argument, NULL, 's'}, @@ -326,6 +334,7 @@ int main(int argc, char **argv) { "Usage: swaymsg [options] [message]\n" "\n" " -h, --help Show help message and quit.\n" + " -m, --monitor Monitor until killed (-t SUBSCRIBE only)\n" " -q, --quiet Be quiet.\n" " -r, --raw Use raw output even if using a tty\n" " -s, --socket <socket> Use the specified socket.\n" @@ -337,16 +346,19 @@ int main(int argc, char **argv) { int c; while (1) { int option_index = 0; - c = getopt_long(argc, argv, "hqrs:t:v", long_options, &option_index); + c = getopt_long(argc, argv, "hmqrs:t:v", long_options, &option_index); if (c == -1) { break; } switch (c) { + case 'm': // Monitor + monitor = true; + break; case 'q': // Quiet - quiet = 1; + quiet = true; break; case 'r': // Raw - raw = 1; + raw = true; break; case 's': // Socket socket_path = strdup(optarg); @@ -400,12 +412,20 @@ int main(int argc, char **argv) { type = IPC_GET_CONFIG; } else if (strcasecmp(cmdtype, "send_tick") == 0) { type = IPC_SEND_TICK; + } else if (strcasecmp(cmdtype, "subscribe") == 0) { + type = IPC_SUBSCRIBE; } else { sway_abort("Unknown message type %s", cmdtype); } free(cmdtype); + if (monitor && type != IPC_SUBSCRIBE) { + wlr_log(WLR_ERROR, "Monitor can only be used with -t SUBSCRIBE"); + free(socket_path); + return 1; + } + char *command = NULL; if (optind < argc) { command = join_args(argv + optind, argc - optind); @@ -422,26 +442,56 @@ int main(int argc, char **argv) { json_object *obj = json_tokener_parse(resp); if (obj == NULL) { - fprintf(stderr, "ERROR: Could not parse json response from ipc. This is a bug in sway."); + fprintf(stderr, "ERROR: Could not parse json response from ipc. " + "This is a bug in sway."); printf("%s\n", resp); ret = 1; } else { if (!success(obj, true)) { ret = 1; } - if (raw) { - printf("%s\n", json_object_to_json_string_ext(obj, - JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)); - } else { - pretty_print(type, obj); + if (type != IPC_SUBSCRIBE || ret != 0) { + if (raw) { + printf("%s\n", json_object_to_json_string_ext(obj, + JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)); + } else { + pretty_print(type, obj); + } } json_object_put(obj); } } - close(socketfd); - free(command); free(resp); + + if (type == IPC_SUBSCRIBE && ret == 0) { + do { + struct ipc_response *reply = ipc_recv_response(socketfd); + if (!reply) { + break; + } + + json_object *obj = json_tokener_parse(reply->payload); + if (obj == NULL) { + fprintf(stderr, "ERROR: Could not parse json response from ipc" + ". This is a bug in sway."); + ret = 1; + break; + } else { + if (raw) { + printf("%s\n", json_object_to_json_string(obj)); + } else { + printf("%s\n", json_object_to_json_string_ext(obj, + JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)); + } + json_object_put(obj); + } + + free_ipc_response(reply); + } while (monitor); + } + + close(socketfd); free(socket_path); return ret; } diff --git a/swaymsg/swaymsg.1.scd b/swaymsg/swaymsg.1.scd index eaac8105..f55f86a9 100644 --- a/swaymsg/swaymsg.1.scd +++ b/swaymsg/swaymsg.1.scd @@ -13,6 +13,12 @@ _swaymsg_ [options...] [message] *-h, --help* Show help message and quit. +*-m, --monitor* + Monitor for responses until killed instead of exiting after the first + response. This can only be used with the IPC message type _subscribe_. If + there is a malformed response or an invalid event type was requested, + swaymsg will stop monitoring and exit. + *-q, --quiet* Sends the IPC message but does not print the response from sway. @@ -71,3 +77,8 @@ _swaymsg_ [options...] [message] *send\_tick* Sends a tick event to all subscribed clients. + +*subscribe* + Subscribe to a list of event types. The argument for this type should be + provided in the form of a valid JSON array. If any of the types are invalid + or if an valid JSON array is not provided, this will result in an failure. diff --git a/swaynag/config.c b/swaynag/config.c index cd34dcc2..85aa380a 100644 --- a/swaynag/config.c +++ b/swaynag/config.c @@ -1,11 +1,10 @@ -#define _XOPEN_SOURCE 700 -#define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200809L #include <getopt.h> +#include <stdio.h> #include <stdlib.h> #include <wordexp.h> #include "log.h" #include "list.h" -#include "readline.h" #include "swaynag/swaynag.h" #include "swaynag/types.h" #include "util.h" @@ -13,21 +12,19 @@ static char *read_from_stdin(void) { char *buffer = NULL; - while (!feof(stdin)) { - char *line = read_line(stdin); - if (!line) { - continue; - } - - size_t curlen = buffer ? strlen(buffer) : 0; - buffer = realloc(buffer, curlen + strlen(line) + 2); - snprintf(buffer + curlen, strlen(line) + 2, "%s\n", line); - - free(line); + size_t buffer_len = 0; + char *line = NULL; + size_t line_size = 0; + ssize_t nread; + while ((nread = getline(&line, &line_size, stdin)) != -1) { + buffer = realloc(buffer, buffer_len + nread); + snprintf(&buffer[buffer_len], nread + 1, "%s", line); + buffer_len += nread; } + free(line); - while (buffer && buffer[strlen(buffer) - 1] == '\n') { - buffer[strlen(buffer) - 1] = '\0'; + while (buffer && buffer[buffer_len - 1] == '\n') { + buffer[--buffer_len] = '\0'; } return buffer; @@ -53,6 +50,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, static struct option opts[] = { {"button", required_argument, NULL, 'b'}, + {"button-no-terminal", required_argument, NULL, 'B'}, {"config", required_argument, NULL, 'c'}, {"debug", no_argument, NULL, 'd'}, {"edge", required_argument, NULL, 'e'}, @@ -87,7 +85,10 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, "Usage: swaynag [options...]\n" "\n" " -b, --button <text> <action> Create a button with text that " - "executes action when pressed. Multiple buttons can be defined.\n" + "executes action in a terminal when pressed. Multiple buttons can " + "be defined.\n" + " -B, --button-no-terminal <text> <action> Like --button, but does" + "not run the action in a terminal.\n" " -c, --config <path> Path to config file.\n" " -d, --debug Enable debugging.\n" " -e, --edge top|bottom Set the edge to use.\n" @@ -118,12 +119,13 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, optind = 1; while (1) { - int c = getopt_long(argc, argv, "b:c:de:f:hlL:m:o:s:t:v", opts, NULL); + int c = getopt_long(argc, argv, "b:B:c:de:f:hlL:m:o:s:t:v", opts, NULL); if (c == -1) { break; } switch (c) { case 'b': // Button + case 'B': // Button (No Terminal) if (swaynag) { if (optind >= argc) { fprintf(stderr, "Missing action for button %s\n", optarg); @@ -134,6 +136,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, button->text = strdup(optarg); button->type = SWAYNAG_ACTION_COMMAND; button->action = strdup(argv[optind]); + button->terminal = c == 'b'; list_add(swaynag->buttons, button); } optind++; @@ -343,32 +346,24 @@ int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) { type->name = strdup("<config>"); list_add(types, type); - char *line; + char *line = NULL; + size_t line_size = 0; + ssize_t nread; int line_number = 0; - while (!feof(config)) { - line = read_line(config); - if (!line) { - continue; - } - + int result = 0; + while ((nread = getline(&line, &line_size, config)) != -1) { line_number++; - if (line[0] == '#') { - free(line); - continue; - } - if (strlen(line) == 0) { - free(line); + if (!*line || line[0] == '\n' || line[0] == '#') { continue; } if (line[0] == '[') { char *close = strchr(line, ']'); if (!close) { - free(line); - fclose(config); fprintf(stderr, "Closing bracket not found on line %d\n", line_number); - return 1; + result = 1; + break; } char *name = calloc(1, close - line); strncat(name, line + 1, close - line - 1); @@ -380,22 +375,17 @@ int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) { } free(name); } else { - char flag[strlen(line) + 3]; + char flag[nread + 3]; sprintf(flag, "--%s", line); char *argv[] = {"swaynag", flag}; - int result; result = swaynag_parse_options(2, argv, swaynag, types, type, NULL, NULL); if (result != 0) { - free(line); - fclose(config); - return result; + break; } } - - free(line); } + free(line); fclose(config); - return 0; + return result; } - diff --git a/swaynag/main.c b/swaynag/main.c index bae3c0e2..9f00ac7e 100644 --- a/swaynag/main.c +++ b/swaynag/main.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <signal.h> #include "log.h" @@ -130,4 +130,3 @@ cleanup: swaynag_destroy(&swaynag); return exit_code; } - diff --git a/swaynag/swaynag.1.scd b/swaynag/swaynag.1.scd index bb69e47d..b25568a0 100644 --- a/swaynag/swaynag.1.scd +++ b/swaynag/swaynag.1.scd @@ -12,7 +12,14 @@ _swaynag_ [options...] *-b, --button* <text> <action> Create a button with the text _text_ that executes _action_ when pressed. - Multiple buttons can be defined by providing the flag multiple times. + If the environment variable `TERMINAL` is set, _action_ will be run inside + the terminal. Otherwise, it will fallback to running directly. Multiple + buttons can be defined by providing the flag multiple times. + +*-B, --button-no-terminal* <text> <action> + Create a button with the text _text_ that executes _action_ when pressed. + _action_ will be run directly instead of in a terminal. Multiple buttons + can be defined by providing the flag multiple times. *-c, --config* <path> The config file to use. By default, the following paths are checked: diff --git a/swaynag/swaynag.c b/swaynag/swaynag.c index 06185f20..674c24b5 100644 --- a/swaynag/swaynag.c +++ b/swaynag/swaynag.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <assert.h> #include <sys/stat.h> @@ -49,14 +49,17 @@ static void swaynag_button_execute(struct swaynag *swaynag, if (fork() == 0) { // Child of the child. Will be reparented to the init process char *terminal = getenv("TERMINAL"); - if (terminal && strlen(terminal)) { + if (button->terminal && terminal && strlen(terminal)) { wlr_log(WLR_DEBUG, "Found $TERMINAL: %s", terminal); if (!terminal_execute(terminal, button->action)) { swaynag_destroy(swaynag); exit(EXIT_FAILURE); } } else { - wlr_log(WLR_DEBUG, "$TERMINAL not found. Running directly"); + if (button->terminal) { + wlr_log(WLR_DEBUG, + "$TERMINAL not found. Running directly"); + } execl("/bin/sh", "/bin/sh", "-c", button->action, NULL); } } @@ -126,6 +129,8 @@ static void update_cursor(struct swaynag *swaynag) { pointer->cursor_surface, pointer->cursor_image->hotspot_x / swaynag->scale, pointer->cursor_image->hotspot_y / swaynag->scale); + wl_surface_damage_buffer(pointer->cursor_surface, 0, 0, + INT32_MAX, INT32_MAX); wl_surface_commit(pointer->cursor_surface); } @@ -290,7 +295,7 @@ static void handle_global(void *data, struct wl_registry *registry, struct swaynag *swaynag = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { swaynag->compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 3); + &wl_compositor_interface, 4); } else if (strcmp(interface, wl_seat_interface.name) == 0) { swaynag->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(swaynag->seat, &seat_listener, swaynag); @@ -404,15 +409,13 @@ void swaynag_destroy(struct swaynag *swaynag) { swaynag->run_display = false; free(swaynag->message); - while (swaynag->buttons->length) { - struct swaynag_button *button = swaynag->buttons->items[0]; - list_del(swaynag->buttons, 0); + for (int i = 0; i < swaynag->buttons->length; ++i) { + struct swaynag_button *button = swaynag->buttons->items[i]; free(button->text); free(button->action); free(button); } list_free(swaynag->buttons); - free(swaynag->details.button_details); free(swaynag->details.message); free(swaynag->details.button_up.text); free(swaynag->details.button_down.text); diff --git a/swaynag/types.c b/swaynag/types.c index 1e0a138b..bc17bd33 100644 --- a/swaynag/types.c +++ b/swaynag/types.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <getopt.h> #include <stdbool.h> #include <stdlib.h> @@ -147,10 +147,8 @@ void swaynag_type_free(struct swaynag_type *type) { } void swaynag_types_free(list_t *types) { - while (types->length) { - struct swaynag_type *type = types->items[0]; - swaynag_type_free(type); - list_del(types, 0); + for (int i = 0; i < types->length; ++i) { + swaynag_type_free(types->items[i]); } list_free(types); } |