diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33cb225e..4954400f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,7 +98,7 @@ Some distributions carry labwc in their repositories or user repositories. - @adcdam (Slackware) - @bdantas (Tiny Core Linux) - @Visone-Selektah (Venom Linux) -- @tranzystorek-io (Void Linux) +- @tranzystorekk (Void Linux) kindly maintain the packages in their respective distro. @@ -350,19 +350,32 @@ Base both bugfixes and new features on `master`. # Native Language Support +## Translators + +### Weblate Instance + +Translators can create an account at [LXQt Weblate](https://translate.lxqt-project.org/projects/labwc/labwc/) +and use the web interface. Adding new languages should work, otherwise the +administrators can be contacted. Suggestions for improving existing translations +can be added without account. + +### Github Pull Request + Translators can add their `MY_LOCALE.po` files to the `po` directory based on `po/labwc.pot` and issue a pull request. To do this they can generate their `MY_LOCALE.po` file in a few steps: -1. Edit the `po/LINGUAS` file to add their locale name by adding a space - to the end of the field and typing the locale code. -2. Copy the po/labwc.pot to po/MY_LOCALE.po -3. Edit the newly generated MY_LOCALE.po file with some of their +1. Edit the `po/LINGUAS` file to add their locale code in English + alphabetical order to the field of locale codes. +2. Copy the `po/labwc.pot` to `po/MY_LOCALE.po` +3. Edit the newly generated `MY_LOCALE.po` file with some of their contact and locale details in the header of the file then add the translation strings under each English string. [See this tutorial for further guidance](https://www.labri.fr/perso/fleury/posts/programming/a-quick-gettext-tutorial.html) +## Coders + Code contributors may need to update relevant files if their additions affect UI elements (at the moment only `src/menu/menu.c`). In this case the `po/labwc.pot` file needs to be updated so that translators can diff --git a/NEWS.md b/NEWS.md index 11c0cebd..dbafc05f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2024-01-23 | [unreleased] | 0.17.1 | | +| 2024-03-01 | [0.7.1] | 0.17.1 | 18624 | | 2023-12-22 | [0.7.0] | 0.17.1 | 16576 | | 2023-11-25 | [0.6.6] | 0.16.2 | 15796 | | 2023-09-23 | [0.6.5] | 0.16.2 | 14809 | @@ -28,10 +28,33 @@ The format is based on [Keep a Changelog] | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | -## [unreleased] +## [0.7.1] ### Added +- Support libinput option sendEventsMode to allow enabling/disabling devices. + Co-Authored-By: @Sachin-Bhat + +```xml + + + yes|no|disabledOnExternalMouse + + +``` + +- Add click method libinput option. Written-by: @datMaffin + +```xml + + + none|buttonAreas|clickfinger + + +``` + +- Add `data/labwc.svg` & `data/labwc-symbolic.svg`, and specify icon name + in labwc.desktop to enable Display Managers to show an icons for labwc. - Expose output configuration test to clients. For example, this enables `wlr-randr --dryrun` - Add window-edge resistance for interactive moves/resizes and support negative @@ -90,6 +113,7 @@ The format is based on [Keep a Changelog] - Add config option `` with supported values `center`, `cursor` and `automatic`. The latter minimizes overlap with other windows already on screen and is similar to Openbox's smart window placement. + The placement policies honour ``. Written-by: @ahesford #1312 ```xml @@ -100,6 +124,13 @@ The format is based on [Keep a Changelog] ### Fixed +- Delay popup-unconstrain until after first commit in response to a changed + wlroots 0.17 interface and to get rid of the error message below. Issue #1372 + + [types/xdg_shell/wlr_xdg_surface.c:169] A configure is scheduled for an uninitialized xdg_surface + +- Notify clients about configuration errors when changing output settings. + Issue #1528. - Fix output configuration bug causing compositor crash when refresh rate is zero. Issue #1458 - Fix disappearing cursor bug on view destruction. Issue #1393 @@ -124,6 +155,8 @@ The format is based on [Keep a Changelog] ### Changed +- Make `MoveToCursor` honour ``. Issue #1494 +- Add `Roll Up/Down` client-menu entry for `ToggleShade` - When a Wayland-native window is snapped to a screen edges or user-defined region, labwc will notify the application that it is "tiled", allowing the application to better adapt its rendering to constrained layouts. Windows @@ -1043,7 +1076,8 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 ShowMenu [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ -[unreleased]: https://github.com/labwc/labwc/compare/0.7.0...HEAD +[unreleased]: https://github.com/labwc/labwc/compare/0.7.1...HEAD +[0.7.1]: https://github.com/labwc/labwc/compare/0.7.0...0.7.1 [0.7.0]: https://github.com/labwc/labwc/compare/0.6.6...0.7.0 [0.6.6]: https://github.com/labwc/labwc/compare/0.6.5...0.6.6 [0.6.5]: https://github.com/labwc/labwc/compare/0.6.4...0.6.5 diff --git a/README.md b/README.md index f2c4c375..10339da7 100644 --- a/README.md +++ b/README.md @@ -127,8 +127,11 @@ High-level summary of items that Labwc supports: The obligatory screenshot: - - + + +
+ + Screenshot description ## 2. Build and Installation diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 9d55f63f..facb2d68 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -163,7 +163,7 @@ Actions are used in menus and keyboard/mouse bindings. to the center of the window. If the given output does not contain any windows, the cursor is centered on the given output. -** +** Moves active window to other output, unless the window state is fullscreen. @@ -172,6 +172,9 @@ Actions are used in menus and keyboard/mouse bindings. be one of "left", "right", "up" or "down" to indicate that the window should be moved to the next output in that direction (if one exists). + *wrap* [yes|no] When using the direction attribute, wrap around from + right-to-left or top-to-bottom, and vice versa. Default no. + ** Resizes active window size to width and height of the output when the window size exceeds the output size. diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 13751813..57e76bdc 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -8,7 +8,7 @@ labwc - configuration files Labwc uses openbox-3.6 specification for configuration and theming, but does not support all options. The following files form the basis of the labwc -configuration: rc.xml, menu.xml, autostart and environment. +configuration: rc.xml, menu.xml, autostart, shutdown and environment. No configuration files are needed to start and run labwc. @@ -34,11 +34,8 @@ alternative. The configuration directory location can be override with the -C command line option. -All configuration and theme files except autostart are re-loaded on receiving -signal SIGHUP. - -The *autostart* file is executed as a shell script. This is the place for -executing clients for handling background images, panels and similar. +All configuration and theme files except autostart and shutdown are re-loaded on +receiving signal SIGHUP. The *environment* file is parsed as *variable=value* and sets environment variables accordingly. It is recommended to specify keyboard layout settings and @@ -48,6 +45,20 @@ sourced prior to running openbox. Note: Tilde (~) and environment variables in the value are expanded, but subshell syntax and apostrophes are ignored. +The *autostart* file is executed as a shell script after labwc has read its +configuration and set variables defined in the environment file. Additionally, +the environment variables WAYLAND_DISPLAY and (when labwc is built with Xwayland +support) DISPLAY will be defined. This is the place for executing clients for +handling background images, panels and other tasks that should run automatically +when labwc launches. + +The *shutdown* file is executed as a shell script when labwc is preparing to +terminate itself. All environment variables, including WAYLAND_DISPLAY and +DISPLAY, will be available to the script. However, because the script runs +asynchronously with other termination tasks, the shutdown file should not assume +that the display will be usable. This file is useful to perform any custom +operations necessary to finalize a labwc session. + The *menu.xml* file defines the context/root-menus and is described in labwc-menu(5). @@ -511,7 +522,7 @@ extending outward from the snapped edge. ``` ** - A touch configuration can be bound to a specifc device. If device + A touch configuration can be bound to a specific device. If device name is left empty, the touch configuration applies to all touch devices or functions as a fallback. Multiple touch configurations can exist. @@ -592,6 +603,8 @@ extending outward from the snapped edge. + + ``` @@ -667,6 +680,36 @@ extending outward from the snapped edge. any motion events while a keyboard is typing, and for a short while after as well. +** [none|buttonAreas|clickfinger] + Configure the method by which physical clicks on a touchpad are mapped to + mouse-button events. + + The click methods available are: + - *buttonAreas* - The bottom of the touchpad is divided into distinct + regions corresponding to left, middle and right buttons; clicking within + the region will trigger the corresponding event. Clicking the main area + further up produces a left button event. + - *clickfinger* - Clicking with one, two or three finger(s) will produce + left, right or middle button event without regard to the location of a + click. + - *none* - Physical clicks will not produce button events. + + The default method depends on the touchpad hardware. + +** [yes|no|disabledOnExternalMouse] + Optionally enable or disable sending any device events. + + The options available are: + - *yes* - Events are sent as usual + - *no* - No events are sent from this device + - *disabledOnExternalMouse* - This device does not send events if an + external mouse has been detected. + + It is possible to prevent events from a device in the config and then do + a Reconfigure to temporarily enable / disable specific devices. + + By default, this setting is not configured. + ## WINDOW RULES Two types of window rules are supported, actions and properties. They are diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index ca9f1ae9..64eb1a98 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -2,7 +2,7 @@ labwc(1) # NAME -labwc - a wayland stacking compositor +labwc - a Wayland stacking compositor # SYNOPSIS @@ -16,8 +16,10 @@ It is light-weight and independent with a focus on simply stacking windows well and rendering some window decorations. Where practicable it uses clients for wall-paper, panels, screenshots and so on. +# SIGNALS + The compositor will exit or reload its configuration upon receiving SIGTERM -and SIGHUP respectively. For example: +and SIGHUP respectively. For example: ``` kill -s $LABWC_PID @@ -40,7 +42,7 @@ the `--exit` and `--reconfigure` options use. Enable full logging, including debug information *-e, --exit* - Exit the compositor + Exit the compositor by sending SIGTERM to `$LABWC_PID` *-h, --help* Show help message and quit @@ -49,7 +51,7 @@ the `--exit` and `--reconfigure` options use. Merge user config/theme files in all XDG Base Directories *-r, --reconfigure* - Reload the compositor configuration + Reload the compositor configuration by sending SIGHUP to `$LABWC_PID` *-s, --startup* Run command on startup @@ -60,6 +62,48 @@ the `--exit` and `--reconfigure` options use. *-V, --verbose* Enable more verbose logging +# SESSION MANAGEMENT + +To enable the use of graphical clients launched via D-Bus or systemd servie +activation, labwc can update both activation environments on launch. Provided +that labwc is aware of an active D-Bus user session (*i.e.*, the environment +variable `DBUS_SESSION_BUS_ADDRESS` is defined), the compositor will invoke the +commands + +``` +dbus-update-activation-environment +systemctl --user import-environment +``` + +(when available) to notify D-Bus and systemd with the values of the following +environment variables: + +``` +WAYLAND_DISPLAY +DISPLAY +XDG_CURRENT_DESKTOP +XDG_SESSION_TYPE +XCURSOR_SIZE +XCURSOR_THEME +LABWC_PWD +``` + +This behavior is enabled by default whenever labwc uses the "DRM" wlroots +backend (which implies that labwc is the primary compositor on the console). +When other backends are employed (for example, when labwc runs nested in another +Wayland compositor or an X11 server), updates to the activation environment are +disabled by default. Updates to the activation environment can be forced by +setting the environment variable `LABWC_UPDATE_ACTIVATION_ENV` to one of the +truthy values `1`, `true`, `yes` or `on`; or suppressed by setting the variable +to one of the falsy values `0`, `false`, `no` or `off`. + +Whenever labwc updates the activation environment on launch, it will also +attempt to clear the activation environment on exit. For D-Bus, which does not +provide a means for properly un-setting variables in the activation environment, +this is accomplished by setting the session variables to empty strings. For +systemd, the command `systemctl --user unset-environment` will be invoked to +actually remove the variables from the activation environment. + # SEE ALSO -labwc-config(5), labwc-theme(5), labwc-actions(5) +labwc-actions(5), labwc-config(5), labwc-menu(5), labwc-theme(5) diff --git a/docs/rc.xml b/docs/rc.xml index 3bf2d116..edcca868 100644 --- a/docs/rc.xml +++ b/docs/rc.xml @@ -7,10 +7,6 @@ - - 10 - - 8 diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 423bcb74..c3586ae0 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -465,13 +465,15 @@ non-touch, default or the name of a device. You can obtain device names by running *libinput list-devices* as root or member of the input group. - Tap is set to *yes* be default. All others are left blank in order to use + Tap is set to *yes* by default. All others are left blank in order to use device defaults. All values are [yes|no] except for: - pointerSpeed [-1.0 to 1.0] - accelProfile [flat|adaptive] - tapButtonMap [lrm|lmr] + - clickMethod [none|buttonAreas|clickfinger] + - sendEventsMode [yes|no|disabledOnExternalMouse] --> @@ -485,6 +487,8 @@ + + diff --git a/include/common/string-helpers.h b/include/common/string-helpers.h index 4e204302..6d0ec905 100644 --- a/include/common/string-helpers.h +++ b/include/common/string-helpers.h @@ -43,4 +43,23 @@ void string_truncate_at_pattern(char *buf, const char *pattern); */ char *strdup_printf(const char *fmt, ...); +/** + * str_join - format and join an array of strings with a separator + * @parts: NULL-terminated array of string parts to be joined + * @fmt: printf-style format string applied to each part + * @sep: separator inserted between parts when joining + * + * A new string is allocated to hold the joined result. The user must free the + * returned string. Returns NULL on error. + * + * Each part of the array is converted via the equivalent of sprintf(output, + * fmt, part), so fmt should include a single "%s" format specification. If fmt + * is NULL, a default "%s" will be used to copy each part verbatim. + * + * The separator is arbitrary. When the separator is NULL, a single space will + * be used. + */ +char *str_join(const char * const parts[], + const char *restrict fmt, const char *restrict sep); + #endif /* LABWC_STRING_HELPERS_H */ diff --git a/include/config/libinput.h b/include/config/libinput.h index 4ec0ebb7..912009ea 100644 --- a/include/config/libinput.h +++ b/include/config/libinput.h @@ -23,11 +23,13 @@ struct libinput_category { int left_handed; enum libinput_config_tap_state tap; enum libinput_config_tap_button_map tap_button_map; - int tap_and_drag; /* -1 or libinput_config_drag_state */ - int drag_lock; /* -1 or libinput_config_drag_lock_state */ - int accel_profile; /* -1 or libinput_config_accel_profile */ - int middle_emu; /* -1 or libinput_config_middle_emulation_state */ - int dwt; /* -1 or libinput_config_dwt_state */ + int tap_and_drag; /* -1 or libinput_config_drag_state */ + int drag_lock; /* -1 or libinput_config_drag_lock_state */ + int accel_profile; /* -1 or libinput_config_accel_profile */ + int middle_emu; /* -1 or libinput_config_middle_emulation_state */ + int dwt; /* -1 or libinput_config_dwt_state */ + int click_method; /* -1 or libinput_config_click_method */ + int send_events_mode; /* -1 or libinput_config_send_events_mode */ }; enum lab_libinput_device_type get_device_type(const char *s); diff --git a/include/config/session.h b/include/config/session.h index d1efbb60..e882f0e8 100644 --- a/include/config/session.h +++ b/include/config/session.h @@ -2,6 +2,8 @@ #ifndef LABWC_SESSION_H #define LABWC_SESSION_H +struct server; + /** * session_environment_init - set enrivonment variables based on = * pairs in `${XDG_CONFIG_DIRS:-/etc/xdg}/lawbc/environment` with user override @@ -13,6 +15,12 @@ void session_environment_init(void); * session_autostart_init - run autostart file as shell script * Note: Same as `sh ~/.config/labwc/autostart` (or equivalent XDG config dir) */ -void session_autostart_init(void); +void session_autostart_init(struct server *server); + +/** + * session_shutdown - run session shutdown file as shell script + * Note: Same as `sh ~/.config/labwc/shutdown` (or equivalent XDG config dir) + */ +void session_shutdown(struct server *server); #endif /* LABWC_SESSION_H */ diff --git a/include/edges.h b/include/edges.h index 4e7549eb..6ce029c3 100644 --- a/include/edges.h +++ b/include/edges.h @@ -3,11 +3,15 @@ #define LABWC_EDGES_H #include +#include +#include #include "common/macros.h" struct border; struct output; +struct server; struct view; +struct wlr_box; static inline int clipped_add(int a, int b) @@ -102,7 +106,7 @@ void edges_adjust_geom(struct view *view, struct border edges, void edges_find_neighbors(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, - edge_validator_t validator, bool use_pending); + edge_validator_t validator, bool use_pending, bool ignore_hidden); void edges_find_outputs(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, @@ -116,4 +120,5 @@ void edges_adjust_resize_geom(struct view *view, struct border edges, bool edges_traverse_edge(struct edge current, struct edge target, struct edge edge); +void edges_calculate_visibility(struct server *server, struct view *ignored_view); #endif /* LABWC_EDGES_H */ diff --git a/include/layers.h b/include/layers.h index d64c434f..52583035 100644 --- a/include/layers.h +++ b/include/layers.h @@ -28,6 +28,7 @@ struct lab_layer_popup { /* To simplify moving popup nodes from the bottom to the top layer */ struct wlr_box output_toplevel_sx_box; + struct wl_listener commit; struct wl_listener destroy; struct wl_listener new_popup; }; diff --git a/include/view.h b/include/view.h index fdedbe14..b0cb8471 100644 --- a/include/view.h +++ b/include/view.h @@ -166,6 +166,7 @@ struct view { bool tearing_hint; bool visible_on_all_workspaces; enum view_edge tiled; + uint32_t edges_visible; /* enum wlr_edges bitset */ bool inhibits_keybinds; xkb_layout_index_t keyboard_layout; @@ -502,7 +503,8 @@ void view_on_output_destroy(struct view *view); void view_connect_map(struct view *view, struct wlr_surface *surface); void view_destroy(struct view *view); -struct output *view_get_adjacent_output(struct view *view, enum view_edge edge); +struct output *view_get_adjacent_output(struct view *view, enum view_edge edge, + bool wrap); enum view_axis view_axis_parse(const char *direction); enum view_edge view_edge_parse(const char *direction); diff --git a/meson.build b/meson.build index a0a43263..adbd5455 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.7.0', + version: '0.7.1', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ diff --git a/po/LINGUAS b/po/LINGUAS index 58f25cb0..1b2407b5 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1 +1 @@ -de es et eu fi gl id it ja ka lt nl pa pl pt ru sv tr uk zh_CN +cs de es et eu fi gl hu id it ja ka lt nl pa pl pt ru sv tr uk zh_CN diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 00000000..104e5012 --- /dev/null +++ b/po/cs.po @@ -0,0 +1,69 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# zenobit , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-01-15 16:00-0500\n" +"PO-Revision-Date: 2024-03-02 02:00+0100\n" +"Last-Translator: zenobit \n" +"Language-Team: Czech \n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/menu/menu.c:697 +msgid "Reconfigure" +msgstr "Překonfigurovat" + +#: src/menu/menu.c:699 +msgid "Exit" +msgstr "Odejít" + +#: src/menu/menu.c:715 +msgid "Minimize" +msgstr "Minimalizovat" + +#: src/menu/menu.c:717 +msgid "Maximize" +msgstr "Maximalizovat" + +#: src/menu/menu.c:719 +msgid "Fullscreen" +msgstr "Na celou obrazovku" + +#: src/menu/menu.c:721 +msgid "Roll up/down" +msgstr "Rolovat nahoru/dolů" + +#: src/menu/menu.c:723 +msgid "Decorations" +msgstr "Dekorace" + +#: src/menu/menu.c:725 +msgid "Always on Top" +msgstr "Vždy nahoře" + +#: src/menu/menu.c:730 +msgid "Move left" +msgstr "Posunout doleva" + +#: src/menu/menu.c:737 +msgid "Move right" +msgstr "Posunout doprava" + +#: src/menu/menu.c:742 +msgid "Always on Visible Workspace" +msgstr "Vždy na viditelné Pracovní Ploše" + +#: src/menu/menu.c:745 +msgid "Workspace" +msgstr "Pracovní Plocha" + +#: src/menu/menu.c:748 +msgid "Close" +msgstr "Zavřít" diff --git a/po/fi.po b/po/fi.po index d67d0355..b5021b65 100644 --- a/po/fi.po +++ b/po/fi.po @@ -8,13 +8,16 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2024-02-29 14:23+0000\n" +"Last-Translator: Jouni Järvinen \n" +"Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" @@ -22,7 +25,7 @@ msgstr "" #: src/menu/menu.c:699 msgid "Exit" -msgstr "" +msgstr "Poistu" #: src/menu/menu.c:715 msgid "Minimize" diff --git a/po/hu.po b/po/hu.po new file mode 100644 index 00000000..da861355 --- /dev/null +++ b/po/hu.po @@ -0,0 +1,72 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-01-15 16:00-0500\n" +"PO-Revision-Date: 2024-02-19 21:23+0000\n" +"Last-Translator: winerysearch \n" +"Language-Team: Hungarian \n" +"Language: hu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.2.1\n" + +#: src/menu/menu.c:697 +msgid "Reconfigure" +msgstr "Rekonfigurál" + +#: src/menu/menu.c:699 +msgid "Exit" +msgstr "Kilépés" + +#: src/menu/menu.c:715 +msgid "Minimize" +msgstr "Kis méret" + +#: src/menu/menu.c:717 +msgid "Maximize" +msgstr "Teljes méret" + +#: src/menu/menu.c:719 +msgid "Fullscreen" +msgstr "Teljes képernyő" + +#: src/menu/menu.c:721 +msgid "Roll up/down" +msgstr "Felhúz / Legördül" + +#: src/menu/menu.c:723 +msgid "Decorations" +msgstr "Dekorációk" + +#: src/menu/menu.c:725 +msgid "Always on Top" +msgstr "Mindig felül" + +#: src/menu/menu.c:730 +msgid "Move left" +msgstr "Balra dokkol" + +#: src/menu/menu.c:737 +msgid "Move right" +msgstr "Jobbra dokkol" + +#: src/menu/menu.c:742 +msgid "Always on Visible Workspace" +msgstr "Kitűz" + +#: src/menu/menu.c:745 +msgid "Workspace" +msgstr "Munkaasztal" + +#: src/menu/menu.c:748 +msgid "Close" +msgstr "Bezárás" diff --git a/po/lt.po b/po/lt.po index 65ab1166..7ac63f85 100644 --- a/po/lt.po +++ b/po/lt.po @@ -8,14 +8,17 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" -"PO-Revision-Date: 2024-01-04 15:23+0000\n" +"PO-Revision-Date: 2024-02-29 14:23+0000\n" "Last-Translator: Moo \n" -"Language-Team: Lithuanian \n" +"Language-Team: Lithuanian \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n % 10 == 1 && (n % 100 < 11 || n % 100 > 19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n % 10 == 1 && (n % 100 < 11 || n % 100 > " +"19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? " +"1 : 2);\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 @@ -40,7 +43,7 @@ msgstr "Visas ekranas" #: src/menu/menu.c:721 msgid "Roll up/down" -msgstr "" +msgstr "Užraityti/atraityti" #: src/menu/menu.c:723 msgid "Decorations" diff --git a/po/pa.po b/po/pa.po index 77cd6b2c..655a83b1 100644 --- a/po/pa.po +++ b/po/pa.po @@ -8,9 +8,10 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" -"PO-Revision-Date: 2023-12-26 07:23+0000\n" +"PO-Revision-Date: 2024-02-21 14:23+0000\n" "Last-Translator: A S Alam \n" -"Language-Team: Punjabi \n" +"Language-Team: Punjabi \n" "Language: pa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -40,7 +41,7 @@ msgstr "ਪੂਰੀ ਸਕਰੀਨ" #: src/menu/menu.c:721 msgid "Roll up/down" -msgstr "" +msgstr "ਉੱਤੇ/ਹੇਠਾਂ ਸਕਰਾਓ" #: src/menu/menu.c:723 msgid "Decorations" diff --git a/po/ru.po b/po/ru.po index f9e34051..be979099 100644 --- a/po/ru.po +++ b/po/ru.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" -"PO-Revision-Date: 2024-01-30 08:23+0000\n" -"Last-Translator: pixis1 \n" +"PO-Revision-Date: 2024-02-26 12:23+0000\n" +"Last-Translator: Alice Ventus \n" "Language-Team: Russian \n" "Language: ru\n" @@ -41,7 +41,6 @@ msgid "Fullscreen" msgstr "На весь экран" #: src/menu/menu.c:721 -#, fuzzy msgid "Roll up/down" msgstr "Свернуть/развернуть в заголовок" diff --git a/po/tr.po b/po/tr.po index 64ad85f6..20505905 100644 --- a/po/tr.po +++ b/po/tr.po @@ -8,9 +8,10 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" -"PO-Revision-Date: 2023-12-26 09:00+0000\n" +"PO-Revision-Date: 2024-02-24 22:23+0000\n" "Last-Translator: Sabri Ünal \n" -"Language-Team: Turkish \n" +"Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -40,7 +41,7 @@ msgstr "Tam Ekran" #: src/menu/menu.c:721 msgid "Roll up/down" -msgstr "" +msgstr "Yukarı/aşağı katla" #: src/menu/menu.c:723 msgid "Decorations" diff --git a/protocols/meson.build b/protocols/meson.build index 0ec9154b..acaf0215 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -18,6 +18,7 @@ server_protocols = [ wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', + wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', diff --git a/src/action.c b/src/action.c index e1187906..1cf8960a 100644 --- a/src/action.c +++ b/src/action.c @@ -397,6 +397,10 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char } goto cleanup; } + if (!strcmp(argument, "wrap")) { + action_arg_add_bool(action, argument, parse_bool(content, false)); + goto cleanup; + } break; case ACTION_TYPE_VIRTUAL_OUTPUT_ADD: case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE: @@ -924,10 +928,15 @@ actions_run(struct view *activator, struct server *server, } else { /* Config parsing makes sure that direction is a valid direction */ enum view_edge edge = action_get_int(action, "direction", 0); - target = view_get_adjacent_output(view, edge); + bool wrap = action_get_bool(action, "wrap", false); + target = view_get_adjacent_output(view, edge, wrap); } if (!target) { - wlr_log(WLR_ERROR, "Invalid output."); + /* + * Most likely because we're already on the + * output furthest in the requested direction. + */ + wlr_log(WLR_DEBUG, "Invalid output"); break; } view_move_to_output(view, target); diff --git a/src/common/parse-bool.c b/src/common/parse-bool.c index 289ac0af..edef698a 100644 --- a/src/common/parse-bool.c +++ b/src/common/parse-bool.c @@ -14,12 +14,16 @@ parse_bool(const char *str, int default_value) return true; } else if (!strcasecmp(str, "on")) { return true; + } else if (!strcmp(str, "1")) { + return true; } else if (!strcasecmp(str, "no")) { return false; } else if (!strcasecmp(str, "false")) { return false; } else if (!strcasecmp(str, "off")) { return false; + } else if (!strcmp(str, "0")) { + return false; } error_not_a_boolean: wlr_log(WLR_ERROR, "(%s) is not a boolean value", str); diff --git a/src/common/string-helpers.c b/src/common/string-helpers.c index 37fdf4d0..0a1a8c81 100644 --- a/src/common/string-helpers.c +++ b/src/common/string-helpers.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only +#include #include #include #include @@ -83,3 +84,73 @@ strdup_printf(const char *fmt, ...) } return p; } + +char * +str_join(const char * const parts[], + const char *restrict fmt, const char *restrict sep) +{ + assert(parts); + + if (!fmt) { + fmt = "%s"; + } + + if (!sep) { + sep = " "; + } + + size_t size = 0; + size_t n_parts = 0; + + size_t sep_len = strlen(sep); + + /* Count the length of each formatted string */ + for (const char *const *s = parts; *s; ++s) { + int n = snprintf(NULL, 0, fmt, *s); + if (n < 0) { + return NULL; + } + size += (size_t)n; + ++n_parts; + } + + if (n_parts < 1) { + return NULL; + } + + /* Need (n_parts - 1) separators, plus one NULL terminator */ + size += (n_parts - 1) * sep_len + 1; + + /* Concatenate the strings and separators */ + char *buf = xzalloc(size); + char *p = buf; + for (const char *const *s = parts; *s; ++s) { + int n = 0; + + if (p != buf) { + n = snprintf(p, size, "%s", sep); + if (n < 0 || (size_t)n >= size) { + p = NULL; + break; + } + size -= (size_t)n; + p += (size_t)n; + } + + n = snprintf(p, size, fmt, *s); + if (n < 0 || (size_t)n >= size) { + p = NULL; + break; + } + size -= (size_t)n; + p += (size_t)n; + } + + if (!p) { + free(buf); + return NULL; + } + + return buf; +} + diff --git a/src/config/libinput.c b/src/config/libinput.c index 7ce62b79..93ab9af2 100644 --- a/src/config/libinput.c +++ b/src/config/libinput.c @@ -23,6 +23,8 @@ libinput_category_init(struct libinput_category *l) l->accel_profile = -1; l->middle_emu = -1; l->dwt = -1; + l->click_method = -1; + l->send_events_mode = -1; } enum lab_libinput_device_type diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 61784e13..56f93e36 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -461,6 +461,29 @@ get_accel_profile(const char *s) return -1; } +static int +get_send_events_mode(const char *s) +{ + if (!s) { + goto err; + } + + int ret = parse_bool(s, -1); + if (ret >= 0) { + return ret + ? LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; + } + + if (!strcasecmp(s, "disabledOnExternalMouse")) { + return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + } + +err: + wlr_log(WLR_INFO, "Not a recognised send events mode"); + return -1; +} + static void fill_libinput_category(char *nodename, char *content) { @@ -563,6 +586,22 @@ fill_libinput_category(char *nodename, char *content) current_libinput_category->dwt = ret ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; + } else if (!strcasecmp(nodename, "clickMethod")) { + if (!strcasecmp(content, "none")) { + current_libinput_category->click_method = + LIBINPUT_CONFIG_CLICK_METHOD_NONE; + } else if (!strcasecmp(content, "clickfinger")) { + current_libinput_category->click_method = + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; + } else if (!strcasecmp(content, "buttonAreas")) { + current_libinput_category->click_method = + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + } else { + wlr_log(WLR_ERROR, "invalid clickMethod"); + } + } else if (!strcasecmp(nodename, "sendEventsMode")) { + current_libinput_category->send_events_mode = + get_send_events_mode(content); } } diff --git a/src/config/session.c b/src/config/session.c index c87b0865..56cef717 100644 --- a/src/config/session.c +++ b/src/config/session.c @@ -1,19 +1,35 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L +#include #include #include #include #include #include +#include +#include #include #include "common/buf.h" #include "common/dir.h" #include "common/file-helpers.h" +#include "common/mem.h" +#include "common/parse-bool.h" #include "common/spawn.h" #include "common/string-helpers.h" #include "config/session.h" #include "labwc.h" +static const char *const env_vars[] = { + "DISPLAY", + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "XCURSOR_SIZE", + "XCURSOR_THEME", + "XDG_SESSION_TYPE", + "LABWC_PID", + NULL +}; + static void process_line(char *line) { @@ -65,23 +81,71 @@ read_environment_file(const char *filename) } static void -update_activation_env(const char *env_keys) +backend_check_drm(struct wlr_backend *backend, void *is_drm) { + if (wlr_backend_is_drm(backend)) { + *(bool *)is_drm = true; + } +} + +static bool +should_update_activation(struct server *server) +{ + assert(server); + + static const char *act_env = "LABWC_UPDATE_ACTIVATION_ENV"; + char *env = getenv(act_env); + if (env) { + /* Respect any valid preference from the environment */ + int enabled = parse_bool(env, -1); + + if (enabled == -1) { + wlr_log(WLR_ERROR, "ignoring non-Boolean variable %s", act_env); + } else { + wlr_log(WLR_DEBUG, "%s is %s", + act_env, enabled ? "true" : "false"); + return enabled; + } + } + + /* With no valid preference, update when a DRM backend is in use */ + bool have_drm = false; + wlr_multi_for_each_backend(server->backend, backend_check_drm, &have_drm); + return have_drm; +} + +static void +update_activation_env(struct server *server, bool initialize) +{ + if (!should_update_activation(server)) { + return; + } + if (!getenv("DBUS_SESSION_BUS_ADDRESS")) { /* Prevent accidentally auto-launching a dbus session */ wlr_log(WLR_INFO, "Not updating dbus execution environment: " "DBUS_SESSION_BUS_ADDRESS not set"); return; } + wlr_log(WLR_INFO, "Updating dbus execution environment"); - char *cmd = strdup_printf("dbus-update-activation-environment %s", env_keys); + char *env_keys = str_join(env_vars, "%s", " "); + char *env_unset_keys = initialize ? NULL : str_join(env_vars, "%s=", " "); + + char *cmd = + strdup_printf("dbus-update-activation-environment %s", + initialize ? env_keys : env_unset_keys); spawn_async_no_shell(cmd); free(cmd); - cmd = strdup_printf("systemctl --user import-environment %s", env_keys); + cmd = strdup_printf("systemctl --user %s %s", + initialize ? "import-environment" : "unset-environment", env_keys); spawn_async_no_shell(cmd); free(cmd); + + free(env_keys); + free(env_unset_keys); } void @@ -120,14 +184,11 @@ session_environment_init(void) paths_destroy(&paths); } -void -session_autostart_init(void) +static void +run_session_script(const char *script) { - /* Update dbus and systemd user environment, each may fail gracefully */ - update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"); - struct wl_list paths; - paths_config_create(&paths, "autostart"); + paths_config_create(&paths, script); bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); @@ -138,7 +199,7 @@ session_autostart_init(void) if (!file_exists(path->string)) { continue; } - wlr_log(WLR_INFO, "run autostart file %s", path->string); + wlr_log(WLR_INFO, "run session script %s", path->string); char *cmd = strdup_printf("sh %s", path->string); spawn_async_no_shell(cmd); free(cmd); @@ -149,3 +210,20 @@ session_autostart_init(void) } paths_destroy(&paths); } + +void +session_autostart_init(struct server *server) +{ + /* Update dbus and systemd user environment, each may fail gracefully */ + update_activation_env(server, /* initialize */ true); + run_session_script("autostart"); +} + +void +session_shutdown(struct server *server) +{ + run_session_script("shutdown"); + + /* Clear the dbus and systemd user environment, each may fail gracefully */ + update_activation_env(server, /* initialize */ false); +} diff --git a/src/debug.c b/src/debug.c index bac6cbaa..af722f3e 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,19 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-only #include #include +#include "common/graphic-helpers.h" #include "common/scene-helpers.h" #include "debug.h" #include "labwc.h" #include "node.h" #include "ssd.h" #include "view.h" +#include "workspaces.h" #define HEADER_CHARS "------------------------------" #define INDENT_SIZE 3 +#define LEFT_COL_SPACE 35 + #define IGNORE_SSD true #define IGNORE_MENU true -#define LEFT_COL_SPACE 35 +#define IGNORE_OSD_PREVIEW_OUTLINE true static struct view *last_view; @@ -42,13 +46,13 @@ get_layer_name(uint32_t layer) { switch (layer) { case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: - return "layer-background"; + return "output->layer-background"; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: - return "layer-bottom"; + return "output->layer-bottom"; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: - return "layer-top"; + return "output->layer-top"; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: - return "layer-overlay"; + return "output->layer-overlay"; default: abort(); } @@ -57,16 +61,27 @@ get_layer_name(uint32_t layer) static const char * get_view_part(struct view *view, struct wlr_scene_node *node) { - if (view && node == &view->scene_tree->node) { - return "view"; + static char view_name[LEFT_COL_SPACE]; + if (!view) { + return NULL; } - if (view && node == view->scene_node) { + if (node == &view->scene_tree->node) { + const char *app_id = view_get_string_prop(view, "app_id"); + if (!app_id) { + return "view"; + } + snprintf(view_name, sizeof(view_name), "view (%s)", app_id); + return view_name; + } + if (node == view->scene_node) { return "view->scene_node"; } - if (view) { - return ssd_debug_get_node_name(view->ssd, node); + if (view->resize_indicator.tree + && node == &view->resize_indicator.tree->node) { + /* Created on-demand */ + return "view->resize_indicator"; } - return NULL; + return ssd_debug_get_node_name(view->ssd, node); } static const char * @@ -81,12 +96,20 @@ get_special(struct server *server, struct wlr_scene_node *node) if (node == &server->view_tree->node) { return "server->view_tree"; } + if (node == &server->view_tree_always_on_bottom->node) { + return "server->always_on_bottom"; + } if (node == &server->view_tree_always_on_top->node) { - return "server->view_tree_always_on_top"; + return "server->always_on_top"; } if (node->parent == server->view_tree) { - /* Add node_descriptor just to get the name here? */ - return "workspace"; + struct workspace *workspace; + wl_list_for_each(workspace, &server->workspaces, link) { + if (&workspace->tree->node == node) { + return workspace->name; + } + } + return "unknown workspace"; } if (node->parent == &server->scene->tree) { struct output *output; @@ -95,15 +118,34 @@ get_special(struct server *server, struct wlr_scene_node *node) return "output->osd_tree"; } if (node == &output->layer_popup_tree->node) { - return "output->popup_tree"; + return "output->layer_popup_tree"; } for (int i = 0; i < 4; i++) { if (node == &output->layer_tree[i]->node) { return get_layer_name(i); } } + if (node == &output->session_lock_tree->node) { + return "output->session_lock_tree"; + } } } + if (node == &server->xdg_popup_tree->node) { + return "server->xdg_popup_tree"; + } + if (node == &server->seat.drag.icons->node) { + return "seat->drag.icons"; + } + if (server->seat.region_overlay.tree + && node == &server->seat.region_overlay.tree->node) { + /* Created on-demand */ + return "seat->region_overlay"; + } + if (server->osd_state.preview_outline + && node == &server->osd_state.preview_outline->tree->node) { + /* Created on-demand */ + return "osd_state->preview_outline"; + } #if HAVE_XWAYLAND if (node == &server->unmanaged_tree->node) { return "server->unmanaged_tree"; @@ -155,15 +197,21 @@ dump_tree(struct server *server, struct wlr_scene_node *node, HEADER_CHARS, HEADER_CHARS, HEADER_CHARS); printf(" "); } - int padding = LEFT_COL_SPACE - pos - strlen(type); + int max_width = LEFT_COL_SPACE - pos; + int padding = max_width - strlen(type); + if (padding < 0) { + padding = 0; + } if (!pos) { padding += 3; } - printf("%s %*c %4d %4d [%p]\n", type, padding, ' ', x, y, node); + printf("%.*s %*c %4d %4d [%p]\n", max_width - 1, type, padding, ' ', x, y, node); if ((IGNORE_MENU && node == &server->menu_tree->node) || (IGNORE_SSD && last_view - && ssd_debug_is_root_node(last_view->ssd, node))) { + && ssd_debug_is_root_node(last_view->ssd, node)) + || (IGNORE_OSD_PREVIEW_OUTLINE && server->osd_state.preview_outline + && node == &server->osd_state.preview_outline->tree->node)) { printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', ""); return; } diff --git a/src/edges.c b/src/edges.c index 86736a4d..3b507c3e 100644 --- a/src/edges.c +++ b/src/edges.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only #include #include +#include +#include #include #include "common/border.h" #include "common/macros.h" @@ -8,6 +10,7 @@ #include "edges.h" #include "labwc.h" #include "view.h" +#include "node.h" static void edges_for_target_geometry(struct border *edges, struct view *view, @@ -35,34 +38,54 @@ edges_initialize(struct border *edges) } static inline struct edge -build_edge(struct border region, enum view_edge direction, int pad) +build_edge(struct border region, enum wlr_edges direction, int pad) { struct edge edge = { 0 }; switch (direction) { - case VIEW_EDGE_LEFT: + case WLR_EDGE_LEFT: edge.offset = clipped_sub(region.left, pad); edge.min = region.top; edge.max = region.bottom; break; - case VIEW_EDGE_RIGHT: + case WLR_EDGE_RIGHT: edge.offset = clipped_add(region.right, pad); edge.min = region.top; edge.max = region.bottom; break; - case VIEW_EDGE_UP: + case WLR_EDGE_TOP: edge.offset = clipped_sub(region.top, pad); edge.min = region.left; edge.max = region.right; break; - case VIEW_EDGE_DOWN: + case WLR_EDGE_BOTTOM: edge.offset = clipped_add(region.bottom, pad); edge.min = region.left; edge.max = region.right; break; - default: + case WLR_EDGE_NONE: /* Should never be reached */ - assert(false); + wlr_log(WLR_ERROR, "invalid direction"); + abort(); + } + + return edge; +} + +static inline bool +is_lesser(enum wlr_edges direction) +{ + return direction == WLR_EDGE_LEFT || direction == WLR_EDGE_TOP; +} + +static inline struct edge +build_visible_edge(struct border region, enum wlr_edges direction, + int pad, uint32_t edges_visible) +{ + struct edge edge = build_edge(region, direction, pad); + + if (!(edges_visible & direction)) { + edge.offset = is_lesser(direction) ? INT_MIN : INT_MAX; } return edge; @@ -72,7 +95,7 @@ static void validate_single_region_edge(int *valid_edge, struct border view, struct border target, struct border region, edge_validator_t validator, - enum view_edge direction) + enum wlr_edges direction, uint32_t edges_visible) { /* * When a view snaps to another while moving to its target, it can do @@ -90,42 +113,63 @@ validate_single_region_edge(int *valid_edge, * the region borders for aligned edges only. */ - bool lesser = direction == VIEW_EDGE_LEFT || direction == VIEW_EDGE_UP; + enum wlr_edges opposing = WLR_EDGE_NONE; + + switch (direction) { + case WLR_EDGE_TOP: + opposing = WLR_EDGE_BOTTOM; + break; + case WLR_EDGE_BOTTOM: + opposing = WLR_EDGE_TOP; + break; + case WLR_EDGE_LEFT: + opposing = WLR_EDGE_RIGHT; + break; + case WLR_EDGE_RIGHT: + opposing = WLR_EDGE_LEFT; + break; + case WLR_EDGE_NONE: + /* Should never be reached */ + assert(false); + return; + } validator(valid_edge, build_edge(view, direction, 0), build_edge(target, direction, 0), - build_edge(region, view_edge_invert(direction), 0), - build_edge(region, direction, rc.gap), lesser); + build_visible_edge(region, opposing, 0, edges_visible), + build_visible_edge(region, direction, rc.gap, edges_visible), + is_lesser(direction)); } static void validate_edges(struct border *valid_edges, struct border view, struct border target, - struct border region, edge_validator_t validator) + struct border region, uint32_t edges_visible, + edge_validator_t validator) { /* Check for edges encountered during movement of left edge */ validate_single_region_edge(&valid_edges->left, - view, target, region, validator, VIEW_EDGE_LEFT); + view, target, region, validator, WLR_EDGE_LEFT, edges_visible); /* Check for edges encountered during movement of right edge */ validate_single_region_edge(&valid_edges->right, - view, target, region, validator, VIEW_EDGE_RIGHT); + view, target, region, validator, WLR_EDGE_RIGHT, edges_visible); /* Check for edges encountered during movement of top edge */ validate_single_region_edge(&valid_edges->top, - view, target, region, validator, VIEW_EDGE_UP); + view, target, region, validator, WLR_EDGE_TOP, edges_visible); /* Check for edges encountered during movement of bottom edge */ validate_single_region_edge(&valid_edges->bottom, - view, target, region, validator, VIEW_EDGE_DOWN); + view, target, region, validator, WLR_EDGE_BOTTOM, edges_visible); } static void validate_single_output_edge(int *valid_edge, struct border view, struct border target, struct border region, edge_validator_t validator, - enum view_edge direction) + enum wlr_edges direction) { static struct border unbounded = { .top = INT_MIN, @@ -134,13 +178,11 @@ validate_single_output_edge(int *valid_edge, .left = INT_MIN, }; - bool lesser = direction == VIEW_EDGE_LEFT || direction == VIEW_EDGE_UP; - validator(valid_edge, build_edge(view, direction, 0), build_edge(target, direction, 0), build_edge(region, direction, 0), - build_edge(unbounded, direction, 0), lesser); + build_edge(unbounded, direction, 0), is_lesser(direction)); } static void @@ -182,32 +224,170 @@ validate_output_edges(struct border *valid_edges, /* Left edge encounters a half-infinite region to the left of the output */ validate_single_output_edge(&valid_edges->left, - view, target, output, validator, VIEW_EDGE_LEFT); + view, target, output, validator, WLR_EDGE_LEFT); /* Right edge encounters a half-infinite region to the right of the output */ validate_single_output_edge(&valid_edges->right, - view, target, output, validator, VIEW_EDGE_RIGHT); + view, target, output, validator, WLR_EDGE_RIGHT); /* Top edge encounters a half-infinite region above the output */ validate_single_output_edge(&valid_edges->top, - view, target, output, validator, VIEW_EDGE_UP); + view, target, output, validator, WLR_EDGE_TOP); /* Bottom edge encounters a half-infinite region below the output */ validate_single_output_edge(&valid_edges->bottom, - view, target, output, validator, VIEW_EDGE_DOWN); + view, target, output, validator, WLR_EDGE_BOTTOM); +} + +/* Test if parts of the current view is covered by the remaining space in the region */ +static void +subtract_view_from_space(struct view *view, pixman_region32_t *available) +{ + struct wlr_box view_size = ssd_max_extents(view); + pixman_box32_t view_rect = { + .x1 = view_size.x, + .x2 = view_size.x + view_size.width, + .y1 = view_size.y, + .y2 = view_size.y + view_size.height + }; + + pixman_region_overlap_t overlap = + pixman_region32_contains_rectangle(available, &view_rect); + + switch (overlap) { + case PIXMAN_REGION_IN: + view->edges_visible = WLR_EDGE_TOP | WLR_EDGE_RIGHT + | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; + break; + case PIXMAN_REGION_OUT: + view->edges_visible = 0; + return; + case PIXMAN_REGION_PART: + ; /* works around "a label can only be part of a statement" */ + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, available, + view_size.x, view_size.y, + view_size.width, view_size.height); + + int nrects; + const pixman_box32_t *rects = + pixman_region32_rectangles(&intersection, &nrects); + + view->edges_visible = 0; + for (int i = 0; i < nrects; i++) { + if (rects[i].x1 == view_rect.x1) { + view->edges_visible |= WLR_EDGE_LEFT; + } + if (rects[i].y1 == view_rect.y1) { + view->edges_visible |= WLR_EDGE_TOP; + } + if (rects[i].x2 == view_rect.x2) { + view->edges_visible |= WLR_EDGE_RIGHT; + } + if (rects[i].y2 == view_rect.y2) { + view->edges_visible |= WLR_EDGE_BOTTOM; + } + } + pixman_region32_fini(&intersection); + break; + } + + /* Subtract the view geometry from the available region for the next check */ + pixman_region32_t view_region; + pixman_region32_init_rects(&view_region, &view_rect, 1); + pixman_region32_subtract(available, available, &view_region); + pixman_region32_fini(&view_region); +} + +static void +subtract_node_tree(struct wlr_scene_tree *tree, pixman_region32_t *available, + struct view *ignored_view) +{ + struct view *view; + struct wlr_scene_node *node; + struct node_descriptor *node_desc; + wl_list_for_each_reverse(node, &tree->children, link) { + if (!node->enabled) { + /* + * This skips everything that is not being + * rendered, including minimized / unmapped + * windows and workspaces other than the + * current one. + */ + continue; + } + + node_desc = node->data; + if (node_desc && node_desc->type == LAB_NODE_DESC_VIEW) { + view = node_view_from_node(node); + if (view != ignored_view) { + subtract_view_from_space(view, available); + } + } else if (node->type == WLR_SCENE_NODE_TREE) { + subtract_node_tree(wlr_scene_tree_from_node(node), + available, ignored_view); + } + } +} + +void +edges_calculate_visibility(struct server *server, struct view *ignored_view) +{ + /* + * The region stores the available output layout space + * and subtracts the window geometries in reverse rendering + * order, e.g. a window rendered on top is subtracted first. + * + * This allows to detect if a window is actually visible. + * If there is no overlap of its geometry and the remaining + * region it must be completely covered by other windows. + * + */ + pixman_region32_t region; + pixman_region32_init(®ion); + + /* + * Initialize the region with each individual output. + * + * If we were to use NULL for the reference output we + * would get a single combined wlr_box of the whole + * layout which could cover actual invisible areas + * in case the output resolutions differ. + */ + struct output *output; + struct wlr_box layout_box; + wl_list_for_each(output, &server->outputs, link) { + if (!output_is_usable(output)) { + continue; + } + wlr_output_layout_get_box(server->output_layout, + output->wlr_output, &layout_box); + pixman_region32_union_rect(®ion, ®ion, + layout_box.x, layout_box.y, layout_box.width, layout_box.height); + } + + subtract_node_tree(&server->scene->tree, ®ion, ignored_view); + + pixman_region32_fini(®ion); } void edges_find_neighbors(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, - edge_validator_t validator, bool use_pending) + edge_validator_t validator, bool use_pending, bool ignore_hidden) { assert(view); assert(validator); assert(nearest_edges); + if (!output_is_usable(view->output)) { + wlr_log(WLR_DEBUG, "ignoring edge search for view on unsable output"); + return; + } + struct border view_edges = { 0 }; struct border target_edges = { 0 }; @@ -223,22 +403,21 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view, continue; } - if (output && v->output != output) { + uint32_t edges_visible = ignore_hidden ? v->edges_visible : + WLR_EDGE_TOP | WLR_EDGE_LEFT + | WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; + + if (edges_visible == 0) { continue; } - /* - * If view and v are on different outputs, make sure part of - * view is actually in the usable area of the output of v. - */ - if (view->output != v->output) { - struct wlr_box usable = - output_usable_area_in_layout_coords(v->output); + if (output && output != v->output && !view_on_output(v, output)) { + continue; + } - struct wlr_box ol; - if (!wlr_box_intersection(&ol, view_geom, &usable)) { - continue; - } + /* Both view and v must share a common output */ + if (view->output != v->output && !(view->outputs & v->outputs)) { + continue; } struct border border = ssd_get_margin(v->ssd); @@ -252,7 +431,7 @@ edges_find_neighbors(struct border *nearest_edges, struct view *view, }; validate_edges(nearest_edges, view_edges, - target_edges, win_edges, validator); + target_edges, win_edges, edges_visible, validator); } } @@ -265,6 +444,12 @@ edges_find_outputs(struct border *nearest_edges, struct view *view, assert(validator); assert(nearest_edges); + if (!output_is_usable(view->output)) { + wlr_log(WLR_DEBUG, + "ignoring edge search for view on unsable output"); + return; + } + struct border view_edges = { 0 }; struct border target_edges = { 0 }; diff --git a/src/input/key-state.c b/src/input/key-state.c index c512a762..8ce66ff0 100644 --- a/src/input/key-state.c +++ b/src/input/key-state.c @@ -79,9 +79,7 @@ key_state_pressed_sent_keycodes(void) report(&bound, "before - bound:"); /* pressed_sent = pressed - bound */ - memcpy(pressed_sent.keys, pressed.keys, - MAX_PRESSED_KEYS * sizeof(uint32_t)); - pressed_sent.nr_keys = pressed.nr_keys; + pressed_sent = pressed; for (int i = 0; i < bound.nr_keys; ++i) { remove_key(&pressed_sent, bound.keys[i]); } diff --git a/src/interactive.c b/src/interactive.c index 432027a6..f4b09e1d 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only +#include "edges.h" #include "input/keyboard.h" #include "labwc.h" #include "regions.h" @@ -123,6 +124,9 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) if (rc.resize_indicator) { resize_indicator_show(view); } + if (rc.window_edge_strength) { + edges_calculate_visibility(server, view); + } } /* Returns true if view was snapped to any edge */ diff --git a/src/layers.c b/src/layers.c index 79cffe94..20e9e683 100644 --- a/src/layers.c +++ b/src/layers.c @@ -260,14 +260,35 @@ popup_handle_destroy(struct wl_listener *listener, void *data) wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); + + /* Usually already removed unless there was no commit at all */ + if (popup->commit.notify) { + wl_list_remove(&popup->commit.link); + } + free(popup); } +static void +popup_handle_commit(struct wl_listener *listener, void *data) +{ + struct lab_layer_popup *popup = + wl_container_of(listener, popup, commit); + + if (popup->wlr_popup->base->initial_commit) { + wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, + &popup->output_toplevel_sx_box); + + /* Prevent getting called over and over again */ + wl_list_remove(&popup->commit.link); + popup->commit.notify = NULL; + } +} + static void popup_handle_new_popup(struct wl_listener *listener, void *data); static struct lab_layer_popup * -create_popup(struct wlr_xdg_popup *wlr_popup, struct wlr_scene_tree *parent, - struct wlr_box *output_toplevel_sx_box) +create_popup(struct wlr_xdg_popup *wlr_popup, struct wlr_scene_tree *parent) { struct lab_layer_popup *popup = znew(*popup); popup->wlr_popup = wlr_popup; @@ -282,10 +303,13 @@ create_popup(struct wlr_xdg_popup *wlr_popup, struct wlr_scene_tree *parent, popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); - wlr_xdg_popup_unconstrain_from_box(wlr_popup, output_toplevel_sx_box); + popup->commit.notify = popup_handle_commit; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + return popup; } @@ -297,8 +321,7 @@ popup_handle_new_popup(struct wl_listener *listener, void *data) wl_container_of(listener, lab_layer_popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; struct lab_layer_popup *new_popup = create_popup(wlr_popup, - lab_layer_popup->scene_tree, - &lab_layer_popup->output_toplevel_sx_box); + lab_layer_popup->scene_tree); new_popup->output_toplevel_sx_box = lab_layer_popup->output_toplevel_sx_box; } @@ -357,8 +380,7 @@ handle_new_popup(struct wl_listener *listener, void *data) .width = output_box.width, .height = output_box.height, }; - struct lab_layer_popup *popup = create_popup(wlr_popup, - surface->tree, &output_toplevel_sx_box); + struct lab_layer_popup *popup = create_popup(wlr_popup, surface->tree); popup->output_toplevel_sx_box = output_toplevel_sx_box; if (surface->layer_surface->current.layer diff --git a/src/main.c b/src/main.c index 9bfe3a1d..35b2f3fa 100644 --- a/src/main.c +++ b/src/main.c @@ -171,13 +171,15 @@ main(int argc, char *argv[]) menu_init(&server); - session_autostart_init(); + session_autostart_init(&server); if (startup_cmd) { spawn_async_no_shell(startup_cmd); } wl_display_run(server.wl_display); + session_shutdown(&server); + server_finish(&server); menu_finish(&server); diff --git a/src/output.c b/src/output.c index 6d05ee94..16eee4b8 100644 --- a/src/output.c +++ b/src/output.c @@ -150,6 +150,32 @@ output_request_state_notify(struct wl_listener *listener, void *data) struct output *output = wl_container_of(listener, output, request_state); const struct wlr_output_event_request_state *event = data; + /* + * If wlroots ever requests other state changes here we could + * restore more of ddc9047a67cd53b2948f71fde1bbe9118000dd3f. + */ + if (event->state->committed == WLR_OUTPUT_STATE_MODE) { + /* Only the mode has changed */ + switch (event->state->mode_type) { + case WLR_OUTPUT_STATE_MODE_FIXED: + wlr_output_set_mode(output->wlr_output, event->state->mode); + break; + case WLR_OUTPUT_STATE_MODE_CUSTOM: + wlr_output_set_custom_mode(output->wlr_output, + event->state->custom_mode.width, + event->state->custom_mode.height, + event->state->custom_mode.refresh); + break; + } + wlr_output_schedule_frame(output->wlr_output); + return; + } + + /* + * Fallback path for everything that we didn't handle above. + * The commit will cause a black frame injection so this + * path causes flickering during resize of nested outputs. + */ if (!wlr_output_commit_state(output->wlr_output, event->state)) { wlr_log(WLR_ERROR, "Backend requested a new state that could not be applied"); } @@ -405,10 +431,11 @@ output_update_for_layout_change(struct server *server) cursor_update_image(&server->seat); } -static void +static bool output_config_apply(struct server *server, struct wlr_output_configuration_v1 *config) { + bool success = true; server->pending_output_layout_change++; struct wlr_output_configuration_head_v1 *head; @@ -436,8 +463,15 @@ output_config_apply(struct server *server, output_enable_adaptive_sync(o, head->state.adaptive_sync_enabled); } if (!wlr_output_commit(o)) { - wlr_log(WLR_ERROR, "Output config commit failed"); - continue; + /* + * FIXME: This is only part of the story, we should revert + * all previously commited outputs as well here. + * + * See https://github.com/labwc/labwc/pull/1528 + */ + wlr_log(WLR_INFO, "Output config commit failed: %s", o->name); + success = false; + break; } /* Only do Layout specific actions if the commit went trough */ @@ -477,6 +511,7 @@ output_config_apply(struct server *server, server->pending_output_layout_change--; do_output_layout_change(server); + return success; } static bool @@ -509,11 +544,17 @@ verify_output_config_v1(const struct wlr_output_configuration_v1 *config) if (wlr_output_is_wl(head->state.output) && refresh != 0) { /* Wayland backend does not support refresh rates */ - err_msg = "Wayland backend refresh rate unsupported"; + err_msg = "Wayland backend refresh rates unsupported"; goto custom_mode_failed; } } + if (wlr_output_is_wl(head->state.output) + && !head->state.adaptive_sync_enabled) { + err_msg = "Wayland backend requires adaptive sync"; + goto custom_mode_failed; + } + /* * Ensure the new output state can be applied on * its own and inform the client when it can not. @@ -569,8 +610,7 @@ handle_output_manager_apply(struct wl_listener *listener, void *data) bool config_is_good = verify_output_config_v1(config); - if (config_is_good) { - output_config_apply(server, config); + if (config_is_good && output_config_apply(server, config)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); diff --git a/src/resistance.c b/src/resistance.c index e7869fc1..7d317de8 100644 --- a/src/resistance.c +++ b/src/resistance.c @@ -114,8 +114,9 @@ resistance_move_apply(struct view *view, double *x, double *y) if (rc.window_edge_strength != 0) { /* Find any relevant window edges encountered by this move */ - edges_find_neighbors(&next_edges, view, target, NULL, - check_edge_window, /* use_pending */ false); + edges_find_neighbors(&next_edges, + view, target, NULL, check_edge_window, + /* use_pending */ false, /* ignore_hidden */ true); } /* If any "best" edges were encountered during this move, snap motion */ @@ -143,8 +144,9 @@ resistance_resize_apply(struct view *view, struct wlr_box *new_geom) if (rc.window_edge_strength != 0) { /* Find any relevant window edges encountered by this move */ - edges_find_neighbors(&next_edges, view, *new_geom, NULL, - check_edge_window, /* use_pending */ false); + edges_find_neighbors(&next_edges, + view, *new_geom, NULL, check_edge_window, + /* use_pending */ false, /* ignore_hidden */ true); } /* If any "best" edges were encountered during this move, snap motion */ diff --git a/src/seat.c b/src/seat.c index 52b1b5d0..e4e69efe 100644 --- a/src/seat.c +++ b/src/seat.c @@ -91,6 +91,24 @@ get_category(struct wlr_input_device *device) static void configure_libinput(struct wlr_input_device *wlr_input_device) { + /* + * TODO: We do not check any return values for the various + * libinput_device_config_*_set_*() calls. It would + * be nice if we could inform the users via log file + * that some libinput setting could not be applied. + * + * TODO: We are currently using int32_t with -1 as default + * to desribe the not-configured state. This is not + * really optimal as we can't properly deal with + * enum values that are 0. After some discussion via + * IRC the best way forward seem to be to use a + * uint32_t instead and UINT32_MAX as indicator for + * a not-configured state. This allows to properly + * test the enum being a member of a bitset via + * mask & value == value. All libinput enums are + * way below UINT32_MAX. + */ + if (!wlr_input_device) { wlr_log(WLR_ERROR, "no wlr_input_device"); return; @@ -189,6 +207,36 @@ configure_libinput(struct wlr_input_device *wlr_input_device) wlr_log(WLR_INFO, "dwt configured"); libinput_device_config_dwt_set_enabled(libinput_dev, dc->dwt); } + + if ((dc->click_method != LIBINPUT_CONFIG_CLICK_METHOD_NONE + && (libinput_device_config_click_get_methods(libinput_dev) + & dc->click_method) == 0) + || dc->click_method < 0) { + wlr_log(WLR_INFO, "click method not configured"); + } else { + wlr_log(WLR_INFO, "click method configured"); + + /* + * Note, the documentation claims that: + * > [...] The device may require changing to a neutral state + * > first before activating the new method. + * + * However, just setting the method seems to work without + * issues. + */ + + libinput_device_config_click_set_method(libinput_dev, dc->click_method); + } + + if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + && (libinput_device_config_send_events_get_modes(libinput_dev) + & dc->send_events_mode) == 0) + || dc->send_events_mode < 0) { + wlr_log(WLR_INFO, "send events mode not configured"); + } else { + wlr_log(WLR_INFO, "send events mode configured"); + libinput_device_config_send_events_set_mode(libinput_dev, dc->send_events_mode); + } } static struct wlr_output * diff --git a/src/server.c b/src/server.c index c0ee7682..e1e48dd6 100644 --- a/src/server.c +++ b/src/server.c @@ -17,6 +17,7 @@ #include #if HAVE_XWAYLAND #include +#include "xwayland-shell-v1-protocol.h" #endif #include "drm-lease-v1-protocol.h" #include "config/rcxml.h" @@ -172,9 +173,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo (void)iface; (void)server; #if HAVE_XWAYLAND - struct wl_client *xwayland_client = - server->xwayland ? server->xwayland->server->client : NULL; - if (xwayland_client && client == xwayland_client) { + struct wl_client *xwayland_client = (server->xwayland && server->xwayland->server) + ? server->xwayland->server->client + : NULL; + + if (client == xwayland_client) { /* * Filter out wp_drm_lease_device_v1 for now as it is resulting in * issues with Xwayland applications lagging over time. @@ -184,6 +187,9 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo if (!strcmp(iface->name, wp_drm_lease_device_v1_interface.name)) { return false; } + } else if (!strcmp(iface->name, xwayland_shell_v1_interface.name)) { + /* Filter out the xwayland shell for usual clients */ + return false; } #endif diff --git a/src/snap.c b/src/snap.c index 1215f0be..0db5d11c 100644 --- a/src/snap.c +++ b/src/snap.c @@ -121,8 +121,9 @@ snap_move_to_edge(struct view *view, enum view_edge direction, struct border next_edges; edges_initialize(&next_edges); - edges_find_neighbors(&next_edges, view, target, - output, check_edge, /* use_pending */ true); + edges_find_neighbors(&next_edges, + view, target, output, check_edge, + /* use_pending */ true, /* ignore_hidden */ false); /* If any "best" edges were encountered, limit motion */ edges_adjust_move_coords(view, next_edges, @@ -196,8 +197,9 @@ snap_grow_to_next_edge(struct view *view, enum view_edge direction, edges_initialize(&next_edges); /* Limit motion to any intervening edge of other views on this output */ - edges_find_neighbors(&next_edges, view, *geo, - output, check_edge, /* use_pending */ true); + edges_find_neighbors(&next_edges, + view, *geo, output, check_edge, + /* use_pending */ true, /* ignore_hidden */ false); edges_adjust_resize_geom(view, next_edges, resize_edges, geo, /* use_pending */ true); } @@ -255,8 +257,9 @@ snap_shrink_to_next_edge(struct view *view, enum view_edge direction, view->output, check_edge, /* use_pending */ true); /* Limit motion to any intervening edge of ther views on this output */ - edges_find_neighbors(&next_edges, view, *geo, - view->output, check_edge, /* use_pending */ true); + edges_find_neighbors(&next_edges, + view, *geo, view->output, check_edge, + /* use_pending */ true, /* ignore_hidden */ false); edges_adjust_resize_geom(view, next_edges, resize_edges, geo, /* use_pending */ true); diff --git a/src/view.c b/src/view.c index 87b29e00..52627643 100644 --- a/src/view.c +++ b/src/view.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "common/macros.h" #include "common/match.h" #include "common/mem.h" @@ -1519,8 +1520,44 @@ view_on_output_destroy(struct view *view) view->output = NULL; } +static enum wlr_direction +opposite_direction(enum wlr_direction direction) +{ + switch (direction) { + case WLR_DIRECTION_RIGHT: + return WLR_DIRECTION_LEFT; + case WLR_DIRECTION_LEFT: + return WLR_DIRECTION_RIGHT; + case WLR_DIRECTION_DOWN: + return WLR_DIRECTION_UP; + case WLR_DIRECTION_UP: + return WLR_DIRECTION_DOWN; + default: + return 0; + } +} + +static enum wlr_direction +get_wlr_direction(enum view_edge edge) +{ + switch (edge) { + case VIEW_EDGE_LEFT: + return WLR_DIRECTION_LEFT; + case VIEW_EDGE_RIGHT: + return WLR_DIRECTION_RIGHT; + case VIEW_EDGE_UP: + return WLR_DIRECTION_UP; + case VIEW_EDGE_DOWN: + return WLR_DIRECTION_DOWN; + case VIEW_EDGE_CENTER: + case VIEW_EDGE_INVALID: + default: + return 0; + } +} + struct output * -view_get_adjacent_output(struct view *view, enum view_edge edge) +view_get_adjacent_output(struct view *view, enum view_edge edge, bool wrap) { assert(view); struct output *output = view->output; @@ -1530,32 +1567,31 @@ view_get_adjacent_output(struct view *view, enum view_edge edge) return NULL; } + struct wlr_box box = output_usable_area_in_layout_coords(output); + int lx = box.x + box.width / 2; + int ly = box.y + box.height / 2; + /* Determine any adjacent output in the appropriate direction */ struct wlr_output *new_output = NULL; struct wlr_output *current_output = output->wlr_output; struct wlr_output_layout *layout = view->server->output_layout; - switch (edge) { - case VIEW_EDGE_LEFT: - new_output = wlr_output_layout_adjacent_output( - layout, WLR_DIRECTION_LEFT, current_output, 1, 0); - break; - case VIEW_EDGE_RIGHT: - new_output = wlr_output_layout_adjacent_output( - layout, WLR_DIRECTION_RIGHT, current_output, 1, 0); - break; - case VIEW_EDGE_UP: - new_output = wlr_output_layout_adjacent_output( - layout, WLR_DIRECTION_UP, current_output, 0, 1); - break; - case VIEW_EDGE_DOWN: - new_output = wlr_output_layout_adjacent_output( - layout, WLR_DIRECTION_DOWN, current_output, 0, 1); - break; - default: - break; + enum wlr_direction direction = get_wlr_direction(edge); + new_output = wlr_output_layout_adjacent_output(layout, direction, + current_output, lx, ly); + + /* + * Optionally wrap around from top-to-bottom or left-to-right, and vice + * versa. + */ + if (wrap && !new_output) { + new_output = wlr_output_layout_farthest_output(layout, + opposite_direction(direction), current_output, lx, ly); } - /* When "adjacent" output is the same as the original, there is no adjacent */ + /* + * When "adjacent" output is the same as the original, there is no + * adjacent + */ if (!new_output || new_output == current_output) { return NULL; } @@ -1629,7 +1665,8 @@ view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_wind } /* Otherwise, move to edge of next adjacent display, if possible */ - struct output *output = view_get_adjacent_output(view, direction); + struct output *output = + view_get_adjacent_output(view, direction, /* wrap */ false); if (!output) { return; } @@ -1788,7 +1825,7 @@ view_snap_to_edge(struct view *view, enum view_edge edge, if (across_outputs && view->tiled == edge && view->maximized == VIEW_AXIS_NONE) { /* We are already tiled for this edge; try to switch outputs */ - output = view_get_adjacent_output(view, edge); + output = view_get_adjacent_output(view, edge, /* wrap */ false); if (!output) { /* diff --git a/src/xdg-popup.c b/src/xdg-popup.c index 496c5c0c..65c13e8d 100644 --- a/src/xdg-popup.c +++ b/src/xdg-popup.c @@ -16,15 +16,17 @@ struct xdg_popup { struct view *parent_view; struct wlr_xdg_popup *wlr_popup; + struct wl_listener commit; struct wl_listener destroy; struct wl_listener new_popup; }; static void -popup_unconstrain(struct view *view, struct wlr_xdg_popup *popup) +popup_unconstrain(struct xdg_popup *popup) { + struct view *view = popup->parent_view; struct server *server = view->server; - struct wlr_box *popup_box = &popup->current.geometry; + struct wlr_box *popup_box = &popup->wlr_popup->current.geometry; struct wlr_output_layout *output_layout = server->output_layout; struct wlr_output *wlr_output = wlr_output_layout_output_at( output_layout, view->current.x + popup_box->x, @@ -39,7 +41,7 @@ popup_unconstrain(struct view *view, struct wlr_xdg_popup *popup) .width = output_box.width, .height = output_box.height, }; - wlr_xdg_popup_unconstrain_from_box(popup, &output_toplevel_box); + wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); } static void @@ -48,9 +50,28 @@ handle_xdg_popup_destroy(struct wl_listener *listener, void *data) struct xdg_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); + + /* Usually already removed unless there was no commit at all */ + if (popup->commit.notify) { + wl_list_remove(&popup->commit.link); + } free(popup); } +static void +handle_xdg_popup_commit(struct wl_listener *listener, void *data) +{ + struct xdg_popup *popup = wl_container_of(listener, popup, commit); + + if (popup->wlr_popup->base->initial_commit) { + popup_unconstrain(popup); + + /* Prevent getting called over and over again */ + wl_list_remove(&popup->commit.link); + popup->commit.notify = NULL; + } +} + static void popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) { @@ -75,9 +96,13 @@ xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) popup->destroy.notify = handle_xdg_popup_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->new_popup.notify = popup_handle_new_xdg_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); + popup->commit.notify = handle_xdg_popup_commit; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + /* * We must add xdg popups to the scene graph so they get rendered. The * wlroots scene graph provides a helper for this, but to use it we must @@ -100,6 +125,4 @@ xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) wlr_scene_xdg_surface_create(parent_tree, wlr_popup->base); node_descriptor_create(wlr_popup->base->surface->data, LAB_NODE_DESC_XDG_POPUP, view); - - popup_unconstrain(view, wlr_popup); }