diff options
113 files changed, 5187 insertions, 1026 deletions
diff --git a/README.de.md b/README.de.md index 206a1040..a872e888 100644 --- a/README.de.md +++ b/README.de.md @@ -64,8 +64,6 @@ Abhängigkeiten: * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (erforderlich für Bildaufnahme mit swaygrab) -* ffmpeg (erforderlich für Videoaufnahme swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (erforderlich für man pages) _\*Nur erforderlich für swaybar, swaybg, und swaylock_ diff --git a/README.el.md b/README.el.md index 5c70beff..6887fe8e 100644 --- a/README.el.md +++ b/README.el.md @@ -57,8 +57,6 @@ To username μου στο Freenode είναι kon14 και θα με βρείτ * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (αναγκαίο για καταγραφή εικόνας μέσω του swaygrab) -* ffmpeg (αναγκαίο για καταγραφή video μέσω του swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) _\*Απαιτείται μόνο για swaybar, swaybg, and swaylock_ diff --git a/README.fr.md b/README.fr.md index 0d2573f9..6ea4d14c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -59,8 +59,6 @@ Installez les dépendances : * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (requis pour la capture d'image avec swaygrab) -* ffmpeg (requis pour la capture vidéo avec swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (requis pour les pages man) _\*Uniquement requis pour swaybar, swaybg, and swaylock_ diff --git a/README.it.md b/README.it.md index 0d81ea54..3b1b1ebc 100644 --- a/README.it.md +++ b/README.it.md @@ -60,8 +60,6 @@ Installa queste dipendenze: * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (richiesto per catturare immagini con swaygrab) -* ffmpeg (rrichiesto per catturare video con swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (rrichiesto per man pages) _\*Richiesto solo per swaybar, swaybg, e swaylock_ diff --git a/README.ja.md b/README.ja.md index 476d7472..7b437966 100644 --- a/README.ja.md +++ b/README.ja.md @@ -50,8 +50,6 @@ Swayは沢山のディストリビューションで提供されています。" * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (swaygrabでスクリーンショットを撮るのに必要です) -* ffmpeg (swaygrabで画面を録画するのに必要です) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (manで必要です) _\*swaybar,swaybg,swaylockでのみ必要です_ @@ -58,8 +58,6 @@ Install dependencies: * gdk-pixbuf2 * * pam ** * dbus >= 1.10 *** -* imagemagick (required for image capture with swaygrab) -* ffmpeg (required for video capture with swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) _\*Only required for swaybar, swaybg, and swaylock_ diff --git a/README.pt.md b/README.pt.md index d1ef245f..9089c8c6 100644 --- a/README.pt.md +++ b/README.pt.md @@ -66,8 +66,6 @@ Antes de iniciar a compilação, instale as dependências: * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (capturar imagem com o swaygrab) -* ffmpeg (capturar vídeo com o swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (man pages) _\*Dependência apenas de swaybar, swaybg, e swaylock_ diff --git a/README.ru.md b/README.ru.md index 3b3de19a..68675db3 100644 --- a/README.ru.md +++ b/README.ru.md @@ -62,8 +62,6 @@ Sway доступен во многих дистрибутивах и наход * gdk-pixbuf2 * * pam ** * dbus >= 1.10 *** -* imagemagick (требуется для захвата изображений через swaygrab) -* ffmpeg (требуется для захвата видео через swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) _\*Требуется только для swaybar, swaybg и swaylock_ diff --git a/README.uk.md b/README.uk.md index 55698487..c31a3ea9 100644 --- a/README.uk.md +++ b/README.uk.md @@ -66,8 +66,6 @@ Sway доступний у багатьох дистрибутивах Linux (а * cairo * gdk-pixbuf2 * * pam ** -* imagemagick (для захоплення зображень за допомогою swaygrab) -* ffmpeg (для захоплення відео за допомогою swaygrab) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) _\*Лише для swaybar, swaybg та swaylock_ diff --git a/common/util.c b/common/util.c index e8a88772..467aa4b5 100644 --- a/common/util.c +++ b/common/util.c @@ -123,6 +123,22 @@ uint32_t parse_color(const char *color) { return res; } +bool parse_boolean(const char *boolean, bool current) { + if (strcasecmp(boolean, "1") == 0 + || strcasecmp(boolean, "yes") == 0 + || strcasecmp(boolean, "on") == 0 + || strcasecmp(boolean, "true") == 0 + || strcasecmp(boolean, "enable") == 0 + || strcasecmp(boolean, "enabled") == 0 + || strcasecmp(boolean, "active") == 0) { + return true; + } else if (strcasecmp(boolean, "toggle") == 0) { + return !current; + } + // All other values are false to match i3 + return false; +} + char* resolve_path(const char* path) { struct stat sb; ssize_t r; diff --git a/completions/bash/sway b/completions/bash/sway new file mode 100644 index 00000000..edd752cd --- /dev/null +++ b/completions/bash/sway @@ -0,0 +1,46 @@ +# sway(1) completion + +_sway() +{ + local cur prev + _get_comp_words_by_ref cur prev + + short=( + -h + -c + -C + -d + -v + -V + ) + + long=( + --help + --config + --validate + --debug + --version + --verbose + --get-socketpath + ) + + case $prev in + -c|--config) + _filedir + return + ;; + esac + + if [[ $cur == --* ]]; then + COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) + elif [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) + COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) + else + COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) + COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) + COMPREPLY+=($(compgen -c -- "$cur")) + fi + +} && +complete -F _sway sway diff --git a/completions/bash/swayidle b/completions/bash/swayidle new file mode 100644 index 00000000..a0cdc8b2 --- /dev/null +++ b/completions/bash/swayidle @@ -0,0 +1,48 @@ +# 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/bash/swaylock b/completions/bash/swaylock new file mode 100644 index 00000000..33925480 --- /dev/null +++ b/completions/bash/swaylock @@ -0,0 +1,66 @@ +# swaylock(1) completion + +_swaylock() +{ + local cur prev + _get_comp_words_by_ref -n : cur prev + + short=( + -h + -c + -s + -t + -v + -i + -u + -f + ) + + long=( + --help + --color + --scaling + --tiling + --version + --image + --no-unlock-indicator + --daemonize + ) + + scaling=( + 'stretch' + 'fill' + 'fit' + 'center' + 'tile' + ) + + case $prev in + -c|--color) + return + ;; + --scaling) + COMPREPLY=($(compgen -W "${scaling[*]}" -- "$cur")) + return + ;; + -i|--image) + if grep -q : <<< "$cur"; then + output="${cur%%:*}:" + cur="${cur#*:}" + else + output= + fi + COMPREPLY=($(compgen -f -- "$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 _swaylock swaylock diff --git a/completions/bash/swaymsg b/completions/bash/swaymsg new file mode 100644 index 00000000..20092bdc --- /dev/null +++ b/completions/bash/swaymsg @@ -0,0 +1,59 @@ +# swaymsg(1) completion + +_swaymsg() +{ + local cur prev + _get_comp_words_by_ref cur prev + + types=( + 'get_workspaces' + 'get_seats' + 'get_inputs' + 'get_outputs' + 'get_tree' + 'get_marks' + 'get_bar_config' + 'get_version' + 'get_binding_modes' + 'get_config' + 'send_tick' + ) + + short=( + -h + -q + -r + -s + -t + -v + ) + + long=( + --help + --quiet + --raw + --socket + --type + --verbose + ) + + case $prev in + -s|--socket) + _filedir + return + ;; + -t|--type) + COMPREPLY=($(compgen -W "${types[*]}" -- "$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 _swaymsg swaymsg diff --git a/completions/zsh/_sway b/completions/zsh/_sway index bab90fbf..05112002 100644 --- a/completions/zsh/_sway +++ b/completions/zsh/_sway @@ -18,5 +18,5 @@ _arguments -s \ '(-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]' \ - '(-v --verbose)'{-v,--verbose}'[Enables more verbose logging]' \ + '(-V --verbose)'{-V,--verbose}'[Enables more verbose logging]' \ '(--get-socketpath)'--get-socketpath'[Gets the IPC socket path and prints it, then exits]' diff --git a/completions/zsh/_swaygrab b/completions/zsh/_swaygrab deleted file mode 100644 index 0f9846f4..00000000 --- a/completions/zsh/_swaygrab +++ /dev/null @@ -1,23 +0,0 @@ -#compdef swaygrab -#----------------- -# Description -# ----------- -# -# Completion script for swaygrab in sway wm (http://swaywm.org) -# -# ----------------------------------------------------- -# Author -# ------ -# -# * Seth Barberee <seth.barberee@gmail.com> -# -# ------------------------------------------ - -_arguments -s \ - '(-h --help)'{-h,--help}'[Shows help message]' \ - '(-c --capture)'{-c,--capture}'[Captures multiple frames as video and passes them to ffmpeg]' \ - '(-o --output)'{-o,--output}'[Use the specified output. If not specified then current focused output will be used]' \ - '(-v --version)'{-v,--version}'[Print the version (of swaymsg) and quit]' \ - '(-s --socket)'{-s,--socket}'[Use the specified socket path.]:files:_files' \ - '(-r --rate)'{-r,--rate}'[Specify a framerate (in fps). Used in combination with -c. Default is 30 and must be an integer]' \ - '(--raw)--raw[Instead of ImageMagick or ffmpeg, dump raw rgba data to stdout]' diff --git a/completions/zsh/_swaymsg b/completions/zsh/_swaymsg index 2e39deb6..a7a1c8e0 100644 --- a/completions/zsh/_swaymsg +++ b/completions/zsh/_swaymsg @@ -22,6 +22,9 @@ types=( 'get_marks' 'get_bar_config' 'get_version' +'get_binding_modes' +'get_config' +'send_tick' ) _arguments -s \ @@ -16,7 +16,8 @@ set $right l # Your preferred terminal emulator set $term urxvt # Your preferred application launcher -set $menu dmenu_run +# Note: it's recommended that you pass the final command to sway +set $menu dmenu_path | dmenu | xargs swaymsg exec ### Output configuration # diff --git a/include/ipc.h b/include/ipc.h index 0010718b..a3f60e19 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -15,6 +15,7 @@ enum ipc_command_type { IPC_GET_VERSION = 7, IPC_GET_BINDING_MODES = 8, IPC_GET_CONFIG = 9, + IPC_SEND_TICK = 10, // sway-specific command types IPC_GET_INPUTS = 100, @@ -27,8 +28,8 @@ enum ipc_command_type { IPC_EVENT_WINDOW = ((1<<31) | 3), IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4), IPC_EVENT_BINDING = ((1<<31) | 5), - IPC_EVENT_MODIFIER = ((1<<31) | 6), - IPC_EVENT_INPUT = ((1<<31) | 7), + IPC_EVENT_SHUTDOWN = ((1<<31) | 6), + IPC_EVENT_TICK = ((1<<31) | 7), }; #endif diff --git a/include/sway/commands.h b/include/sway/commands.h index e71a7228..41858ccc 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -106,7 +106,7 @@ sway_cmd cmd_exit; sway_cmd cmd_floating; sway_cmd cmd_floating_maximum_size; sway_cmd cmd_floating_minimum_size; -sway_cmd cmd_floating_mod; +sway_cmd cmd_floating_modifier; sway_cmd cmd_floating_scroll; sway_cmd cmd_focus; sway_cmd cmd_focus_follows_mouse; @@ -213,8 +213,10 @@ sway_cmd input_cmd_scroll_button; sway_cmd input_cmd_scroll_method; sway_cmd input_cmd_tap; sway_cmd input_cmd_tap_button_map; +sway_cmd input_cmd_xkb_capslock; sway_cmd input_cmd_xkb_layout; sway_cmd input_cmd_xkb_model; +sway_cmd input_cmd_xkb_numlock; sway_cmd input_cmd_xkb_options; sway_cmd input_cmd_xkb_rules; sway_cmd input_cmd_xkb_variant; diff --git a/include/sway/config.h b/include/sway/config.h index b8da29c5..909b6827 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -1,6 +1,5 @@ #ifndef _SWAY_CONFIG_H #define _SWAY_CONFIG_H -#define PID_WORKSPACE_TIMEOUT 60 #include <libinput.h> #include <stdint.h> #include <string.h> @@ -22,14 +21,28 @@ struct sway_variable { char *value; }; + +enum binding_input_type { + BINDING_KEYCODE, + BINDING_KEYSYM, + BINDING_MOUSE, +}; + +enum binding_flags { + BINDING_RELEASE=1, + BINDING_LOCKED=2, // keyboard only + BINDING_BORDER=4, // mouse only; trigger on container border + BINDING_CONTENTS=8, // mouse only; trigger on container contents + BINDING_TITLEBAR=16 // mouse only; trigger on container titlebar +}; + /** * A key binding and an associated command. */ struct sway_binding { + enum binding_input_type type; int order; - bool release; - bool locked; - bool bindcode; + uint32_t flags; list_t *keys; // sorted in ascending order uint32_t modifiers; char *command; @@ -50,6 +63,7 @@ struct sway_mode { char *name; list_t *keysym_bindings; list_t *keycode_bindings; + list_t *mouse_bindings; bool pango; }; @@ -87,6 +101,9 @@ struct input_config { char *xkb_rules; char *xkb_variant; + int xkb_numlock; + int xkb_capslock; + struct input_config_mapped_from_region *mapped_from_region; char *mapped_to_output; @@ -146,12 +163,6 @@ struct workspace_output { char *workspace; }; -struct pid_workspace { - pid_t *pid; - char *workspace; - time_t *time_added; -}; - struct bar_config { /** * One of "dock", "hide", "invisible" @@ -302,7 +313,6 @@ struct sway_config { list_t *bars; list_t *cmd_queue; list_t *workspace_outputs; - list_t *pid_workspaces; list_t *output_configs; list_t *input_configs; list_t *seat_configs; @@ -313,6 +323,7 @@ struct sway_config { struct bar_config *current_bar; char *swaybg_command; uint32_t floating_mod; + bool floating_mod_inverse; uint32_t dragging_key; uint32_t resizing_key; char *floating_scroll_up_cmd; @@ -388,9 +399,6 @@ struct sway_config { } handler_context; }; -void pid_workspace_add(struct pid_workspace *pw); -void free_pid_workspace(struct pid_workspace *pw); - /** * Loads the main config from the given path. is_active should be true when * reloading the config. @@ -480,7 +488,7 @@ int sway_binding_cmp_keys(const void *a, const void *b); void free_sway_binding(struct sway_binding *sb); -struct sway_binding *sway_binding_dup(struct sway_binding *sb); +void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding); void load_swaybars(); diff --git a/include/sway/criteria.h b/include/sway/criteria.h index 6a8337c5..b4ff7d49 100644 --- a/include/sway/criteria.h +++ b/include/sway/criteria.h @@ -2,6 +2,7 @@ #define _SWAY_CRITERIA_H #include <pcre.h> +#include "config.h" #include "list.h" #include "tree/view.h" @@ -25,7 +26,9 @@ struct criteria { pcre *instance; pcre *con_mark; uint32_t con_id; // internal ID +#ifdef HAVE_XWAYLAND uint32_t id; // X11 window ID +#endif pcre *window_role; uint32_t window_type; bool floating; diff --git a/include/sway/desktop/transaction.h b/include/sway/desktop/transaction.h index cee4afed..56361d94 100644 --- a/include/sway/desktop/transaction.h +++ b/include/sway/desktop/transaction.h @@ -42,17 +42,4 @@ void transaction_notify_view_ready(struct sway_view *view, uint32_t serial); void transaction_notify_view_ready_by_size(struct sway_view *view, int width, int height); -/** - * Get the saved texture that should be rendered for a view. - * - * The addresses pointed at by the width and height pointers will be populated - * with the surface's dimensions, which may be different to the texture's - * dimensions if output scaling is used. - * - * This function should only be called if it is known that the view has - * instructions. - */ -struct wlr_texture *transaction_get_saved_texture(struct sway_view *view, - int *width, int *height); - #endif diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h index 5dd109ca..7ec45120 100644 --- a/include/sway/input/cursor.h +++ b/include/sway/input/cursor.h @@ -3,6 +3,8 @@ #include <stdint.h> #include "sway/input/seat.h" +#define SWAY_CURSOR_PRESSED_BUTTONS_CAP 32 + struct sway_cursor { struct sway_seat *seat; struct wlr_cursor *cursor; @@ -11,6 +13,7 @@ struct sway_cursor { } previous; struct wlr_xcursor_manager *xcursor_manager; + const char *image; struct wl_client *image_client; struct wl_listener motion; @@ -28,6 +31,10 @@ struct sway_cursor { uint32_t tool_buttons; struct wl_listener request_set_cursor; + + // Mouse binding state + uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP]; + size_t pressed_button_count; }; void sway_cursor_destroy(struct sway_cursor *cursor); @@ -37,4 +44,7 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, void dispatch_cursor_button(struct sway_cursor *cursor, uint32_t time_msec, uint32_t button, enum wlr_button_state state); +void cursor_set_image(struct sway_cursor *cursor, const char *image, + struct wl_client *client); + #endif diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index 89a3ac71..aa2f6f19 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -2,6 +2,7 @@ #define _SWAY_INPUT_INPUT_MANAGER_H #include <libinput.h> #include <wlr/types/wlr_input_inhibitor.h> +#include <wlr/types/wlr_virtual_keyboard_v1.h> #include "sway/server.h" #include "sway/config.h" #include "list.h" @@ -25,10 +26,12 @@ struct sway_input_manager { struct wl_list seats; struct wlr_input_inhibit_manager *inhibit; + struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wl_listener new_input; struct wl_listener inhibit_activate; struct wl_listener inhibit_deactivate; + struct wl_listener virtual_keyboard_new; }; struct sway_input_manager *input_manager_create(struct sway_server *server); diff --git a/include/sway/input/keyboard.h b/include/sway/input/keyboard.h index 6713398e..6d28454c 100644 --- a/include/sway/input/keyboard.h +++ b/include/sway/input/keyboard.h @@ -38,6 +38,9 @@ struct sway_keyboard { struct sway_shortcut_state state_keysyms_raw; struct sway_shortcut_state state_keycodes; struct sway_binding *held_binding; + + struct wl_event_source *key_repeat_source; + struct sway_binding *repeat_binding; }; struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index eac1626b..92387601 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -3,6 +3,7 @@ #include <wlr/types/wlr_layer_shell.h> #include <wlr/types/wlr_seat.h> +#include <wlr/util/edges.h> #include "sway/input/input-manager.h" struct sway_seat_device { @@ -52,6 +53,24 @@ struct sway_seat { int32_t touch_id; double touch_x, touch_y; + // Operations (drag and resize) + enum { + OP_NONE, + OP_MOVE, + OP_RESIZE, + } operation; + + struct sway_container *op_container; + 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 + + uint32_t last_button; + uint32_t last_button_serial; + struct wl_listener focus_destroy; struct wl_listener new_container; struct wl_listener new_drag_icon; @@ -80,7 +99,7 @@ void seat_configure_xcursor(struct sway_seat *seat); void seat_set_focus(struct sway_seat *seat, struct sway_container *container); void seat_set_focus_warp(struct sway_seat *seat, - struct sway_container *container, bool warp); + struct sway_container *container, bool warp, bool notify); void seat_set_focus_surface(struct sway_seat *seat, struct wlr_surface *surface, bool unfocus); @@ -105,6 +124,9 @@ struct sway_container *seat_get_focus(struct sway_seat *seat); struct sway_container *seat_get_focus_inactive(struct sway_seat *seat, struct sway_container *container); +struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat, + struct sway_container *container); + /** * Descend into the focus stack to find the focus-inactive view. Useful for * container placement when they change position in the tree. @@ -134,4 +156,15 @@ bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); void drag_icon_update_position(struct sway_drag_icon *icon); +void seat_begin_move(struct sway_seat *seat, struct sway_container *con, + uint32_t button); + +void seat_begin_resize(struct sway_seat *seat, struct sway_container *con, + uint32_t button, enum wlr_edges edge); + +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); + #endif diff --git a/include/sway/ipc-server.h b/include/sway/ipc-server.h index 6469f097..4b6d0e25 100644 --- a/include/sway/ipc-server.h +++ b/include/sway/ipc-server.h @@ -16,5 +16,7 @@ void ipc_event_workspace(struct sway_container *old, void ipc_event_window(struct sway_container *window, const char *change); void ipc_event_barconfig_update(struct bar_config *bar); void ipc_event_mode(const char *mode, bool pango); +void ipc_event_shutdown(const char *reason); +void ipc_event_binding(struct sway_binding *binding); #endif diff --git a/include/sway/output.h b/include/sway/output.h index b6cda83c..80dcd37b 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -5,6 +5,7 @@ #include <wayland-server.h> #include <wlr/types/wlr_box.h> #include <wlr/types/wlr_output.h> +#include "config.h" #include "sway/tree/view.h" struct sway_server; @@ -38,15 +39,9 @@ struct sway_output { } events; }; -/** - * Contains a surface's root geometry information. For instance, when rendering - * a popup, this will contain the parent view's position and size. - */ -struct root_geometry { - double x, y; - int width, height; - float rotation; -}; +typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, + struct wlr_surface *surface, struct wlr_box *box, float rotation, + void *user_data); void output_damage_whole(struct sway_output *output); @@ -65,36 +60,37 @@ struct sway_container *output_by_name(const char *name); void output_enable(struct sway_output *output); -bool output_has_opaque_lockscreen(struct sway_output *output, - struct sway_seat *seat); +bool output_has_opaque_overlay_layer_surface(struct sway_output *output); struct sway_container *output_get_active_workspace(struct sway_output *output); void output_render(struct sway_output *output, struct timespec *when, pixman_region32_t *damage); -bool output_get_surface_box(struct root_geometry *geo, - struct sway_output *output, struct wlr_surface *surface, int sx, int sy, - struct wlr_box *surface_box); +void output_surface_for_each_surface(struct sway_output *output, + struct wlr_surface *surface, double ox, double oy, + sway_surface_iterator_func_t iterator, void *user_data); -void output_surface_for_each_surface(struct wlr_surface *surface, - double ox, double oy, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data); +void output_view_for_each_surface(struct sway_output *output, + struct sway_view *view, sway_surface_iterator_func_t iterator, + void *user_data); -void output_view_for_each_surface(struct sway_view *view, - struct sway_output *output, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data); +void output_view_for_each_popup(struct sway_output *output, + struct sway_view *view, sway_surface_iterator_func_t iterator, + void *user_data); -void output_layer_for_each_surface(struct wl_list *layer_surfaces, - struct root_geometry *geo, wlr_surface_iterator_func_t iterator, +void output_layer_for_each_surface(struct sway_output *output, + struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, void *user_data); -void output_unmanaged_for_each_surface(struct wl_list *unmanaged, - struct sway_output *output, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data); +#ifdef HAVE_XWAYLAND +void output_unmanaged_for_each_surface(struct sway_output *output, + struct wl_list *unmanaged, sway_surface_iterator_func_t iterator, + void *user_data); +#endif -void output_drag_icons_for_each_surface(struct wl_list *drag_icons, - struct sway_output *output, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data); +void output_drag_icons_for_each_surface(struct sway_output *output, + struct wl_list *drag_icons, sway_surface_iterator_func_t iterator, + void *user_data); #endif diff --git a/include/sway/scratchpad.h b/include/sway/scratchpad.h new file mode 100644 index 00000000..5af5256f --- /dev/null +++ b/include/sway/scratchpad.h @@ -0,0 +1,26 @@ +#ifndef _SWAY_SCRATCHPAD_H +#define _SWAY_SCRATCHPAD_H + +#include "tree/container.h" + +/** + * Move a container to the scratchpad. + */ +void scratchpad_add_container(struct sway_container *con); + +/** + * Remove a container from the scratchpad. + */ +void scratchpad_remove_container(struct sway_container *con); + +/** + * Show or hide the next container on the scratchpad. + */ +void scratchpad_toggle_auto(void); + +/** + * Show or hide a specific container on the scratchpad. + */ +void scratchpad_toggle_container(struct sway_container *con); + +#endif diff --git a/include/sway/server.h b/include/sway/server.h index 70bde6d4..a3782f91 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -12,7 +12,10 @@ #include <wlr/render/wlr_renderer.h> // TODO WLR: make Xwayland optional #include "list.h" +#include "config.h" +#ifdef HAVE_XWAYLAND #include "sway/xwayland.h" +#endif struct sway_server { struct wl_display *wl_display; @@ -39,11 +42,11 @@ struct sway_server { struct wlr_xdg_shell *xdg_shell; struct wl_listener xdg_shell_surface; - +#ifdef HAVE_XWAYLAND struct sway_xwayland xwayland; struct wl_listener xwayland_surface; struct wl_listener xwayland_ready; - +#endif bool debug_txn_timings; list_t *transactions; @@ -65,6 +68,7 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); void handle_layer_shell_surface(struct wl_listener *listener, void *data); void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data); void handle_xdg_shell_surface(struct wl_listener *listener, void *data); +#ifdef HAVE_XWAYLAND void handle_xwayland_surface(struct wl_listener *listener, void *data); - +#endif #endif diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index ca7a3288..c3942e9e 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -60,6 +60,8 @@ struct sway_container_state { double swayc_x, swayc_y; double swayc_width, swayc_height; + bool is_fullscreen; + bool has_gaps; double current_gaps; double gaps_inner; @@ -74,7 +76,6 @@ struct sway_container_state { // View properties double view_x, view_y; double view_width, view_height; - bool is_fullscreen; enum sway_container_border border; int border_thickness; @@ -84,7 +85,7 @@ struct sway_container_state { bool border_right; // Workspace properties - struct sway_view *ws_fullscreen; + struct sway_container *ws_fullscreen; struct sway_container *ws_floating; }; @@ -124,6 +125,8 @@ struct sway_container { double saved_x, saved_y; double saved_width, saved_height; + bool is_fullscreen; + // The gaps currently applied to the container. double current_gaps; @@ -135,6 +138,11 @@ struct sway_container { struct sway_container *parent; + // Indicates that the container is a scratchpad container. + // Both hidden and visible scratchpad containers have scratchpad=true. + // Hidden scratchpad containers have a NULL parent. + bool scratchpad; + float alpha; struct wlr_texture *title_focused; @@ -222,16 +230,13 @@ struct sway_container *container_parent(struct sway_container *container, * surface-local coordinates of the given layout coordinates if the container * is a view and the view contains a surface at those coordinates. */ -struct sway_container *container_at(struct sway_container *container, - double ox, double oy, struct wlr_surface **surface, +struct sway_container *container_at(struct sway_container *workspace, + double lx, double ly, struct wlr_surface **surface, double *sx, double *sy); -/** - * Same as container_at, but only checks floating views and expects coordinates - * to be layout coordinates, as that's what floating views use. - */ -struct sway_container *floating_container_at(double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy); +struct sway_container *container_at_view(struct sway_container *view, + double lx, double ly, struct wlr_surface **surface, + double *sx, double *sy); /** * Apply the function for each descendant of the container breadth first. @@ -262,6 +267,8 @@ int container_count_descendants_of_type(struct sway_container *con, void container_create_notify(struct sway_container *container); +void container_update_textures_recursive(struct sway_container *con); + void container_damage_whole(struct sway_container *container); bool container_reap_empty(struct sway_container *con); @@ -289,6 +296,11 @@ void container_notify_subtree_changed(struct sway_container *container); */ size_t container_titlebar_height(void); +/** + * Resize and center the container in its workspace. + */ +void container_init_floating(struct sway_container *container); + void container_set_floating(struct sway_container *container, bool enable); void container_set_geometry_from_floating_view(struct sway_container *con); @@ -305,6 +317,12 @@ bool container_is_floating(struct sway_container *container); void container_get_box(struct sway_container *container, struct wlr_box *box); /** + * Move a floating container by the specified amount. + */ +void container_floating_translate(struct sway_container *con, + double x_amount, double y_amount); + +/** * Move a floating container to a new layout-local position. */ void container_floating_move_to(struct sway_container *con, @@ -318,4 +336,32 @@ void container_set_dirty(struct sway_container *container); bool container_has_urgent_child(struct sway_container *container); +/** + * If the container is involved in a drag or resize operation via a mouse, this + * ends the operation. + */ +void container_end_mouse_operation(struct sway_container *container); + +void container_set_fullscreen(struct sway_container *container, bool enable); + +/** + * Return true if the container is floating, or a child of a floating split + * container. + */ +bool container_is_floating_or_child(struct sway_container *container); + +/** + * Return true if the container is fullscreen, or a child of a fullscreen split + * container. + */ +bool container_is_fullscreen_or_child(struct sway_container *container); + +/** + * Wrap the children of parent in a new container. The new container will be the + * only child of parent. + * + * The new container is returned. + */ +struct sway_container *container_wrap_children(struct sway_container *parent); + #endif diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h index ba265623..a4c31bf6 100644 --- a/include/sway/tree/layout.h +++ b/include/sway/tree/layout.h @@ -3,6 +3,7 @@ #include <wlr/types/wlr_output_layout.h> #include <wlr/render/wlr_texture.h> #include "sway/tree/container.h" +#include "config.h" enum movement_direction { MOVE_LEFT, @@ -14,10 +15,11 @@ enum movement_direction { }; enum resize_edge { - RESIZE_EDGE_LEFT, - RESIZE_EDGE_RIGHT, - RESIZE_EDGE_TOP, - RESIZE_EDGE_BOTTOM, + RESIZE_EDGE_NONE = 0, + RESIZE_EDGE_LEFT = 1, + RESIZE_EDGE_RIGHT = 2, + RESIZE_EDGE_TOP = 4, + RESIZE_EDGE_BOTTOM = 8, }; struct sway_container; @@ -26,14 +28,17 @@ struct sway_root { struct wlr_output_layout *output_layout; struct wl_listener output_layout_change; - +#ifdef HAVE_XWAYLAND struct wl_list xwayland_unmanaged; // sway_xwayland_unmanaged::link +#endif struct wl_list drag_icons; // sway_drag_icon::link struct wlr_texture *debug_tree; struct wl_list outputs; // sway_output::link + list_t *scratchpad; // struct sway_container + struct { struct wl_signal new_container; } events; diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 068d92c6..37fd02bc 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -3,7 +3,10 @@ #include <wayland-server.h> #include <wlr/types/wlr_surface.h> #include <wlr/types/wlr_xdg_shell_v6.h> +#include "config.h" +#ifdef HAVE_XWAYLAND #include <wlr/xwayland.h> +#endif #include "sway/input/input-manager.h" #include "sway/input/seat.h" @@ -12,7 +15,9 @@ struct sway_container; enum sway_view_type { SWAY_VIEW_XDG_SHELL_V6, SWAY_VIEW_XDG_SHELL, +#ifdef HAVE_XWAYLAND SWAY_VIEW_XWAYLAND, +#endif }; enum sway_view_prop { @@ -22,10 +27,14 @@ enum sway_view_prop { VIEW_PROP_INSTANCE, VIEW_PROP_WINDOW_TYPE, VIEW_PROP_WINDOW_ROLE, +#ifdef HAVE_XWAYLAND VIEW_PROP_X11_WINDOW_ID, +#endif }; struct sway_view_impl { + void (*get_constraints)(struct sway_view *view, double *min_width, + double *max_width, double *min_height, double *max_height); const char *(*get_string_prop)(struct sway_view *view, enum sway_view_prop prop); uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop); @@ -38,7 +47,10 @@ struct sway_view_impl { bool (*has_client_side_decorations)(struct sway_view *view); void (*for_each_surface)(struct sway_view *view, wlr_surface_iterator_func_t iterator, void *user_data); + void (*for_each_popup)(struct sway_view *view, + wlr_surface_iterator_func_t iterator, void *user_data); void (*close)(struct sway_view *view); + void (*close_popups)(struct sway_view *view); void (*destroy)(struct sway_view *view); }; @@ -60,8 +72,6 @@ struct sway_view { // Used when changing a view from tiled to floating. int natural_width, natural_height; - bool is_fullscreen; - char *title_format; enum sway_container_border border; int border_thickness; @@ -75,6 +85,9 @@ struct sway_view { bool allow_request_urgent; struct wl_event_source *urgent_timer; + struct wlr_buffer *saved_buffer; + int saved_buffer_width, saved_buffer_height; + bool destroying; list_t *executed_criteria; // struct criteria * @@ -88,7 +101,9 @@ struct sway_view { union { struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6; struct wlr_xdg_surface *wlr_xdg_surface; +#ifdef HAVE_XWAYLAND struct wlr_xwayland_surface *wlr_xwayland_surface; +#endif struct wlr_wl_shell_surface *wlr_wl_shell_surface; }; @@ -108,6 +123,8 @@ struct sway_xdg_shell_v6_view { struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_app_id; struct wl_listener new_popup; struct wl_listener map; struct wl_listener unmap; @@ -122,12 +139,14 @@ struct sway_xdg_shell_view { struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_app_id; struct wl_listener new_popup; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; }; - +#ifdef HAVE_XWAYLAND struct sway_xwayland_view { struct sway_view view; @@ -159,7 +178,7 @@ struct sway_xwayland_unmanaged { struct wl_listener unmap; struct wl_listener destroy; }; - +#endif struct sway_view_child; struct sway_view_child_impl { @@ -215,15 +234,13 @@ uint32_t view_get_window_type(struct sway_view *view); const char *view_get_shell(struct sway_view *view); +void view_get_constraints(struct sway_view *view, double *min_width, + double *max_width, double *min_height, double *max_height); + uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, int height); /** - * Center the view in its workspace and build the swayc decorations around it. - */ -void view_init_floating(struct sway_view *view); - -/** * Configure the view's position and size based on the swayc's position and * size, taking borders into consideration. */ @@ -233,17 +250,24 @@ void view_set_activated(struct sway_view *view, bool activated); void view_set_tiled(struct sway_view *view, bool tiled); -void view_set_fullscreen_raw(struct sway_view *view, bool fullscreen); - -void view_set_fullscreen(struct sway_view *view, bool fullscreen); - void view_close(struct sway_view *view); +void view_close_popups(struct sway_view *view); + void view_damage_from(struct sway_view *view); +/** + * Iterate all surfaces of a view (toplevels + popups). + */ void view_for_each_surface(struct sway_view *view, wlr_surface_iterator_func_t iterator, void *user_data); +/** + * Iterate all popups recursively. + */ +void view_for_each_popup(struct sway_view *view, + wlr_surface_iterator_func_t iterator, void *user_data); + // view implementation void view_init(struct sway_view *view, enum sway_view_type type, @@ -272,9 +296,10 @@ struct sway_view *view_from_wlr_xdg_surface( struct wlr_xdg_surface *xdg_surface); struct sway_view *view_from_wlr_xdg_surface_v6( struct wlr_xdg_surface_v6 *xdg_surface_v6); +#ifdef HAVE_XWAYLAND struct sway_view *view_from_wlr_xwayland_surface( struct wlr_xwayland_surface *xsurface); - +#endif struct sway_view *view_from_wlr_surface(struct wlr_surface *surface); /** @@ -303,6 +328,8 @@ void view_clear_marks(struct sway_view *view); bool view_has_mark(struct sway_view *view, char *mark); +void view_add_mark(struct sway_view *view, char *mark); + void view_update_marks_textures(struct sway_view *view); /** @@ -315,4 +342,8 @@ void view_set_urgent(struct sway_view *view, bool enable); bool view_is_urgent(struct sway_view *view); +void view_remove_saved_buffer(struct sway_view *view); + +void view_save_buffer(struct sway_view *view); + #endif diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index bc95317a..5ae0ae3a 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -7,7 +7,7 @@ struct sway_view; struct sway_workspace { struct sway_container *swayc; - struct sway_view *fullscreen; + struct sway_container *fullscreen; struct sway_container *floating; list_t *output_priority; bool urgent; @@ -44,6 +44,10 @@ void workspace_output_add_priority(struct sway_container *workspace, struct sway_container *workspace_output_get_highest_available( struct sway_container *ws, struct sway_container *exclude); +struct sway_container *workspace_for_pid(pid_t pid); + +void workspace_record_pid(pid_t pid); + void workspace_detect_urgent(struct sway_container *workspace); #endif diff --git a/include/swaygrab/json.h b/include/swaygrab/json.h deleted file mode 100644 index c1093ef1..00000000 --- a/include/swaygrab/json.h +++ /dev/null @@ -1,10 +0,0 @@ -#include <json-c/json.h> -#include "wlc/wlc.h" - -void init_json_tree(int socketfd); -void free_json_tree(); -char *get_focused_output(); -char *create_payload(const char *output, struct wlc_geometry *g); -struct wlc_geometry *get_container_geometry(json_object *container); -json_object *get_focused_container(); -json_object *get_output_container(const char *output); diff --git a/include/swaynag/config.h b/include/swaynag/config.h new file mode 100644 index 00000000..0d8889de --- /dev/null +++ b/include/swaynag/config.h @@ -0,0 +1,13 @@ +#ifndef _SWAYNAG_CONFIG_H +#define _SWAYNAG_CONFIG_H +#include "swaynag/swaynag.h" +#include "list.h" + +int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, + list_t *types, struct swaynag_type *type, char **config, bool *debug); + +char *swaynag_get_config_path(void); + +int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types); + +#endif diff --git a/include/swaynag/render.h b/include/swaynag/render.h new file mode 100644 index 00000000..d09e5929 --- /dev/null +++ b/include/swaynag/render.h @@ -0,0 +1,7 @@ +#ifndef _SWAYNAG_RENDER_H +#define _SWAYNAG_RENDER_H +#include "swaynag/swaynag.h" + +void render_frame(struct swaynag *swaynag); + +#endif diff --git a/include/swaynag/swaynag.h b/include/swaynag/swaynag.h new file mode 100644 index 00000000..1bf8b640 --- /dev/null +++ b/include/swaynag/swaynag.h @@ -0,0 +1,100 @@ +#ifndef _SWAYNAG_SWAYNAG_H +#define _SWAYNAG_SWAYNAG_H +#include <stdint.h> +#include <strings.h> +#include "list.h" +#include "pool-buffer.h" +#include "swaynag/types.h" +#include "xdg-output-unstable-v1-client-protocol.h" + +#define SWAYNAG_MAX_HEIGHT 500 + +struct swaynag; + +enum swaynag_action_type { + SWAYNAG_ACTION_DISMISS, + SWAYNAG_ACTION_EXPAND, + SWAYNAG_ACTION_COMMAND, +}; + +struct swaynag_pointer { + struct wl_pointer *pointer; + uint32_t serial; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor_image *cursor_image; + struct wl_surface *cursor_surface; + int x; + int y; +}; + +struct swaynag_output { + char *name; + struct wl_output *wl_output; + uint32_t wl_name; + uint32_t scale; + struct swaynag *swaynag; + struct wl_list link; +}; + +struct swaynag_button { + char *text; + enum swaynag_action_type type; + char *action; + int x; + int y; + int width; + int height; +}; + +struct swaynag_details { + bool visible; + char *message; + + int x; + int y; + int width; + int height; + + int offset; + int visible_lines; + int total_lines; + struct swaynag_button button_details; + struct swaynag_button button_up; + struct swaynag_button button_down; +}; + +struct swaynag { + bool run_display; + int querying_outputs; + + struct wl_display *display; + struct wl_compositor *compositor; + struct wl_seat *seat; + struct wl_shm *shm; + struct swaynag_pointer pointer; + struct zxdg_output_manager_v1 *xdg_output_manager; + struct wl_list outputs; // swaynag_output::link + struct swaynag_output *output; + struct zwlr_layer_shell_v1 *layer_shell; + struct zwlr_layer_surface_v1 *layer_surface; + struct wl_surface *surface; + + uint32_t width; + uint32_t height; + int32_t scale; + struct pool_buffer buffers[2]; + struct pool_buffer *current_buffer; + + struct swaynag_type *type; + char *message; + list_t *buttons; + struct swaynag_details details; +}; + +void swaynag_setup(struct swaynag *swaynag); + +void swaynag_run(struct swaynag *swaynag); + +void swaynag_destroy(struct swaynag *swaynag); + +#endif diff --git a/include/swaynag/types.h b/include/swaynag/types.h new file mode 100644 index 00000000..2183ce22 --- /dev/null +++ b/include/swaynag/types.h @@ -0,0 +1,39 @@ +#ifndef _SWAYNAG_TYPES_H +#define _SWAYNAG_TYPES_H + +struct swaynag_type { + char *name; + + char *font; + char *output; + uint32_t anchors; + + uint32_t button_background; + uint32_t background; + uint32_t text; + uint32_t border; + uint32_t border_bottom; + + uint32_t bar_border_thickness; + uint32_t message_padding; + uint32_t details_border_thickness; + uint32_t button_border_thickness; + uint32_t button_gap; + uint32_t button_gap_close; + uint32_t button_margin_right; + uint32_t button_padding; +}; + +void swaynag_types_add_default(list_t *types); + +struct swaynag_type *swaynag_type_get(list_t *types, char *name); + +struct swaynag_type *swaynag_type_clone(struct swaynag_type *type); + +void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src); + +void swaynag_type_free(struct swaynag_type *type); + +void swaynag_types_free(list_t *types); + +#endif diff --git a/include/util.h b/include/util.h index f68deae8..9277fa6e 100644 --- a/include/util.h +++ b/include/util.h @@ -2,6 +2,7 @@ #define _SWAY_UTIL_H #include <stdint.h> +#include <stdbool.h> #include <unistd.h> #include <sys/types.h> #include <xkbcommon/xkbcommon.h> @@ -51,6 +52,14 @@ pid_t get_parent_pid(pid_t pid); uint32_t parse_color(const char *color); /** + * Given a string that represents a boolean, return the boolean value. This + * function also takes in the current boolean value to support toggling. If + * toggling is not desired, pass in true for current so that toggling values + * get parsed as not true. + */ +bool parse_boolean(const char *boolean, bool current); + +/** * Given a path string, recurseively resolves any symlinks to their targets * (which may be a file, directory) and returns the result. * argument is returned. Caller must free the returned buffer. diff --git a/meson.build b/meson.build index 1d40581a..2a020323 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ project( add_project_arguments('-Wno-unused-parameter', language: 'c') add_project_arguments('-Wno-unused-function', language: 'c') add_project_arguments('-Wno-unused-result', language: 'c') +add_project_arguments('-DWLR_USE_UNSTABLE', language: 'c') cc = meson.get_compiler('c') @@ -43,11 +44,17 @@ systemd = dependency('libsystemd', required: false) elogind = dependency('libelogind', required: false) math = cc.find_library('m') rt = cc.find_library('rt') -xcb = dependency('xcb') git = find_program('git', required: false) conf_data = configuration_data() +if get_option('enable-xwayland') + conf_data.set('HAVE_XWAYLAND', true) + xcb = dependency('xcb') +else + conf_data.set('HAVE_XWAYLAND', false) +endif + if gdk_pixbuf.found() conf_data.set('HAVE_GDK_PIXBUF', true) endif @@ -75,6 +82,8 @@ if scdoc.found() 'swaylock/swaylock.1.scd', 'swaymsg/swaymsg.1.scd', 'swayidle/swayidle.1.scd', + 'swaynag/swaynag.1.scd', + 'swaynag/swaynag.5.scd', ] foreach filename : man_files topic = filename.split('.')[-3].split('/')[-1] @@ -123,6 +132,7 @@ subdir('swaybg') subdir('swaybar') subdir('swaylock') subdir('swayidle') +subdir('swaynag') config = configuration_data() config.set('sysconfdir', join_paths(prefix, sysconfdir)) @@ -176,7 +186,6 @@ endif if (get_option('zsh_completions')) zsh_files = files( 'completions/zsh/_sway', - 'completions/zsh/_swaygrab', 'completions/zsh/_swaylock', 'completions/zsh/_swaymsg', ) @@ -184,3 +193,15 @@ if (get_option('zsh_completions')) install_data(zsh_files, install_dir: zsh_install_dir) endif + +if (get_option('bash_completions')) + bash_files = files( + 'completions/bash/sway', + 'completions/bash/swayidle', + 'completions/bash/swaylock', + 'completions/bash/swaymsg', + ) + bash_install_dir = datadir + '/bash-completion/completions' + + install_data(bash_files, install_dir: bash_install_dir) +endif diff --git a/meson_options.txt b/meson_options.txt index 541ccf13..7a23c206 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,5 @@ option('sway_version', type : 'string', description: 'The version string reported in `sway --version`.') option('default_wallpaper', type: 'boolean', value: true, description: 'Install the default wallpaper.') option('zsh_completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') +option('bash_completions', type: 'boolean', value: true, description: 'Install bash shell completions.') +option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') diff --git a/security.d/00-defaults.in b/security.d/00-defaults.in index e4626477..be7b9d06 100644 --- a/security.d/00-defaults.in +++ b/security.d/00-defaults.in @@ -12,7 +12,6 @@ permit * fullscreen keyboard mouse permit @prefix@/bin/swaylock lock permit @prefix@/bin/swaybg background -permit @prefix@/bin/swaygrab screenshot permit @prefix@/bin/swaybar panel # Configures enabled IPC features for specific programs @@ -36,11 +35,6 @@ ipc @prefix@/bin/swaybar { } } -ipc @prefix@/bin/swaygrab { - outputs enabled - tree enabled -} - ipc @prefix@/bin/swaylock { outputs enabled } diff --git a/sway/commands.c b/sway/commands.c index f1f03574..fdae1961 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -103,6 +103,7 @@ static struct cmd_handler handlers[] = { { "exec_always", cmd_exec_always }, { "floating_maximum_size", cmd_floating_maximum_size }, { "floating_minimum_size", cmd_floating_minimum_size }, + { "floating_modifier", cmd_floating_modifier }, { "focus", cmd_focus }, { "focus_follows_mouse", cmd_focus_follows_mouse }, { "focus_wrapping", cmd_focus_wrapping }, @@ -148,6 +149,7 @@ static struct cmd_handler command_handlers[] = { { "reload", cmd_reload }, { "rename", cmd_rename }, { "resize", cmd_resize }, + { "scratchpad", cmd_scratchpad }, { "split", cmd_split }, { "splith", cmd_splith }, { "splitt", cmd_splitt }, @@ -325,7 +327,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { } while(head); cleanup: free(exec); - free(views); + list_free(views); if (!results) { results = cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 83e9e432..8270b958 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -1,3 +1,4 @@ +#define _XOPEN_SOURCE 500 #ifdef __linux__ #include <linux/input-event-codes.h> #elif __FreeBSD__ @@ -5,9 +6,11 @@ #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/ipc-server.h" #include "list.h" #include "log.h" #include "stringop.h" @@ -27,6 +30,33 @@ void free_sway_binding(struct sway_binding *binding) { free(binding); } +static struct sway_binding *sway_binding_dup(struct sway_binding *sb) { + struct sway_binding *new_sb = calloc(1, sizeof(struct sway_binding)); + if (!new_sb) { + return NULL; + } + + new_sb->type = sb->type; + new_sb->order = sb->order; + new_sb->flags = sb->flags; + new_sb->modifiers = sb->modifiers; + new_sb->command = strdup(sb->command); + + new_sb->keys = create_list(); + int i; + for (i = 0; i < sb->keys->length; ++i) { + xkb_keysym_t *key = malloc(sizeof(xkb_keysym_t)); + if (!key) { + free_sway_binding(new_sb); + return NULL; + } + *key = *(xkb_keysym_t *)sb->keys->items[i]; + list_add(new_sb->keys, key); + } + + return new_sb; +} + /** * Returns true if the bindings have the same key and modifier combinations. * Note that keyboard layout is not considered, so the bindings might actually @@ -34,11 +64,14 @@ void free_sway_binding(struct sway_binding *binding) { */ static bool binding_key_compare(struct sway_binding *binding_a, struct sway_binding *binding_b) { - if (binding_a->release != binding_b->release) { + if (binding_a->type != binding_b->type) { return false; } - if (binding_a->bindcode != binding_b->bindcode) { + uint32_t conflict_generating_flags = BINDING_RELEASE | BINDING_BORDER + | BINDING_CONTENTS | BINDING_TITLEBAR; + if ((binding_a->flags & conflict_generating_flags) != + (binding_b->flags & conflict_generating_flags)) { return false; } @@ -69,6 +102,66 @@ 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 + * and set *key_val if successful, otherwise return a specific error. Change + * the value of *type if the initial type guess was incorrect and if this + * was the first identified key. + */ +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 + 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); + } + *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; + } + + 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) { + return cmd_results_new(CMD_INVALID, "bindsym", + "Mixed keysym '%s' into button sequence", name); + } else { + return cmd_results_new(CMD_INVALID, "bindsym", + "Unknown button '%s'", name); + } + } + } + return NULL; +} + static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, bool bindcode) { const char *bindtype = bindcode ? "bindcode" : "bindsym"; @@ -85,22 +178,34 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, } binding->keys = create_list(); binding->modifiers = 0; - binding->release = false; - binding->locked = false; - binding->bindcode = bindcode; + binding->flags = 0; + binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM; + + bool exclude_titlebar = false; // Handle --release and --locked while (argc > 0) { if (strcmp("--release", argv[0]) == 0) { - binding->release = true; + binding->flags |= BINDING_RELEASE; } else if (strcmp("--locked", argv[0]) == 0) { - binding->locked = true; + binding->flags |= BINDING_LOCKED; + } else if (strcmp("--whole-window", argv[0]) == 0) { + binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR; + } else if (strcmp("--border", argv[0]) == 0) { + binding->flags |= BINDING_BORDER; + } else if (strcmp("--exclude-titlebar", argv[0]) == 0) { + exclude_titlebar = true; } else { break; } argv++; argc--; } + if (binding->flags & (BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR) + || exclude_titlebar) { + binding->type = BINDING_MOUSE; + } + if (argc < 2) { free_sway_binding(binding); return cmd_results_new(CMD_FAILURE, bindtype, @@ -119,64 +224,47 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, continue; } - xkb_keycode_t keycode; - xkb_keysym_t keysym; - if (bindcode) { - // parse keycode - keycode = (int)strtol(split->items[i], NULL, 10); - if (!xkb_keycode_is_legal_ext(keycode)) { - error = - cmd_results_new(CMD_INVALID, "bindcode", - "Invalid keycode '%s'", (char *)split->items[i]); - free_sway_binding(binding); - list_free(split); - return error; - } - } else { - // Check for xkb key - keysym = xkb_keysym_from_name(split->items[i], - XKB_KEYSYM_CASE_INSENSITIVE); - - // Check for mouse binding - if (strncasecmp(split->items[i], "button", strlen("button")) == 0 && - strlen(split->items[i]) == strlen("button0")) { - keysym = ((char *)split->items[i])[strlen("button")] - '1' + BTN_LEFT; - } - if (!keysym) { - struct cmd_results *ret = cmd_results_new(CMD_INVALID, "bindsym", - "Unknown key '%s'", (char *)split->items[i]); - free_sway_binding(binding); - free_flat_list(split); - return ret; - } + // Identify the key and possibly change binding->type + uint32_t key_val = 0; + error = identify_key(split->items[i], binding->keys->length == 0, + &key_val, &binding->type); + if (error) { + free_sway_binding(binding); + list_free(split); + return error; } + uint32_t *key = calloc(1, sizeof(uint32_t)); if (!key) { free_sway_binding(binding); free_flat_list(split); return cmd_results_new(CMD_FAILURE, bindtype, - "Unable to allocate binding"); - } - - if (bindcode) { - *key = (uint32_t)keycode; - } else { - *key = (uint32_t)keysym; + "Unable to allocate binding key"); } - + *key = key_val; list_add(binding->keys, key); } free_flat_list(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) { + binding->flags |= BINDING_TITLEBAR; + } + // sort ascending list_qsort(binding->keys, key_qsort_cmp); list_t *mode_bindings; - if (bindcode) { + if (binding->type == BINDING_KEYCODE) { mode_bindings = config->current_mode->keycode_bindings; - } else { + } else if (binding->type == BINDING_KEYSYM) { mode_bindings = config->current_mode->keysym_bindings; + } else { + mode_bindings = config->current_mode->mouse_bindings; } // overwrite the binding if it already exists @@ -209,3 +297,39 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) { 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); + + struct sway_binding *binding_copy = binding; + bool reload = false; + // if this is a reload command we need to make a duplicate of the + // binding since it will be gone after the reload has completed. + if (strcasecmp(binding->command, "reload") == 0) { + reload = true; + binding_copy = sway_binding_dup(binding); + if (!binding_copy) { + wlr_log(WLR_ERROR, "Failed to duplicate binding during reload"); + return; + } + } + + config->handler_context.seat = seat; + struct cmd_results *results = execute_command(binding->command, NULL); + if (results->status == CMD_SUCCESS) { + ipc_event_binding(binding_copy); + } else { + wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)", + binding->command, results->error); + } + + if (reload) { // free the binding if we made a copy + free_sway_binding(binding_copy); + } + free_cmd_results(results); +} diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c index c7727857..c730cb8b 100644 --- a/sway/commands/exec_always.c +++ b/sway/commands/exec_always.c @@ -4,6 +4,7 @@ #include <string.h> #include <sys/wait.h> #include <unistd.h> +#include <signal.h> #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/container.h" @@ -47,6 +48,9 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) { if ((pid = fork()) == 0) { // Fork child process again setsid(); + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); close(fd[0]); if ((child = fork()) == 0) { close(fd[1]); @@ -74,7 +78,7 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) { waitpid(pid, NULL, 0); if (child > 0) { wlr_log(WLR_DEBUG, "Child process created with pid %d", child); - // TODO: add PID to active workspace + workspace_record_pid(child); } else { return cmd_results_new(CMD_FAILURE, "exec_always", "Second fork() failed"); diff --git a/sway/commands/floating.c b/sway/commands/floating.c index 6ab56c3b..31de5ec3 100644 --- a/sway/commands/floating.c +++ b/sway/commands/floating.c @@ -17,9 +17,24 @@ struct cmd_results *cmd_floating(int argc, char **argv) { } struct sway_container *container = config->handler_context.current_container; - if (container->type != C_VIEW) { - // TODO: This doesn't strictly speaking have to be true - return cmd_results_new(CMD_INVALID, "float", "Only views can float"); + if (container->type == C_WORKSPACE && container->children->length == 0) { + return cmd_results_new(CMD_INVALID, "floating", + "Can't float an empty workspace"); + } + if (container->type == C_WORKSPACE) { + // Wrap the workspace's children in a container so we can float it + struct sway_container *workspace = container; + container = container_wrap_children(container); + workspace->layout = L_HORIZ; + seat_set_focus(config->handler_context.seat, container); + } + + // If the container is in a floating split container, + // operate on the split container instead of the child. + if (container_is_floating_or_child(container)) { + while (container->parent->layout != L_FLOATING) { + container = container->parent; + } } bool wants_floating; diff --git a/sway/commands/floating_modifier.c b/sway/commands/floating_modifier.c new file mode 100644 index 00000000..f5d2b3fe --- /dev/null +++ b/sway/commands/floating_modifier.c @@ -0,0 +1,30 @@ +#include "strings.h" +#include "sway/commands.h" +#include "sway/config.h" +#include "util.h" + +struct cmd_results *cmd_floating_modifier(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "floating_modifier", EXPECTED_AT_LEAST, 1))) { + return error; + } + + uint32_t mod = get_modifier_mask_by_name(argv[0]); + if (!mod) { + return cmd_results_new(CMD_INVALID, "floating_modifier", + "Invalid modifier"); + } + + if (argc == 1 || strcasecmp(argv[1], "normal") == 0) { + config->floating_mod_inverse = false; + } else if (strcasecmp(argv[1], "inverse") == 0) { + config->floating_mod_inverse = true; + } else { + return cmd_results_new(CMD_INVALID, "floating_modifier", + "Usage: floating_modifier <mod> [inverse|normal]"); + } + + config->floating_mod = mod; + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/focus.c b/sway/commands/focus.c index 9cd8bfae..76d3f1dc 100644 --- a/sway/commands/focus.c +++ b/sway/commands/focus.c @@ -35,14 +35,25 @@ static struct cmd_results *focus_mode(struct sway_container *con, struct sway_seat *seat, bool floating) { struct sway_container *ws = con->type == C_WORKSPACE ? con : container_parent(con, C_WORKSPACE); - struct sway_container *new_focus = ws; - if (floating) { - new_focus = ws->sway_workspace->floating; - if (new_focus->children->length == 0) { - return cmd_results_new(CMD_SUCCESS, NULL, NULL); + + // If the container is in a floating split container, + // operate on the split container instead of the child. + if (container_is_floating_or_child(con)) { + while (con->parent->layout != L_FLOATING) { + con = con->parent; } } - seat_set_focus(seat, seat_get_active_child(seat, new_focus)); + + struct sway_container *new_focus = NULL; + if (floating) { + new_focus = seat_get_focus_inactive(seat, ws->sway_workspace->floating); + } else { + new_focus = seat_get_focus_inactive_tiling(seat, ws); + } + if (!new_focus) { + new_focus = ws; + } + seat_set_focus(seat, new_focus); return cmd_results_new(CMD_SUCCESS, NULL, NULL); } @@ -97,7 +108,7 @@ struct cmd_results *cmd_focus(int argc, char **argv) { } else if (strcmp(argv[0], "tiling") == 0) { return focus_mode(con, seat, false); } else if (strcmp(argv[0], "mode_toggle") == 0) { - return focus_mode(con, seat, !container_is_floating(con)); + return focus_mode(con, seat, !container_is_floating_or_child(con)); } if (strcmp(argv[0], "output") == 0) { diff --git a/sway/commands/focus_follows_mouse.c b/sway/commands/focus_follows_mouse.c index 661e7852..0b0e334c 100644 --- a/sway/commands/focus_follows_mouse.c +++ b/sway/commands/focus_follows_mouse.c @@ -1,12 +1,14 @@ #include <string.h> #include <strings.h> #include "sway/commands.h" +#include "util.h" struct cmd_results *cmd_focus_follows_mouse(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) { return error; } - config->focus_follows_mouse = !strcasecmp(argv[0], "yes"); + config->focus_follows_mouse = + parse_boolean(argv[0], config->focus_follows_mouse); return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/focus_wrapping.c b/sway/commands/focus_wrapping.c index 0a9e0bf2..562ee4f9 100644 --- a/sway/commands/focus_wrapping.c +++ b/sway/commands/focus_wrapping.c @@ -1,6 +1,7 @@ #include <strings.h> #include "sway/commands.h" #include "sway/config.h" +#include "util.h" struct cmd_results *cmd_focus_wrapping(int argc, char **argv) { struct cmd_results *error = NULL; @@ -8,15 +9,12 @@ struct cmd_results *cmd_focus_wrapping(int argc, char **argv) { return error; } - if (strcasecmp(argv[0], "no") == 0) { - config->focus_wrapping = WRAP_NO; - } else if (strcasecmp(argv[0], "yes") == 0) { - config->focus_wrapping = WRAP_YES; - } else if (strcasecmp(argv[0], "force") == 0) { + if (strcasecmp(argv[0], "force") == 0) { config->focus_wrapping = WRAP_FORCE; + } else if (parse_boolean(argv[0], config->focus_wrapping == WRAP_YES)) { + config->focus_wrapping = WRAP_YES; } else { - return cmd_results_new(CMD_INVALID, "focus_wrapping", - "Expected 'focus_wrapping yes|no|force'"); + config->focus_wrapping = WRAP_NO; } return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/force_focus_wrapping.c b/sway/commands/force_focus_wrapping.c index bc1d067f..0892d9e9 100644 --- a/sway/commands/force_focus_wrapping.c +++ b/sway/commands/force_focus_wrapping.c @@ -1,6 +1,7 @@ #include <strings.h> #include "sway/commands.h" #include "sway/config.h" +#include "util.h" struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) { struct cmd_results *error = @@ -9,13 +10,10 @@ struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) { return error; } - if (strcasecmp(argv[0], "no") == 0) { - config->focus_wrapping = WRAP_YES; - } else if (strcasecmp(argv[0], "yes") == 0) { + if (parse_boolean(argv[0], config->focus_wrapping == WRAP_FORCE)) { config->focus_wrapping = WRAP_FORCE; } else { - return cmd_results_new(CMD_INVALID, "force_focus_wrapping", - "Expected 'force_focus_wrapping yes|no'"); + config->focus_wrapping = WRAP_YES; } return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c index 0b5beaa2..5ad06e40 100644 --- a/sway/commands/fullscreen.c +++ b/sway/commands/fullscreen.c @@ -5,6 +5,7 @@ #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/layout.h" +#include "util.h" struct cmd_results *cmd_fullscreen(int argc, char **argv) { struct cmd_results *error = NULL; @@ -13,25 +14,24 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) { } struct sway_container *container = config->handler_context.current_container; - if (container->type != C_VIEW) { + if (container->type == C_WORKSPACE && container->children->length == 0) { return cmd_results_new(CMD_INVALID, "fullscreen", - "Only views can fullscreen"); + "Can't fullscreen an empty workspace"); } - struct sway_view *view = container->sway_view; - bool wants_fullscreen; + if (container->type == C_WORKSPACE) { + // Wrap the workspace's children in a container so we can fullscreen it + struct sway_container *workspace = container; + container = container_wrap_children(container); + workspace->layout = L_HORIZ; + seat_set_focus(config->handler_context.seat, container); + } + bool enable = !container->is_fullscreen; - if (argc == 0 || strcmp(argv[0], "toggle") == 0) { - wants_fullscreen = !view->is_fullscreen; - } else if (strcmp(argv[0], "enable") == 0) { - wants_fullscreen = true; - } else if (strcmp(argv[0], "disable") == 0) { - wants_fullscreen = false; - } else { - return cmd_results_new(CMD_INVALID, "fullscreen", - "Expected 'fullscreen' or 'fullscreen <enable|disable|toggle>'"); + if (argc) { + enable = parse_boolean(argv[0], container->is_fullscreen); } - view_set_fullscreen(view, wants_fullscreen); + container_set_fullscreen(container, enable); struct sway_container *workspace = container_parent(container, C_WORKSPACE); arrange_windows(workspace->parent); diff --git a/sway/commands/input.c b/sway/commands/input.c index 5b203ea0..84888fbb 100644 --- a/sway/commands/input.c +++ b/sway/commands/input.c @@ -31,6 +31,12 @@ static struct cmd_handler input_handlers[] = { { "xkb_variant", input_cmd_xkb_variant }, }; +// must be in order for the bsearch +static struct cmd_handler input_config_handlers[] = { + { "xkb_capslock", input_cmd_xkb_capslock }, + { "xkb_numlock", input_cmd_xkb_numlock }, +}; + struct cmd_results *cmd_input(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) { @@ -44,8 +50,21 @@ struct cmd_results *cmd_input(int argc, char **argv) { return cmd_results_new(CMD_FAILURE, NULL, "Couldn't allocate config"); } - struct cmd_results *res = config_subcommand(argv + 1, argc - 1, + struct cmd_results *res; + + if (find_handler(argv[1], input_config_handlers, + sizeof(input_config_handlers))) { + if (config->reading) { + res = config_subcommand(argv + 1, argc - 1, + input_config_handlers, sizeof(input_config_handlers)); + } else { + res = cmd_results_new(CMD_FAILURE, "input", + "Can only be used in config file."); + } + } else { + res = config_subcommand(argv + 1, argc - 1, input_handlers, sizeof(input_handlers)); + } free_input_config(config->handler_context.input_config); config->handler_context.input_config = NULL; diff --git a/sway/commands/input/drag_lock.c b/sway/commands/input/drag_lock.c index 9e32816f..f9ddeef2 100644 --- a/sway/commands/input/drag_lock.c +++ b/sway/commands/input/drag_lock.c @@ -3,6 +3,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "util.h" struct cmd_results *input_cmd_drag_lock(int argc, char **argv) { struct cmd_results *error = NULL; @@ -18,14 +19,10 @@ struct cmd_results *input_cmd_drag_lock(int argc, char **argv) { struct input_config *new_config = new_input_config(current_input_config->identifier); - if (strcasecmp(argv[0], "enabled") == 0) { + if (parse_boolean(argv[0], true)) { new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; - } else if (strcasecmp(argv[0], "disabled") == 0) { - new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } else { - free_input_config(new_config); - return cmd_results_new(CMD_INVALID, "drag_lock", - "Expected 'drag_lock <enabled|disabled>'"); + new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } apply_input_config(new_config); diff --git a/sway/commands/input/dwt.c b/sway/commands/input/dwt.c index 73937507..15134268 100644 --- a/sway/commands/input/dwt.c +++ b/sway/commands/input/dwt.c @@ -3,6 +3,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "util.h" struct cmd_results *input_cmd_dwt(int argc, char **argv) { struct cmd_results *error = NULL; @@ -17,14 +18,10 @@ struct cmd_results *input_cmd_dwt(int argc, char **argv) { struct input_config *new_config = new_input_config(current_input_config->identifier); - if (strcasecmp(argv[0], "enabled") == 0) { + if (parse_boolean(argv[0], true)) { new_config->dwt = LIBINPUT_CONFIG_DWT_ENABLED; - } else if (strcasecmp(argv[0], "disabled") == 0) { - new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED; } else { - free_input_config(new_config); - return cmd_results_new(CMD_INVALID, "dwt", - "Expected 'dwt <enabled|disabled>'"); + new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED; } apply_input_config(new_config); diff --git a/sway/commands/input/left_handed.c b/sway/commands/input/left_handed.c index 769ce98c..e770043a 100644 --- a/sway/commands/input/left_handed.c +++ b/sway/commands/input/left_handed.c @@ -3,6 +3,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "util.h" struct cmd_results *input_cmd_left_handed(int argc, char **argv) { struct cmd_results *error = NULL; @@ -18,15 +19,7 @@ struct cmd_results *input_cmd_left_handed(int argc, char **argv) { struct input_config *new_config = new_input_config(current_input_config->identifier); - if (strcasecmp(argv[0], "enabled") == 0) { - new_config->left_handed = 1; - } else if (strcasecmp(argv[0], "disabled") == 0) { - new_config->left_handed = 0; - } else { - free_input_config(new_config); - return cmd_results_new(CMD_INVALID, "left_handed", - "Expected 'left_handed <enabled|disabled>'"); - } + new_config->left_handed = parse_boolean(argv[0], true); apply_input_config(new_config); return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/input/middle_emulation.c b/sway/commands/input/middle_emulation.c index 7ca01629..414d4d2b 100644 --- a/sway/commands/input/middle_emulation.c +++ b/sway/commands/input/middle_emulation.c @@ -3,6 +3,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "util.h" struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) { struct cmd_results *error = NULL; @@ -18,15 +19,11 @@ struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) { struct input_config *new_config = new_input_config(current_input_config->identifier); - if (strcasecmp(argv[0], "enabled") == 0) { + if (parse_boolean(argv[0], true)) { new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; - } else if (strcasecmp(argv[0], "disabled") == 0) { + } else { new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; - } else { - free_input_config(new_config); - return cmd_results_new(CMD_INVALID, "middle_emulation", - "Expected 'middle_emulation <enabled|disabled>'"); } apply_input_config(new_config); diff --git a/sway/commands/input/natural_scroll.c b/sway/commands/input/natural_scroll.c index 55236790..77c3ff00 100644 --- a/sway/commands/input/natural_scroll.c +++ b/sway/commands/input/natural_scroll.c @@ -3,6 +3,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "util.h" struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) { struct cmd_results *error = NULL; @@ -18,15 +19,7 @@ struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) { struct input_config *new_config = new_input_config(current_input_config->identifier); - if (strcasecmp(argv[0], "enabled") == 0) { - new_config->natural_scroll = 1; - } else if (strcasecmp(argv[0], "disabled") == 0) { - new_config->natural_scroll = 0; - } else { - free_input_config(new_config); - return cmd_results_new(CMD_INVALID, "natural_scroll", - "Expected 'natural_scroll <enabled|disabled>'"); - } + new_config->natural_scroll = parse_boolean(argv[0], true); apply_input_config(new_config); return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/commands/input/tap.c b/sway/commands/input/tap.c index a8d1a10c..ac3b8237 100644 --- a/sway/commands/input/tap.c +++ b/sway/commands/input/tap.c @@ -4,6 +4,7 @@ #include "sway/commands.h" #include "sway/input/input-manager.h" #include "log.h" +#include "util.h" struct cmd_results *input_cmd_tap(int argc, char **argv) { struct cmd_results *error = NULL; @@ -18,14 +19,10 @@ struct cmd_results *input_cmd_tap(int argc, char **argv) { struct input_config *new_config = new_input_config(current_input_config->identifier); - if (strcasecmp(argv[0], "enabled") == 0) { + if (parse_boolean(argv[0], true)) { new_config->tap = LIBINPUT_CONFIG_TAP_ENABLED; - } else if (strcasecmp(argv[0], "disabled") == 0) { - new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED; } else { - free_input_config(new_config); - return cmd_results_new(CMD_INVALID, "tap", - "Expected 'tap <enabled|disabled>'"); + new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED; } wlr_log(WLR_DEBUG, "apply-tap for device: %s", diff --git a/sway/commands/input/xkb_capslock.c b/sway/commands/input/xkb_capslock.c new file mode 100644 index 00000000..5442c463 --- /dev/null +++ b/sway/commands/input/xkb_capslock.c @@ -0,0 +1,33 @@ +#include <string.h> +#include <strings.h> +#include "sway/config.h" +#include "sway/commands.h" +#include "sway/input/input-manager.h" + +struct cmd_results *input_cmd_xkb_capslock(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "xkb_capslock", EXPECTED_AT_LEAST, 1))) { + return error; + } + struct input_config *current_input_config = + config->handler_context.input_config; + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "xkb_capslock", + "No input device defined."); + } + struct input_config *new_config = + new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->xkb_capslock = 1; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->xkb_capslock = 0; + } else { + free_input_config(new_config); + return cmd_results_new(CMD_INVALID, "xkb_capslock", + "Expected 'xkb_capslock <enabled|disabled>'"); + } + + apply_input_config(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/input/xkb_numlock.c b/sway/commands/input/xkb_numlock.c new file mode 100644 index 00000000..39675366 --- /dev/null +++ b/sway/commands/input/xkb_numlock.c @@ -0,0 +1,33 @@ +#include <string.h> +#include <strings.h> +#include "sway/config.h" +#include "sway/commands.h" +#include "sway/input/input-manager.h" + +struct cmd_results *input_cmd_xkb_numlock(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "xkb_numlock", EXPECTED_AT_LEAST, 1))) { + return error; + } + struct input_config *current_input_config = + config->handler_context.input_config; + if (!current_input_config) { + return cmd_results_new(CMD_FAILURE, "xkb_numlock", + "No input device defined."); + } + struct input_config *new_config = + new_input_config(current_input_config->identifier); + + if (strcasecmp(argv[0], "enabled") == 0) { + new_config->xkb_numlock = 1; + } else if (strcasecmp(argv[0], "disabled") == 0) { + new_config->xkb_numlock = 0; + } else { + free_input_config(new_config); + return cmd_results_new(CMD_INVALID, "xkb_numlock", + "Expected 'xkb_numlock <enabled|disabled>'"); + } + + apply_input_config(new_config); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/mark.c b/sway/commands/mark.c index 5a897e69..9ea8c301 100644 --- a/sway/commands/mark.c +++ b/sway/commands/mark.c @@ -58,7 +58,7 @@ struct cmd_results *cmd_mark(int argc, char **argv) { view_find_and_unmark(mark); if (!toggle || !had_mark) { - list_add(view->marks, strdup(mark)); + view_add_mark(view, mark); } free(mark); diff --git a/sway/commands/mode.c b/sway/commands/mode.c index b460fcb5..637ca45e 100644 --- a/sway/commands/mode.c +++ b/sway/commands/mode.c @@ -56,6 +56,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) { mode->name = strdup(mode_name); mode->keysym_bindings = create_list(); mode->keycode_bindings = create_list(); + mode->mouse_bindings = create_list(); mode->pango = pango; list_add(config->modes, mode); } diff --git a/sway/commands/move.c b/sway/commands/move.c index 6ec050a8..702b42d9 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -9,6 +9,7 @@ #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/output.h" +#include "sway/scratchpad.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/layout.h" @@ -58,8 +59,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current, && strcasecmp(argv[2], "workspace") == 0) { // move container to workspace x if (current->type == C_WORKSPACE) { - // TODO: Wrap children in a container and move that - return cmd_results_new(CMD_FAILURE, "move", "Unimplemented"); + current = container_wrap_children(current); } else if (current->type != C_CONTAINER && current->type != C_VIEW) { return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views."); @@ -97,7 +97,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current, container_move_to(current, destination); struct sway_container *focus = seat_get_focus_inactive( config->handler_context.seat, old_parent); - seat_set_focus(config->handler_context.seat, focus); + seat_set_focus_warp(config->handler_context.seat, focus, true, false); container_reap_empty(old_parent); container_reap_empty(destination->parent); @@ -134,7 +134,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current, struct sway_container *old_parent = current->parent; struct sway_container *old_ws = container_parent(current, C_WORKSPACE); container_move_to(current, focus); - seat_set_focus(config->handler_context.seat, old_parent); + seat_set_focus_warp(config->handler_context.seat, old_parent, true, false); container_reap_empty(old_parent); container_reap_empty(focus->parent); @@ -195,7 +195,7 @@ static struct cmd_results *move_in_direction(struct sway_container *container, "Cannot move workspaces in a direction"); } if (container_is_floating(container)) { - if (container->type == C_VIEW && container->sway_view->is_fullscreen) { + if (container->is_fullscreen) { return cmd_results_new(CMD_FAILURE, "move", "Cannot move fullscreen floating container"); } @@ -296,6 +296,34 @@ static struct cmd_results *move_to_position(struct sway_container *container, return cmd_results_new(CMD_SUCCESS, NULL, NULL); } +static struct cmd_results *move_to_scratchpad(struct sway_container *con) { + if (con->type == C_WORKSPACE && con->children->length == 0) { + return cmd_results_new(CMD_INVALID, "move", + "Can't move an empty workspace to the scratchpad"); + } + if (con->type == C_WORKSPACE) { + // Wrap the workspace's children in a container + struct sway_container *workspace = con; + con = container_wrap_children(con); + workspace->layout = L_HORIZ; + } + + // If the container is in a floating split container, + // operate on the split container instead of the child. + if (container_is_floating_or_child(con)) { + while (con->parent->layout != L_FLOATING) { + con = con->parent; + } + } + + if (con->scratchpad) { + return cmd_results_new(CMD_INVALID, "move", + "Container is already in the scratchpad"); + } + scratchpad_add_container(con); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} + struct cmd_results *cmd_move(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { @@ -317,10 +345,9 @@ struct cmd_results *cmd_move(int argc, char **argv) { } else if (strcasecmp(argv[0], "workspace") == 0) { return cmd_move_workspace(current, argc, argv); } else if (strcasecmp(argv[0], "scratchpad") == 0 - || (strcasecmp(argv[0], "to") == 0 + || (strcasecmp(argv[0], "to") == 0 && argc == 2 && strcasecmp(argv[1], "scratchpad") == 0)) { - // TODO: scratchpad - return cmd_results_new(CMD_FAILURE, "move", "Unimplemented"); + return move_to_scratchpad(current); } else if (strcasecmp(argv[0], "position") == 0) { return move_to_position(current, argc, argv); } else if (strcasecmp(argv[0], "absolute") == 0) { diff --git a/sway/commands/output/dpms.c b/sway/commands/output/dpms.c index 0959ea6b..3492061e 100644 --- a/sway/commands/output/dpms.c +++ b/sway/commands/output/dpms.c @@ -1,5 +1,6 @@ #include "sway/commands.h" #include "sway/config.h" +#include "util.h" struct cmd_results *output_cmd_dpms(int argc, char **argv) { if (!config->handler_context.output_config) { @@ -9,13 +10,10 @@ struct cmd_results *output_cmd_dpms(int argc, char **argv) { return cmd_results_new(CMD_INVALID, "output", "Missing dpms argument."); } - if (strcmp(*argv, "on") == 0) { + if (parse_boolean(argv[0], true)) { config->handler_context.output_config->dpms_state = DPMS_ON; - } else if (strcmp(*argv, "off") == 0) { - config->handler_context.output_config->dpms_state = DPMS_OFF; } else { - return cmd_results_new(CMD_INVALID, "output", - "Invalid dpms state, valid states are on/off."); + config->handler_context.output_config->dpms_state = DPMS_OFF; } config->handler_context.leftovers.argc = argc - 1; diff --git a/sway/commands/reload.c b/sway/commands/reload.c index cea6a94b..5c1b19b4 100644 --- a/sway/commands/reload.c +++ b/sway/commands/reload.c @@ -1,17 +1,46 @@ +#define _XOPEN_SOURCE 500 +#include <string.h> #include "sway/commands.h" #include "sway/config.h" +#include "sway/ipc-server.h" #include "sway/tree/arrange.h" +#include "list.h" struct cmd_results *cmd_reload(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) { return error; } + + // store bar ids to check against new bars for barconfig_update events + list_t *bar_ids = create_list(); + for (int i = 0; i < config->bars->length; ++i) { + struct bar_config *bar = config->bars->items[i]; + list_add(bar_ids, strdup(bar->id)); + } + if (!load_main_config(config->current_config_path, true)) { return cmd_results_new(CMD_FAILURE, "reload", "Error(s) reloading config."); } + ipc_event_workspace(NULL, NULL, "reload"); load_swaybars(); + + for (int i = 0; i < config->bars->length; ++i) { + struct bar_config *bar = config->bars->items[i]; + for (int j = 0; j < bar_ids->length; ++j) { + if (strcmp(bar->id, bar_ids->items[j]) == 0) { + ipc_event_barconfig_update(bar); + break; + } + } + } + + for (int i = 0; i < bar_ids->length; ++i) { + free(bar_ids->items[i]); + } + list_free(bar_ids); + arrange_windows(&root_container); return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c new file mode 100644 index 00000000..01a91d65 --- /dev/null +++ b/sway/commands/scratchpad.c @@ -0,0 +1,44 @@ +#include "log.h" +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/scratchpad.h" +#include "sway/tree/container.h" + +struct cmd_results *cmd_scratchpad(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1))) { + return error; + } + if (strcmp(argv[0], "show") != 0) { + return cmd_results_new(CMD_INVALID, "scratchpad", + "Expected 'scratchpad show'"); + } + if (!root_container.sway_root->scratchpad->length) { + return cmd_results_new(CMD_INVALID, "scratchpad", + "Scratchpad is empty"); + } + + if (config->handler_context.using_criteria) { + struct sway_container *con = config->handler_context.current_container; + + // If the container is in a floating split container, + // operate on the split container instead of the child. + if (container_is_floating_or_child(con)) { + while (con->parent->layout != L_FLOATING) { + con = con->parent; + } + } + + // If using criteria, this command is executed for every container which + // matches the criteria. If this container isn't in the scratchpad, + // we'll just silently return a success. + if (!con->scratchpad) { + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } + scratchpad_toggle_container(con); + } else { + scratchpad_toggle_auto(); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c index c7fdc538..434a0e27 100644 --- a/sway/commands/show_marks.c +++ b/sway/commands/show_marks.c @@ -7,6 +7,7 @@ #include "list.h" #include "log.h" #include "stringop.h" +#include "util.h" static void rebuild_marks_iterator(struct sway_container *con, void *data) { if (con->type == C_VIEW) { @@ -20,14 +21,7 @@ struct cmd_results *cmd_show_marks(int argc, char **argv) { return error; } - if (strcmp(*argv, "yes") == 0) { - config->show_marks = true; - } else if (strcmp(*argv, "no") == 0) { - config->show_marks = false; - } else { - return cmd_results_new(CMD_INVALID, "show_marks", - "Expected 'show_marks <yes|no>'"); - } + config->show_marks = parse_boolean(argv[0], config->show_marks); if (config->show_marks) { container_for_each_descendant_dfs(&root_container, diff --git a/sway/commands/split.c b/sway/commands/split.c index 313799da..a8eddf54 100644 --- a/sway/commands/split.c +++ b/sway/commands/split.c @@ -10,10 +10,6 @@ static struct cmd_results *do_split(int layout) { struct sway_container *con = config->handler_context.current_container; - if (container_is_floating(con)) { - return cmd_results_new(CMD_FAILURE, "split", - "Can't split a floating view"); - } struct sway_container *parent = container_split(con, layout); container_create_notify(parent); arrange_windows(parent->parent); diff --git a/sway/commands/swap.c b/sway/commands/swap.c index 2fc88308..4e3a9cce 100644 --- a/sway/commands/swap.c +++ b/sway/commands/swap.c @@ -1,5 +1,6 @@ #include <strings.h> #include <wlr/util/log.h> +#include "config.h" #include "sway/commands.h" #include "sway/tree/arrange.h" #include "sway/tree/layout.h" @@ -14,10 +15,14 @@ static bool test_con_id(struct sway_container *container, void *con_id) { } static bool test_id(struct sway_container *container, void *id) { +#ifdef HAVE_XWAYLAND xcb_window_t *wid = id; return (container->type == C_VIEW && container->sway_view->type == SWAY_VIEW_XWAYLAND && container->sway_view->wlr_xwayland_surface->window_id == *wid); +#else + return false; +#endif } static bool test_mark(struct sway_container *container, void *mark) { @@ -43,8 +48,10 @@ struct cmd_results *cmd_swap(int argc, char **argv) { char *value = join_args(argv + 3, argc - 3); if (strcasecmp(argv[2], "id") == 0) { +#ifdef HAVE_XWAYLAND xcb_window_t id = strtol(value, NULL, 0); other = container_find(&root_container, test_id, (void *)&id); +#endif } else if (strcasecmp(argv[2], "con_id") == 0) { size_t con_id = atoi(value); other = container_find(&root_container, test_con_id, (void *)con_id); diff --git a/sway/commands/urgent.c b/sway/commands/urgent.c index d199858a..51c497c4 100644 --- a/sway/commands/urgent.c +++ b/sway/commands/urgent.c @@ -5,6 +5,7 @@ #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/layout.h" +#include "util.h" struct cmd_results *cmd_urgent(int argc, char **argv) { struct cmd_results *error = NULL; @@ -19,17 +20,12 @@ struct cmd_results *cmd_urgent(int argc, char **argv) { } struct sway_view *view = container->sway_view; - if (strcmp(argv[0], "enable") == 0) { - view_set_urgent(view, true); - } else if (strcmp(argv[0], "disable") == 0) { - view_set_urgent(view, false); - } else if (strcmp(argv[0], "allow") == 0) { + if (strcmp(argv[0], "allow") == 0) { view->allow_request_urgent = true; } else if (strcmp(argv[0], "deny") == 0) { view->allow_request_urgent = false; } else { - return cmd_results_new(CMD_INVALID, "urgent", - "Expected 'urgent <enable|disable|allow|deny>'"); + view_set_urgent(view, parse_boolean(argv[0], view_is_urgent(view))); } return cmd_results_new(CMD_SUCCESS, NULL, NULL); diff --git a/sway/config.c b/sway/config.c index ed624bfa..2afffab1 100644 --- a/sway/config.c +++ b/sway/config.c @@ -56,6 +56,12 @@ static void free_mode(struct sway_mode *mode) { } list_free(mode->keycode_bindings); } + if (mode->mouse_bindings) { + for (i = 0; i < mode->mouse_bindings->length; i++) { + free_sway_binding(mode->mouse_bindings->items[i]); + } + list_free(mode->mouse_bindings); + } free(mode); } @@ -87,7 +93,6 @@ void free_config(struct sway_config *config) { } list_free(config->cmd_queue); list_free(config->workspace_outputs); - list_free(config->pid_workspaces); if (config->output_configs) { for (int i = 0; i < config->output_configs->length; i++) { free_output_config(config->output_configs->items[i]); @@ -157,7 +162,6 @@ static void config_defaults(struct sway_config *config) { if (!(config->modes = create_list())) goto cleanup; if (!(config->bars = create_list())) goto cleanup; if (!(config->workspace_outputs = create_list())) goto cleanup; - if (!(config->pid_workspaces = create_list())) goto cleanup; if (!(config->criteria = create_list())) goto cleanup; if (!(config->no_focus = create_list())) goto cleanup; if (!(config->input_configs = create_list())) goto cleanup; @@ -172,9 +176,11 @@ static void config_defaults(struct sway_config *config) { strcpy(config->current_mode->name, "default"); if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup; if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup; + if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup; list_add(config->modes, config->current_mode); config->floating_mod = 0; + config->floating_mod_inverse = false; config->dragging_key = BTN_LEFT; config->resizing_key = BTN_RIGHT; diff --git a/sway/config/bar.c b/sway/config/bar.c index 3a74331e..ae9383d6 100644 --- a/sway/config/bar.c +++ b/sway/config/bar.c @@ -10,6 +10,7 @@ #include <sys/stat.h> #include <signal.h> #include <strings.h> +#include <signal.h> #include "sway/config.h" #include "stringop.h" #include "list.h" @@ -175,6 +176,9 @@ void invoke_swaybar(struct bar_config *bar) { if (bar->pid == 0) { setpgid(0, 0); close(filedes[0]); + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); // run custom swaybar size_t len = snprintf(NULL, 0, "%s -b %s", diff --git a/sway/config/input.c b/sway/config/input.c index 8d687a6d..9885e85c 100644 --- a/sway/config/input.c +++ b/sway/config/input.c @@ -33,6 +33,8 @@ struct input_config *new_input_config(const char* identifier) { input->left_handed = INT_MIN; input->repeat_delay = INT_MIN; input->repeat_rate = INT_MIN; + input->xkb_numlock = INT_MIN; + input->xkb_capslock = INT_MIN; return input; } @@ -104,6 +106,12 @@ void merge_input_config(struct input_config *dst, struct input_config *src) { free(dst->xkb_variant); dst->xkb_variant = strdup(src->xkb_variant); } + if (src->xkb_numlock != INT_MIN) { + dst->xkb_numlock = src->xkb_numlock; + } + if (src->xkb_capslock != INT_MIN) { + dst->xkb_capslock = src->xkb_capslock; + } if (src->mapped_from_region) { free(dst->mapped_from_region); dst->mapped_from_region = diff --git a/sway/criteria.c b/sway/criteria.c index e2b248de..39d300ea 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -10,6 +10,7 @@ #include "stringop.h" #include "list.h" #include "log.h" +#include "config.h" bool criteria_is_empty(struct criteria *criteria) { return !criteria->title @@ -19,7 +20,9 @@ bool criteria_is_empty(struct criteria *criteria) { && !criteria->instance && !criteria->con_mark && !criteria->con_id +#ifdef HAVE_XWAYLAND && !criteria->id +#endif && !criteria->window_role && !criteria->window_type && !criteria->floating @@ -127,12 +130,14 @@ static bool criteria_matches_view(struct criteria *criteria, } } +#ifdef HAVE_XWAYLAND if (criteria->id) { // X11 window ID uint32_t x11_window_id = view_get_x11_window_id(view); if (!x11_window_id || x11_window_id != criteria->id) { return false; } } +#endif if (criteria->window_role) { // TODO @@ -225,6 +230,15 @@ list_t *criteria_get_views(struct criteria *criteria) { }; container_for_each_descendant_dfs(&root_container, criteria_get_views_iterator, &data); + + // Scratchpad items which are hidden are not in the tree. + for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) { + struct sway_container *con = + root_container.sway_root->scratchpad->items[i]; + if (!con->parent) { + criteria_get_views_iterator(con, &data); + } + } return matches; } @@ -256,7 +270,9 @@ enum criteria_token { T_CON_ID, T_CON_MARK, T_FLOATING, +#ifdef HAVE_XWAYLAND T_ID, +#endif T_INSTANCE, T_SHELL, T_TILING, @@ -278,8 +294,10 @@ static enum criteria_token token_from_name(char *name) { return T_CON_ID; } else if (strcmp(name, "con_mark") == 0) { return T_CON_MARK; +#ifdef HAVE_XWAYLAND } else if (strcmp(name, "id") == 0) { return T_ID; +#endif } else if (strcmp(name, "instance") == 0) { return T_INSTANCE; } else if (strcmp(name, "shell") == 0) { @@ -346,7 +364,9 @@ static char *get_focused_prop(enum criteria_token token) { case T_CON_ID: // These do not support __focused__ case T_CON_MARK: case T_FLOATING: +#ifdef HAVE_XWAYLAND case T_ID: +#endif case T_TILING: case T_URGENT: case T_WINDOW_TYPE: @@ -417,12 +437,14 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { case T_WINDOW_TYPE: // TODO: This is a string but will be stored as an enum or integer break; +#ifdef HAVE_XWAYLAND case T_ID: criteria->id = strtoul(effective_value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'id' should be numeric"); } break; +#endif case T_FLOATING: criteria->floating = true; break; diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c index a7d96717..a2935883 100644 --- a/sway/desktop/layer_shell.c +++ b/sway/desktop/layer_shell.c @@ -7,11 +7,13 @@ #include <wlr/types/wlr_output_damage.h> #include <wlr/types/wlr_output.h> #include <wlr/util/log.h> +#include "sway/desktop/transaction.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" +#include "sway/tree/arrange.h" #include "sway/tree/layout.h" #include "log.h" @@ -245,6 +247,9 @@ static void handle_surface_commit(struct wl_listener *listener, void *data) { output_damage_surface(output, layer->geo.x, layer->geo.y, layer_surface->surface, false); } + + arrange_windows(output->swayc); + transaction_commit_dirty(); } static void unmap(struct sway_layer_surface *sway_layer) { @@ -282,6 +287,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) { struct sway_output *output = sway_layer->layer_surface->output->data; if (output != NULL && output->swayc != NULL) { arrange_layers(output); + arrange_windows(output->swayc); + transaction_commit_dirty(); } wl_list_remove(&sway_layer->output_destroy.link); sway_layer->layer_surface->output = NULL; diff --git a/sway/desktop/output.c b/sway/desktop/output.c index a9808406..66747a3f 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -14,6 +14,7 @@ #include <wlr/types/wlr_surface.h> #include <wlr/util/region.h> #include "log.h" +#include "config.h" #include "sway/config.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" @@ -56,9 +57,21 @@ static void rotate_child_position(double *sx, double *sy, double sw, double sh, *sy = ry + ph/2 - sh/2; } -bool output_get_surface_box(struct root_geometry *geo, - struct sway_output *output, struct wlr_surface *surface, int sx, int sy, +struct surface_iterator_data { + sway_surface_iterator_func_t user_iterator; + void *user_data; + + struct sway_output *output; + double ox, oy; + int width, height; + float rotation; +}; + +static bool get_surface_box(struct surface_iterator_data *data, + struct wlr_surface *surface, int sx, int sy, struct wlr_box *surface_box) { + struct sway_output *output = data->output; + if (!wlr_surface_has_buffer(surface)) { return false; } @@ -67,12 +80,12 @@ bool output_get_surface_box(struct root_geometry *geo, int sh = surface->current.height; double _sx = sx, _sy = sy; - rotate_child_position(&_sx, &_sy, sw, sh, geo->width, geo->height, - geo->rotation); + rotate_child_position(&_sx, &_sy, sw, sh, data->width, data->height, + data->rotation); struct wlr_box box = { - .x = geo->x + _sx, - .y = geo->y + _sy, + .x = data->ox + _sx, + .y = data->oy + _sy, .width = sw, .height = sh, }; @@ -81,7 +94,7 @@ bool output_get_surface_box(struct root_geometry *geo, } struct wlr_box rotated_box; - wlr_box_rotated_bounds(&box, geo->rotation, &rotated_box); + wlr_box_rotated_bounds(&box, data->rotation, &rotated_box); struct wlr_box output_box = { .width = output->swayc->current.swayc_width, @@ -92,46 +105,90 @@ bool output_get_surface_box(struct root_geometry *geo, return wlr_box_intersection(&output_box, &rotated_box, &intersection); } -void output_surface_for_each_surface(struct wlr_surface *surface, - double ox, double oy, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data) { - geo->x = ox; - geo->y = oy; - geo->width = surface->current.width; - geo->height = surface->current.height; - geo->rotation = 0; +static void output_for_each_surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *_data) { + struct surface_iterator_data *data = _data; - wlr_surface_for_each_surface(surface, iterator, user_data); + struct wlr_box box; + bool intersects = get_surface_box(data, surface, sx, sy, &box); + if (!intersects) { + return; + } + + data->user_iterator(data->output, surface, &box, data->rotation, + data->user_data); +} + +void output_surface_for_each_surface(struct sway_output *output, + struct wlr_surface *surface, double ox, double oy, + sway_surface_iterator_func_t iterator, void *user_data) { + struct surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .output = output, + .ox = ox, + .oy = oy, + .width = surface->current.width, + .height = surface->current.height, + .rotation = 0, + }; + + wlr_surface_for_each_surface(surface, + output_for_each_surface_iterator, &data); } -void output_view_for_each_surface(struct sway_view *view, - struct sway_output *output, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data) { - geo->x = view->swayc->current.view_x - output->swayc->current.swayc_x; - geo->y = view->swayc->current.view_y - output->swayc->current.swayc_y; - geo->width = view->swayc->current.view_width; - geo->height = view->swayc->current.view_height; - geo->rotation = 0; // TODO +void output_view_for_each_surface(struct sway_output *output, + struct sway_view *view, sway_surface_iterator_func_t iterator, + void *user_data) { + struct surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .output = output, + .ox = view->swayc->current.view_x - output->swayc->current.swayc_x, + .oy = view->swayc->current.view_y - output->swayc->current.swayc_y, + .width = view->swayc->current.view_width, + .height = view->swayc->current.view_height, + .rotation = 0, // TODO + }; - view_for_each_surface(view, iterator, user_data); + view_for_each_surface(view, + output_for_each_surface_iterator, &data); } -void output_layer_for_each_surface(struct wl_list *layer_surfaces, - struct root_geometry *geo, wlr_surface_iterator_func_t iterator, +void output_view_for_each_popup(struct sway_output *output, + struct sway_view *view, sway_surface_iterator_func_t iterator, + void *user_data) { + struct surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .output = output, + .ox = view->swayc->current.view_x - output->swayc->current.swayc_x, + .oy = view->swayc->current.view_y - output->swayc->current.swayc_y, + .width = view->swayc->current.view_width, + .height = view->swayc->current.view_height, + .rotation = 0, // TODO + }; + + view_for_each_popup(view, output_for_each_surface_iterator, &data); +} + +void output_layer_for_each_surface(struct sway_output *output, + struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, void *user_data) { struct sway_layer_surface *layer_surface; wl_list_for_each(layer_surface, layer_surfaces, link) { struct wlr_layer_surface *wlr_layer_surface = layer_surface->layer_surface; - output_surface_for_each_surface(wlr_layer_surface->surface, - layer_surface->geo.x, layer_surface->geo.y, geo, iterator, + output_surface_for_each_surface(output, wlr_layer_surface->surface, + layer_surface->geo.x, layer_surface->geo.y, iterator, user_data); } } -void output_unmanaged_for_each_surface(struct wl_list *unmanaged, - struct sway_output *output, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data) { +#ifdef HAVE_XWAYLAND +void output_unmanaged_for_each_surface(struct sway_output *output, + struct wl_list *unmanaged, sway_surface_iterator_func_t iterator, + void *user_data) { struct sway_xwayland_unmanaged *unmanaged_surface; wl_list_for_each(unmanaged_surface, unmanaged, link) { struct wlr_xwayland_surface *xsurface = @@ -139,22 +196,24 @@ void output_unmanaged_for_each_surface(struct wl_list *unmanaged, double ox = unmanaged_surface->lx - output->swayc->current.swayc_x; double oy = unmanaged_surface->ly - output->swayc->current.swayc_y; - output_surface_for_each_surface(xsurface->surface, ox, oy, geo, + output_surface_for_each_surface(output, xsurface->surface, ox, oy, iterator, user_data); } } +#endif -void output_drag_icons_for_each_surface(struct wl_list *drag_icons, - struct sway_output *output, struct root_geometry *geo, - wlr_surface_iterator_func_t iterator, void *user_data) { +void output_drag_icons_for_each_surface(struct sway_output *output, + struct wl_list *drag_icons, sway_surface_iterator_func_t iterator, + void *user_data) { struct sway_drag_icon *drag_icon; wl_list_for_each(drag_icon, drag_icons, link) { double ox = drag_icon->x - output->swayc->x; double oy = drag_icon->y - output->swayc->y; if (drag_icon->wlr_drag_icon->mapped) { - output_surface_for_each_surface(drag_icon->wlr_drag_icon->surface, - ox, oy, geo, iterator, user_data); + output_surface_for_each_surface(output, + drag_icon->wlr_drag_icon->surface, ox, oy, + iterator, user_data); } } } @@ -181,21 +240,14 @@ struct sway_container *output_get_active_workspace(struct sway_output *output) { return workspace; } -bool output_has_opaque_lockscreen(struct sway_output *output, - struct sway_seat *seat) { - if (!seat->exclusive_client) { - return false; - } - +bool output_has_opaque_overlay_layer_surface(struct sway_output *output) { struct wlr_layer_surface *wlr_layer_surface; wl_list_for_each(wlr_layer_surface, &server.layer_shell->surfaces, link) { - if (wlr_layer_surface->output != output->wlr_output) { + if (wlr_layer_surface->output != output->wlr_output || + wlr_layer_surface->layer != ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { continue; } struct wlr_surface *wlr_surface = wlr_layer_surface->surface; - if (wlr_surface->resource->client != seat->exclusive_client) { - continue; - } struct sway_layer_surface *sway_layer_surface = layer_from_wlr_layer_surface(wlr_layer_surface); pixman_box32_t output_box = { @@ -217,46 +269,38 @@ bool output_has_opaque_lockscreen(struct sway_output *output, return false; } -struct send_frame_done_data { - struct root_geometry root_geo; - struct sway_output *output; - struct timespec *when; - struct wl_client *exclusive_client; -}; - -static void send_frame_done_iterator(struct wlr_surface *surface, - int sx, int sy, void *_data) { - struct send_frame_done_data *data = _data; - if (data->exclusive_client && - data->exclusive_client != surface->resource->client) { - return; - } - - bool intersects = output_get_surface_box(&data->root_geo, data->output, surface, - sx, sy, NULL); - if (intersects) { - wlr_surface_send_frame_done(surface, data->when); - } +static void send_frame_done_iterator(struct sway_output *output, + struct wlr_surface *surface, struct wlr_box *box, float rotation, + void *_data) { + struct timespec *when = _data; + wlr_surface_send_frame_done(surface, when); } -static void send_frame_done_layer(struct send_frame_done_data *data, - struct wl_list *layer_surfaces) { - output_layer_for_each_surface(layer_surfaces, &data->root_geo, - send_frame_done_iterator, data); +static void send_frame_done_layer(struct sway_output *output, + struct wl_list *layer_surfaces, struct timespec *when) { + output_layer_for_each_surface(output, layer_surfaces, + send_frame_done_iterator, when); } -static void send_frame_done_unmanaged(struct send_frame_done_data *data, - struct wl_list *unmanaged) { - output_unmanaged_for_each_surface(unmanaged, data->output, &data->root_geo, - send_frame_done_iterator, data); +#ifdef HAVE_XWAYLAND +static void send_frame_done_unmanaged(struct sway_output *output, + struct wl_list *unmanaged, struct timespec *when) { + output_unmanaged_for_each_surface(output, unmanaged, + send_frame_done_iterator, when); } +#endif -static void send_frame_done_drag_icons(struct send_frame_done_data *data, - struct wl_list *drag_icons) { - output_drag_icons_for_each_surface(drag_icons, data->output, &data->root_geo, - send_frame_done_iterator, data); +static void send_frame_done_drag_icons(struct sway_output *output, + struct wl_list *drag_icons, struct timespec *when) { + output_drag_icons_for_each_surface(output, drag_icons, + send_frame_done_iterator, when); } +struct send_frame_done_data { + struct sway_output *output; + struct timespec *when; +}; + static void send_frame_done_container_iterator(struct sway_container *con, void *_data) { struct send_frame_done_data *data = _data; @@ -268,52 +312,62 @@ static void send_frame_done_container_iterator(struct sway_container *con, return; } - output_view_for_each_surface(con->sway_view, data->output, &data->root_geo, - send_frame_done_iterator, data); -} - -static void send_frame_done_container(struct send_frame_done_data *data, - struct sway_container *con) { - container_descendants(con, C_VIEW, - send_frame_done_container_iterator, data); + output_view_for_each_surface(data->output, con->sway_view, + send_frame_done_iterator, data->when); } -static void send_frame_done(struct sway_output *output, struct timespec *when) { - struct sway_seat *seat = input_manager_current_seat(input_manager); +static void send_frame_done_container(struct sway_output *output, + struct sway_container *con, struct timespec *when) { struct send_frame_done_data data = { .output = output, .when = when, - .exclusive_client = output_has_opaque_lockscreen(output, seat) ? - seat->exclusive_client : NULL, }; + container_descendants(con, C_VIEW, + send_frame_done_container_iterator, &data); +} + +static void send_frame_done(struct sway_output *output, struct timespec *when) { + if (output_has_opaque_overlay_layer_surface(output)) { + goto send_frame_overlay; + } struct sway_container *workspace = output_get_active_workspace(output); if (workspace->current.ws_fullscreen) { - send_frame_done_container_iterator( - workspace->current.ws_fullscreen->swayc, &data); - - if (workspace->current.ws_fullscreen->type == SWAY_VIEW_XWAYLAND) { - send_frame_done_unmanaged(&data, - &root_container.sway_root->xwayland_unmanaged); + if (workspace->current.ws_fullscreen->type == C_VIEW) { + output_view_for_each_surface(output, + workspace->current.ws_fullscreen->sway_view, + send_frame_done_iterator, when); + } else { + send_frame_done_container(output, workspace->current.ws_fullscreen, + when); } +#ifdef HAVE_XWAYLAND + send_frame_done_unmanaged(output, + &root_container.sway_root->xwayland_unmanaged, when); +#endif } else { - send_frame_done_layer(&data, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); - send_frame_done_layer(&data, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); - - send_frame_done_container(&data, workspace); - send_frame_done_container(&data, workspace->sway_workspace->floating); - - send_frame_done_unmanaged(&data, - &root_container.sway_root->xwayland_unmanaged); - send_frame_done_layer(&data, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); + send_frame_done_layer(output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], when); + send_frame_done_layer(output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], when); + + send_frame_done_container(output, workspace, when); + send_frame_done_container(output, workspace->sway_workspace->floating, + when); + +#ifdef HAVE_XWAYLAND + send_frame_done_unmanaged(output, + &root_container.sway_root->xwayland_unmanaged, when); +#endif + send_frame_done_layer(output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], when); } - send_frame_done_layer(&data, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); - send_frame_done_drag_icons(&data, &root_container.sway_root->drag_icons); +send_frame_overlay: + send_frame_done_layer(output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], when); + send_frame_done_drag_icons(output, &root_container.sway_root->drag_icons, + when); } static void damage_handle_frame(struct wl_listener *listener, void *data) { @@ -348,26 +402,13 @@ void output_damage_whole(struct sway_output *output) { wlr_output_damage_add_whole(output->damage); } -struct damage_data { - struct root_geometry root_geo; - struct sway_output *output; - bool whole; -}; - -static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy, +static void damage_surface_iterator(struct sway_output *output, + struct wlr_surface *surface, struct wlr_box *_box, float rotation, void *_data) { - struct damage_data *data = _data; - struct sway_output *output = data->output; - float rotation = data->root_geo.rotation; - bool whole = data->whole; - - struct wlr_box box; - bool intersects = output_get_surface_box(&data->root_geo, data->output, surface, - sx, sy, &box); - if (!intersects) { - return; - } + bool *data = _data; + bool whole = *data; + struct wlr_box box = *_box; scale_box(&box, output->wlr_output->scale); int center_x = box.x + box.width/2; @@ -407,13 +448,8 @@ static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy, void output_damage_surface(struct sway_output *output, double ox, double oy, struct wlr_surface *surface, bool whole) { - struct damage_data data = { - .output = output, - .whole = whole, - }; - - output_surface_for_each_surface(surface, ox, oy, &data.root_geo, - damage_surface_iterator, &data); + output_surface_for_each_surface(output, surface, ox, oy, + damage_surface_iterator, &whole); } static void output_damage_view(struct sway_output *output, @@ -426,13 +462,7 @@ static void output_damage_view(struct sway_output *output, return; } - struct damage_data data = { - .output = output, - .whole = whole, - }; - - output_view_for_each_surface(view, output, &data.root_geo, - damage_surface_iterator, &data); + output_view_for_each_surface(output, view, damage_surface_iterator, &whole); } void output_damage_from_view(struct sway_output *output, @@ -463,11 +493,12 @@ static void output_damage_whole_container_iterator(struct sway_container *con, void output_damage_whole_container(struct sway_output *output, struct sway_container *con) { + // Pad the box by 1px, because the width is a double and might be a fraction struct wlr_box box = { - .x = con->current.swayc_x - output->wlr_output->lx, - .y = con->current.swayc_y - output->wlr_output->ly, - .width = con->current.swayc_width, - .height = con->current.swayc_height, + .x = con->current.swayc_x - output->wlr_output->lx - 1, + .y = con->current.swayc_y - output->wlr_output->ly - 1, + .width = con->current.swayc_width + 2, + .height = con->current.swayc_height + 2, }; scale_box(&box, output->wlr_output->scale); wlr_output_damage_add_box(output->damage, &box); @@ -509,22 +540,14 @@ static void handle_transform(struct wl_listener *listener, void *data) { transaction_commit_dirty(); } -static void handle_scale_iterator(struct sway_container *view, void *data) { - view_update_marks_textures(view->sway_view); -} - static void handle_scale(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, scale); arrange_layers(output); - container_descendants(output->swayc, C_VIEW, handle_scale_iterator, NULL); + container_update_textures_recursive(output->swayc); arrange_windows(output->swayc); transaction_commit_dirty(); } -struct sway_output *output_from_wlr_output(struct wlr_output *wlr_output) { - return wlr_output->data; -} - void handle_new_output(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 4c85e516..cdac9c72 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -14,6 +14,7 @@ #include <wlr/types/wlr_surface.h> #include <wlr/util/region.h> #include "log.h" +#include "config.h" #include "sway/config.h" #include "sway/debug.h" #include "sway/input/input-manager.h" @@ -28,10 +29,7 @@ #include "sway/tree/workspace.h" struct render_data { - struct root_geometry root_geo; - struct sway_output *output; pixman_region32_t *damage; - struct sway_view *view; float alpha; }; @@ -91,11 +89,11 @@ damage_finish: pixman_region32_fini(&damage); } -static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy, +static void render_surface_iterator(struct sway_output *output, + struct wlr_surface *surface, struct wlr_box *_box, float rotation, void *_data) { struct render_data *data = _data; - struct wlr_output *wlr_output = data->output->wlr_output; - float rotation = data->root_geo.rotation; + struct wlr_output *wlr_output = output->wlr_output; pixman_region32_t *output_damage = data->damage; float alpha = data->alpha; @@ -104,13 +102,7 @@ static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy, return; } - struct wlr_box box; - bool intersects = output_get_surface_box(&data->root_geo, data->output, - surface, sx, sy, &box); - if (!intersects) { - return; - } - + struct wlr_box box = *_box; scale_box(&box, wlr_output->scale); float matrix[9]; @@ -125,33 +117,32 @@ static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy, static void render_layer(struct sway_output *output, pixman_region32_t *damage, struct wl_list *layer_surfaces) { struct render_data data = { - .output = output, .damage = damage, .alpha = 1.0f, }; - output_layer_for_each_surface(layer_surfaces, &data.root_geo, + output_layer_for_each_surface(output, layer_surfaces, render_surface_iterator, &data); } +#ifdef HAVE_XWAYLAND static void render_unmanaged(struct sway_output *output, pixman_region32_t *damage, struct wl_list *unmanaged) { struct render_data data = { - .output = output, .damage = damage, .alpha = 1.0f, }; - output_unmanaged_for_each_surface(unmanaged, output, &data.root_geo, + output_unmanaged_for_each_surface(output, unmanaged, render_surface_iterator, &data); } +#endif static void render_drag_icons(struct sway_output *output, pixman_region32_t *damage, struct wl_list *drag_icons) { struct render_data data = { - .output = output, .damage = damage, .alpha = 1.0f, }; - output_drag_icons_for_each_surface(drag_icons, output, &data.root_geo, + output_drag_icons_for_each_surface(output, drag_icons, render_surface_iterator, &data); } @@ -195,33 +186,51 @@ static void premultiply_alpha(float color[4], float opacity) { color[2] *= color[3]; } -static void render_view_surfaces(struct sway_view *view, +static void render_view_toplevels(struct sway_view *view, struct sway_output *output, pixman_region32_t *damage, float alpha) { struct render_data data = { - .output = output, .damage = damage, - .view = view, .alpha = alpha, }; - output_view_for_each_surface(view, output, &data.root_geo, - render_surface_iterator, &data); + // Render all toplevels without descending into popups + output_surface_for_each_surface(output, view->surface, + view->swayc->current.view_x - output->wlr_output->lx, + view->swayc->current.view_y - output->wlr_output->ly, + render_surface_iterator, &data); +} + +static void render_popup_iterator(struct sway_output *output, + struct wlr_surface *surface, struct wlr_box *box, float rotation, + void *data) { + // Render this popup's surface + render_surface_iterator(output, surface, box, rotation, data); + + // Render this popup's child toplevels + output_surface_for_each_surface(output, surface, box->x, box->y, + render_surface_iterator, data); +} + +static void render_view_popups(struct sway_view *view, + struct sway_output *output, pixman_region32_t *damage, float alpha) { + struct render_data data = { + .damage = damage, + .alpha = alpha, + }; + output_view_for_each_popup(output, view, render_popup_iterator, &data); } static void render_saved_view(struct sway_view *view, struct sway_output *output, pixman_region32_t *damage, float alpha) { struct wlr_output *wlr_output = output->wlr_output; - int width, height; - struct wlr_texture *texture = - transaction_get_saved_texture(view, &width, &height); - if (!texture) { + if (!view->saved_buffer || !view->saved_buffer->texture) { return; } struct wlr_box box = { .x = view->swayc->current.view_x - output->swayc->current.swayc_x, .y = view->swayc->current.view_y - output->swayc->current.swayc_y, - .width = width, - .height = height, + .width = view->saved_buffer_width, + .height = view->saved_buffer_height, }; struct wlr_box output_box = { @@ -241,7 +250,8 @@ static void render_saved_view(struct sway_view *view, wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, wlr_output->transform_matrix); - render_texture(wlr_output, damage, texture, &box, matrix, alpha); + render_texture(wlr_output, damage, view->saved_buffer->texture, + &box, matrix, alpha); } /** @@ -250,10 +260,10 @@ static void render_saved_view(struct sway_view *view, static void render_view(struct sway_output *output, pixman_region32_t *damage, struct sway_container *con, struct border_colors *colors) { struct sway_view *view = con->sway_view; - if (view->swayc->instructions->length) { + if (view->saved_buffer) { render_saved_view(view, output, damage, view->swayc->alpha); } else { - render_view_surfaces(view, output, damage, view->swayc->alpha); + render_view_toplevels(view, output, damage, view->swayc->alpha); } if (view->using_csd) { @@ -778,7 +788,7 @@ static void render_floating_container(struct sway_output *soutput, } render_view(soutput, damage, con, colors); } else { - render_container(soutput, damage, con, false); + render_container(soutput, damage, con, con->current.focused); } } @@ -835,22 +845,13 @@ void output_render(struct sway_output *output, struct timespec *when, } struct sway_container *workspace = output_get_active_workspace(output); - struct sway_view *fullscreen_view = workspace->current.ws_fullscreen; - struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *fullscreen_con = workspace->current.ws_fullscreen; + + if (output_has_opaque_overlay_layer_surface(output)) { + goto render_overlay; + } - if (output_has_opaque_lockscreen(output, seat)) { - struct wlr_layer_surface *wlr_layer_surface = seat->focused_layer; - struct sway_layer_surface *sway_layer_surface = - layer_from_wlr_layer_surface(seat->focused_layer); - struct render_data data = { - .output = output, - .damage = damage, - .alpha = 1.0f, - }; - output_surface_for_each_surface(wlr_layer_surface->surface, - sway_layer_surface->geo.x, sway_layer_surface->geo.y, - &data.root_geo, render_surface_iterator, &data); - } else if (fullscreen_view) { + if (fullscreen_con) { float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; int nrects; @@ -861,16 +862,22 @@ void output_render(struct sway_output *output, struct timespec *when, } // TODO: handle views smaller than the output - if (fullscreen_view->swayc->instructions->length) { - render_saved_view(fullscreen_view, output, damage, 1.0f); + if (fullscreen_con->type == C_VIEW) { + if (fullscreen_con->sway_view->saved_buffer) { + render_saved_view(fullscreen_con->sway_view, + output, damage, 1.0f); + } else { + render_view_toplevels(fullscreen_con->sway_view, + output, damage, 1.0f); + } } else { - render_view_surfaces(fullscreen_view, output, damage, 1.0f); - } - - if (fullscreen_view->type == SWAY_VIEW_XWAYLAND) { - render_unmanaged(output, damage, - &root_container.sway_root->xwayland_unmanaged); + render_container(output, damage, fullscreen_con, + fullscreen_con->current.focused); } +#ifdef HAVE_XWAYLAND + render_unmanaged(output, damage, + &root_container.sway_root->xwayland_unmanaged); +#endif } else { float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f}; @@ -888,12 +895,21 @@ void output_render(struct sway_output *output, struct timespec *when, render_container(output, damage, workspace, workspace->current.focused); render_floating(output, damage); - +#ifdef HAVE_XWAYLAND render_unmanaged(output, damage, &root_container.sway_root->xwayland_unmanaged); +#endif render_layer(output, damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); } + + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *focus = seat_get_focus(seat); + if (focus && focus->type == C_VIEW) { + render_view_popups(focus->sway_view, output, damage, focus->alpha); + } + +render_overlay: render_layer(output, damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); render_drag_icons(output, damage, &root_container.sway_root->drag_icons); diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 19f41efc..4e6af86a 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 200809L +#include <errno.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> @@ -40,8 +41,6 @@ struct sway_transaction_instruction { struct sway_transaction *transaction; struct sway_container *container; struct sway_container_state state; - struct wlr_buffer *saved_buffer; - int saved_buffer_width, saved_buffer_height; uint32_t serial; bool ready; }; @@ -56,27 +55,6 @@ static struct sway_transaction *transaction_create() { return transaction; } -static void remove_saved_view_buffer( - struct sway_transaction_instruction *instruction) { - if (instruction->saved_buffer) { - wlr_buffer_unref(instruction->saved_buffer); - instruction->saved_buffer = NULL; - } -} - -static void save_view_buffer(struct sway_view *view, - struct sway_transaction_instruction *instruction) { - if (!sway_assert(instruction->saved_buffer == NULL, - "Didn't expect instruction to have a saved buffer already")) { - remove_saved_view_buffer(instruction); - } - if (view->surface && wlr_surface_has_buffer(view->surface)) { - instruction->saved_buffer = wlr_buffer_ref(view->surface->buffer); - instruction->saved_buffer_width = view->surface->current.width; - instruction->saved_buffer_height = view->surface->current.height; - } -} - static void transaction_destroy(struct sway_transaction *transaction) { // Free instructions for (int i = 0; i < transaction->instructions->length; ++i) { @@ -92,7 +70,6 @@ static void transaction_destroy(struct sway_transaction *transaction) { if (con->destroying && !con->instructions->length) { container_free(con); } - remove_saved_view_buffer(instruction); free(instruction); } list_free(transaction->instructions); @@ -110,6 +87,7 @@ static void copy_pending_state(struct sway_container *container, state->swayc_y = container->y; state->swayc_width = container->width; state->swayc_height = container->height; + state->is_fullscreen = container->is_fullscreen; state->has_gaps = container->has_gaps; state->current_gaps = container->current_gaps; state->gaps_inner = container->gaps_inner; @@ -122,7 +100,6 @@ static void copy_pending_state(struct sway_container *container, state->view_y = view->y; state->view_width = view->width; state->view_height = view->height; - state->is_fullscreen = view->is_fullscreen; state->border = view->border; state->border_thickness = view->border_thickness; state->border_top = view->border_top; @@ -157,9 +134,6 @@ static void transaction_add_container(struct sway_transaction *transaction, copy_pending_state(container, &instruction->state); - if (container->type == C_VIEW) { - save_view_buffer(container->sway_view, instruction); - } list_add(transaction->instructions, instruction); } @@ -219,27 +193,35 @@ static void transaction_apply(struct sway_transaction *transaction) { memcpy(&container->current, &instruction->state, sizeof(struct sway_container_state)); + + if (container->type == C_VIEW) { + if (container->destroying) { + if (container->instructions->length == 1 && + container->sway_view->saved_buffer) { + view_remove_saved_buffer(container->sway_view); + } + } else { + if (container->sway_view->saved_buffer) { + view_remove_saved_buffer(container->sway_view); + } + if (container->instructions->length > 1) { + view_save_buffer(container->sway_view); + } + } + } } } -/** - * For simplicity, we only progress the queue if it can be completely flushed. - */ static void transaction_progress_queue() { - // We iterate this list in reverse because we're more likely to find a - // waiting transactions at the end of the list. - for (int i = server.transactions->length - 1; i >= 0; --i) { - struct sway_transaction *transaction = server.transactions->items[i]; + while (server.transactions->length) { + struct sway_transaction *transaction = server.transactions->items[0]; if (transaction->num_waiting) { return; } - } - for (int i = 0; i < server.transactions->length; ++i) { - struct sway_transaction *transaction = server.transactions->items[i]; transaction_apply(transaction); transaction_destroy(transaction); + list_del(server.transactions, 0); } - server.transactions->length = 0; idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1); } @@ -302,6 +284,9 @@ static void transaction_commit(struct sway_transaction *transaction) { struct timespec when; wlr_surface_send_frame_done(con->sway_view->surface, &when); } + if (con->type == C_VIEW && !con->sway_view->saved_buffer) { + view_save_buffer(con->sway_view); + } list_add(con->instructions, instruction); } transaction->num_configures = transaction->num_waiting; @@ -324,7 +309,14 @@ static void transaction_commit(struct sway_transaction *transaction) { // Set up a timer which the views must respond within transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, handle_timeout, transaction); - wl_event_source_timer_update(transaction->timer, txn_timeout_ms); + if (transaction->timer) { + wl_event_source_timer_update(transaction->timer, txn_timeout_ms); + } else { + wlr_log(WLR_ERROR, "Unable to create transaction timer (%s). " + "Some imperfect frames might be rendered.", + strerror(errno)); + handle_timeout(transaction); + } } // The debug tree shows the pending/live tree. Here is a good place to @@ -352,13 +344,11 @@ static void set_instruction_ready( } - // If all views are ready, apply the transaction. // If the transaction has timed out then its num_waiting will be 0 already. if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { if (!txn_debug) { wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction); wl_event_source_timer_update(transaction->timer, 0); - transaction_progress_queue(); } } } @@ -375,6 +365,7 @@ static void set_instructions_ready(struct sway_view *view, int index) { set_instruction_ready(instruction); } } + transaction_progress_queue(); } void transaction_notify_view_ready(struct sway_view *view, uint32_t serial) { @@ -401,18 +392,6 @@ void transaction_notify_view_ready_by_size(struct sway_view *view, } } -struct wlr_texture *transaction_get_saved_texture(struct sway_view *view, - int *width, int *height) { - struct sway_transaction_instruction *instruction = - view->swayc->instructions->items[0]; - if (!instruction->saved_buffer) { - return NULL; - } - *width = instruction->saved_buffer_width; - *height = instruction->saved_buffer_height; - return instruction->saved_buffer->texture; -} - void transaction_commit_dirty(void) { if (!server.dirty_containers->length) { return; diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 98c16faf..b364663d 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 199309L +#include <float.h> #include <stdbool.h> #include <stdlib.h> #include <wayland-server.h> @@ -95,6 +96,16 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view( return (struct sway_xdg_shell_view *)view; } +static void get_constraints(struct sway_view *view, double *min_width, + double *max_width, double *min_height, double *max_height) { + struct wlr_xdg_toplevel_state *state = + &view->wlr_xdg_surface->toplevel->current; + *min_width = state->min_width > 0 ? state->min_width : DBL_MIN; + *max_width = state->max_width > 0 ? state->max_width : DBL_MAX; + *min_height = state->min_height > 0 ? state->min_height : DBL_MIN; + *max_height = state->max_height > 0 ? state->max_height : DBL_MAX; +} + static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { if (xdg_shell_view_from_view(view) == NULL) { return NULL; @@ -168,6 +179,14 @@ static void for_each_surface(struct sway_view *view, user_data); } +static void for_each_popup(struct sway_view *view, + wlr_surface_iterator_func_t iterator, void *user_data) { + if (xdg_shell_view_from_view(view) == NULL) { + return; + } + wlr_xdg_surface_for_each_popup(view->wlr_xdg_surface, iterator, user_data); +} + static void _close(struct sway_view *view) { if (xdg_shell_view_from_view(view) == NULL) { return; @@ -178,6 +197,18 @@ static void _close(struct sway_view *view) { } } +static void close_popups_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_wlr_surface(surface); + wlr_xdg_surface_send_close(xdg_surface); +} + +static void close_popups(struct sway_view *view) { + wlr_xdg_surface_for_each_popup(view->wlr_xdg_surface, + close_popups_iterator, NULL); +} + static void destroy(struct sway_view *view) { struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); @@ -188,6 +219,7 @@ static void destroy(struct sway_view *view) { } static const struct sway_view_impl view_impl = { + .get_constraints = get_constraints, .get_string_prop = get_string_prop, .configure = configure, .set_activated = set_activated, @@ -195,7 +227,9 @@ static const struct sway_view_impl view_impl = { .set_fullscreen = set_fullscreen, .wants_floating = wants_floating, .for_each_surface = for_each_surface, + .for_each_popup = for_each_popup, .close = _close, + .close_popups = close_popups, .destroy = destroy, }; @@ -213,10 +247,24 @@ static void handle_commit(struct wl_listener *listener, void *data) { transaction_notify_view_ready(view, xdg_surface->configure_serial); } - view_update_title(view, false); view_damage_from(view); } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, set_title); + struct sway_view *view = &xdg_shell_view->view; + view_update_title(view, false); + view_execute_criteria(view); +} + +static void handle_set_app_id(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, set_app_id); + struct sway_view *view = &xdg_shell_view->view; + view_execute_criteria(view); +} + static void handle_new_popup(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup); @@ -241,13 +289,41 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data) return; } - view_set_fullscreen(view, e->fullscreen); + container_set_fullscreen(view->swayc, e->fullscreen); struct sway_container *output = container_parent(view->swayc, C_OUTPUT); arrange_windows(output); transaction_commit_dirty(); } +static void handle_request_move(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, request_move); + struct sway_view *view = &xdg_shell_view->view; + if (!container_is_floating(view->swayc)) { + return; + } + 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(seat, view->swayc, seat->last_button); + } +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_view *xdg_shell_view = + wl_container_of(listener, xdg_shell_view, request_resize); + struct sway_view *view = &xdg_shell_view->view; + if (!container_is_floating(view->swayc)) { + return; + } + 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(seat, view->swayc, seat->last_button, e->edges); + } +} + static void handle_unmap(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); @@ -262,6 +338,10 @@ static void handle_unmap(struct wl_listener *listener, void *data) { wl_list_remove(&xdg_shell_view->commit.link); wl_list_remove(&xdg_shell_view->new_popup.link); wl_list_remove(&xdg_shell_view->request_fullscreen.link); + wl_list_remove(&xdg_shell_view->request_move.link); + wl_list_remove(&xdg_shell_view->request_resize.link); + wl_list_remove(&xdg_shell_view->set_title.link); + wl_list_remove(&xdg_shell_view->set_app_id.link); } static void handle_map(struct wl_listener *listener, void *data) { @@ -280,7 +360,7 @@ static void handle_map(struct wl_listener *listener, void *data) { view_map(view, view->wlr_xdg_surface->surface); if (xdg_surface->toplevel->client_pending.fullscreen) { - view_set_fullscreen(view, true); + container_set_fullscreen(view->swayc, true); struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); arrange_windows(ws); } else { @@ -299,6 +379,22 @@ static void handle_map(struct wl_listener *listener, void *data) { xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen); + + xdg_shell_view->request_move.notify = handle_request_move; + wl_signal_add(&xdg_surface->toplevel->events.request_move, + &xdg_shell_view->request_move); + + xdg_shell_view->request_resize.notify = handle_request_resize; + wl_signal_add(&xdg_surface->toplevel->events.request_resize, + &xdg_shell_view->request_resize); + + xdg_shell_view->set_title.notify = handle_set_title; + wl_signal_add(&xdg_surface->toplevel->events.set_title, + &xdg_shell_view->set_title); + + xdg_shell_view->set_app_id.notify = handle_set_app_id; + wl_signal_add(&xdg_surface->toplevel->events.set_app_id, + &xdg_shell_view->set_app_id); } static void handle_destroy(struct wl_listener *listener, void *data) { @@ -344,9 +440,6 @@ void handle_xdg_shell_surface(struct wl_listener *listener, void *data) { view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl); xdg_shell_view->view.wlr_xdg_surface = xdg_surface; - // TODO: - // - Look up pid and open on appropriate workspace - xdg_shell_view->map.notify = handle_map; wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c index 4d76f0a7..ffea03ad 100644 --- a/sway/desktop/xdg_shell_v6.c +++ b/sway/desktop/xdg_shell_v6.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 199309L +#include <float.h> #include <stdbool.h> #include <stdlib.h> #include <wayland-server.h> @@ -94,6 +95,16 @@ static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view( return (struct sway_xdg_shell_v6_view *)view; } +static void get_constraints(struct sway_view *view, double *min_width, + double *max_width, double *min_height, double *max_height) { + struct wlr_xdg_toplevel_v6_state *state = + &view->wlr_xdg_surface_v6->toplevel->current; + *min_width = state->min_width > 0 ? state->min_width : DBL_MIN; + *max_width = state->max_width > 0 ? state->max_width : DBL_MAX; + *min_height = state->min_height > 0 ? state->min_height : DBL_MIN; + *max_height = state->max_height > 0 ? state->max_height : DBL_MAX; +} + static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { if (xdg_shell_v6_view_from_view(view) == NULL) { return NULL; @@ -164,6 +175,15 @@ static void for_each_surface(struct sway_view *view, user_data); } +static void for_each_popup(struct sway_view *view, + wlr_surface_iterator_func_t iterator, void *user_data) { + if (xdg_shell_v6_view_from_view(view) == NULL) { + return; + } + wlr_xdg_surface_v6_for_each_popup(view->wlr_xdg_surface_v6, iterator, + user_data); +} + static void _close(struct sway_view *view) { if (xdg_shell_v6_view_from_view(view) == NULL) { return; @@ -174,6 +194,18 @@ static void _close(struct sway_view *view) { } } +static void close_popups_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct wlr_xdg_surface_v6 *xdg_surface_v6 = + wlr_xdg_surface_v6_from_wlr_surface(surface); + wlr_xdg_surface_v6_send_close(xdg_surface_v6); +} + +static void close_popups(struct sway_view *view) { + wlr_xdg_surface_v6_for_each_popup(view->wlr_xdg_surface_v6, + close_popups_iterator, NULL); +} + static void destroy(struct sway_view *view) { struct sway_xdg_shell_v6_view *xdg_shell_v6_view = xdg_shell_v6_view_from_view(view); @@ -184,6 +216,7 @@ static void destroy(struct sway_view *view) { } static const struct sway_view_impl view_impl = { + .get_constraints = get_constraints, .get_string_prop = get_string_prop, .configure = configure, .set_activated = set_activated, @@ -191,7 +224,9 @@ static const struct sway_view_impl view_impl = { .set_fullscreen = set_fullscreen, .wants_floating = wants_floating, .for_each_surface = for_each_surface, + .for_each_popup = for_each_popup, .close = _close, + .close_popups = close_popups, .destroy = destroy, }; @@ -208,10 +243,24 @@ static void handle_commit(struct wl_listener *listener, void *data) { transaction_notify_view_ready(view, xdg_surface_v6->configure_serial); } - view_update_title(view, false); view_damage_from(view); } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_v6_view *xdg_shell_v6_view = + wl_container_of(listener, xdg_shell_v6_view, set_title); + struct sway_view *view = &xdg_shell_v6_view->view; + view_update_title(view, false); + view_execute_criteria(view); +} + +static void handle_set_app_id(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_v6_view *xdg_shell_v6_view = + wl_container_of(listener, xdg_shell_v6_view, set_app_id); + struct sway_view *view = &xdg_shell_v6_view->view; + view_execute_criteria(view); +} + static void handle_new_popup(struct wl_listener *listener, void *data) { struct sway_xdg_shell_v6_view *xdg_shell_v6_view = wl_container_of(listener, xdg_shell_v6_view, new_popup); @@ -236,13 +285,41 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data) return; } - view_set_fullscreen(view, e->fullscreen); + container_set_fullscreen(view->swayc, e->fullscreen); struct sway_container *output = container_parent(view->swayc, C_OUTPUT); arrange_windows(output); transaction_commit_dirty(); } +static void handle_request_move(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_v6_view *xdg_shell_v6_view = + wl_container_of(listener, xdg_shell_v6_view, request_move); + struct sway_view *view = &xdg_shell_v6_view->view; + if (!container_is_floating(view->swayc)) { + return; + } + 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(seat, view->swayc, seat->last_button); + } +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct sway_xdg_shell_v6_view *xdg_shell_v6_view = + wl_container_of(listener, xdg_shell_v6_view, request_resize); + struct sway_view *view = &xdg_shell_v6_view->view; + if (!container_is_floating(view->swayc)) { + return; + } + 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(seat, view->swayc, seat->last_button, e->edges); + } +} + static void handle_unmap(struct wl_listener *listener, void *data) { struct sway_xdg_shell_v6_view *xdg_shell_v6_view = wl_container_of(listener, xdg_shell_v6_view, unmap); @@ -257,6 +334,10 @@ static void handle_unmap(struct wl_listener *listener, void *data) { wl_list_remove(&xdg_shell_v6_view->commit.link); wl_list_remove(&xdg_shell_v6_view->new_popup.link); wl_list_remove(&xdg_shell_v6_view->request_fullscreen.link); + wl_list_remove(&xdg_shell_v6_view->request_move.link); + wl_list_remove(&xdg_shell_v6_view->request_resize.link); + wl_list_remove(&xdg_shell_v6_view->set_title.link); + wl_list_remove(&xdg_shell_v6_view->set_app_id.link); } static void handle_map(struct wl_listener *listener, void *data) { @@ -275,7 +356,7 @@ static void handle_map(struct wl_listener *listener, void *data) { view_map(view, view->wlr_xdg_surface_v6->surface); if (xdg_surface->toplevel->client_pending.fullscreen) { - view_set_fullscreen(view, true); + container_set_fullscreen(view->swayc, true); struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); arrange_windows(ws); } else { @@ -294,6 +375,22 @@ static void handle_map(struct wl_listener *listener, void *data) { xdg_shell_v6_view->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_v6_view->request_fullscreen); + + xdg_shell_v6_view->request_move.notify = handle_request_move; + wl_signal_add(&xdg_surface->toplevel->events.request_move, + &xdg_shell_v6_view->request_move); + + xdg_shell_v6_view->request_resize.notify = handle_request_resize; + wl_signal_add(&xdg_surface->toplevel->events.request_resize, + &xdg_shell_v6_view->request_resize); + + xdg_shell_v6_view->set_title.notify = handle_set_title; + wl_signal_add(&xdg_surface->toplevel->events.set_title, + &xdg_shell_v6_view->set_title); + + xdg_shell_v6_view->set_app_id.notify = handle_set_app_id; + wl_signal_add(&xdg_surface->toplevel->events.set_app_id, + &xdg_shell_v6_view->set_app_id); } static void handle_destroy(struct wl_listener *listener, void *data) { @@ -335,9 +432,6 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) { view_init(&xdg_shell_v6_view->view, SWAY_VIEW_XDG_SHELL_V6, &view_impl); xdg_shell_v6_view->view.wlr_xdg_surface_v6 = xdg_surface; - // TODO: - // - Look up pid and open on appropriate workspace - xdg_shell_v6_view->map.notify = handle_map; wl_signal_add(&xdg_surface->events.map, &xdg_shell_v6_view->map); diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c index bce0a37b..398446f8 100644 --- a/sway/desktop/xwayland.c +++ b/sway/desktop/xwayland.c @@ -69,11 +69,13 @@ static void unmanaged_handle_map(struct wl_listener *listener, void *data) { surface->ly = xsurface->y; desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true); - struct sway_seat *seat = input_manager_current_seat(input_manager); - struct wlr_xwayland *xwayland = - seat->input->server->xwayland.wlr_xwayland; - wlr_xwayland_set_seat(xwayland, seat->wlr_seat); - seat_set_focus_surface(seat, xsurface->surface, false); + if (wlr_xwayland_or_surface_wants_focus(xsurface)) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct wlr_xwayland *xwayland = + seat->input->server->xwayland.wlr_xwayland; + wlr_xwayland_set_seat(xwayland, seat->wlr_seat); + seat_set_focus_surface(seat, xsurface->surface, false); + } } static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { @@ -305,6 +307,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&xwayland_view->destroy.link); wl_list_remove(&xwayland_view->request_configure.link); wl_list_remove(&xwayland_view->request_fullscreen.link); + wl_list_remove(&xwayland_view->request_move.link); + wl_list_remove(&xwayland_view->request_resize.link); wl_list_remove(&xwayland_view->set_title.link); wl_list_remove(&xwayland_view->set_class.link); wl_list_remove(&xwayland_view->set_window_type.link); @@ -355,7 +359,7 @@ static void handle_map(struct wl_listener *listener, void *data) { view_map(view, xsurface->surface); if (xsurface->fullscreen) { - view_set_fullscreen(view, true); + container_set_fullscreen(view->swayc, true); struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); arrange_windows(ws); } else { @@ -393,13 +397,44 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data) if (!xsurface->mapped) { return; } - view_set_fullscreen(view, xsurface->fullscreen); + container_set_fullscreen(view->swayc, xsurface->fullscreen); struct sway_container *output = container_parent(view->swayc, C_OUTPUT); arrange_windows(output); transaction_commit_dirty(); } +static void handle_request_move(struct wl_listener *listener, void *data) { + struct sway_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, request_move); + struct sway_view *view = &xwayland_view->view; + struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; + if (!xsurface->mapped) { + return; + } + if (!container_is_floating(view->swayc)) { + return; + } + struct sway_seat *seat = input_manager_current_seat(input_manager); + seat_begin_move(seat, view->swayc, seat->last_button); +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct sway_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, request_resize); + struct sway_view *view = &xwayland_view->view; + struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; + if (!xsurface->mapped) { + return; + } + if (!container_is_floating(view->swayc)) { + return; + } + struct wlr_xwayland_resize_event *e = data; + struct sway_seat *seat = input_manager_current_seat(input_manager); + seat_begin_resize(seat, view->swayc, seat->last_button, e->edges); +} + static void handle_set_title(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_title); @@ -481,9 +516,6 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) { view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl); xwayland_view->view.wlr_xwayland_surface = xsurface; - // TODO: - // - Look up pid and open on appropriate workspace - wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy); xwayland_view->destroy.notify = handle_destroy; @@ -495,6 +527,14 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) { &xwayland_view->request_fullscreen); xwayland_view->request_fullscreen.notify = handle_request_fullscreen; + wl_signal_add(&xsurface->events.request_move, + &xwayland_view->request_move); + xwayland_view->request_move.notify = handle_request_move; + + wl_signal_add(&xsurface->events.request_resize, + &xwayland_view->request_resize); + xwayland_view->request_resize.notify = handle_request_resize; + wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); xwayland_view->set_title.notify = handle_set_title; diff --git a/sway/input/cursor.c b/sway/input/cursor.c index c76c20b3..c2fc4e9e 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -5,15 +5,20 @@ #elif __FreeBSD__ #include <dev/evdev/input-event-codes.h> #endif +#include <limits.h> #include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_idle.h> #include "list.h" #include "log.h" +#include "config.h" +#include "sway/desktop.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" +#include "sway/input/keyboard.h" #include "sway/layers.h" #include "sway/output.h" +#include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "wlr-layer-shell-unstable-v1-protocol.h" @@ -50,6 +55,7 @@ static struct sway_container *container_at_coords( struct sway_seat *seat, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { // check for unmanaged views first +#ifdef HAVE_XWAYLAND struct wl_list *unmanaged = &root_container.sway_root->xwayland_unmanaged; struct sway_xwayland_unmanaged *unmanaged_surface; wl_list_for_each_reverse(unmanaged_surface, unmanaged, link) { @@ -65,7 +71,7 @@ static struct sway_container *container_at_coords( return NULL; } } - +#endif // find the output the cursor is on struct wlr_output_layout *output_layout = root_container.sway_root->output_layout; @@ -93,14 +99,8 @@ static struct sway_container *container_at_coords( return ws; } if (ws->sway_workspace->fullscreen) { - struct wlr_surface *wlr_surface = ws->sway_workspace->fullscreen->surface; - if (wlr_surface_point_accepts_input(wlr_surface, ox, oy)) { - *sx = ox; - *sy = oy; - *surface = wlr_surface; - return ws->sway_workspace->fullscreen->swayc; - } - return NULL; + return container_at_view(ws->sway_workspace->fullscreen, lx, ly, + surface, sx, sy); } if ((*surface = layer_surface_at(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], @@ -109,9 +109,6 @@ static struct sway_container *container_at_coords( } struct sway_container *c; - if ((c = floating_container_at(lx, ly, surface, sx, sy))) { - return c; - } if ((c = container_at(ws, lx, ly, surface, sx, sy))) { return c; } @@ -127,7 +124,7 @@ static struct sway_container *container_at_coords( return ws; } - c = seat_get_focus_inactive(seat, output->swayc); + c = seat_get_active_child(seat, output->swayc); if (c) { return c; } @@ -139,6 +136,171 @@ static struct sway_container *container_at_coords( return output->swayc; } +static enum wlr_edges find_resize_edge(struct sway_container *cont, + struct sway_cursor *cursor) { + if (cont->type != C_VIEW) { + return WLR_EDGE_NONE; + } + struct sway_view *view = cont->sway_view; + if (view->border == B_NONE || !view->border_thickness || view->using_csd) { + return WLR_EDGE_NONE; + } + + enum wlr_edges edge = 0; + if (cursor->cursor->x < cont->x + view->border_thickness) { + edge |= WLR_EDGE_LEFT; + } + if (cursor->cursor->y < cont->y + view->border_thickness) { + edge |= WLR_EDGE_TOP; + } + if (cursor->cursor->x >= cont->x + cont->width - view->border_thickness) { + edge |= WLR_EDGE_RIGHT; + } + if (cursor->cursor->y >= cont->y + cont->height - view->border_thickness) { + edge |= WLR_EDGE_BOTTOM; + } + return edge; +} + +static void handle_move_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 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 + struct sway_container *ws = container_parent(con, C_WORKSPACE); + *max_width = ws->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 + struct sway_container *ws = container_parent(con, C_WORKSPACE); + *max_height = ws->height; + } else { + *max_height = config->floating_maximum_height; + } +} + +static void handle_resize_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->type == C_VIEW) { + double view_min_width, view_max_width, view_min_height, view_max_height; + view_get_constraints(con->sway_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; + + if (con->type == C_VIEW) { + struct sway_view *view = con->sway_view; + view->x += relative_grow_x; + view->y += relative_grow_y; + view->width += relative_grow_width; + view->height += relative_grow_height; + } + + arrange_windows(con); +} + void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, bool allow_refocusing) { if (time_msec == 0) { @@ -146,6 +308,18 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, } struct sway_seat *seat = cursor->seat; + + if (seat->operation != OP_NONE) { + if (seat->operation == OP_MOVE) { + handle_move_motion(seat, cursor); + } else { + handle_resize_motion(seat, cursor); + } + cursor->previous.x = cursor->cursor->x; + cursor->previous.y = cursor->cursor->y; + return; + } + struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *surface = NULL; double sx, sy; @@ -172,7 +346,7 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, output = container_parent(c, C_OUTPUT); } if (output != focus) { - seat_set_focus_warp(seat, c, false); + seat_set_focus_warp(seat, c, false, true); } } else if (c->type == C_VIEW) { // Focus c if the following are true: @@ -182,27 +356,33 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, if (!wlr_seat_keyboard_has_grab(cursor->seat->wlr_seat) && c != prev_c && view_is_visible(c->sway_view)) { - seat_set_focus_warp(seat, c, false); + seat_set_focus_warp(seat, c, false, true); } else { struct sway_container *next_focus = seat_get_focus_inactive(seat, &root_container); if (next_focus && next_focus->type == C_VIEW && view_is_visible(next_focus->sway_view)) { - seat_set_focus_warp(seat, next_focus, false); + seat_set_focus_warp(seat, next_focus, false, true); } } } } - // reset cursor if switching between clients - struct wl_client *client = NULL; - if (surface != NULL) { - client = wl_resource_get_client(surface->resource); - } - if (client != cursor->image_client) { - wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, - "left_ptr", cursor->cursor); - cursor->image_client = client; + // Handle cursor image + if (surface) { + // Reset cursor if switching between clients + struct wl_client *client = wl_resource_get_client(surface->resource); + if (client != cursor->image_client) { + cursor_set_image(cursor, "left_ptr", client); + } + } else if (c && container_is_floating(c)) { + // Try a floating container's resize edge + enum wlr_edges edge = find_resize_edge(c, cursor); + const char *image = edge == WLR_EDGE_NONE ? + "left_ptr" : wlr_xcursor_get_resize_name(edge); + cursor_set_image(cursor, image, NULL); + } else { + cursor_set_image(cursor, "left_ptr", NULL); } // send pointer enter/leave @@ -243,8 +423,142 @@ static void handle_cursor_motion_absolute( transaction_commit_dirty(); } +static void dispatch_cursor_button_floating(struct sway_cursor *cursor, + uint32_t time_msec, uint32_t button, enum wlr_button_state state, + struct wlr_surface *surface, double sx, double sy, + struct sway_container *cont) { + struct sway_seat *seat = cursor->seat; + + // Deny moving or resizing a fullscreen container + if (container_is_fullscreen_or_child(cont)) { + seat_pointer_notify_button(seat, time_msec, button, state); + return; + } + struct sway_container *floater = cont; + while (floater->parent->layout != L_FLOATING) { + floater = floater->parent; + } + + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); + bool mod_pressed = keyboard && + (wlr_keyboard_get_modifiers(keyboard) & config->floating_mod); + enum wlr_edges edge = find_resize_edge(floater, cursor); + bool over_title = edge == WLR_EDGE_NONE && !surface; + + // Check for beginning move + uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; + if (button == btn_move && state == WLR_BUTTON_PRESSED && + (mod_pressed || over_title)) { + seat_begin_move(seat, floater, button); + return; + } + + // Check for beginning resize + bool resizing_via_border = button == BTN_LEFT && edge != WLR_EDGE_NONE; + uint32_t btn_resize = config->floating_mod_inverse ? BTN_LEFT : BTN_RIGHT; + bool resizing_via_mod = button == btn_resize && mod_pressed; + if ((resizing_via_border || resizing_via_mod) && + state == WLR_BUTTON_PRESSED) { + if (edge == WLR_EDGE_NONE) { + edge |= cursor->cursor->x > floater->x + floater->width / 2 ? + WLR_EDGE_RIGHT : WLR_EDGE_LEFT; + edge |= cursor->cursor->y > floater->y + floater->height / 2 ? + WLR_EDGE_BOTTOM : WLR_EDGE_TOP; + } + seat_begin_resize(seat, floater, button, edge); + return; + } + + // Send event to surface + seat_set_focus(seat, cont); + seat_pointer_notify_button(seat, time_msec, button, state); +} + +/** + * Remove a button (and duplicates) to the sorted list of currently pressed buttons + */ +static void state_erase_button(struct sway_cursor *cursor, uint32_t button) { + size_t j = 0; + for (size_t i = 0; i < cursor->pressed_button_count; ++i) { + if (i > j) { + cursor->pressed_buttons[j] = cursor->pressed_buttons[i]; + } + if (cursor->pressed_buttons[i] != button) { + ++j; + } + } + while (cursor->pressed_button_count > j) { + --cursor->pressed_button_count; + cursor->pressed_buttons[cursor->pressed_button_count] = 0; + } +} + +/** + * Add a button to the sorted list of currently pressed buttons, if there + * is space. + */ +static void state_add_button(struct sway_cursor *cursor, uint32_t button) { + if (cursor->pressed_button_count >= SWAY_CURSOR_PRESSED_BUTTONS_CAP) { + return; + } + size_t i = 0; + while (i < cursor->pressed_button_count && cursor->pressed_buttons[i] < button) { + ++i; + } + size_t j = cursor->pressed_button_count; + while (j > i) { + cursor->pressed_buttons[j] = cursor->pressed_buttons[j - 1]; + --j; + } + cursor->pressed_buttons[i] = button; + cursor->pressed_button_count++; +} + +/** + * Return the mouse binding which matches modifier, click location, release, + * and pressed button state, otherwise return null. + */ +static struct sway_binding* get_active_mouse_binding(const struct sway_cursor *cursor, + list_t *bindings, uint32_t modifiers, bool release, bool on_titlebar, + bool on_border, bool on_content) { + uint32_t click_region = (on_titlebar ? BINDING_TITLEBAR : 0) | + (on_border ? BINDING_BORDER : 0) | + (on_content ? BINDING_CONTENTS : 0); + + for (int i = 0; i < bindings->length; ++i) { + struct sway_binding *binding = bindings->items[i]; + if (modifiers ^ binding->modifiers || + cursor->pressed_button_count != (size_t)binding->keys->length || + release != (binding->flags & BINDING_RELEASE) || + !(click_region & binding->flags)) { + continue; + } + + bool match = true; + for (size_t j = 0; j < cursor->pressed_button_count; j++) { + uint32_t key = *(uint32_t *)binding->keys->items[j]; + if (key != cursor->pressed_buttons[j]) { + match = false; + break; + } + } + if (!match) { + continue; + } + + return binding; + } + return NULL; +} + void dispatch_cursor_button(struct sway_cursor *cursor, uint32_t time_msec, uint32_t button, enum wlr_button_state state) { + if (cursor->seat->operation != OP_NONE && + button == cursor->seat->op_button && state == WLR_BUTTON_RELEASED) { + seat_end_mouse_operation(cursor->seat); + seat_pointer_notify_button(cursor->seat, time_msec, button, state); + return; + } if (time_msec == 0) { time_msec = get_current_time_msec(); } @@ -253,12 +567,41 @@ void dispatch_cursor_button(struct sway_cursor *cursor, double sx, sy; struct sway_container *cont = container_at_coords(cursor->seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); + + // Handle mouse bindings + bool on_border = cont && (find_resize_edge(cont, cursor) != WLR_EDGE_NONE); + bool on_contents = !on_border && surface; + bool on_titlebar = !on_border && !surface; + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(cursor->seat->wlr_seat); + uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + + struct sway_binding *binding = NULL; + if (state == WLR_BUTTON_PRESSED) { + state_add_button(cursor, button); + binding = get_active_mouse_binding(cursor, + config->current_mode->mouse_bindings, modifiers, false, + on_titlebar, on_border, on_contents); + } else { + binding = get_active_mouse_binding(cursor, + config->current_mode->mouse_bindings, modifiers, true, + on_titlebar, on_border, on_contents); + state_erase_button(cursor, button); + } + if (binding) { + seat_execute_command(cursor->seat, binding); + // TODO: do we want to pass on the event? + } + if (surface && wlr_surface_is_layer_surface(surface)) { struct wlr_layer_surface *layer = wlr_layer_surface_from_wlr_surface(surface); if (layer->current.keyboard_interactive) { seat_set_focus_layer(cursor->seat, layer); } + seat_pointer_notify_button(cursor->seat, time_msec, button, state); + } else if (cont && container_is_floating_or_child(cont)) { + dispatch_cursor_button_floating(cursor, time_msec, button, state, + surface, sx, sy, cont); } else if (surface && cont && cont->type != C_VIEW) { // Avoid moving keyboard focus from a surface that accepts it to one // that does not unless the change would move us to a new workspace. @@ -275,12 +618,14 @@ void dispatch_cursor_button(struct sway_cursor *cursor, if (new_ws != old_ws) { seat_set_focus(cursor->seat, cont); } + seat_pointer_notify_button(cursor->seat, time_msec, button, state); } else if (cont) { seat_set_focus(cursor->seat, cont); + seat_pointer_notify_button(cursor->seat, time_msec, button, state); + } else { + seat_pointer_notify_button(cursor->seat, time_msec, button, state); } - wlr_seat_pointer_notify_button(cursor->seat->wlr_seat, - time_msec, button, state); transaction_commit_dirty(); } @@ -467,6 +812,9 @@ 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) { + return; + } struct wlr_seat_pointer_request_set_cursor_event *event = data; struct wl_client *focused_client = NULL; @@ -485,9 +833,20 @@ static void handle_request_set_cursor(struct wl_listener *listener, wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x, event->hotspot_y); + cursor->image = NULL; cursor->image_client = focused_client; } +void cursor_set_image(struct sway_cursor *cursor, const char *image, + struct wl_client *client) { + if (!cursor->image || strcmp(cursor->image, image) != 0) { + wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, image, + cursor->cursor); + cursor->image = image; + } + cursor->image_client = client; +} + void sway_cursor_destroy(struct sway_cursor *cursor) { if (!cursor) { return; diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 0b7cb766..c820e032 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -8,6 +8,7 @@ #include <math.h> #include <wlr/backend/libinput.h> #include <wlr/types/wlr_input_inhibitor.h> +#include <wlr/types/wlr_virtual_keyboard_v1.h> #include "sway/config.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" @@ -303,6 +304,35 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) } } +void handle_virtual_keyboard(struct wl_listener *listener, void *data) { + struct sway_input_manager *input_manager = + wl_container_of(listener, input_manager, virtual_keyboard_new); + struct wlr_virtual_keyboard_v1 *keyboard = data; + struct wlr_input_device *device = &keyboard->input_device; + + struct sway_seat *seat = input_manager_get_default_seat(input_manager); + + // TODO: The user might want this on a different seat + struct sway_input_device *input_device = + calloc(1, sizeof(struct sway_input_device)); + if (!sway_assert(input_device, "could not allocate input device")) { + return; + } + device->data = input_device; + + input_device->wlr_device = device; + input_device->identifier = get_device_identifier(device); + wl_list_insert(&input_manager->devices, &input_device->link); + + wlr_log(WLR_DEBUG, "adding virtual keyboard: '%s'", + input_device->identifier); + + wl_signal_add(&device->events.destroy, &input_device->device_destroy); + input_device->device_destroy.notify = handle_device_destroy; + + seat_add_device(seat, input_device); +} + struct sway_input_manager *input_manager_create( struct sway_server *server) { struct sway_input_manager *input = @@ -321,6 +351,12 @@ struct sway_input_manager *input_manager_create( input->new_input.notify = handle_new_input; wl_signal_add(&server->backend->events.new_input, &input->new_input); + input->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create( + server->wl_display); + wl_signal_add(&input->virtual_keyboard->events.new_virtual_keyboard, + &input->virtual_keyboard_new); + input->virtual_keyboard_new.notify = handle_virtual_keyboard; + input->inhibit = wlr_input_inhibit_manager_create(server->wl_display); input->inhibit_activate.notify = handle_inhibit_activate; wl_signal_add(&input->inhibit->events.activate, diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index ede38519..160ef10b 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -3,11 +3,12 @@ #include <wlr/backend/multi.h> #include <wlr/backend/session.h> #include <wlr/types/wlr_idle.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include "sway/commands.h" #include "sway/desktop/transaction.h" -#include "sway/input/seat.h" -#include "sway/input/keyboard.h" #include "sway/input/input-manager.h" -#include "sway/commands.h" +#include "sway/input/keyboard.h" +#include "sway/input/seat.h" #include "log.h" /** @@ -88,11 +89,13 @@ static void get_active_binding(const struct sway_shortcut_state *state, uint32_t modifiers, bool release, bool locked) { for (int i = 0; i < bindings->length; ++i) { struct sway_binding *binding = bindings->items[i]; + bool binding_locked = binding->flags & BINDING_LOCKED; + bool binding_release = binding->flags & BINDING_RELEASE; if (modifiers ^ binding->modifiers || state->npressed != (size_t)binding->keys->length || - locked > binding->locked || - release != binding->release) { + release != binding_release || + locked > binding_locked) { continue; } @@ -119,23 +122,6 @@ static void get_active_binding(const struct sway_shortcut_state *state, } /** - * Execute the command associated to a binding - */ -static void keyboard_execute_command(struct sway_keyboard *keyboard, - struct sway_binding *binding) { - wlr_log(WLR_DEBUG, "running command for binding: %s", - binding->command); - config->handler_context.seat = keyboard->seat_device->sway_seat; - struct cmd_results *results = execute_command(binding->command, NULL); - transaction_commit_dirty(); - if (results->status != CMD_SUCCESS) { - wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)", - binding->command, results->error); - } - free_cmd_results(results); -} - -/** * Execute a built-in, hardcoded compositor binding. These are triggered from a * single keysym. * @@ -211,12 +197,13 @@ static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard, static void handle_keyboard_key(struct wl_listener *listener, void *data) { struct sway_keyboard *keyboard = wl_container_of(listener, keyboard, keyboard_key); - struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; + struct sway_seat* seat = keyboard->seat_device->sway_seat; + struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_input_device *wlr_device = keyboard->seat_device->input_device->wlr_device; - wlr_idle_notify_activity(keyboard->seat_device->sway_seat->input->server->idle, wlr_seat); + wlr_idle_notify_activity(seat->input->server->idle, wlr_seat); struct wlr_event_keyboard_key *event = data; - bool input_inhibited = keyboard->seat_device->sway_seat->exclusive_client != NULL; + bool input_inhibited = seat->exclusive_client != NULL; // Identify new keycode, raw keysym(s), and translated keysym(s) xkb_keycode_t keycode = event->keycode + 8; @@ -266,7 +253,7 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { // Execute stored release binding once no longer active if (keyboard->held_binding && binding_released != keyboard->held_binding && event->state == WLR_KEY_RELEASED) { - keyboard_execute_command(keyboard, keyboard->held_binding); + seat_execute_command(seat, keyboard->held_binding); handled = true; } if (binding_released != keyboard->held_binding) { @@ -277,6 +264,7 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { } // Identify and execute active pressed binding + struct sway_binding *next_repeat_binding = NULL; if (event->state == WLR_KEY_PRESSED) { struct sway_binding *binding_pressed = NULL; get_active_binding(&keyboard->state_keycodes, @@ -290,8 +278,23 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { raw_modifiers, false, input_inhibited); if (binding_pressed) { - keyboard_execute_command(keyboard, binding_pressed); + seat_execute_command(seat, binding_pressed); handled = true; + next_repeat_binding = binding_pressed; + } + } + + // Set up (or clear) keyboard repeat for a pressed binding + if (next_repeat_binding && wlr_device->keyboard->repeat_info.delay > 0) { + keyboard->repeat_binding = next_repeat_binding; + if (wl_event_source_timer_update(keyboard->key_repeat_source, + wlr_device->keyboard->repeat_info.delay) < 0) { + wlr_log(WLR_DEBUG, "failed to set key repeat timer"); + } + } else if (keyboard->repeat_binding) { + keyboard->repeat_binding = NULL; + if (wl_event_source_timer_update(keyboard->key_repeat_source, 0) < 0) { + wlr_log(WLR_DEBUG, "failed to disarm key repeat timer"); } } @@ -312,6 +315,28 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) { wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); } + + transaction_commit_dirty(); +} + +static int handle_keyboard_repeat(void *data) { + struct sway_keyboard *keyboard = (struct sway_keyboard *)data; + struct wlr_keyboard *wlr_device = + keyboard->seat_device->input_device->wlr_device->keyboard; + if (keyboard->repeat_binding) { + if (wlr_device->repeat_info.rate > 0) { + // We queue the next event first, as the command might cancel it + if (wl_event_source_timer_update(keyboard->key_repeat_source, + 1000 / wlr_device->repeat_info.rate) < 0) { + wlr_log(WLR_DEBUG, "failed to update key repeat timer"); + } + } + + seat_execute_command(keyboard->seat_device->sway_seat, + keyboard->repeat_binding); + transaction_commit_dirty(); + } + return 0; } static void handle_keyboard_modifiers(struct wl_listener *listener, @@ -339,6 +364,9 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, wl_list_init(&keyboard->keyboard_key.link); wl_list_init(&keyboard->keyboard_modifiers.link); + keyboard->key_repeat_source = wl_event_loop_add_timer(server.wl_event_loop, + handle_keyboard_repeat, keyboard); + return keyboard; } @@ -397,6 +425,31 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) { keyboard->keymap = keymap; wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); + xkb_mod_mask_t locked_mods = 0; + if (input_config && input_config->xkb_numlock > 0) { + xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, XKB_MOD_NAME_NUM); + if (mod_index != XKB_MOD_INVALID) { + locked_mods |= (uint32_t)1 << mod_index; + } + } + if (input_config && input_config->xkb_capslock > 0) { + xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, XKB_MOD_NAME_CAPS); + if (mod_index != XKB_MOD_INVALID) { + locked_mods |= (uint32_t)1 << mod_index; + } + } + if (locked_mods) { + wlr_keyboard_notify_modifiers(wlr_device->keyboard, 0, 0, locked_mods, 0); + uint32_t leds = 0; + for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) { + if (xkb_state_led_index_is_active(wlr_device->keyboard->xkb_state, + wlr_device->keyboard->led_indexes[i])) { + leds |= (1 << i); + } + } + wlr_keyboard_led_update(wlr_device->keyboard, leds); + } + if (input_config && input_config->repeat_delay != INT_MIN && input_config->repeat_rate != INT_MIN) { wlr_keyboard_set_repeat_info(wlr_device->keyboard, @@ -427,5 +480,6 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) { } wl_list_remove(&keyboard->keyboard_key.link); wl_list_remove(&keyboard->keyboard_modifiers.link); + wl_event_source_remove(keyboard->key_repeat_source); free(keyboard); } diff --git a/sway/input/seat.c b/sway/input/seat.c index e77d88a8..dd4d5c3b 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1,12 +1,19 @@ #define _XOPEN_SOURCE 700 #define _POSIX_C_SOURCE 199309L #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> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_xcursor_manager.h> #include "log.h" +#include "config.h" #include "sway/debug.h" #include "sway/desktop.h" #include "sway/input/cursor.h" @@ -98,11 +105,13 @@ static void seat_send_focus(struct sway_container *con, if (con->type == C_VIEW && seat_is_input_allowed(seat, con->sway_view->surface)) { +#ifdef HAVE_XWAYLAND if (con->sway_view->type == SWAY_VIEW_XWAYLAND) { struct wlr_xwayland *xwayland = seat->input->server->xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); } +#endif struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); if (keyboard) { wlr_seat_keyboard_notify_enter(seat->wlr_seat, @@ -116,12 +125,14 @@ static void seat_send_focus(struct sway_container *con, } static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat, - struct sway_container *container, enum sway_container_type type) { + struct sway_container *container, enum sway_container_type type, + bool only_tiling) { if (container->type == C_VIEW) { return container; } - struct sway_container *floating = container->type == C_WORKSPACE ? + struct sway_container *floating = + container->type == C_WORKSPACE && !only_tiling ? container->sway_workspace->floating : NULL; if (container->children->length == 0 && (!floating || floating->children->length == 0)) { @@ -135,6 +146,10 @@ static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat, } if (container_has_child(container, current->container)) { + if (only_tiling && + container_is_floating_or_child(current->container)) { + continue; + } return current->container; } if (floating && container_has_child(floating, current->container)) { @@ -161,7 +176,7 @@ void seat_focus_inactive_children_for_each(struct sway_seat *seat, struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, struct sway_container *container) { - return seat_get_focus_by_type(seat, container, C_VIEW); + return seat_get_focus_by_type(seat, container, C_VIEW, false); } static void handle_seat_container_destroy(struct wl_listener *listener, @@ -183,7 +198,7 @@ static void handle_seat_container_destroy(struct wl_listener *listener, if (set_focus) { struct sway_container *next_focus = NULL; while (next_focus == NULL) { - next_focus = seat_get_focus_by_type(seat, parent, C_VIEW); + next_focus = seat_get_focus_by_type(seat, parent, C_VIEW, false); if (next_focus == NULL && parent->type == C_WORKSPACE) { next_focus = parent; @@ -348,6 +363,7 @@ struct sway_seat *seat_create(struct sway_input_manager *input, free(seat); return NULL; } + seat->wlr_seat->data = seat; seat->cursor = sway_cursor_create(seat); if (!seat->cursor) { @@ -377,7 +393,6 @@ struct sway_seat *seat_create(struct sway_input_manager *input, WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH); - seat_configure_xcursor(seat); wl_list_insert(&input->seats, &seat->link); @@ -422,6 +437,7 @@ static void seat_apply_input_config(struct sway_seat *seat, static void seat_configure_pointer(struct sway_seat *seat, struct sway_seat_device *sway_device) { + seat_configure_xcursor(seat); wlr_cursor_attach_input_device(seat->cursor->cursor, sway_device->input_device->wlr_device); seat_apply_input_config(seat, sway_device); @@ -601,7 +617,7 @@ static int handle_urgent_timeout(void *data) { } void seat_set_focus_warp(struct sway_seat *seat, - struct sway_container *container, bool warp) { + struct sway_container *container, bool warp, bool notify) { if (seat->focused_layer) { return; } @@ -622,7 +638,7 @@ void seat_set_focus_warp(struct sway_seat *seat, if (last_workspace && last_workspace == new_workspace && last_workspace->sway_workspace->fullscreen - && !container->sway_view->is_fullscreen) { + && container && !container_is_fullscreen_or_child(container)) { return; } @@ -639,7 +655,7 @@ void seat_set_focus_warp(struct sway_seat *seat, struct sway_container *new_output_last_ws = NULL; if (last_output && new_output && last_output != new_output) { new_output_last_ws = - seat_get_focus_by_type(seat, new_output, C_WORKSPACE); + seat_get_focus_by_type(seat, new_output, C_WORKSPACE, false); } if (container && container->parent) { @@ -686,8 +702,14 @@ void seat_set_focus_warp(struct sway_seat *seat, config->urgent_timeout > 0) { view->urgent_timer = wl_event_loop_add_timer(server.wl_event_loop, handle_urgent_timeout, view); - wl_event_source_timer_update(view->urgent_timer, - config->urgent_timeout); + if (view->urgent_timer) { + wl_event_source_timer_update(view->urgent_timer, + config->urgent_timeout); + } else { + wlr_log(WLR_ERROR, "Unable to create urgency timer (%s)", + strerror(errno)); + handle_urgent_timeout(view); + } } else { view_set_urgent(view, false); } @@ -715,9 +737,18 @@ void seat_set_focus_warp(struct sway_seat *seat, } } + // Close any popups on the old focus + if (last_focus && last_focus != container) { + if (last_focus->type == C_VIEW) { + view_close_popups(last_focus->sway_view); + } + } + if (last_focus) { if (last_workspace) { - ipc_event_workspace(last_workspace, container, "focus"); + if (notify && last_workspace != new_workspace) { + ipc_event_workspace(last_workspace, new_workspace, "focus"); + } if (!workspace_is_visible(last_workspace) && workspace_is_empty(last_workspace)) { if (last_workspace == last_focus) { @@ -744,8 +775,12 @@ void seat_set_focus_warp(struct sway_seat *seat, } } - if (last_focus != NULL) { - cursor_send_pointer_motion(seat->cursor, 0, true); + if (container) { + if (container->type == C_VIEW) { + ipc_event_window(container, "focus"); + } else if (container->type == C_WORKSPACE) { + ipc_event_workspace(NULL, container, "focus"); + } } seat->has_focus = (container != NULL); @@ -755,7 +790,7 @@ void seat_set_focus_warp(struct sway_seat *seat, void seat_set_focus(struct sway_seat *seat, struct sway_container *container) { - seat_set_focus_warp(seat, container, true); + seat_set_focus_warp(seat, container, true, true); } void seat_set_focus_surface(struct sway_seat *seat, @@ -848,7 +883,12 @@ void seat_set_exclusive_client(struct sway_seat *seat, struct sway_container *seat_get_focus_inactive(struct sway_seat *seat, struct sway_container *container) { - return seat_get_focus_by_type(seat, container, C_TYPES); + return seat_get_focus_by_type(seat, container, C_TYPES, false); +} + +struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat, + struct sway_container *container) { + return seat_get_focus_by_type(seat, container, C_TYPES, true); } struct sway_container *seat_get_active_child(struct sway_seat *seat, @@ -894,3 +934,68 @@ struct seat_config *seat_get_config(struct sway_seat *seat) { return NULL; } + +void seat_begin_move(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; + seat->op_container = con; + seat->op_button = button; + cursor_set_image(seat->cursor, "grab", NULL); +} + +void seat_begin_resize(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; + 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 ? + RESIZE_EDGE_BOTTOM | RESIZE_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; + + const char *image = edge == WLR_EDGE_NONE ? + "se-resize" : wlr_xcursor_get_resize_name(edge); + cursor_set_image(seat->cursor, image, NULL); +} + +void seat_end_mouse_operation(struct sway_seat *seat) { + switch (seat->operation) { + case OP_MOVE: + { + // 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); + } + case OP_RESIZE: + // Don't need to do anything here. + break; + case OP_NONE: + break; + } + seat->operation = OP_NONE; + seat->op_container = NULL; + cursor_set_image(seat->cursor, "left_ptr", NULL); +} + +void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, + uint32_t button, enum wlr_button_state state) { + seat->last_button = button; + seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat, + time_msec, button, state); +} diff --git a/sway/ipc-json.c b/sway/ipc-json.c index c49ea47e..4c2bcc98 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -201,6 +201,15 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object bool urgent = c->type == C_VIEW ? view_is_urgent(c->sway_view) : container_has_urgent_child(c); json_object_object_add(object, "urgent", json_object_new_boolean(urgent)); + + if (c->type == C_VIEW) { + json_object *marks = json_object_new_array(); + list_t *view_marks = c->sway_view->marks; + for (int i = 0; i < view_marks->length; ++i) { + json_object_array_add(marks, json_object_new_string(view_marks->items[i])); + } + json_object_object_add(object, "marks", marks); + } } static void focus_inactive_children_iterator(struct sway_container *c, void *data) { diff --git a/sway/ipc-server.c b/sway/ipc-server.c index be703915..7d2d8969 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -3,12 +3,18 @@ // Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0) #define _XOPEN_SOURCE 700 #endif +#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> #include <json-c/json.h> #include <stdbool.h> #include <stdint.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> @@ -28,6 +34,7 @@ #include "sway/tree/view.h" #include "list.h" #include "log.h" +#include "util.h" static int ipc_socket = -1; static struct wl_event_source *ipc_event_source = NULL; @@ -291,13 +298,11 @@ void ipc_event_workspace(struct sway_container *old, wlr_log(WLR_DEBUG, "Sending workspace::%s event", change); json_object *obj = json_object_new_object(); json_object_object_add(obj, "change", json_object_new_string(change)); - if (strcmp("focus", change) == 0) { - if (old) { - json_object_object_add(obj, "old", - ipc_json_describe_container_recursive(old)); - } else { - json_object_object_add(obj, "old", NULL); - } + if (old) { + json_object_object_add(obj, "old", + ipc_json_describe_container_recursive(old)); + } else { + json_object_object_add(obj, "old", NULL); } if (new) { @@ -353,6 +358,104 @@ void ipc_event_mode(const char *mode, bool pango) { json_object_put(obj); } +void ipc_event_shutdown(const char *reason) { + if (!ipc_has_event_listeners(IPC_EVENT_SHUTDOWN)) { + return; + } + wlr_log(WLR_DEBUG, "Sending shutdown::%s event", reason); + + json_object *json = json_object_new_object(); + json_object_object_add(json, "change", json_object_new_string(reason)); + + const char *json_string = json_object_to_json_string(json); + ipc_send_event(json_string, IPC_EVENT_SHUTDOWN); + json_object_put(json); +} + +void ipc_event_binding(struct sway_binding *binding) { + if (!ipc_has_event_listeners(IPC_EVENT_BINDING)) { + return; + } + wlr_log(WLR_DEBUG, "Sending binding event"); + + json_object *json_binding = json_object_new_object(); + json_object_object_add(json_binding, "command", json_object_new_string(binding->command)); + + const char *names[10]; + int len = get_modifier_names(names, binding->modifiers); + json_object *modifiers = json_object_new_array(); + for (int i = 0; i < len; ++i) { + json_object_array_add(modifiers, json_object_new_string(names[i])); + } + json_object_object_add(json_binding, "event_state_mask", modifiers); + + json_object *input_codes = json_object_new_array(); + int input_code = 0; + json_object *symbols = json_object_new_array(); + json_object *symbol = NULL; + + if (binding->type == BINDING_KEYCODE) { // bindcode: populate input_codes + uint32_t keycode; + for (int i = 0; i < binding->keys->length; ++i) { + keycode = *(uint32_t *)binding->keys->items[i]; + json_object_array_add(input_codes, json_object_new_int(keycode)); + if (i == 0) { + input_code = keycode; + } + } + } else { // bindsym/mouse: populate symbols + uint32_t keysym; + char buffer[64]; + for (int i = 0; i < binding->keys->length; ++i) { + keysym = *(uint32_t *)binding->keys->items[i]; + if (keysym >= BTN_LEFT && keysym <= BTN_LEFT + 8) { + snprintf(buffer, 64, "button%u", keysym - BTN_LEFT + 1); + } else if (xkb_keysym_get_name(keysym, buffer, 64) < 0) { + continue; + } + + json_object *str = json_object_new_string(buffer); + if (i == 0) { + // str is owned by both symbol and symbols. Make sure + // to bump the ref count. + json_object_array_add(symbols, json_object_get(str)); + symbol = str; + } else { + json_object_array_add(symbols, str); + } + } + } + + json_object_object_add(json_binding, "input_codes", input_codes); + 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")); + + json_object *json = json_object_new_object(); + json_object_object_add(json, "change", json_object_new_string("run")); + json_object_object_add(json, "binding", json_binding); + const char *json_string = json_object_to_json_string(json); + ipc_send_event(json_string, IPC_EVENT_BINDING); + json_object_put(json); +} + +static void ipc_event_tick(const char *payload) { + if (!ipc_has_event_listeners(IPC_EVENT_TICK)) { + return; + } + wlr_log(WLR_DEBUG, "Sending tick event"); + + json_object *json = json_object_new_object(); + json_object_object_add(json, "first", json_object_new_boolean(false)); + json_object_object_add(json, "payload", json_object_new_string(payload)); + + const char *json_string = json_object_to_json_string(json); + ipc_send_event(json_string, IPC_EVENT_TICK); + json_object_put(json); +} + int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { struct ipc_client *client = data; @@ -494,6 +597,13 @@ void ipc_client_handle_command(struct ipc_client *client) { goto exit_cleanup; } + case IPC_SEND_TICK: + { + ipc_event_tick(buf); + ipc_send_reply(client, "{\"success\": true}", 17); + goto exit_cleanup; + } + case IPC_GET_OUTPUTS: { json_object *outputs = json_object_new_array(); @@ -540,6 +650,7 @@ void ipc_client_handle_command(struct ipc_client *client) { goto exit_cleanup; } + bool is_tick = false; // parse requested event types for (size_t i = 0; i < json_object_array_length(request); i++) { const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); @@ -549,12 +660,15 @@ void ipc_client_handle_command(struct ipc_client *client) { client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); } else if (strcmp(event_type, "mode") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_MODE); + } else if (strcmp(event_type, "shutdown") == 0) { + client->subscribed_events |= event_mask(IPC_EVENT_SHUTDOWN); } else if (strcmp(event_type, "window") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_WINDOW); - } else if (strcmp(event_type, "modifier") == 0) { - client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER); } else if (strcmp(event_type, "binding") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BINDING); + } else if (strcmp(event_type, "tick") == 0) { + client->subscribed_events |= event_mask(IPC_EVENT_TICK); + is_tick = true; } else { client_valid = ipc_send_reply(client, "{\"success\": false}", 18); @@ -566,6 +680,10 @@ void ipc_client_handle_command(struct ipc_client *client) { json_object_put(request); client_valid = ipc_send_reply(client, "{\"success\": true}", 17); + if (is_tick) { + client->current_command = IPC_EVENT_TICK; + ipc_send_reply(client, "{\"first\": true, \"payload\": \"\"}", 30); + } goto exit_cleanup; } diff --git a/sway/main.c b/sway/main.c index a20f1dac..477ffa5a 100644 --- a/sway/main.c +++ b/sway/main.c @@ -36,6 +36,7 @@ struct sway_server server; void sway_terminate(int exit_code) { terminate_request = true; exit_value = exit_code; + ipc_event_shutdown("exit"); wl_display_terminate(server.wl_display); } diff --git a/sway/meson.build b/sway/meson.build index 09bc40b8..a9503c3b 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -7,6 +7,7 @@ sway_sources = files( 'debug-tree.c', 'ipc-json.c', 'ipc-server.c', + 'scratchpad.c', 'security.c', 'desktop/desktop.c', @@ -17,7 +18,6 @@ sway_sources = files( 'desktop/transaction.c', 'desktop/xdg_shell_v6.c', 'desktop/xdg_shell.c', - 'desktop/xwayland.c', 'input/input-manager.c', 'input/seat.c', @@ -42,6 +42,7 @@ sway_sources = files( 'commands/exec_always.c', 'commands/floating.c', 'commands/floating_minmax_size.c', + 'commands/floating_modifier.c', 'commands/focus.c', 'commands/focus_follows_mouse.c', 'commands/focus_wrapping.c', @@ -66,6 +67,7 @@ sway_sources = files( 'commands/reload.c', 'commands/rename.c', 'commands/resize.c', + 'commands/scratchpad.c', 'commands/seat.c', 'commands/seat/attach.c', 'commands/seat/cursor.c', @@ -126,8 +128,10 @@ sway_sources = files( 'commands/input/scroll_method.c', 'commands/input/tap.c', 'commands/input/tap_button_map.c', + 'commands/input/xkb_capslock.c', 'commands/input/xkb_layout.c', 'commands/input/xkb_model.c', + 'commands/input/xkb_numlock.c', 'commands/input/xkb_options.c', 'commands/input/xkb_rules.c', 'commands/input/xkb_variant.c', @@ -162,10 +166,14 @@ sway_deps = [ server_protos, wayland_server, wlroots, - xcb, xkbcommon, ] +if get_option('enable-xwayland') + sway_sources += 'desktop/xwayland.c' + sway_deps += xcb +endif + executable( 'sway', sway_sources, diff --git a/sway/scratchpad.c b/sway/scratchpad.c new file mode 100644 index 00000000..b7d6fd99 --- /dev/null +++ b/sway/scratchpad.c @@ -0,0 +1,181 @@ +#define _XOPEN_SOURCE 700 +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include "sway/scratchpad.h" +#include "sway/input/seat.h" +#include "sway/tree/arrange.h" +#include "sway/tree/container.h" +#include "sway/tree/view.h" +#include "sway/tree/workspace.h" +#include "list.h" +#include "log.h" + +void scratchpad_add_container(struct sway_container *con) { + if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) { + return; + } + con->scratchpad = true; + list_add(root_container.sway_root->scratchpad, con); + + struct sway_container *parent = con->parent; + container_set_floating(con, true); + container_remove_child(con); + arrange_windows(parent); + + struct sway_seat *seat = input_manager_current_seat(input_manager); + seat_set_focus(seat, seat_get_focus_inactive(seat, parent)); +} + +void scratchpad_remove_container(struct sway_container *con) { + if (!sway_assert(con->scratchpad, "Container is not in scratchpad")) { + return; + } + con->scratchpad = false; + for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) { + if (root_container.sway_root->scratchpad->items[i] == con) { + list_del(root_container.sway_root->scratchpad, i); + break; + } + } +} + +/** + * Show a single scratchpad container. + * The container might be visible on another workspace already. + */ +static void scratchpad_show(struct sway_container *con) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *ws = seat_get_focus(seat); + if (ws->type != C_WORKSPACE) { + ws = container_parent(ws, C_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->sway_workspace->fullscreen) { + container_set_fullscreen(ws->sway_workspace->fullscreen, false); + } + + // Show the container + if (con->parent) { + container_remove_child(con); + } + container_add_child(ws->sway_workspace->floating, 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; + container_get_box(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) { + 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; + container_floating_move_to(con, new_lx, new_ly); + } + + arrange_windows(ws); + seat_set_focus(seat, seat_get_focus_inactive(seat, con)); + + container_set_dirty(con->parent); +} + +/** + * Hide a single scratchpad container. + * The container might not be the focused container (eg. when using criteria). + */ +static void scratchpad_hide(struct sway_container *con) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *focus = seat_get_focus(seat); + struct sway_container *ws = container_parent(con, C_WORKSPACE); + + container_remove_child(con); + arrange_windows(ws); + if (con == focus) { + seat_set_focus(seat, seat_get_focus_inactive(seat, ws)); + } + list_move_to_end(root_container.sway_root->scratchpad, con); +} + +void scratchpad_toggle_auto(void) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *focus = seat_get_focus(seat); + struct sway_container *ws = focus->type == C_WORKSPACE ? + focus : container_parent(focus, C_WORKSPACE); + + // If the focus is in a floating split container, + // operate on the split container instead of the child. + if (container_is_floating_or_child(focus)) { + while (focus->parent->layout != L_FLOATING) { + focus = focus->parent; + } + } + + + // Check if the currently focused window is a scratchpad window and should + // be hidden again. + if (focus->scratchpad) { + wlr_log(WLR_DEBUG, "Focus is a scratchpad window - hiding %s", + focus->name); + scratchpad_hide(focus); + return; + } + + // Check if there is an unfocused scratchpad window on the current workspace + // and focus it. + for (int i = 0; i < ws->sway_workspace->floating->children->length; ++i) { + struct sway_container *floater = + ws->sway_workspace->floating->children->items[i]; + if (floater->scratchpad && focus != floater) { + wlr_log(WLR_DEBUG, + "Focusing other scratchpad window (%s) in this workspace", + floater->name); + scratchpad_show(floater); + return; + } + } + + // Check if there is a visible scratchpad window on another workspace. + // In this case we move it to the current workspace. + for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) { + struct sway_container *con = + root_container.sway_root->scratchpad->items[i]; + if (con->parent) { + wlr_log(WLR_DEBUG, + "Moving a visible scratchpad window (%s) to this workspace", + con->name); + scratchpad_show(con); + return; + } + } + + // Take the container at the bottom of the scratchpad list + if (!sway_assert(root_container.sway_root->scratchpad->length, + "Scratchpad is empty")) { + return; + } + struct sway_container *con = root_container.sway_root->scratchpad->items[0]; + wlr_log(WLR_DEBUG, "Showing %s from list", con->name); + scratchpad_show(con); +} + +void scratchpad_toggle_container(struct sway_container *con) { + if (!sway_assert(con->scratchpad, "Container isn't in the scratchpad")) { + return; + } + + // Check if it matches a currently visible scratchpad window and hide it. + if (con->parent) { + scratchpad_hide(con); + return; + } + + scratchpad_show(con); +} diff --git a/sway/server.c b/sway/server.c index 91ae7c97..e8755360 100644 --- a/sway/server.c +++ b/sway/server.c @@ -26,7 +26,10 @@ #include "sway/input/input-manager.h" #include "sway/server.h" #include "sway/tree/layout.h" +#include "config.h" +#ifdef HAVE_XWAYLAND #include "sway/xwayland.h" +#endif bool server_privileged_prepare(struct sway_server *server) { wlr_log(WLR_DEBUG, "Preparing Wayland server initialization"); @@ -83,6 +86,7 @@ bool server_init(struct sway_server *server) { server->xdg_shell_surface.notify = handle_xdg_shell_surface; // TODO make xwayland optional +#ifdef HAVE_XWAYLAND server->xwayland.wlr_xwayland = wlr_xwayland_create(server->wl_display, server->compositor, true); wl_signal_add(&server->xwayland.wlr_xwayland->events.new_surface, @@ -103,6 +107,7 @@ bool server_init(struct sway_server *server) { image->width * 4, image->width, image->height, image->hotspot_x, image->hotspot_y); } +#endif // TODO: Integration with sway borders struct wlr_server_decoration_manager *deco_manager = diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd index b6391431..707c36af 100644 --- a/sway/sway-input.5.scd +++ b/sway/sway-input.5.scd @@ -33,6 +33,14 @@ For more information on these xkb configuration options, see *input* <identifier> xkb\_variant <variant> Sets the variant of the keyboard like _dvorak_ or _colemak_. +The following commands may only be used in the configuration file. + +*input* <identifier> xkb\_capslock enabled|disabled + Initially enables or disables CapsLock, the default is disabled. + +*input* <identifier> xkb\_numlock enabled|disabled + Initially enables or disables NumLock, the default is disabled. + ## MAPPING CONFIGURATION *input* <identifier> map\_to\_output <identifier> @@ -100,7 +108,7 @@ For more information on these xkb configuration options, see *input* <identifier> tap enabled|disabled Enables or disables tap for specified input device. -*input* <identifier> tap_button_map lrm|lmr +*input* <identifier> tap\_button\_map lrm|lmr Specifies which button mapping to use for tapping. _lrm_ treats 1 finger as left click, 2 fingers as right click, and 3 fingers as middle click. _lmr_ treats 1 finger as left click, 2 fingers as middle click, and 3 fingers as diff --git a/sway/sway.1.scd b/sway/sway.1.scd index 5b770cce..0c2ee974 100644 --- a/sway/sway.1.scd +++ b/sway/sway.1.scd @@ -92,4 +92,4 @@ source contributors. For more information about sway development, see # SEE ALSO -*sway*(5) *swaymsg*(1) *swaygrab*(1) *sway-input*(5) *sway-bar*(5) +*sway*(5) *swaymsg*(1) *sway-input*(5) *sway-bar*(5) diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c index 533cf71c..5452b13c 100644 --- a/sway/tree/arrange.c +++ b/sway/tree/arrange.c @@ -220,8 +220,22 @@ static void arrange_workspace(struct sway_container *workspace) { container_set_dirty(workspace); wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, workspace->x, workspace->y); - arrange_floating(workspace->sway_workspace->floating); - arrange_children_of(workspace); + if (workspace->sway_workspace->fullscreen) { + struct sway_container *fs = workspace->sway_workspace->fullscreen; + fs->x = workspace->parent->x; + fs->y = workspace->parent->y; + fs->width = workspace->parent->width; + fs->height = workspace->parent->height; + if (fs->type == C_VIEW) { + view_autoconfigure(fs->sway_view); + } else { + arrange_children_of(fs); + } + container_set_dirty(fs); + } else { + arrange_floating(workspace->sway_workspace->floating); + arrange_children_of(workspace); + } } static void arrange_output(struct sway_container *output) { diff --git a/sway/tree/container.c b/sway/tree/container.c index 4dbfbb29..46c54e2d 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -17,6 +17,7 @@ #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" +#include "sway/scratchpad.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/layout.h" @@ -61,13 +62,17 @@ void container_create_notify(struct sway_container *container) { // TODO send ipc event type based on the container type wl_signal_emit(&root_container.sway_root->events.new_container, container); - if (container->type == C_VIEW || container->type == C_CONTAINER) { + if (container->type == C_VIEW) { ipc_event_window(container, "new"); + } else if (container->type == C_WORKSPACE) { + ipc_event_workspace(NULL, container, "init"); } } -static void container_update_textures_recursive(struct sway_container *con) { - container_update_title_textures(con); +void container_update_textures_recursive(struct sway_container *con) { + if (con->type == C_CONTAINER || con->type == C_VIEW) { + container_update_title_textures(con); + } if (con->type == C_VIEW) { view_update_marks_textures(con->sway_view); @@ -76,6 +81,10 @@ static void container_update_textures_recursive(struct sway_container *con) { struct sway_container *child = con->children->items[i]; container_update_textures_recursive(child); } + + if (con->type == C_WORKSPACE) { + container_update_textures_recursive(con->sway_workspace->floating); + } } } @@ -139,8 +148,6 @@ struct sway_container *container_create(enum sway_container_type type) { static void container_workspace_free(struct sway_workspace *ws) { list_foreach(ws->output_priority, free); list_free(ws->output_priority); - ws->floating->destroying = true; - container_free(ws->floating); free(ws); } @@ -193,6 +200,9 @@ void container_free(struct sway_container *cont) { free(cont); } +static struct sway_container *container_destroy_noreaping( + struct sway_container *con); + static struct sway_container *container_workspace_destroy( struct sway_container *workspace) { if (!sway_assert(workspace, "cannot destroy null workspace")) { @@ -237,6 +247,8 @@ static struct sway_container *container_workspace_destroy( } } + container_destroy_noreaping(workspace->sway_workspace->floating); + return output; } @@ -271,7 +283,7 @@ static struct sway_container *container_output_destroy( container_remove_child(workspace); if (!workspace_is_empty(workspace)) { container_add_child(new_output, workspace); - ipc_event_workspace(workspace, NULL, "move"); + ipc_event_workspace(NULL, workspace, "move"); } else { container_destroy(workspace); } @@ -309,7 +321,13 @@ static struct sway_container *container_destroy_noreaping( } wl_signal_emit(&con->events.destroy, con); - ipc_event_window(con, "close"); + + // emit IPC event + if (con->type == C_VIEW) { + ipc_event_window(con, "close"); + } else if (con->type == C_WORKSPACE) { + ipc_event_workspace(NULL, con, "empty"); + } // The below functions move their children to somewhere else. if (con->type == C_OUTPUT) { @@ -323,9 +341,15 @@ static struct sway_container *container_destroy_noreaping( } } + container_end_mouse_operation(con); + con->destroying = true; container_set_dirty(con); + if (con->scratchpad) { + scratchpad_remove_container(con); + } + if (!con->parent) { return NULL; } @@ -398,6 +422,10 @@ struct sway_container *container_flatten(struct sway_container *container) { * This function just wraps container_destroy_noreaping(), then does reaping. */ struct sway_container *container_destroy(struct sway_container *con) { + if (con->is_fullscreen) { + struct sway_container *ws = container_parent(con, C_WORKSPACE); + ws->sway_workspace->fullscreen = NULL; + } struct sway_container *parent = container_destroy_noreaping(con); if (!parent) { @@ -507,7 +535,7 @@ struct sway_container *container_parent(struct sway_container *container, return container; } -static struct sway_container *container_at_view(struct sway_container *swayc, +struct sway_container *container_at_view(struct sway_container *swayc, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { if (!sway_assert(swayc->type == C_VIEW, "Expected a view")) { @@ -520,10 +548,12 @@ static struct sway_container *container_at_view(struct sway_container *swayc, double _sx, _sy; struct wlr_surface *_surface = NULL; switch (sview->type) { +#ifdef HAVE_XWAYLAND case SWAY_VIEW_XWAYLAND: _surface = wlr_surface_surface_at(sview->surface, view_sx, view_sy, &_sx, &_sy); break; +#endif case SWAY_VIEW_XDG_SHELL_V6: _surface = wlr_xdg_surface_v6_surface_at( sview->wlr_xdg_surface_v6, @@ -539,10 +569,15 @@ static struct sway_container *container_at_view(struct sway_container *swayc, *sx = _sx; *sy = _sy; *surface = _surface; + return swayc; } - return swayc; + return NULL; } +static struct sway_container *tiling_container_at( + struct sway_container *con, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy); + /** * container_at for a container with layout L_TABBED. */ @@ -569,7 +604,7 @@ static struct sway_container *container_at_tabbed(struct sway_container *parent, // Surfaces struct sway_container *current = seat_get_active_child(seat, parent); - return container_at(current, lx, ly, surface, sx, sy); + return tiling_container_at(current, lx, ly, surface, sx, sy); } /** @@ -594,7 +629,7 @@ static struct sway_container *container_at_stacked( // Surfaces struct sway_container *current = seat_get_active_child(seat, parent); - return container_at(current, lx, ly, surface, sx, sy); + return tiling_container_at(current, lx, ly, surface, sx, sy); } /** @@ -612,45 +647,13 @@ static struct sway_container *container_at_linear(struct sway_container *parent, .height = child->height, }; if (wlr_box_contains_point(&box, lx, ly)) { - return container_at(child, lx, ly, surface, sx, sy); + return tiling_container_at(child, lx, ly, surface, sx, sy); } } return NULL; } -struct sway_container *container_at(struct sway_container *parent, - double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy) { - if (!sway_assert(parent->type >= C_WORKSPACE, - "Expected workspace or deeper")) { - return NULL; - } - if (parent->type == C_VIEW) { - return container_at_view(parent, lx, ly, surface, sx, sy); - } - if (!parent->children->length) { - return NULL; - } - - switch (parent->layout) { - case L_HORIZ: - case L_VERT: - return container_at_linear(parent, lx, ly, surface, sx, sy); - case L_TABBED: - return container_at_tabbed(parent, lx, ly, surface, sx, sy); - case L_STACKED: - return container_at_stacked(parent, lx, ly, surface, sx, sy); - case L_FLOATING: - sway_assert(false, "Didn't expect to see floating here"); - return NULL; - case L_NONE: - return NULL; - } - - return NULL; -} - -struct sway_container *floating_container_at(double lx, double ly, +static struct sway_container *floating_container_at(double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { for (int i = 0; i < root_container.children->length; ++i) { struct sway_container *output = root_container.children->items[i]; @@ -672,7 +675,8 @@ struct sway_container *floating_container_at(double lx, double ly, .height = floater->height, }; if (wlr_box_contains_point(&box, lx, ly)) { - return container_at(floater, lx, ly, surface, sx, sy); + return tiling_container_at(floater, lx, ly, + surface, sx, sy); } } } @@ -680,6 +684,90 @@ struct sway_container *floating_container_at(double lx, double ly, return NULL; } +static struct sway_container *tiling_container_at( + struct sway_container *con, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + if (con->type == C_VIEW) { + return container_at_view(con, lx, ly, surface, sx, sy); + } + if (!con->children->length) { + return NULL; + } + + switch (con->layout) { + case L_HORIZ: + case L_VERT: + return container_at_linear(con, lx, ly, surface, sx, sy); + case L_TABBED: + return container_at_tabbed(con, lx, ly, surface, sx, sy); + case L_STACKED: + return container_at_stacked(con, lx, ly, surface, sx, sy); + case L_FLOATING: + sway_assert(false, "Didn't expect to see floating here"); + return NULL; + case L_NONE: + return NULL; + } + return NULL; +} + +static bool surface_is_popup(struct wlr_surface *surface) { + if (wlr_surface_is_xdg_surface(surface)) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_wlr_surface(surface); + while (xdg_surface) { + if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { + return true; + } + xdg_surface = xdg_surface->toplevel->parent; + } + return false; + } + + if (wlr_surface_is_xdg_surface_v6(surface)) { + struct wlr_xdg_surface_v6 *xdg_surface_v6 = + wlr_xdg_surface_v6_from_wlr_surface(surface); + while (xdg_surface_v6) { + if (xdg_surface_v6->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) { + return true; + } + xdg_surface_v6 = xdg_surface_v6->toplevel->parent; + } + return false; + } + + return false; +} + +struct sway_container *container_at(struct sway_container *workspace, + double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) { + return NULL; + } + struct sway_container *c; + // Focused view's popups + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *focus = + seat_get_focus_inactive(seat, &root_container); + if (focus && focus->type == C_VIEW) { + container_at_view(focus, lx, ly, surface, sx, sy); + if (*surface && surface_is_popup(*surface)) { + return focus; + } + *surface = NULL; + } + // Floating + if ((c = floating_container_at(lx, ly, surface, sx, sy))) { + return c; + } + // Tiling + if ((c = tiling_container_at(workspace, lx, ly, surface, sx, sy))) { + return c; + } + return NULL; +} + void container_for_each_descendant_dfs(struct sway_container *container, void (*f)(struct sway_container *container, void *data), void *data) { @@ -934,36 +1022,104 @@ size_t container_titlebar_height() { return config->font_height + TITLEBAR_V_PADDING * 2; } +void container_init_floating(struct sway_container *con) { + if (!sway_assert(con->type == C_VIEW || con->type == C_CONTAINER, + "Expected a view or container")) { + return; + } + struct sway_container *ws = container_parent(con, C_WORKSPACE); + int min_width, min_height; + int max_width, 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 = ws->width * 0.6666; + } 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 = ws->height * 0.6666; + } else { + max_height = config->floating_maximum_height; + } + + if (con->type == C_CONTAINER) { + con->width = max_width; + con->height = max_height; + con->x = ws->x + (ws->width - con->width) / 2; + con->y = ws->y + (ws->height - con->height) / 2; + } else { + struct sway_view *view = con->sway_view; + view->width = fmax(min_width, fmin(view->natural_width, max_width)); + view->height = fmax(min_height, fmin(view->natural_height, max_height)); + view->x = ws->x + (ws->width - view->width) / 2; + view->y = ws->y + (ws->height - view->height) / 2; + + // If the view's border is B_NONE then these properties are ignored. + view->border_top = view->border_bottom = true; + view->border_left = view->border_right = true; + + container_set_geometry_from_floating_view(view->swayc); + } +} + void container_set_floating(struct sway_container *container, bool enable) { if (container_is_floating(container) == enable) { return; } - struct sway_container *workspace = container_parent(container, C_WORKSPACE); struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *workspace = container_parent(container, C_WORKSPACE); if (enable) { container_remove_child(container); container_add_child(workspace->sway_workspace->floating, container); + container_init_floating(container); if (container->type == C_VIEW) { - view_init_floating(container->sway_view); view_set_tiled(container->sway_view, false); } - seat_set_focus(seat, seat_get_focus_inactive(seat, container)); - container_reap_empty_recursive(workspace); } else { // Returning to tiled + if (container->scratchpad) { + scratchpad_remove_container(container); + } container_remove_child(container); - container_add_child(workspace, container); + struct sway_container *reference = + seat_get_focus_inactive_tiling(seat, workspace); + if (reference->type == C_VIEW) { + reference = reference->parent; + } + container_add_child(reference, container); container->width = container->parent->width; container->height = container->parent->height; if (container->type == C_VIEW) { view_set_tiled(container->sway_view, true); } container->is_sticky = false; - container_reap_empty_recursive(workspace->sway_workspace->floating); } + container_end_mouse_operation(container); + ipc_event_window(container, "floating"); } @@ -1009,7 +1165,7 @@ void container_get_box(struct sway_container *container, struct wlr_box *box) { /** * Translate the container's position as well as all children. */ -static void container_floating_translate(struct sway_container *con, +void container_floating_translate(struct sway_container *con, double x_amount, double y_amount) { con->x += x_amount; con->y += y_amount; @@ -1105,3 +1261,110 @@ static bool find_urgent_iterator(struct sway_container *con, bool container_has_urgent_child(struct sway_container *container) { return container_find(container, find_urgent_iterator, NULL); } + +void container_end_mouse_operation(struct sway_container *container) { + struct sway_seat *seat; + wl_list_for_each(seat, &input_manager->seats, link) { + if (seat->op_container == container) { + seat_end_mouse_operation(seat); + } + } +} + +static void set_fullscreen_iterator(struct sway_container *con, void *data) { + if (con->type != C_VIEW) { + return; + } + if (con->sway_view->impl->set_fullscreen) { + bool *enable = data; + con->sway_view->impl->set_fullscreen(con->sway_view, *enable); + } +} + +void container_set_fullscreen(struct sway_container *container, bool enable) { + if (container->is_fullscreen == enable) { + return; + } + + struct sway_container *workspace = container_parent(container, C_WORKSPACE); + if (enable && workspace->sway_workspace->fullscreen) { + container_set_fullscreen(workspace->sway_workspace->fullscreen, false); + } + + container_for_each_descendant_dfs(container, + set_fullscreen_iterator, &enable); + + container->is_fullscreen = enable; + + if (enable) { + workspace->sway_workspace->fullscreen = container; + container->saved_x = container->x; + container->saved_y = container->y; + container->saved_width = container->width; + container->saved_height = container->height; + + struct sway_seat *seat; + struct sway_container *focus, *focus_ws; + wl_list_for_each(seat, &input_manager->seats, link) { + focus = seat_get_focus(seat); + if (focus) { + focus_ws = focus; + if (focus_ws->type != C_WORKSPACE) { + focus_ws = container_parent(focus_ws, C_WORKSPACE); + } + if (focus_ws == workspace) { + seat_set_focus(seat, container); + } + } + } + } else { + workspace->sway_workspace->fullscreen = NULL; + if (container_is_floating(container)) { + container->x = container->saved_x; + container->y = container->saved_y; + container->width = container->saved_width; + container->height = container->saved_height; + } else { + container->width = container->saved_width; + container->height = container->saved_height; + } + } + + container_end_mouse_operation(container); + + ipc_event_window(container, "fullscreen_mode"); +} + +bool container_is_floating_or_child(struct sway_container *container) { + do { + if (container->parent && container->parent->layout == L_FLOATING) { + return true; + } + container = container->parent; + } while (container && container->type != C_WORKSPACE); + + return false; +} + +bool container_is_fullscreen_or_child(struct sway_container *container) { + do { + if (container->is_fullscreen) { + return true; + } + container = container->parent; + } while (container && container->type != C_WORKSPACE); + + return false; +} + +struct sway_container *container_wrap_children(struct sway_container *parent) { + struct sway_container *middle = container_create(C_CONTAINER); + middle->layout = parent->layout; + while (parent->children->length) { + struct sway_container *child = parent->children->items[0]; + container_remove_child(child); + container_add_child(middle, child); + } + container_add_child(parent, middle); + return middle; +} diff --git a/sway/tree/layout.c b/sway/tree/layout.c index 1f898f8a..1f82e534 100644 --- a/sway/tree/layout.c +++ b/sway/tree/layout.c @@ -6,6 +6,7 @@ #include <string.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output_layout.h> +#include "config.h" #include "sway/debug.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" @@ -39,9 +40,12 @@ void layout_init(void) { root_container.sway_root = calloc(1, sizeof(*root_container.sway_root)); root_container.sway_root->output_layout = wlr_output_layout_create(); wl_list_init(&root_container.sway_root->outputs); +#ifdef HAVE_XWAYLAND wl_list_init(&root_container.sway_root->xwayland_unmanaged); +#endif wl_list_init(&root_container.sway_root->drag_icons); wl_signal_init(&root_container.sway_root->events.new_container); + root_container.sway_root->scratchpad = create_list(); root_container.sway_root->output_layout_change.notify = output_layout_handle_change; @@ -62,10 +66,9 @@ static int index_child(const struct sway_container *child) { static void container_handle_fullscreen_reparent(struct sway_container *con, struct sway_container *old_parent) { - if (con->type != C_VIEW || !con->sway_view->is_fullscreen) { + if (!con->is_fullscreen) { return; } - struct sway_view *view = con->sway_view; struct sway_container *old_workspace = old_parent; if (old_workspace && old_workspace->type != C_WORKSPACE) { old_workspace = container_parent(old_workspace, C_WORKSPACE); @@ -81,19 +84,27 @@ static void container_handle_fullscreen_reparent(struct sway_container *con, // Mark the new workspace as fullscreen if (new_workspace->sway_workspace->fullscreen) { - view_set_fullscreen(new_workspace->sway_workspace->fullscreen, false); + container_set_fullscreen( + new_workspace->sway_workspace->fullscreen, false); } - new_workspace->sway_workspace->fullscreen = view; - // Resize view to new output dimensions + new_workspace->sway_workspace->fullscreen = con; + + // Resize container to new output dimensions struct sway_container *output = new_workspace->parent; - view->x = output->x; - view->y = output->y; - view->width = output->width; - view->height = output->height; con->x = output->x; con->y = output->y; con->width = output->width; con->height = output->height; + + if (con->type == C_VIEW) { + struct sway_view *view = con->sway_view; + view->x = output->x; + view->y = output->y; + view->width = output->width; + view->height = output->height; + } else { + arrange_windows(new_workspace); + } } void container_insert_child(struct sway_container *parent, @@ -135,10 +146,14 @@ void container_add_child(struct sway_container *parent, list_add(parent->children, child); child->parent = parent; container_handle_fullscreen_reparent(child, old_parent); + if (old_parent) { + container_set_dirty(old_parent); + } + container_set_dirty(child); } struct sway_container *container_remove_child(struct sway_container *child) { - if (child->type == C_VIEW && child->sway_view->is_fullscreen) { + if (child->is_fullscreen) { struct sway_container *workspace = container_parent(child, C_WORKSPACE); workspace->sway_workspace->fullscreen = NULL; } @@ -153,6 +168,9 @@ struct sway_container *container_remove_child(struct sway_container *child) { child->parent = NULL; container_notify_subtree_changed(parent); + container_set_dirty(parent); + container_set_dirty(child); + return parent; } @@ -199,7 +217,9 @@ void container_move_to(struct sway_container *container, container_sort_workspaces(new_parent); seat_set_focus(seat, new_parent); workspace_output_raise_priority(container, old_parent, new_parent); - ipc_event_workspace(container, NULL, "move"); + ipc_event_workspace(NULL, container, "move"); + } else if (container->type == C_VIEW) { + ipc_event_window(container, "move"); } container_notify_subtree_changed(old_parent); container_notify_subtree_changed(new_parent); @@ -218,10 +238,10 @@ void container_move_to(struct sway_container *container, if (focus_ws->type != C_WORKSPACE) { focus_ws = container_parent(focus_ws, C_WORKSPACE); } - seat_set_focus(seat, - new_workspace->sway_workspace->fullscreen->swayc); - if (focus_ws != new_workspace) { - seat_set_focus(seat, focus); + if (focus_ws == new_workspace) { + struct sway_container *new_focus = seat_get_focus_inactive(seat, + new_workspace->sway_workspace->fullscreen); + seat_set_focus(seat, new_focus); } } } @@ -364,10 +384,18 @@ void container_move(struct sway_container *container, struct sway_container *sibling = NULL; struct sway_container *current = container; struct sway_container *parent = current->parent; + struct sway_container *top = &root_container; // If moving a fullscreen view, only consider outputs - if (container->type == C_VIEW && container->sway_view->is_fullscreen) { + if (container->is_fullscreen) { current = container_parent(container, C_OUTPUT); + } else if (container_is_fullscreen_or_child(container) || + container_is_floating_or_child(container)) { + // If we've fullscreened a split container, only allow the child to move + // around within the fullscreen parent. + // Same with floating a split container. + struct sway_container *ws = container_parent(container, C_WORKSPACE); + top = ws->sway_workspace->fullscreen; } struct sway_container *new_parent = container_flatten(parent); @@ -377,7 +405,7 @@ void container_move(struct sway_container *container, } while (!sibling) { - if (current->type == C_ROOT) { + if (current == top) { return; } @@ -441,8 +469,12 @@ void container_move(struct sway_container *container, if ((index == parent->children->length - 1 && offs > 0) || (index == 0 && offs < 0)) { if (current->parent == container->parent) { - if (parent->layout == L_TABBED - || parent->layout == L_STACKED) { + if (parent->parent->layout == L_FLOATING) { + return; + } + if (!parent->is_fullscreen && + (parent->layout == L_TABBED || + parent->layout == L_STACKED)) { move_out_of_tabs_stacks(container, current, move_dir, offs); return; @@ -463,10 +495,14 @@ void container_move(struct sway_container *container, sibling = parent->children->items[index + offs]; wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id); } - } else if (parent->layout == L_TABBED - || parent->layout == L_STACKED) { + } else if (!parent->is_fullscreen && + parent->parent->layout != L_FLOATING && + (parent->layout == L_TABBED || + parent->layout == L_STACKED)) { move_out_of_tabs_stacks(container, current, move_dir, offs); return; + } else if (parent->parent->layout == L_FLOATING) { + return; } else { wlr_log(WLR_DEBUG, "Moving up to find a parallel container"); current = current->parent; @@ -544,6 +580,10 @@ void container_move(struct sway_container *container, container_notify_subtree_changed(old_parent); container_notify_subtree_changed(container->parent); + if (container->type == C_VIEW) { + ipc_event_window(container, "move"); + } + if (old_parent) { seat_set_focus(config->handler_context.seat, old_parent); seat_set_focus(config->handler_context.seat, container); @@ -558,10 +598,11 @@ void container_move(struct sway_container *container, next_ws = container_parent(next_ws, C_WORKSPACE); } if (last_ws && next_ws && last_ws != next_ws) { - ipc_event_workspace(last_ws, container, "focus"); + ipc_event_workspace(last_ws, next_ws, "focus"); workspace_detect_urgent(last_ws); workspace_detect_urgent(next_ws); } + container_end_mouse_operation(container); } enum sway_container_layout container_get_default_layout( @@ -691,22 +732,18 @@ struct sway_container *container_get_in_direction( enum movement_direction dir) { struct sway_container *parent = container->parent; - if (container_is_floating(container)) { - return NULL; + if (dir == MOVE_CHILD) { + return seat_get_focus_inactive(seat, container); } - - if (container->type == C_VIEW && container->sway_view->is_fullscreen) { - if (dir == MOVE_PARENT || dir == MOVE_CHILD) { + if (container->is_fullscreen) { + if (dir == MOVE_PARENT) { return NULL; } container = container_parent(container, C_OUTPUT); parent = container->parent; } else { - if (dir == MOVE_CHILD) { - return seat_get_focus_inactive(seat, container); - } if (dir == MOVE_PARENT) { - if (parent->type == C_OUTPUT) { + if (parent->type == C_OUTPUT || container_is_floating(container)) { return NULL; } else { return parent; @@ -755,7 +792,8 @@ struct sway_container *container_get_in_direction( } sway_assert(next_workspace, "Next container has no workspace"); if (next_workspace->sway_workspace->fullscreen) { - return next_workspace->sway_workspace->fullscreen->swayc; + return seat_get_focus_inactive(seat, + next_workspace->sway_workspace->fullscreen); } if (next->children && next->children->length) { // TODO consider floating children as well @@ -963,13 +1001,13 @@ static void swap_focus(struct sway_container *con1, if (focus == con1 && (con2->parent->layout == L_TABBED || con2->parent->layout == L_STACKED)) { if (workspace_is_visible(ws2)) { - seat_set_focus_warp(seat, con2, false); + seat_set_focus_warp(seat, con2, false, true); } seat_set_focus(seat, ws1 != ws2 ? con2 : con1); } else if (focus == con2 && (con1->parent->layout == L_TABBED || con1->parent->layout == L_STACKED)) { if (workspace_is_visible(ws1)) { - seat_set_focus_warp(seat, con1, false); + seat_set_focus_warp(seat, con1, false, true); } seat_set_focus(seat, ws1 != ws2 ? con1 : con2); } else if (ws1 != ws2) { @@ -1002,13 +1040,13 @@ void container_swap(struct sway_container *con1, struct sway_container *con2) { wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id); - int fs1 = con1->type == C_VIEW && con1->sway_view->is_fullscreen; - int fs2 = con2->type == C_VIEW && con2->sway_view->is_fullscreen; + int fs1 = con1->is_fullscreen; + int fs2 = con2->is_fullscreen; if (fs1) { - view_set_fullscreen(con1->sway_view, false); + container_set_fullscreen(con1, false); } if (fs2) { - view_set_fullscreen(con2->sway_view, false); + container_set_fullscreen(con2, false); } struct sway_seat *seat = input_manager_get_default_seat(input_manager); @@ -1041,10 +1079,10 @@ void container_swap(struct sway_container *con1, struct sway_container *con2) { prev_workspace_name = stored_prev_name; } - if (fs1 && con2->type == C_VIEW) { - view_set_fullscreen(con2->sway_view, true); + if (fs1) { + container_set_fullscreen(con2, true); } - if (fs2 && con1->type == C_VIEW) { - view_set_fullscreen(con1->sway_view, true); + if (fs2) { + container_set_fullscreen(con1, true); } } diff --git a/sway/tree/output.c b/sway/tree/output.c index da535c18..31e3bf9b 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -22,7 +22,7 @@ static void restore_workspaces(struct sway_container *output) { if (highest == output) { container_remove_child(ws); container_add_child(output, ws); - ipc_event_workspace(ws, NULL, "move"); + ipc_event_workspace(NULL, ws, "move"); j--; } } diff --git a/sway/tree/view.c b/sway/tree/view.c index 7881e6d7..97318daa 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -2,7 +2,12 @@ #include <stdlib.h> #include <wayland-server.h> #include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_buffer.h> #include <wlr/types/wlr_output_layout.h> +#include "config.h" +#ifdef HAVE_XWAYLAND +#include <wlr/xwayland.h> +#endif #include "list.h" #include "log.h" #include "sway/criteria.h" @@ -107,14 +112,14 @@ const char *view_get_instance(struct sway_view *view) { } return NULL; } - +#ifdef HAVE_XWAYLAND uint32_t view_get_x11_window_id(struct sway_view *view) { if (view->impl->get_int_prop) { return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID); } return 0; } - +#endif const char *view_get_window_role(struct sway_view *view) { if (view->impl->get_string_prop) { return view->impl->get_string_prop(view, VIEW_PROP_WINDOW_ROLE); @@ -135,12 +140,27 @@ const char *view_get_shell(struct sway_view *view) { return "xdg_shell_v6"; case SWAY_VIEW_XDG_SHELL: return "xdg_shell"; +#ifdef HAVE_XWAYLAND case SWAY_VIEW_XWAYLAND: return "xwayland"; +#endif } return "unknown"; } +void view_get_constraints(struct sway_view *view, double *min_width, + double *max_width, double *min_height, double *max_height) { + if (view->impl->get_constraints) { + view->impl->get_constraints(view, + min_width, max_width, min_height, max_height); + } else { + *min_width = DBL_MIN; + *max_width = DBL_MAX; + *min_height = DBL_MIN; + *max_height = DBL_MAX; + } +} + uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, int height) { if (view->impl->configure) { @@ -149,55 +169,6 @@ uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, return 0; } -void view_init_floating(struct sway_view *view) { - struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); - int min_width, min_height; - int max_width, 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 = ws->width * 0.6666; - } 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 = ws->height * 0.6666; - } else { - max_height = config->floating_maximum_height; - } - - view->width = fmax(min_width, fmin(view->natural_width, max_width)); - view->height = fmax(min_height, fmin(view->natural_height, max_height)); - view->x = ws->x + (ws->width - view->width) / 2; - view->y = ws->y + (ws->height - view->height) / 2; - - // If the view's border is B_NONE then these properties are ignored. - view->border_top = view->border_bottom = true; - view->border_left = view->border_right = true; - - container_set_geometry_from_floating_view(view->swayc); -} - void view_autoconfigure(struct sway_view *view) { if (!sway_assert(view->swayc, "Called view_autoconfigure() on a view without a swayc")) { @@ -206,7 +177,7 @@ void view_autoconfigure(struct sway_view *view) { struct sway_container *output = container_parent(view->swayc, C_OUTPUT); - if (view->is_fullscreen) { + if (view->swayc->is_fullscreen) { view->x = output->x; view->y = output->y; view->width = output->width; @@ -214,10 +185,6 @@ void view_autoconfigure(struct sway_view *view) { return; } - if (container_is_floating(view->swayc)) { - return; - } - struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); int other_views = 0; @@ -330,72 +297,18 @@ void view_set_tiled(struct sway_view *view, bool tiled) { } } -void view_set_fullscreen(struct sway_view *view, bool fullscreen) { - if (view->is_fullscreen == fullscreen) { - return; - } - - struct sway_container *workspace = - container_parent(view->swayc, C_WORKSPACE); - - if (view->impl->set_fullscreen) { - view->impl->set_fullscreen(view, fullscreen); - } - - view->is_fullscreen = fullscreen; - - if (fullscreen) { - if (workspace->sway_workspace->fullscreen) { - view_set_fullscreen(workspace->sway_workspace->fullscreen, false); - } - workspace->sway_workspace->fullscreen = view; - view->saved_x = view->x; - view->saved_y = view->y; - view->saved_width = view->width; - view->saved_height = view->height; - view->swayc->saved_x = view->swayc->x; - view->swayc->saved_y = view->swayc->y; - view->swayc->saved_width = view->swayc->width; - view->swayc->saved_height = view->swayc->height; - - struct sway_seat *seat; - struct sway_container *focus, *focus_ws; - wl_list_for_each(seat, &input_manager->seats, link) { - focus = seat_get_focus(seat); - if (focus) { - focus_ws = focus; - if (focus && focus_ws->type != C_WORKSPACE) { - focus_ws = container_parent(focus_ws, C_WORKSPACE); - } - seat_set_focus(seat, view->swayc); - if (focus_ws != workspace) { - seat_set_focus(seat, focus); - } - } - } - } else { - workspace->sway_workspace->fullscreen = NULL; - if (container_is_floating(view->swayc)) { - view->x = view->saved_x; - view->y = view->saved_y; - view->width = view->saved_width; - view->height = view->saved_height; - container_set_geometry_from_floating_view(view->swayc); - } else { - view->swayc->width = view->swayc->saved_width; - view->swayc->height = view->swayc->saved_height; - } - } - - ipc_event_window(view->swayc, "fullscreen_mode"); -} - void view_close(struct sway_view *view) { if (view->impl->close) { view->impl->close(view); } } +void view_close_popups(struct sway_view *view) { + if (view->impl->close_popups) { + view->impl->close_popups(view); + } +} + void view_damage_from(struct sway_view *view) { for (int i = 0; i < root_container.children->length; ++i) { struct sway_container *cont = root_container.children->items[i]; @@ -426,6 +339,16 @@ void view_for_each_surface(struct sway_view *view, } } +void view_for_each_popup(struct sway_view *view, + wlr_surface_iterator_func_t iterator, void *user_data) { + if (!view->surface) { + return; + } + if (view->impl->for_each_popup) { + view->impl->for_each_popup(view, iterator, user_data); + } +} + static void view_subsurface_create(struct sway_view *view, struct wlr_subsurface *subsurface); @@ -521,12 +444,82 @@ void view_execute_criteria(struct sway_view *view) { seat_set_focus(seat, prior_focus); } +static struct sway_container *select_workspace(struct sway_view *view) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + + // Check if there's any `assign` criteria for the view + list_t *criterias = criteria_for_view(view, + CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT); + struct sway_container *ws = NULL; + for (int i = 0; i < criterias->length; ++i) { + struct criteria *criteria = criterias->items[i]; + if (criteria->type == CT_ASSIGN_WORKSPACE) { + ws = workspace_by_name(criteria->target); + if (!ws) { + ws = workspace_create(NULL, criteria->target); + } + break; + } else { + // CT_ASSIGN_OUTPUT + struct sway_container *output = output_by_name(criteria->target); + if (output) { + ws = seat_get_active_child(seat, output); + break; + } + } + } + list_free(criterias); + if (ws) { + return ws; + } + + // Check if there's a PID mapping + pid_t pid; +#ifdef HAVE_XWAYLAND + if (view->type == SWAY_VIEW_XWAYLAND) { + struct wlr_xwayland_surface *surf = + wlr_xwayland_surface_from_wlr_surface(view->surface); + pid = surf->pid; + } else { + struct wl_client *client = + wl_resource_get_client(view->surface->resource); + wl_client_get_credentials(client, &pid, NULL, NULL); + } +#else + struct wl_client *client = + wl_resource_get_client(view->surface->resource); + wl_client_get_credentials(client, &pid, NULL, NULL); +#endif + ws = workspace_for_pid(pid); + if (ws) { + return ws; + } + + // Use the focused workspace + ws = seat_get_focus_inactive(seat, &root_container); + if (ws->type != C_WORKSPACE) { + ws = container_parent(ws, C_WORKSPACE); + } + return ws; +} + static bool should_focus(struct sway_view *view) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *prev_focus = + seat_get_focus_inactive(seat, &root_container); + struct sway_container *prev_ws = prev_focus->type == C_WORKSPACE ? + prev_focus : container_parent(prev_focus, C_WORKSPACE); + struct sway_container *map_ws = container_parent(view->swayc, C_WORKSPACE); + + // Views can only take focus if they are mapped into the active workspace + if (prev_ws != map_ws) { + return false; + } + // If the view is the only one in the focused workspace, it'll get focus // regardless of any no_focus criteria. struct sway_container *parent = view->swayc->parent; - struct sway_seat *seat = input_manager_current_seat(input_manager); - if (parent->type == C_WORKSPACE && seat_get_focus(seat) == parent) { + if (parent->type == C_WORKSPACE && prev_focus == parent) { size_t num_children = parent->children->length + parent->sway_workspace->floating->children->length; if (num_children == 1) { @@ -545,42 +538,19 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { if (!sway_assert(view->surface == NULL, "cannot map mapped view")) { return; } + view->surface = wlr_surface; struct sway_seat *seat = input_manager_current_seat(input_manager); - struct sway_container *focus = - seat_get_focus_inactive(seat, &root_container); - struct sway_container *cont = NULL; + struct sway_container *ws = select_workspace(view); + struct sway_container *target_sibling = seat_get_focus_inactive(seat, ws); - // Check if there's any `assign` criteria for the view - list_t *criterias = criteria_for_view(view, - CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT); - struct sway_container *workspace = NULL; - if (criterias->length) { - struct criteria *criteria = criterias->items[0]; - if (criteria->type == CT_ASSIGN_WORKSPACE) { - workspace = workspace_by_name(criteria->target); - if (!workspace) { - workspace = workspace_create(NULL, criteria->target); - } - focus = seat_get_focus_inactive(seat, workspace); - } else { - // CT_ASSIGN_OUTPUT - struct sway_container *output = output_by_name(criteria->target); - if (output) { - focus = seat_get_focus_inactive(seat, output); - } - } - } // If we're about to launch the view into the floating container, then // launch it as a tiled view in the root of the workspace instead. - if (container_is_floating(focus)) { - focus = focus->parent->parent; + if (container_is_floating(target_sibling)) { + target_sibling = target_sibling->parent->parent; } - list_free(criterias); - cont = container_view_create(focus, view); - view->surface = wlr_surface; - view->swayc = cont; + view->swayc = container_view_create(target_sibling, view); view_init_subsurfaces(view, wlr_surface); wl_signal_add(&wlr_surface->events.new_subsurface, @@ -601,10 +571,7 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { } if (should_focus(view)) { - input_manager_set_focus(input_manager, cont); - if (workspace) { - workspace_switch(workspace); - } + input_manager_set_focus(input_manager, view->swayc); } view_update_title(view, false); @@ -628,10 +595,8 @@ void view_unmap(struct sway_view *view) { struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); struct sway_container *parent; - if (view->is_fullscreen) { - ws->sway_workspace->fullscreen = NULL; + if (container_is_fullscreen_or_child(view->swayc)) { parent = container_destroy(view->swayc); - arrange_windows(ws->parent); } else { parent = container_destroy(view->swayc); @@ -784,11 +749,13 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { wlr_xdg_surface_v6_from_wlr_surface(wlr_surface); return view_from_wlr_xdg_surface_v6(xdg_surface_v6); } +#ifdef HAVE_XWAYLAND if (wlr_surface_is_xwayland_surface(wlr_surface)) { struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_from_wlr_surface(wlr_surface); return view_from_wlr_xwayland_surface(xsurface); } +#endif if (wlr_surface_is_subsurface(wlr_surface)) { struct wlr_subsurface *subsurface = wlr_subsurface_from_wlr_surface(wlr_surface); @@ -915,6 +882,8 @@ void view_update_title(struct sway_view *view, bool force) { // Update title after the global font height is updated container_update_title_textures(view->swayc); + + ipc_event_window(view->swayc, "title"); } static bool find_by_mark_iterator(struct sway_container *con, @@ -937,6 +906,7 @@ bool view_find_and_unmark(char *mark) { free(view_mark); list_del(view->marks, i); view_update_marks_textures(view); + ipc_event_window(container, "mark"); return true; } } @@ -944,11 +914,10 @@ bool view_find_and_unmark(char *mark) { } void view_clear_marks(struct sway_view *view) { - for (int i = 0; i < view->marks->length; ++i) { - free(view->marks->items[i]); + while (view->marks->length) { + list_del(view->marks, 0); + ipc_event_window(view->swayc, "mark"); } - list_free(view->marks); - view->marks = create_list(); } bool view_has_mark(struct sway_view *view, char *mark) { @@ -961,6 +930,11 @@ bool view_has_mark(struct sway_view *view, char *mark) { return false; } +void view_add_mark(struct sway_view *view, char *mark) { + list_add(view->marks, strdup(mark)); + ipc_event_window(view->swayc, "mark"); +} + static void update_marks_texture(struct sway_view *view, struct wlr_texture **texture, struct border_colors *class) { struct sway_container *output = container_parent(view->swayc, C_OUTPUT); @@ -1055,6 +1029,9 @@ bool view_is_visible(struct sway_view *view) { } struct sway_container *workspace = container_parent(view->swayc, C_WORKSPACE); + if (!workspace) { + return false; + } // Determine if view is nested inside a floating container which is sticky. // A simple floating view will have this ancestry: // C_VIEW -> floating -> workspace @@ -1079,7 +1056,8 @@ bool view_is_visible(struct sway_view *view) { container = container->parent; } // Check view isn't hidden by another fullscreen view - if (workspace->sway_workspace->fullscreen && !view->is_fullscreen) { + if (workspace->sway_workspace->fullscreen && + !container_is_fullscreen_or_child(view->swayc)) { return false; } // Check the workspace is visible @@ -1117,3 +1095,22 @@ void view_set_urgent(struct sway_view *view, bool enable) { bool view_is_urgent(struct sway_view *view) { return view->urgent.tv_sec || view->urgent.tv_nsec; } + +void view_remove_saved_buffer(struct sway_view *view) { + if (!sway_assert(view->saved_buffer, "Expected a saved buffer")) { + return; + } + wlr_buffer_unref(view->saved_buffer); + view->saved_buffer = NULL; +} + +void view_save_buffer(struct sway_view *view) { + if (!sway_assert(!view->saved_buffer, "Didn't expect saved buffer")) { + view_remove_saved_buffer(view); + } + if (view->surface && wlr_surface_has_buffer(view->surface)) { + view->saved_buffer = wlr_buffer_ref(view->surface->buffer); + view->saved_buffer_width = view->surface->current.width; + view->saved_buffer_height = view->surface->current.height; + } +} diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 622f01ec..588e2aae 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -9,6 +9,7 @@ #include "sway/input/input-manager.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" #include "sway/tree/view.h" @@ -107,96 +108,100 @@ static bool workspace_valid_on_output(const char *output_name, return true; } -char *workspace_next_name(const char *output_name) { - wlr_log(WLR_DEBUG, "Workspace: Generating new workspace name for output %s", - output_name); - // Scan all workspace bindings to find the next available workspace name, - // if none are found/available then default to a number - struct sway_mode *mode = config->current_mode; +static void workspace_name_from_binding(const struct sway_binding * binding, + const char* output_name, int *min_order, char **earliest_name) { + char *cmdlist = strdup(binding->command); + char *dup = cmdlist; + char *name = NULL; - // TODO: iterate over keycode bindings too - int order = INT_MAX; - char *target = NULL; - for (int i = 0; i < mode->keysym_bindings->length; ++i) { - struct sway_binding *binding = mode->keysym_bindings->items[i]; - char *cmdlist = strdup(binding->command); - char *dup = cmdlist; - char *name = NULL; - - // workspace n - char *cmd = argsep(&cmdlist, " "); - if (cmdlist) { - name = argsep(&cmdlist, ",;"); - } + // workspace n + char *cmd = argsep(&cmdlist, " "); + if (cmdlist) { + name = argsep(&cmdlist, ",;"); + } - if (strcmp("workspace", cmd) == 0 && name) { - char *_target = strdup(name); - _target = do_var_replacement(_target); - strip_quotes(_target); - while (isspace(*_target)) { - memmove(_target, _target+1, strlen(_target+1)); - } - wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'", - _target); + if (strcmp("workspace", cmd) == 0 && name) { + char *_target = strdup(name); + _target = do_var_replacement(_target); + strip_quotes(_target); + wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'", + _target); - // Make sure that the command references an actual workspace - // not a command about workspaces - if (strcmp(_target, "next") == 0 || + // Make sure that the command references an actual workspace + // not a command about workspaces + if (strcmp(_target, "next") == 0 || strcmp(_target, "prev") == 0 || strcmp(_target, "next_on_output") == 0 || strcmp(_target, "prev_on_output") == 0 || strcmp(_target, "number") == 0 || strcmp(_target, "back_and_forth") == 0 || - strcmp(_target, "current") == 0) - { - free(_target); - free(dup); - continue; - } - - // If the command is workspace number <name>, isolate the name - if (strncmp(_target, "number ", strlen("number ")) == 0) { - size_t length = strlen(_target) - strlen("number ") + 1; - char *temp = malloc(length); - strncpy(temp, _target + strlen("number "), length - 1); - temp[length - 1] = '\0'; - free(_target); - _target = temp; - wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target); - - // Make sure the workspace number doesn't already exist - if (workspace_by_number(_target)) { - free(_target); - free(dup); - continue; - } - } + strcmp(_target, "current") == 0) { + free(_target); + free(dup); + return; + } - // Make sure that the workspace doesn't already exist - if (workspace_by_name(_target)) { + // If the command is workspace number <name>, isolate the name + if (strncmp(_target, "number ", strlen("number ")) == 0) { + size_t length = strlen(_target) - strlen("number ") + 1; + char *temp = malloc(length); + strncpy(temp, _target + strlen("number "), length - 1); + temp[length - 1] = '\0'; + free(_target); + _target = temp; + wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target); + + // Make sure the workspace number doesn't already exist + if (workspace_by_number(_target)) { free(_target); free(dup); - continue; + return; } + } - // make sure that the workspace can appear on the given - // output - if (!workspace_valid_on_output(output_name, _target)) { - free(_target); - free(dup); - continue; - } + // Make sure that the workspace doesn't already exist + if (workspace_by_name(_target)) { + free(_target); + free(dup); + return; + } - if (binding->order < order) { - order = binding->order; - free(target); - target = _target; - wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target); - } else { - free(_target); - } + // make sure that the workspace can appear on the given + // output + if (!workspace_valid_on_output(output_name, _target)) { + free(_target); + free(dup); + return; } - free(dup); + + if (binding->order < *min_order) { + *min_order = binding->order; + free(*earliest_name); + *earliest_name = _target; + wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target); + } else { + free(_target); + } + } + free(dup); +} + +char *workspace_next_name(const char *output_name) { + wlr_log(WLR_DEBUG, "Workspace: Generating new workspace name for output %s", + output_name); + // Scan all workspace bindings to find the next available workspace name, + // if none are found/available then default to a number + struct sway_mode *mode = config->current_mode; + + int order = INT_MAX; + char *target = NULL; + for (int i = 0; i < mode->keysym_bindings->length; ++i) { + workspace_name_from_binding(mode->keysym_bindings->items[i], + output_name, &order, &target); + } + for (int i = 0; i < mode->keycode_bindings->length; ++i) { + workspace_name_from_binding(mode->keycode_bindings->items[i], + output_name, &order, &target); } if (target != NULL) { return target; @@ -529,3 +534,116 @@ void workspace_detect_urgent(struct sway_container *workspace) { container_damage_whole(workspace); } } + +struct pid_workspace { + pid_t pid; + char *workspace; + struct timespec time_added; + + struct sway_container *output; + struct wl_listener output_destroy; + + struct wl_list link; +}; + +static struct wl_list pid_workspaces; + +struct sway_container *workspace_for_pid(pid_t pid) { + if (!pid_workspaces.prev && !pid_workspaces.next) { + wl_list_init(&pid_workspaces); + return NULL; + } + + struct sway_container *ws = NULL; + struct pid_workspace *pw = NULL; + + wlr_log(WLR_DEBUG, "Looking up workspace for pid %d", pid); + + do { + struct pid_workspace *_pw = NULL; + wl_list_for_each(_pw, &pid_workspaces, link) { + if (pid == _pw->pid) { + pw = _pw; + wlr_log(WLR_DEBUG, + "found pid_workspace for pid %d, workspace %s", + pid, pw->workspace); + goto found; + } + } + pid = get_parent_pid(pid); + } while (pid > 1); +found: + + if (pw && pw->workspace) { + ws = workspace_by_name(pw->workspace); + + if (!ws) { + wlr_log(WLR_DEBUG, + "Creating workspace %s for pid %d because it disappeared", + pw->workspace, pid); + ws = workspace_create(pw->output, pw->workspace); + } + + wl_list_remove(&pw->output_destroy.link); + wl_list_remove(&pw->link); + free(pw->workspace); + free(pw); + } + + return ws; +} + +static void pw_handle_output_destroy(struct wl_listener *listener, void *data) { + struct pid_workspace *pw = wl_container_of(listener, pw, output_destroy); + pw->output = NULL; + wl_list_remove(&pw->output_destroy.link); + wl_list_init(&pw->output_destroy.link); +} + +void workspace_record_pid(pid_t pid) { + wlr_log(WLR_DEBUG, "Recording workspace for process %d", pid); + if (!pid_workspaces.prev && !pid_workspaces.next) { + wl_list_init(&pid_workspaces); + } + + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *ws = + seat_get_focus_inactive(seat, &root_container); + if (ws && ws->type != C_WORKSPACE) { + ws = container_parent(ws, C_WORKSPACE); + } + if (!ws) { + wlr_log(WLR_DEBUG, "Bailing out, no workspace"); + return; + } + struct sway_container *output = ws->parent; + if (!output) { + wlr_log(WLR_DEBUG, "Bailing out, no output"); + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + // Remove expired entries + static const int timeout = 60; + struct pid_workspace *old, *_old; + wl_list_for_each_safe(old, _old, &pid_workspaces, link) { + if (now.tv_sec - old->time_added.tv_sec >= timeout) { + wl_list_remove(&old->output_destroy.link); + wl_list_remove(&old->link); + free(old->workspace); + free(old); + } + } + + struct pid_workspace *pw = calloc(1, sizeof(struct pid_workspace)); + pw->workspace = strdup(ws->name); + pw->output = output; + pw->pid = pid; + memcpy(&pw->time_added, &now, sizeof(struct timespec)); + pw->output_destroy.notify = pw_handle_output_destroy; + wl_signal_add(&output->sway_output->wlr_output->events.destroy, + &pw->output_destroy); + wl_list_insert(&pid_workspaces, &pw->link); +} diff --git a/swayidle/swayidle.1.scd b/swayidle/swayidle.1.scd index 5cd4a7fd..7c1b138a 100644 --- a/swayidle/swayidle.1.scd +++ b/swayidle/swayidle.1.scd @@ -58,4 +58,4 @@ https://github.com/swaywm/sway. # SEE ALSO -*sway*(5) *swaymsg*(1) *swaygrab*(1) *sway-input*(5) *sway-bar*(5) +*sway*(5) *swaymsg*(1) *sway-input*(5) *sway-bar*(5) diff --git a/swaymsg/main.c b/swaymsg/main.c index c4141ca5..3767daf3 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -250,12 +250,16 @@ static void pretty_print(int type, json_object *resp) { if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES && type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS && type != IPC_GET_VERSION && type != IPC_GET_SEATS && - type != IPC_GET_CONFIG) { + type != IPC_GET_CONFIG && type != IPC_SEND_TICK) { printf("%s\n", json_object_to_json_string_ext(resp, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)); return; } + if (type == IPC_SEND_TICK) { + return; + } + if (type == IPC_GET_VERSION) { pretty_print_version(resp); return; @@ -384,6 +388,8 @@ int main(int argc, char **argv) { type = IPC_GET_BINDING_MODES; } else if (strcasecmp(cmdtype, "get_config") == 0) { type = IPC_GET_CONFIG; + } else if (strcasecmp(cmdtype, "send_tick") == 0) { + type = IPC_SEND_TICK; } else { sway_abort("Unknown message type %s", cmdtype); } diff --git a/swaymsg/swaymsg.1.scd b/swaymsg/swaymsg.1.scd index a6e279da..8cf1b222 100644 --- a/swaymsg/swaymsg.1.scd +++ b/swaymsg/swaymsg.1.scd @@ -64,3 +64,6 @@ _swaymsg_ [options...] [message] *get\_config* Gets a JSON-encoded copy of the current configuration. + +*send\_tick* + Sends a tick event to all subscribed clients. diff --git a/swaynag/config.c b/swaynag/config.c new file mode 100644 index 00000000..d6c5739d --- /dev/null +++ b/swaynag/config.c @@ -0,0 +1,401 @@ +#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200112L +#include <getopt.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" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +static char *read_from_stdin() { + 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); + } + + while (buffer && buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + } + + return buffer; +} + +int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, + list_t *types, struct swaynag_type *type, char **config, bool *debug) { + enum type_options { + TO_COLOR_BACKGROUND = 256, + TO_COLOR_BORDER, + TO_COLOR_BORDER_BOTTOM, + TO_COLOR_BUTTON, + TO_COLOR_TEXT, + TO_THICK_BAR_BORDER, + TO_PADDING_MESSAGE, + TO_THICK_DET_BORDER, + TO_THICK_BTN_BORDER, + TO_GAP_BTN, + TO_GAP_BTN_DISMISS, + TO_MARGIN_BTN_RIGHT, + TO_PADDING_BTN, + }; + + static struct option opts[] = { + {"button", required_argument, NULL, 'b'}, + {"config", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"edge", required_argument, NULL, 'e'}, + {"font", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"detailed-message", no_argument, NULL, 'l'}, + {"detailed-button", required_argument, NULL, 'L'}, + {"message", required_argument, NULL, 'm'}, + {"output", required_argument, NULL, 'o'}, + {"dismiss-button", required_argument, NULL, 's'}, + {"type", required_argument, NULL, 't'}, + {"version", no_argument, NULL, 'v'}, + + {"background", required_argument, NULL, TO_COLOR_BACKGROUND}, + {"border", required_argument, NULL, TO_COLOR_BORDER}, + {"border-bottom", required_argument, NULL, TO_COLOR_BORDER_BOTTOM}, + {"button-background", required_argument, NULL, TO_COLOR_BUTTON}, + {"text", required_argument, NULL, TO_COLOR_TEXT}, + {"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER}, + {"message-padding", required_argument, NULL, TO_PADDING_MESSAGE}, + {"details-border-size", required_argument, NULL, TO_THICK_DET_BORDER}, + {"button-border-size", required_argument, NULL, TO_THICK_BTN_BORDER}, + {"button-gap", required_argument, NULL, TO_GAP_BTN}, + {"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS}, + {"button-margin-right", required_argument, NULL, TO_MARGIN_BTN_RIGHT}, + {"button-padding", required_argument, NULL, TO_PADDING_BTN}, + + {0, 0, 0, 0} + }; + + const char *usage = + "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" + " -c, --config <path> Path to config file.\n" + " -d, --debug Enable debugging.\n" + " -e, --edge top|bottom Set the edge to use.\n" + " -f, --font <font> Set the font to use.\n" + " -h, --help Show help message and quit.\n" + " -l, --detailed-message Read a detailed message from stdin.\n" + " -L, --detailed-button <text> Set the text of the detail button.\n" + " -m, --message <msg> Set the message text.\n" + " -o, --output <output> Set the output to use.\n" + " -s, --dismiss-button <text> Set the dismiss button text.\n" + " -t, --type <type> Set the message type.\n" + " -v, --version Show the version number and quit.\n" + "\n" + "The following appearance options can also be given:\n" + " --background RRGGBB[AA] Background color.\n" + " --border RRGGBB[AA] Border color.\n" + " --border-bottom RRGGBB[AA] Bottom border color.\n" + " --button-background RRGGBB[AA] Button background color.\n" + " --text RRGGBB[AA] Text color.\n" + " --border-bottom-size size Thickness of the bar border.\n" + " --message-padding padding Padding for the message.\n" + " --details-border-size size Thickness for the details border.\n" + " --button-border-size size Thickness for the button border.\n" + " --button-gap gap Size of the gap between buttons\n" + " --button-dismiss-gap gap Size of the gap for dismiss button.\n" + " --button-margin-right margin Margin from dismiss button to edge.\n" + " --button-padding padding Padding for the button text.\n"; + + optind = 1; + while (1) { + int c = getopt_long(argc, argv, "b:c:de:f:hlL:m:o:s:t:v", opts, NULL); + if (c == -1) { + break; + } + switch (c) { + case 'b': // Button + if (swaynag) { + if (optind >= argc) { + fprintf(stderr, "Missing action for button %s\n", optarg); + return EXIT_FAILURE; + } + struct swaynag_button *button; + button = calloc(sizeof(struct swaynag_button), 1); + button->text = strdup(optarg); + button->type = SWAYNAG_ACTION_COMMAND; + button->action = strdup(argv[optind]); + list_add(swaynag->buttons, button); + } + optind++; + break; + case 'c': // Config + if (config) { + *config = strdup(optarg); + } + break; + case 'd': // Debug + if (debug) { + *debug = true; + } + break; + case 'e': // Edge + if (type) { + if (strcmp(optarg, "top") == 0) { + type->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } else if (strcmp(optarg, "bottom") == 0) { + type->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } else { + fprintf(stderr, "Invalid edge: %s\n", optarg); + return EXIT_FAILURE; + } + } + break; + case 'f': // Font + if (type) { + free(type->font); + type->font = strdup(optarg); + } + break; + case 'l': // Detailed Message + if (swaynag) { + free(swaynag->details.message); + swaynag->details.message = read_from_stdin(); + swaynag->details.button_up.text = strdup("▲"); + swaynag->details.button_down.text = strdup("▼"); + } + break; + case 'L': // Detailed Button Text + if (swaynag) { + free(swaynag->details.button_details.text); + swaynag->details.button_details.text = strdup(optarg); + } + break; + case 'm': // Message + if (swaynag) { + free(swaynag->message); + swaynag->message = strdup(optarg); + } + break; + case 'o': // Output + if (type) { + free(type->output); + type->output = strdup(optarg); + } + break; + case 's': // Dismiss Button Text + if (swaynag) { + struct swaynag_button *button_close; + button_close = swaynag->buttons->items[0]; + free(button_close->text); + button_close->text = strdup(optarg); + } + break; + case 't': // Type + if (swaynag) { + swaynag->type = swaynag_type_get(types, optarg); + if (!swaynag->type) { + fprintf(stderr, "Unknown type %s\n", optarg); + return EXIT_FAILURE; + } + } + break; + case 'v': // Version + fprintf(stdout, "swaynag version " SWAY_VERSION "\n"); + return -1; + case TO_COLOR_BACKGROUND: // Background color + if (type) { + type->background = parse_color(optarg); + } + break; + case TO_COLOR_BORDER: // Border color + if (type) { + type->border = parse_color(optarg); + } + break; + case TO_COLOR_BORDER_BOTTOM: // Bottom border color + if (type) { + type->border_bottom = parse_color(optarg); + } + break; + case TO_COLOR_BUTTON: // Button background color + if (type) { + type->button_background = parse_color(optarg); + } + break; + case TO_COLOR_TEXT: // Text color + if (type) { + type->text = parse_color(optarg); + } + break; + case TO_THICK_BAR_BORDER: // Bottom border thickness + if (type) { + type->bar_border_thickness = strtol(optarg, NULL, 0); + } + break; + case TO_PADDING_MESSAGE: // Message padding + if (type) { + type->message_padding = strtol(optarg, NULL, 0); + } + break; + case TO_THICK_DET_BORDER: // Details border thickness + if (type) { + type->details_border_thickness = strtol(optarg, NULL, 0); + } + break; + case TO_THICK_BTN_BORDER: // Button border thickness + if (type) { + type->button_border_thickness = strtol(optarg, NULL, 0); + } + break; + case TO_GAP_BTN: // Gap between buttons + if (type) { + type->button_gap = strtol(optarg, NULL, 0); + } + break; + case TO_GAP_BTN_DISMISS: // Gap between dismiss button + if (type) { + type->button_gap_close = strtol(optarg, NULL, 0); + } + break; + case TO_MARGIN_BTN_RIGHT: // Margin on the right side of button area + if (type) { + type->button_margin_right = strtol(optarg, NULL, 0); + } + break; + case TO_PADDING_BTN: // Padding for the button text + if (type) { + type->button_padding = strtol(optarg, NULL, 0); + } + break; + default: // Help or unknown flag + fprintf(c == 'h' ? stdout : stderr, "%s", usage); + return -1; + } + } + + return 0; +} + +static bool file_exists(const char *path) { + return path && access(path, R_OK) != -1; +} + +char *swaynag_get_config_path(void) { + static const char *config_paths[] = { + "$HOME/.swaynag/config", + "$XDG_CONFIG_HOME/swaynag/config", + SYSCONFDIR "/swaynag/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); + } + } + + wordexp_t p; + char *path; + for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { + if (wordexp(config_paths[i], &p, 0) == 0) { + path = strdup(p.we_wordv[0]); + wordfree(&p); + if (file_exists(path)) { + return path; + } + free(path); + } + } + + return NULL; +} + +int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) { + FILE *config = fopen(path, "r"); + if (!config) { + fprintf(stderr, "Failed to read config. Running without it.\n"); + return 0; + } + + struct swaynag_type *type; + type = calloc(1, sizeof(struct swaynag_type)); + type->name = strdup("<config>"); + list_add(types, type); + + char *line; + int line_number = 0; + while (!feof(config)) { + line = read_line(config); + if (!line) { + continue; + } + + line_number++; + if (line[0] == '#') { + free(line); + continue; + } + if (strlen(line) == 0) { + free(line); + 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; + } + char *name = calloc(1, close - line); + strncat(name, line + 1, close - line - 1); + type = swaynag_type_get(types, name); + if (!type) { + type = calloc(1, sizeof(struct swaynag_type)); + type->name = strdup(name); + list_add(types, type); + } + free(name); + } else { + char flag[strlen(line) + 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; + } + } + + free(line); + } + fclose(config); + return 0; +} + diff --git a/swaynag/main.c b/swaynag/main.c new file mode 100644 index 00000000..854368e5 --- /dev/null +++ b/swaynag/main.c @@ -0,0 +1,129 @@ +#define _XOPEN_SOURCE 500 +#include <signal.h> +#include "log.h" +#include "list.h" +#include "swaynag/config.h" +#include "swaynag/swaynag.h" +#include "swaynag/types.h" + +static struct swaynag swaynag; + +void sig_handler(int signal) { + swaynag_destroy(&swaynag); + exit(EXIT_FAILURE); +} + +void sway_terminate(int code) { + swaynag_destroy(&swaynag); + exit(code); +} + +int main(int argc, char **argv) { + int exit_code = EXIT_SUCCESS; + + list_t *types = create_list(); + swaynag_types_add_default(types); + + memset(&swaynag, 0, sizeof(swaynag)); + swaynag.buttons = create_list(); + + struct swaynag_button *button_close = + calloc(sizeof(struct swaynag_button), 1); + button_close->text = strdup("X"); + button_close->type = SWAYNAG_ACTION_DISMISS; + list_add(swaynag.buttons, button_close); + + swaynag.details.button_details.text = strdup("Toggle Details"); + swaynag.details.button_details.type = SWAYNAG_ACTION_EXPAND; + + + char *config_path = NULL; + bool debug = false; + int launch_status = swaynag_parse_options(argc, argv, NULL, NULL, NULL, + &config_path, &debug); + if (launch_status != 0) { + exit_code = launch_status; + goto cleanup; + } + wlr_log_init(debug ? WLR_DEBUG : WLR_ERROR, NULL); + + if (!config_path) { + config_path = swaynag_get_config_path(); + } + if (config_path) { + wlr_log(WLR_DEBUG, "Loading config file: %s", config_path); + int config_status = swaynag_load_config(config_path, &swaynag, types); + free(config_path); + if (config_status != 0) { + exit_code = config_status; + goto cleanup; + } + } + + if (argc > 1) { + struct swaynag_type *type_args; + type_args = calloc(1, sizeof(struct swaynag_type)); + type_args->name = strdup("<args>"); + list_add(types, type_args); + + int result = swaynag_parse_options(argc, argv, &swaynag, types, + type_args, NULL, NULL); + if (result != 0) { + exit_code = result; + goto cleanup; + } + } + + if (!swaynag.message) { + wlr_log(WLR_ERROR, "No message passed. Please provide --message/-m"); + exit_code = EXIT_FAILURE; + goto cleanup; + } + + if (!swaynag.type) { + swaynag.type = swaynag_type_get(types, "error"); + } + + // Construct a new type using the config defaults as base, then merging + // config type defaults on top, then merging arguments on top of that, and + // finally merging defaults on top. + struct swaynag_type *type = calloc(1, sizeof(struct swaynag_type)); + type->name = strdup(swaynag.type->name); + swaynag_type_merge(type, swaynag_type_get(types, "<args>")); + swaynag_type_merge(type, swaynag.type); + swaynag_type_merge(type, swaynag_type_get(types, "<config>")); + swaynag_type_merge(type, swaynag_type_get(types, "<defaults>")); + swaynag.type = type; + + swaynag_types_free(types); + + if (swaynag.details.message) { + list_add(swaynag.buttons, &swaynag.details.button_details); + } else { + free(swaynag.details.button_details.text); + } + + wlr_log(WLR_DEBUG, "Output: %s", swaynag.type->output); + wlr_log(WLR_DEBUG, "Anchors: %d", swaynag.type->anchors); + wlr_log(WLR_DEBUG, "Type: %s", swaynag.type->name); + wlr_log(WLR_DEBUG, "Message: %s", swaynag.message); + wlr_log(WLR_DEBUG, "Font: %s", swaynag.type->font); + wlr_log(WLR_DEBUG, "Buttons"); + for (int i = 0; i < swaynag.buttons->length; i++) { + struct swaynag_button *button = swaynag.buttons->items[i]; + wlr_log(WLR_DEBUG, "\t[%s] `%s`", button->text, button->action); + } + + signal(SIGTERM, sig_handler); + + swaynag_setup(&swaynag); + swaynag_run(&swaynag); + return exit_code; + +cleanup: + swaynag_types_free(types); + free(swaynag.details.button_details.text); + swaynag_destroy(&swaynag); + return exit_code; +} + diff --git a/swaynag/meson.build b/swaynag/meson.build new file mode 100644 index 00000000..2ba3ed95 --- /dev/null +++ b/swaynag/meson.build @@ -0,0 +1,23 @@ +executable( + 'swaynag', [ + 'config.c', + 'main.c', + 'render.c', + 'swaynag.c', + 'types.c', + ], + include_directories: [sway_inc], + dependencies: [ + cairo, + client_protos, + gdk_pixbuf, + math, + pango, + pangocairo, + wayland_client, + wayland_cursor, + wlroots, + ], + link_with: [lib_sway_common, lib_sway_client], + install: true +) diff --git a/swaynag/render.c b/swaynag/render.c new file mode 100644 index 00000000..766409e4 --- /dev/null +++ b/swaynag/render.c @@ -0,0 +1,308 @@ +#include <stdint.h> +#include "cairo.h" +#include "log.h" +#include "pango.h" +#include "pool-buffer.h" +#include "swaynag/swaynag.h" +#include "swaynag/types.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +static uint32_t render_message(cairo_t *cairo, struct swaynag *swaynag) { + uint32_t height = swaynag->height * swaynag->scale; + height -= swaynag->type->bar_border_thickness * swaynag->scale; + + int text_width, text_height; + get_text_size(cairo, swaynag->type->font, &text_width, &text_height, + swaynag->scale, true, "%s", swaynag->message); + + int padding = swaynag->type->message_padding * swaynag->scale; + + uint32_t ideal_height = text_height + padding * 2; + uint32_t ideal_surface_height = ideal_height / swaynag->scale; + if (swaynag->height < ideal_surface_height) { + return ideal_surface_height; + } + + cairo_set_source_u32(cairo, swaynag->type->text); + cairo_move_to(cairo, padding, (int)(ideal_height - text_height) / 2); + pango_printf(cairo, swaynag->type->font, swaynag->scale, false, + "%s", swaynag->message); + + return ideal_surface_height; +} + +static void render_details_scroll_button(cairo_t *cairo, + struct swaynag *swaynag, struct swaynag_button *button) { + int text_width, text_height; + get_text_size(cairo, swaynag->type->font, &text_width, &text_height, + swaynag->scale, true, "%s", button->text); + + int border = swaynag->type->button_border_thickness * swaynag->scale; + int padding = swaynag->type->button_padding * swaynag->scale; + + cairo_set_source_u32(cairo, swaynag->type->border); + cairo_rectangle(cairo, button->x, button->y, + button->width, button->height); + cairo_fill(cairo); + + cairo_set_source_u32(cairo, swaynag->type->button_background); + cairo_rectangle(cairo, button->x + border, button->y + border, + button->width - (border * 2), button->height - (border * 2)); + cairo_fill(cairo); + + cairo_set_source_u32(cairo, swaynag->type->text); + cairo_move_to(cairo, button->x + border + padding, + button->y + border + (button->height - text_height) / 2); + pango_printf(cairo, swaynag->type->font, swaynag->scale, true, + "%s", button->text); +} + +static int get_detailed_scroll_button_width(cairo_t *cairo, + struct swaynag *swaynag) { + int up_width, down_width, temp_height; + get_text_size(cairo, swaynag->type->font, &up_width, &temp_height, + swaynag->scale, true, + "%s", swaynag->details.button_up.text); + get_text_size(cairo, swaynag->type->font, &down_width, &temp_height, + swaynag->scale, true, + "%s", swaynag->details.button_down.text); + + int text_width = up_width > down_width ? up_width : down_width; + int border = swaynag->type->button_border_thickness * swaynag->scale; + int padding = swaynag->type->button_padding * swaynag->scale; + + return text_width + border * 2 + padding * 2; +} + +static uint32_t render_detailed(cairo_t *cairo, struct swaynag *swaynag, + uint32_t y) { + uint32_t width = swaynag->width * swaynag->scale; + uint32_t height = swaynag->height * swaynag->scale; + height -= swaynag->type->bar_border_thickness * swaynag->scale; + + int border = swaynag->type->details_border_thickness * swaynag->scale; + int padding = swaynag->type->message_padding * swaynag->scale; + int decor = padding + border; + + swaynag->details.x = decor; + swaynag->details.y = y * swaynag->scale + decor; + swaynag->details.width = width - decor * 2; + + PangoLayout *layout = get_pango_layout(cairo, swaynag->type->font, + swaynag->details.message, swaynag->scale, false); + pango_layout_set_width(layout, + (swaynag->details.width - padding * 2) * PANGO_SCALE); + pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); + pango_layout_set_single_paragraph_mode(layout, false); + pango_cairo_update_layout(cairo, layout); + swaynag->details.total_lines = pango_layout_get_line_count(layout); + + PangoLayoutLine *line; + line = pango_layout_get_line_readonly(layout, swaynag->details.offset); + gint offset = line->start_index; + const char *text = pango_layout_get_text(layout); + pango_layout_set_text(layout, text + offset, strlen(text) - offset); + + int text_width, text_height; + pango_cairo_update_layout(cairo, layout); + pango_layout_get_pixel_size(layout, &text_width, &text_height); + + bool show_buttons = swaynag->details.offset > 0; + int button_width = get_detailed_scroll_button_width(cairo, swaynag); + if (show_buttons) { + swaynag->details.width -= button_width; + pango_layout_set_width(layout, + (swaynag->details.width - padding * 2) * PANGO_SCALE); + } + + uint32_t ideal_height; + do { + ideal_height = swaynag->details.y + text_height + decor + padding * 2; + if (ideal_height > SWAYNAG_MAX_HEIGHT) { + ideal_height = SWAYNAG_MAX_HEIGHT; + + if (!show_buttons) { + show_buttons = true; + swaynag->details.width -= button_width; + pango_layout_set_width(layout, + (swaynag->details.width - padding * 2) * PANGO_SCALE); + } + } + + swaynag->details.height = ideal_height - swaynag->details.y - decor; + pango_layout_set_height(layout, + (swaynag->details.height - padding * 2) * PANGO_SCALE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_cairo_update_layout(cairo, layout); + pango_layout_get_pixel_size(layout, &text_width, &text_height); + } while (text_height != (swaynag->details.height - padding * 2)); + + swaynag->details.visible_lines = pango_layout_get_line_count(layout); + + if (show_buttons) { + swaynag->details.button_up.x = + swaynag->details.x + swaynag->details.width; + swaynag->details.button_up.y = swaynag->details.y; + swaynag->details.button_up.width = button_width; + swaynag->details.button_up.height = swaynag->details.height / 2; + render_details_scroll_button(cairo, swaynag, + &swaynag->details.button_up); + + swaynag->details.button_down.x = + swaynag->details.x + swaynag->details.width; + swaynag->details.button_down.y = + swaynag->details.button_up.y + swaynag->details.button_up.height; + swaynag->details.button_down.width = button_width; + swaynag->details.button_down.height = swaynag->details.height / 2; + render_details_scroll_button(cairo, swaynag, + &swaynag->details.button_down); + } + + cairo_set_source_u32(cairo, swaynag->type->border); + cairo_rectangle(cairo, swaynag->details.x, swaynag->details.y, + swaynag->details.width, swaynag->details.height); + cairo_fill(cairo); + + cairo_move_to(cairo, swaynag->details.x + padding, + swaynag->details.y + padding); + cairo_set_source_u32(cairo, swaynag->type->text); + pango_cairo_show_layout(cairo, layout); + g_object_unref(layout); + + return ideal_height / swaynag->scale; +} + +static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag, + int button_index, int *x) { + uint32_t height = swaynag->height * swaynag->scale; + height -= swaynag->type->bar_border_thickness * swaynag->scale; + struct swaynag_button *button = swaynag->buttons->items[button_index]; + + int text_width, text_height; + get_text_size(cairo, swaynag->type->font, &text_width, &text_height, + swaynag->scale, true, "%s", button->text); + + int border = swaynag->type->button_border_thickness * swaynag->scale; + int padding = swaynag->type->button_padding * swaynag->scale; + + uint32_t ideal_height = text_height + padding * 2 + border * 2; + uint32_t ideal_surface_height = ideal_height / swaynag->scale; + if (swaynag->height < ideal_surface_height) { + return ideal_surface_height; + } + + button->x = *x - border - text_width - padding * 2; + button->y = (int)(ideal_height - text_height) / 2 - padding; + button->width = text_width + padding * 2; + button->height = text_height + padding * 2; + + cairo_set_source_u32(cairo, swaynag->type->border); + cairo_rectangle(cairo, button->x - border, button->y - border, + button->width + border * 2, button->height + border * 2); + cairo_fill(cairo); + + cairo_set_source_u32(cairo, swaynag->type->button_background); + cairo_rectangle(cairo, button->x, button->y, + button->width, button->height); + cairo_fill(cairo); + + cairo_set_source_u32(cairo, swaynag->type->text); + cairo_move_to(cairo, button->x + padding, button->y + padding); + pango_printf(cairo, swaynag->type->font, swaynag->scale, true, + "%s", button->text); + + *x = button->x - border; + + return ideal_surface_height; +} + +static uint32_t render_to_cairo(cairo_t *cairo, struct swaynag *swaynag) { + uint32_t max_height = 0; + + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, swaynag->type->background); + cairo_paint(cairo); + + uint32_t h = render_message(cairo, swaynag); + max_height = h > max_height ? h : max_height; + + int x = swaynag->width - swaynag->type->button_margin_right; + x *= swaynag->scale; + for (int i = 0; i < swaynag->buttons->length; i++) { + h = render_button(cairo, swaynag, i, &x); + max_height = h > max_height ? h : max_height; + x -= swaynag->type->button_gap * swaynag->scale; + if (i == 0) { + x -= swaynag->type->button_gap_close * swaynag->scale; + } + } + + if (swaynag->details.visible) { + h = render_detailed(cairo, swaynag, max_height); + max_height = h > max_height ? h : max_height; + } + + int border = swaynag->type->bar_border_thickness * swaynag->scale; + if (max_height > swaynag->height) { + max_height += border; + } + cairo_set_source_u32(cairo, swaynag->type->border_bottom); + cairo_rectangle(cairo, 0, + swaynag->height * swaynag->scale - border, + swaynag->width * swaynag->scale, + border); + cairo_fill(cairo); + + return max_height; +} + +void render_frame(struct swaynag *swaynag) { + if (!swaynag->run_display) { + return; + } + + cairo_surface_t *recorder = cairo_recording_surface_create( + CAIRO_CONTENT_COLOR_ALPHA, NULL); + cairo_t *cairo = cairo_create(recorder); + cairo_save(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); + cairo_paint(cairo); + cairo_restore(cairo); + uint32_t height = render_to_cairo(cairo, swaynag); + if (height != swaynag->height) { + zwlr_layer_surface_v1_set_size(swaynag->layer_surface, 0, height); + zwlr_layer_surface_v1_set_exclusive_zone(swaynag->layer_surface, + height); + wl_surface_commit(swaynag->surface); + wl_display_roundtrip(swaynag->display); + } else { + swaynag->current_buffer = get_next_buffer(swaynag->shm, + swaynag->buffers, + swaynag->width * swaynag->scale, + swaynag->height * swaynag->scale); + if (!swaynag->current_buffer) { + wlr_log(WLR_DEBUG, "Failed to get buffer. Skipping frame."); + goto cleanup; + } + + cairo_t *shm = swaynag->current_buffer->cairo; + cairo_save(shm); + cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR); + cairo_paint(shm); + cairo_restore(shm); + cairo_set_source_surface(shm, recorder, 0.0, 0.0); + cairo_paint(shm); + + wl_surface_set_buffer_scale(swaynag->surface, swaynag->scale); + wl_surface_attach(swaynag->surface, + swaynag->current_buffer->buffer, 0, 0); + wl_surface_damage(swaynag->surface, 0, 0, + swaynag->width, swaynag->height); + wl_surface_commit(swaynag->surface); + wl_display_roundtrip(swaynag->display); + } + +cleanup: + cairo_surface_destroy(recorder); + cairo_destroy(cairo); +} diff --git a/swaynag/swaynag.1.scd b/swaynag/swaynag.1.scd new file mode 100644 index 00000000..1c395aee --- /dev/null +++ b/swaynag/swaynag.1.scd @@ -0,0 +1,106 @@ +swaynag(1) + +# NAME + +swaynag - Show a warning or error message with buttons + +# SYNOPSIS + +_swaynag_ [options...] + +# 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. + +*-c, --config* <path> + The config file to use. By default, the following paths are checked: + _$HOME/.swaynag/config_, _$XDG\_CONFIG\_HOME/swaynag/config_, and + _SYSCONFDIR/swaynag/config_. All flags aside from this one and _debug_ are + valid options in the configuration file using the format + _long-option=value_. All leading dashes should be omitted and the equals + sign is required. See swaynag(5) for more information. + +*-d, --debug* + Enable debugging. + +*-e, --edge* top|bottom + Set the edge to use. + +*-f, --font* <font> + Set the font to use. + +*-h, --help* + Show help message and quit. + +*-l, --detailed-message* + Read a detailed message from stdin. A button to toggle details will be + added. Details are shown in a scrollable multi-line text area. + +*-L, --detailed-button* <text> + Set the text for the button that toggles details. This has no effect if + there is not a detailed message. The default is _Toggle Details_. + +*-m, --message* <msg> + Set the message text. + +*-o, --output* <output> + Set the output to use. This should be the name of a _xdg\_output_. + +*-s, --dismiss-button* <text> + Sets the text for the dismiss nagbar button. The default is _X_. + +*-t, --type* <type> + Set the message type. Two types are created by default _error_ and + _warning_. Custom types can be defined in the config file. See + _--config_ and swaynag(5) for details. Both of the default types can be + overridden in the config file as well. + +*-v, --version* + Show the version number and quit. + +# APPEARANCE OPTIONS + +*--background* <RRGGBB[AA]> + Set the color of the background. + +*--border* <RRGGBB[AA]> + Set the color of the border. + +*--border-bottom* <RRGGBB[AA]> + Set the color of the bottom border. + +*--button-background* <RRGGBB[AA]> + Set the color for the background for buttons. + +*--text* <RRGGBB[AA]> + Set the text color. + +*--border-bottom-size* <size> + Set the thickness of the bottom border. + +*--message-padding* <padding> + Set the padding for the message. + +*--details-border-size* <size> + Set the thickness for the details border. + +*--button-border-size* <size> + Set the thickness for the button border. + +*--button-gap* <gap> + Set the size of the gap between buttons. + +*--button-dismiss-gap* <gap> + Set the size of the gap between the dismiss button and another button. + +*--button-margin-right* <margin> + Set the margin from the right of the dismiss button to edge. + +*--button-padding* <padding> + Set the padding for the button text. + +# SEE + +swaynag(5) diff --git a/swaynag/swaynag.5.scd b/swaynag/swaynag.5.scd new file mode 100644 index 00000000..d3daadf7 --- /dev/null +++ b/swaynag/swaynag.5.scd @@ -0,0 +1,100 @@ +swaynag(5) + +# NAME + +swaynag - swaynag configuration file + +# SYNOPSIS + +$HOME/.swaynag/config, $XDG\_CONFIG\_HOME/swaynag/config, +SYSCONFDIR/swaynag/config + +# CONFIG FILE + +At the top of the config file, _swaynag_ options can be set using the format +_long-option=value_. These will be used as default values if _swaynag_ is not +given the option. This can be useful for setting a preferred font, output, and +edge. + +Below the options, custom types may be defined. To define a type, use the +following format: + +``` +[name-of-type] +option=value +``` + +All colors may be given in the form _RRGGBB_ or _RRGGBBAA_. The following +colors can be set: + +*background=<color>* + The background color for _swaynag_. + +*border=<color>* + The color to use for borders of buttons. + +*border-bottom=<color>* + The color of the border line at the bottom of _swaynag_. + +*button-background=<color>* + The background color for the buttons. + +*text=<color>* + The color of the text. + +The following sizing options can also be set: + +*border-bottom-size=<size>* + Set the thickness of the bottom border. + +*message-padding=<padding>* + Set the padding for the message. + +*details-border-size=<size>* + Set the thickness for the details border. + +*button-border-size=<size>* + Set the thickness for the button border. + +*button-gap=<gap>* + Set the size of the gap between buttons. + +*button-dismiss-gap=<gap>* + Set the size of the gap between the dismiss button and another button. + +*button-margin-right=<margin>* + Set the margin from the right of the dismiss button to edge. + +*button-padding=<padding>* + Set the padding for the button text. + +Additionally, the following options can be assigned a default per-type: + +*edge=top|bottom* + Set the edge to use. + +*font=<font>* + Set the font to use. + +*output=<output>* + Set the output to use. This should be the name of a _xdg\_output_. + +# EXAMPLE + +``` +font=Monospace 12 +edge=bottom + +[green] +edge=top +background=00AA00 +border=006600 +border-bottom=004400 +text=FFFFFF +button-background=00CC00 +message-padding=10 +``` + +# SEE + +swaynag(1) diff --git a/swaynag/swaynag.c b/swaynag/swaynag.c new file mode 100644 index 00000000..3966277d --- /dev/null +++ b/swaynag/swaynag.c @@ -0,0 +1,451 @@ +#define _XOPEN_SOURCE 500 +#include <assert.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <wayland-client.h> +#include <wayland-cursor.h> +#include "log.h" +#include "list.h" +#include "swaynag/render.h" +#include "swaynag/swaynag.h" +#include "swaynag/types.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +static void nop() { + // Intentionally left blank +} + +static bool terminal_execute(char *terminal, char *command) { + char fname[] = "/tmp/swaynagXXXXXX"; + FILE *tmp= fdopen(mkstemp(fname), "w"); + if (!tmp) { + wlr_log(WLR_ERROR, "Failed to create temp script"); + return false; + } + wlr_log(WLR_DEBUG, "Created temp script: %s", fname); + fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command); + fclose(tmp); + chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR); + char cmd[strlen(terminal) + strlen(" -e ") + strlen(fname) + 1]; + sprintf(cmd, "%s -e %s", terminal, fname); + execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); + return true; +} + +static void swaynag_button_execute(struct swaynag *swaynag, + struct swaynag_button *button) { + wlr_log(WLR_DEBUG, "Executing [%s]: %s", button->text, button->action); + if (button->type == SWAYNAG_ACTION_DISMISS) { + swaynag->run_display = false; + } else if (button->type == SWAYNAG_ACTION_EXPAND) { + swaynag->details.visible = !swaynag->details.visible; + render_frame(swaynag); + } else { + if (fork() == 0) { + // Child process. Will be used to prevent zombie processes + setsid(); + if (fork() == 0) { + // Child of the child. Will be reparented to the init process + char *terminal = getenv("TERMINAL"); + if (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"); + execl("/bin/sh", "/bin/sh", "-c", button->action, NULL); + } + } + exit(EXIT_SUCCESS); + } + } + wait(0); +} + +static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + struct swaynag *swaynag = data; + swaynag->width = width; + swaynag->height = height; + zwlr_layer_surface_v1_ack_configure(surface, serial); + render_frame(swaynag); +} + +static void layer_surface_closed(void *data, + struct zwlr_layer_surface_v1 *surface) { + struct swaynag *swaynag = data; + swaynag_destroy(swaynag); +} + +static struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void surface_enter(void *data, struct wl_surface *surface, + struct wl_output *output) { + struct swaynag *swaynag = data; + struct swaynag_output *swaynag_output; + wl_list_for_each(swaynag_output, &swaynag->outputs, link) { + if (swaynag_output->wl_output == output) { + wlr_log(WLR_DEBUG, "Surface enter on output %s", + swaynag_output->name); + swaynag->output = swaynag_output; + swaynag->scale = swaynag->output->scale; + render_frame(swaynag); + break; + } + }; +} + +static struct wl_surface_listener surface_listener = { + .enter = surface_enter, + .leave = nop, +}; + +static void update_cursor(struct swaynag *swaynag) { + struct swaynag_pointer *pointer = &swaynag->pointer; + pointer->cursor_theme = wl_cursor_theme_load(NULL, 24 * swaynag->scale, + swaynag->shm); + struct wl_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, + swaynag->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 / swaynag->scale, + pointer->cursor_image->hotspot_y / swaynag->scale); + wl_surface_commit(pointer->cursor_surface); +} + +static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + struct swaynag *swaynag = data; + struct swaynag_pointer *pointer = &swaynag->pointer; + pointer->serial = serial; + update_cursor(swaynag); +} + +static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + struct swaynag *swaynag = data; + swaynag->pointer.x = wl_fixed_to_int(surface_x); + swaynag->pointer.y = wl_fixed_to_int(surface_y); +} + +static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + struct swaynag *swaynag = data; + + if (state != WL_POINTER_BUTTON_STATE_PRESSED) { + return; + } + + double x = swaynag->pointer.x * swaynag->scale; + double y = swaynag->pointer.y * swaynag->scale; + for (int i = 0; i < swaynag->buttons->length; i++) { + struct swaynag_button *nagbutton = swaynag->buttons->items[i]; + if (x >= nagbutton->x + && y >= nagbutton->y + && x < nagbutton->x + nagbutton->width + && y < nagbutton->y + nagbutton->height) { + swaynag_button_execute(swaynag, nagbutton); + return; + } + } + + if (swaynag->details.visible && + swaynag->details.total_lines != swaynag->details.visible_lines) { + struct swaynag_button button_up = swaynag->details.button_up; + if (x >= button_up.x + && y >= button_up.y + && x < button_up.x + button_up.width + && y < button_up.y + button_up.height + && swaynag->details.offset > 0) { + swaynag->details.offset--; + render_frame(swaynag); + return; + } + + struct swaynag_button button_down = swaynag->details.button_down; + int bot = swaynag->details.total_lines; + bot -= swaynag->details.visible_lines; + if (x >= button_down.x + && y >= button_down.y + && x < button_down.x + button_down.width + && y < button_down.y + button_down.height + && swaynag->details.offset < bot) { + swaynag->details.offset++; + render_frame(swaynag); + return; + } + } +} + +static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + struct swaynag *swaynag = data; + if (!swaynag->details.visible + || swaynag->pointer.x < swaynag->details.x + || swaynag->pointer.y < swaynag->details.y + || swaynag->pointer.x >= swaynag->details.x + swaynag->details.width + || swaynag->pointer.y >= swaynag->details.y + swaynag->details.height + || swaynag->details.total_lines == swaynag->details.visible_lines) { + return; + } + + int direction = wl_fixed_to_int(value); + int bot = swaynag->details.total_lines - swaynag->details.visible_lines; + if (direction < 0 && swaynag->details.offset > 0) { + swaynag->details.offset--; + } else if (direction > 0 && swaynag->details.offset < bot) { + swaynag->details.offset++; + } + + render_frame(swaynag); +} + +static struct wl_pointer_listener pointer_listener = { + .enter = wl_pointer_enter, + .leave = nop, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = wl_pointer_axis, + .frame = nop, + .axis_source = nop, + .axis_stop = nop, + .axis_discrete = nop, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + struct swaynag *swaynag = data; + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + swaynag->pointer.pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(swaynag->pointer.pointer, &pointer_listener, + swaynag); + } +} + +const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = nop, +}; + +static void output_scale(void *data, struct wl_output *output, + int32_t factor) { + struct swaynag_output *swaynag_output = data; + swaynag_output->scale = factor; + if (swaynag_output->swaynag->output == swaynag_output) { + swaynag_output->swaynag->scale = swaynag_output->scale; + update_cursor(swaynag_output->swaynag); + render_frame(swaynag_output->swaynag); + } +} + +static struct wl_output_listener output_listener = { + .geometry = nop, + .mode = nop, + .done = nop, + .scale = output_scale, +}; + +static void xdg_output_handle_name(void *data, + struct zxdg_output_v1 *xdg_output, const char *name) { + struct swaynag_output *swaynag_output = data; + char *outname = swaynag_output->swaynag->type->output; + wlr_log(WLR_DEBUG, "Checking against output %s for %s", name, outname); + if (!swaynag_output->swaynag->output && outname && name + && strcmp(outname, name) == 0) { + wlr_log(WLR_DEBUG, "Using output %s", name); + swaynag_output->swaynag->output = swaynag_output; + } + swaynag_output->name = strdup(name); + zxdg_output_v1_destroy(xdg_output); + swaynag_output->swaynag->querying_outputs--; +} + +static struct zxdg_output_v1_listener xdg_output_listener = { + .logical_position = nop, + .logical_size = nop, + .done = nop, + .name = xdg_output_handle_name, + .description = nop, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct swaynag *swaynag = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + swaynag->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 3); + } 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); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + swaynag->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + if (!swaynag->output && swaynag->xdg_output_manager) { + swaynag->querying_outputs++; + struct swaynag_output *output = + calloc(1, sizeof(struct swaynag_output)); + output->wl_output = wl_registry_bind(registry, name, + &wl_output_interface, 3); + output->wl_name = name; + output->scale = 1; + output->swaynag = swaynag; + wl_list_insert(&swaynag->outputs, &output->link); + wl_output_add_listener(output->wl_output, + &output_listener, output); + + struct zxdg_output_v1 *xdg_output; + xdg_output = zxdg_output_manager_v1_get_xdg_output( + swaynag->xdg_output_manager, output->wl_output); + zxdg_output_v1_add_listener(xdg_output, + &xdg_output_listener, output); + } + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + swaynag->layer_shell = wl_registry_bind( + registry, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 + && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { + swaynag->xdg_output_manager = wl_registry_bind(registry, name, + &zxdg_output_manager_v1_interface, + ZXDG_OUTPUT_V1_NAME_SINCE_VERSION); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + struct swaynag *swaynag = data; + if (swaynag->output->wl_name == name) { + swaynag->run_display = false; + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +void swaynag_setup(struct swaynag *swaynag) { + swaynag->display = wl_display_connect(NULL); + assert(swaynag->display); + + swaynag->scale = 1; + wl_list_init(&swaynag->outputs); + + struct wl_registry *registry = wl_display_get_registry(swaynag->display); + wl_registry_add_listener(registry, ®istry_listener, swaynag); + wl_display_roundtrip(swaynag->display); + assert(swaynag->compositor && swaynag->layer_shell && swaynag->shm); + + while (swaynag->querying_outputs > 0) { + wl_display_roundtrip(swaynag->display); + } + + if (!swaynag->output && swaynag->type->output) { + wlr_log(WLR_ERROR, "Output '%s' not found", swaynag->type->output); + swaynag_destroy(swaynag); + exit(EXIT_FAILURE); + } + + struct swaynag_pointer *pointer = &swaynag->pointer; + pointer->cursor_surface = wl_compositor_create_surface(swaynag->compositor); + assert(pointer->cursor_surface); + + swaynag->surface = wl_compositor_create_surface(swaynag->compositor); + assert(swaynag->surface); + wl_surface_add_listener(swaynag->surface, &surface_listener, swaynag); + + swaynag->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + swaynag->layer_shell, swaynag->surface, + swaynag->output ? swaynag->output->wl_output : NULL, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, "swaynag"); + assert(swaynag->layer_surface); + zwlr_layer_surface_v1_add_listener(swaynag->layer_surface, + &layer_surface_listener, swaynag); + zwlr_layer_surface_v1_set_anchor(swaynag->layer_surface, + swaynag->type->anchors); + + wl_registry_destroy(registry); +} + +void swaynag_run(struct swaynag *swaynag) { + swaynag->run_display = true; + render_frame(swaynag); + while (swaynag->run_display + && wl_display_dispatch(swaynag->display) != -1) { + // This is intentionally left blank + } +} + +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); + free(button->text); + free(button->action); + free(button); + } + list_free(swaynag->buttons); + free(swaynag->details.message); + free(swaynag->details.button_up.text); + free(swaynag->details.button_down.text); + + if (swaynag->type) { + swaynag_type_free(swaynag->type); + } + + if (swaynag->layer_surface) { + zwlr_layer_surface_v1_destroy(swaynag->layer_surface); + } + + if (swaynag->surface) { + wl_surface_destroy(swaynag->surface); + } + + if (swaynag->pointer.cursor_theme) { + wl_cursor_theme_destroy(swaynag->pointer.cursor_theme); + } + + if (&swaynag->buffers[0]) { + destroy_buffer(&swaynag->buffers[0]); + } + + if (&swaynag->buffers[1]) { + destroy_buffer(&swaynag->buffers[1]); + } + + if (swaynag->outputs.prev || swaynag->outputs.next) { + struct swaynag_output *output, *temp; + wl_list_for_each_safe(output, temp, &swaynag->outputs, link) { + wl_output_destroy(output->wl_output); + free(output->name); + wl_list_remove(&output->link); + free(output); + }; + } + + if (swaynag->compositor) { + wl_compositor_destroy(swaynag->compositor); + } + + if (swaynag->shm) { + wl_shm_destroy(swaynag->shm); + } + + if (swaynag->display) { + wl_display_disconnect(swaynag->display); + } +} diff --git a/swaynag/types.c b/swaynag/types.c new file mode 100644 index 00000000..1e0a138b --- /dev/null +++ b/swaynag/types.c @@ -0,0 +1,156 @@ +#define _XOPEN_SOURCE 500 +#include <getopt.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <strings.h> +#include "list.h" +#include "swaynag/config.h" +#include "swaynag/types.h" +#include "util.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +void swaynag_types_add_default(list_t *types) { + struct swaynag_type *type_defaults; + type_defaults = calloc(1, sizeof(struct swaynag_type)); + type_defaults->name = strdup("<defaults>"); + type_defaults->font = strdup("pango:Monospace 10"); + type_defaults->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + type_defaults->button_background = 0x333333FF; + type_defaults->background = 0x323232FF; + type_defaults->text = 0xFFFFFFFF; + type_defaults->border = 0x222222FF; + type_defaults->border_bottom = 0x444444FF; + type_defaults->bar_border_thickness = 2; + type_defaults->message_padding = 8; + type_defaults->details_border_thickness = 3; + type_defaults->button_border_thickness = 3; + type_defaults->button_gap = 20; + type_defaults->button_gap_close = 15; + type_defaults->button_margin_right = 2; + type_defaults->button_padding = 3; + list_add(types, type_defaults); + + struct swaynag_type *type_error; + type_error = calloc(1, sizeof(struct swaynag_type)); + type_error->button_background = 0x680A0AFF; + type_error->background = 0x900000FF; + type_error->text = 0xFFFFFFFF; + type_error->border = 0xD92424FF; + type_error->border_bottom = 0x470909FF; + type_error->name = strdup("error"); + list_add(types, type_error); + + struct swaynag_type *type_warning; + type_warning = calloc(1, sizeof(struct swaynag_type)); + type_warning->name = strdup("warning"); + type_warning->button_background = 0xFFC100FF; + type_warning->background = 0xFFA800FF; + type_warning->text = 0x000000FF; + type_warning->border = 0xAB7100FF; + type_warning->border_bottom = 0xAB7100FF; + list_add(types, type_warning); +} + +struct swaynag_type *swaynag_type_get(list_t *types, char *name) { + for (int i = 0; i < types->length; i++) { + struct swaynag_type *type = types->items[i]; + if (strcasecmp(type->name, name) == 0) { + return type; + } + } + return NULL; +} + +void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) { + if (!dest || !src) { + return; + } + + if (!dest->font && src->font) { + dest->font = strdup(src->font); + } + + if (!dest->output && src->output) { + dest->output = strdup(src->output); + } + + if (dest->anchors == 0 && src->anchors > 0) { + dest->anchors = src->anchors; + } + + // Colors + if (dest->button_background == 0 && src->button_background > 0) { + dest->button_background = src->button_background; + } + + if (dest->background == 0 && src->background > 0) { + dest->background = src->background; + } + + if (dest->text == 0 && src->text > 0) { + dest->text = src->text; + } + + if (dest->border == 0 && src->border > 0) { + dest->border = src->border; + } + + if (dest->border_bottom == 0 && src->border_bottom > 0) { + dest->border_bottom = src->border_bottom; + } + + // Sizing + if (dest->bar_border_thickness == 0 && src->bar_border_thickness > 0) { + dest->bar_border_thickness = src->bar_border_thickness; + } + + if (dest->message_padding == 0 && src->message_padding > 0) { + dest->message_padding = src->message_padding; + } + + if (dest->details_border_thickness == 0 + && src->details_border_thickness > 0) { + dest->details_border_thickness = src->details_border_thickness; + } + + if (dest->button_border_thickness == 0 + && src->button_border_thickness > 0) { + dest->button_border_thickness = src->button_border_thickness; + } + + if (dest->button_gap == 0 && src->button_gap > 0) { + dest->button_gap = src->button_gap; + } + + if (dest->button_gap_close == 0 && src->button_gap_close > 0) { + dest->button_gap_close = src->button_gap_close; + } + + if (dest->button_margin_right == 0 && src->button_margin_right > 0) { + dest->button_margin_right = src->button_margin_right; + } + + if (dest->button_padding == 0 && src->button_padding > 0) { + dest->button_padding = src->button_padding; + } +} + +void swaynag_type_free(struct swaynag_type *type) { + free(type->name); + free(type->font); + free(type->output); + free(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); + } + list_free(types); +} |