├── config.h.in ├── render.h ├── idle_inhibit_v1.h ├── meson_options.txt ├── util.h ├── contrib ├── sign-release ├── release ├── tag-release └── increment-version ├── .clang-format ├── xwayland.h ├── .builds ├── alpine.yml ├── freebsd.yml └── archlinux.yml ├── util.c ├── LICENSE ├── xdg_shell.h ├── output.h ├── server.h ├── cage.1.scd ├── idle_inhibit_v1.c ├── seat.h ├── README.md ├── view.h ├── meson.build ├── render.c ├── xwayland.c ├── view.c ├── xdg_shell.c ├── cage.c ├── output.c └── seat.c /config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CG_CONFIG_H 2 | #define CG_CONFIG_H 3 | 4 | #mesondefine CAGE_HAS_XWAYLAND 5 | 6 | #mesondefine CAGE_VERSION 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /render.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_RENDER_H 2 | #define CG_RENDER_H 3 | 4 | #include "output.h" 5 | 6 | void output_render(struct cg_output *output, pixman_region32_t *damage); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /idle_inhibit_v1.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_IDLE_INHIBIT_H 2 | #define CG_IDLE_INHIBIT_H 3 | 4 | #include 5 | 6 | void handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 2 | option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications') 3 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_UTIL_H 2 | #define CG_UTIL_H 3 | 4 | #include 5 | 6 | /** Apply scale to a width or height. */ 7 | int scale_length(int length, int offset, float scale); 8 | 9 | void scale_box(struct wlr_box *box, float scale); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /contrib/sign-release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | project="$(basename "$(pwd)")" 6 | last=$(git describe --tags --abbrev=0) 7 | 8 | prefix="$project-${last#v}" 9 | archive="$prefix.tar.gz" 10 | 11 | git archive --prefix="$prefix/" -o "$archive" "$last" 12 | gpg --output "$archive".sig --detach-sig "$archive" 13 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | AlignAfterOpenBracket: Align 2 | AlignTrailingComments: false 3 | AlwaysBreakAfterReturnType: TopLevelDefinitions 4 | BreakBeforeBraces: Linux 5 | ColumnLimit: 120 6 | ContinuationIndentWidth: 8 7 | ForEachMacros: [wl_list_for_each, wl_list_for_each_safe, wl_list_for_each_reverse] 8 | IndentWidth: 8 9 | ReflowComments: true 10 | SortIncludes: true 11 | SpaceAfterCStyleCast: true 12 | TabWidth: 8 13 | UseTab: Always 14 | -------------------------------------------------------------------------------- /contrib/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 " >&2 5 | exit 1 6 | fi 7 | 8 | new_version="$1" 9 | 10 | if [ "$new_version" != "${new_version#v}" ]; then 11 | echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2 12 | exit 1 13 | fi 14 | 15 | set -x 16 | 17 | ./increment_version "$new_version" 18 | ./tag-release "$new_version" 19 | ./sign-release 20 | 21 | git push --tags -------------------------------------------------------------------------------- /contrib/tag-release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | if [ "$#" -ne 1 ]; then 6 | echo "usage: $0 " >&2 7 | exit 1 8 | fi 9 | 10 | last=$(git describe --tags --abbrev=0) 11 | echo "Last release was $last" 12 | 13 | next="v$1" 14 | 15 | shortlog="$(git shortlog --no-merges "$last"..)" 16 | 17 | printf "Shortlog: \n\n%s\n\nRelease $next? [y/N] " "$shortlog" 18 | read -r answer 19 | 20 | if [ "$answer" != "y" ]; then 21 | exit 0 22 | fi 23 | 24 | project="$(basename "$(pwd)")" 25 | 26 | (echo "$project $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F - 27 | -------------------------------------------------------------------------------- /contrib/increment-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 " >&2 5 | exit 1 6 | fi 7 | 8 | new_version="$1" 9 | 10 | if [ "$new_version" != "${new_version#v}" ]; then 11 | echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2 12 | exit 1 13 | fi 14 | 15 | set -x 16 | 17 | sed -i meson.build -e "s/^ version: '.*'/ version: '$new_version'/" 18 | 19 | echo -n "Minimum wlroots version? " 20 | read -r wlr_version_min 21 | 22 | sed -i meson.build -e "s/'wlroots', version: '.*'/'wlroots', version: '>= $wlr_version_min'/" 23 | 24 | git add meson.build 25 | git commit -m "Update version to $new_version" -------------------------------------------------------------------------------- /xwayland.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_XWAYLAND_H 2 | #define CG_XWAYLAND_H 3 | 4 | #include 5 | #include 6 | 7 | #include "view.h" 8 | 9 | struct cg_xwayland_view { 10 | struct cg_view view; 11 | struct wlr_xwayland_surface *xwayland_surface; 12 | struct wl_listener destroy; 13 | struct wl_listener unmap; 14 | struct wl_listener map; 15 | struct wl_listener commit; 16 | struct wl_listener request_fullscreen; 17 | }; 18 | 19 | struct cg_xwayland_view *xwayland_view_from_view(struct cg_view *view); 20 | bool xwayland_view_should_manage(struct cg_view *view); 21 | void handle_xwayland_surface_new(struct wl_listener *listener, void *data); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /.builds/alpine.yml: -------------------------------------------------------------------------------- 1 | image: alpine/edge 2 | packages: 3 | - eudev-dev 4 | - mesa-dev 5 | - meson 6 | - libinput-dev 7 | - libxkbcommon-dev 8 | - pixman-dev 9 | - scdoc 10 | - wayland-dev 11 | - wayland-protocols 12 | - xcb-util-wm-dev 13 | - xwayland 14 | sources: 15 | - https://github.com/swaywm/wlroots 16 | - https://github.com/Hjdskes/cage 17 | tasks: 18 | # Install wlroots, which is required by Cage. Note that we compile a tagged 19 | # version, instead of master, to avoid any breaking changes in wlroots. 20 | - wlroots: | 21 | cd wlroots 22 | git checkout 0.14.0 23 | meson --prefix=/usr build -Dexamples=false 24 | ninja -C build 25 | sudo ninja -C build install 26 | - build: | 27 | cd cage 28 | meson build --werror -Dxwayland=true 29 | ninja -C build 30 | rm -rf build 31 | - build-no-xwayland: | 32 | cd cage 33 | meson build --werror -Dxwayland=false 34 | ninja -C build 35 | rm -rf build 36 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2019 The Sway authors 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #include 10 | 11 | #include "util.h" 12 | 13 | int 14 | scale_length(int length, int offset, float scale) 15 | { 16 | /** 17 | * One does not simply multiply the width by the scale. We allow fractional 18 | * scaling, which means the resulting scaled width might be a decimal. 19 | * So we round it. 20 | * 21 | * But even this can produce undesirable results depending on the X or Y 22 | * offset of the box. For example, with a scale of 1.5, a box with 23 | * width=1 should not scale to 2px if its X coordinate is 1, because the 24 | * X coordinate would have scaled to 2px. 25 | */ 26 | return round((offset + length) * scale) - round(offset * scale); 27 | } 28 | 29 | void 30 | scale_box(struct wlr_box *box, float scale) 31 | { 32 | box->width = scale_length(box->width, box->x, scale); 33 | box->height = scale_length(box->height, box->y, scale); 34 | box->x = round(box->x * scale); 35 | box->y = round(box->y * scale); 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Jente Hidskes 2 | Copyright (c) 2019 The Sway authors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /xdg_shell.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_XDG_SHELL_H 2 | #define CG_XDG_SHELL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "view.h" 9 | 10 | struct cg_xdg_shell_view { 11 | struct cg_view view; 12 | struct wlr_xdg_surface *xdg_surface; 13 | 14 | struct wl_listener destroy; 15 | struct wl_listener unmap; 16 | struct wl_listener map; 17 | struct wl_listener commit; 18 | struct wl_listener request_fullscreen; 19 | struct wl_listener new_popup; 20 | }; 21 | 22 | struct cg_xdg_popup { 23 | struct cg_view_child view_child; 24 | struct wlr_xdg_popup *wlr_popup; 25 | 26 | struct wl_listener destroy; 27 | struct wl_listener map; 28 | struct wl_listener unmap; 29 | struct wl_listener new_popup; 30 | }; 31 | 32 | struct cg_xdg_decoration { 33 | struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; 34 | struct cg_server *server; 35 | struct wl_listener destroy; 36 | struct wl_listener request_mode; 37 | }; 38 | 39 | void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data); 40 | 41 | void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /.builds/freebsd.yml: -------------------------------------------------------------------------------- 1 | image: freebsd/latest 2 | packages: 3 | - devel/evdev-proto 4 | - devel/meson 5 | - devel/libepoll-shim 6 | - devel/pkgconf 7 | - graphics/mesa-libs 8 | - graphics/wayland 9 | - graphics/wayland-protocols 10 | - textproc/scdoc 11 | - x11/libinput 12 | - x11/libxkbcommon 13 | - x11/pixman 14 | - x11/xcb-util-wm 15 | - x11-servers/xwayland 16 | sources: 17 | - https://github.com/swaywm/wlroots 18 | - https://github.com/Hjdskes/cage 19 | tasks: 20 | # Install wlroots, which is required by Cage. Note that we compile a tagged 21 | # version, instead of master, to avoid any breaking changes in wlroots. 22 | - wlroots: | 23 | cd wlroots 24 | git checkout 0.14.0 25 | meson --prefix=/usr/local build -Dexamples=false 26 | ninja -C build 27 | sudo ninja -C build install 28 | - build: | 29 | cd cage 30 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build --werror -Dxwayland=true 31 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build 32 | rm -rf build 33 | - build-no-xwayland: | 34 | cd cage 35 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build --werror -Dxwayland=false 36 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build 37 | rm -rf build 38 | -------------------------------------------------------------------------------- /.builds/archlinux.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - clang 4 | - meson 5 | - libinput 6 | - libxkbcommon 7 | - mesa 8 | - scdoc 9 | - wayland 10 | - wayland-protocols 11 | - xcb-util-wm 12 | - xorg-xwayland 13 | sources: 14 | - https://github.com/swaywm/wlroots 15 | - https://github.com/Hjdskes/cage 16 | tasks: 17 | # Install wlroots, which is required by Cage. Note that we compile a tagged 18 | # version, instead of master, to avoid any breaking changes in wlroots. 19 | - wlroots: | 20 | cd wlroots 21 | git checkout 0.14.0 22 | meson --prefix=/usr build -Dexamples=false 23 | ninja -C build 24 | sudo ninja -C build install 25 | - build: | 26 | cd cage 27 | meson build --werror -Dxwayland=true 28 | ninja -C build 29 | rm -rf build 30 | - build-no-xwayland: | 31 | cd cage 32 | meson build --werror -Dxwayland=false 33 | ninja -C build 34 | rm -rf build 35 | - scan-build: | 36 | cd cage 37 | CC=clang meson build --werror -Dxwayland=true 38 | CC=clang ninja -C build scan-build 39 | rm -rf build 40 | - clang-format: | 41 | cd cage 42 | meson build --werror -Dxwayland=true 43 | ninja -C build clang-format 44 | rm -rf build 45 | git diff --exit-code 46 | -------------------------------------------------------------------------------- /output.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_OUTPUT_H 2 | #define CG_OUTPUT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "server.h" 9 | #include "view.h" 10 | 11 | struct cg_output { 12 | struct cg_server *server; 13 | struct wlr_output *wlr_output; 14 | struct wlr_output_damage *damage; 15 | 16 | struct wl_listener commit; 17 | struct wl_listener mode; 18 | struct wl_listener destroy; 19 | struct wl_listener damage_frame; 20 | struct wl_listener damage_destroy; 21 | 22 | struct wl_list link; // cg_server::outputs 23 | }; 24 | 25 | typedef void (*cg_surface_iterator_func_t)(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, 26 | void *user_data); 27 | 28 | void handle_new_output(struct wl_listener *listener, void *data); 29 | void output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy, 30 | cg_surface_iterator_func_t iterator, void *user_data); 31 | void output_view_for_each_popup_surface(struct cg_output *output, struct cg_view *view, 32 | cg_surface_iterator_func_t iterator, void *user_data); 33 | void output_drag_icons_for_each_surface(struct cg_output *output, struct wl_list *drag_icons, 34 | cg_surface_iterator_func_t iterator, void *user_data); 35 | void output_damage_surface(struct cg_output *output, struct wlr_surface *surface, double lx, double ly, bool whole); 36 | void output_set_window_title(struct cg_output *output, const char *title); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /server.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_SERVER_H 2 | #define CG_SERVER_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #if CAGE_HAS_XWAYLAND 12 | #include 13 | #endif 14 | 15 | #include "output.h" 16 | #include "seat.h" 17 | #include "view.h" 18 | 19 | enum cg_multi_output_mode { 20 | CAGE_MULTI_OUTPUT_MODE_EXTEND, 21 | CAGE_MULTI_OUTPUT_MODE_LAST, 22 | }; 23 | 24 | struct cg_server { 25 | struct wl_display *wl_display; 26 | struct wl_list views; 27 | struct wlr_backend *backend; 28 | 29 | struct cg_seat *seat; 30 | struct wlr_idle *idle; 31 | struct wlr_idle_inhibit_manager_v1 *idle_inhibit_v1; 32 | struct wl_listener new_idle_inhibitor_v1; 33 | struct wl_list inhibitors; 34 | 35 | enum cg_multi_output_mode output_mode; 36 | struct wlr_output_layout *output_layout; 37 | /* Includes disabled outputs; depending on the output_mode 38 | * some outputs may be disabled. */ 39 | struct wl_list outputs; // cg_output::link 40 | struct wl_listener new_output; 41 | 42 | struct wl_listener xdg_toplevel_decoration; 43 | struct wl_listener new_xdg_shell_surface; 44 | #if CAGE_HAS_XWAYLAND 45 | struct wl_listener new_xwayland_surface; 46 | #endif 47 | 48 | bool xdg_decoration; 49 | bool allow_vt_switch; 50 | enum wl_output_transform output_transform; 51 | #ifdef DEBUG 52 | bool debug_damage_tracking; 53 | #endif 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /cage.1.scd: -------------------------------------------------------------------------------- 1 | cage(1) 2 | 3 | # NAME 4 | 5 | cage - a Wayland kiosk compositor 6 | 7 | # SYNOPSIS 8 | 9 | *cage* [-dhmrsv] [--] _application_ [application argument ...] 10 | 11 | # DESCRIPTION 12 | 13 | Cage runs a single, maximized application. Cage can run multiple applications, 14 | but only a single one is visible at any point in time. User interaction and 15 | activities outside the scope of the running application are prevented. 16 | 17 | # OPTIONS 18 | 19 | *-d* 20 | Don't draw client side decorations when possible. 21 | 22 | *-h* 23 | Show the help message. 24 | 25 | *-m* 26 | Set the multi-monitor behavior. Supported modes are: 27 | *last* Cage uses only the last connected monitor. 28 | *extend* Cage extends the display across all connected monitors. 29 | 30 | *-r* 31 | Rotate the output 90 degrees clockwise. This can be specified up to three 32 | times, each resulting in an additional 90 degrees clockwise rotation. 33 | 34 | *-s* 35 | Allow VT switching 36 | 37 | *-v* 38 | Show the version number and exit. 39 | 40 | # ENVIRONMENT 41 | 42 | _DISPLAY_ 43 | If compiled with Xwayland support, this will be set to the name of the 44 | X display used for Xwayland. Otherwise, probe the X11 backend. 45 | 46 | _WAYLAND_DISPLAY_ 47 | Specifies the name of the Wayland display that Cage is running on. 48 | 49 | _XCURSOR_PATH_ 50 | Directory where cursors are located. 51 | 52 | _XCURSOR_SIZE_ 53 | Specifies the configured cursor size. 54 | 55 | _XCURSOR_THEME_ 56 | Specifies the configured cursor theme. 57 | 58 | _XKB_DEFAULT_RULES_, _XKB_DEFAULT_MODEL_, _XKB_DEFAULT_LAYOUT_, 59 | _XKB_DEFAULT_VARIANT_, _XKB_DEFAULT_OPTIONS_ 60 | Configures the xkb keyboard settings. See *xkeyboard-config*(7). 61 | 62 | # SEE ALSO 63 | 64 | *xkeyboard-config(7)* 65 | 66 | # BUGS 67 | 68 | Report bugs at https://github.com/Hjdskes/cage 69 | 70 | # AUTHORS 71 | 72 | Jente Hidskes 73 | -------------------------------------------------------------------------------- /idle_inhibit_v1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2019 Jente Hidskes 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "idle_inhibit_v1.h" 15 | #include "server.h" 16 | 17 | struct cg_idle_inhibitor_v1 { 18 | struct cg_server *server; 19 | 20 | struct wl_list link; // server::inhibitors 21 | struct wl_listener destroy; 22 | }; 23 | 24 | static void 25 | idle_inhibit_v1_check_active(struct cg_server *server) 26 | { 27 | /* Due to Cage's unique window management, we don't need to 28 | check for visibility. In the worst cage, the inhibitor is 29 | spawned by a dialog that _may_ be obscured by another 30 | dialog, but this is really an edge case that, until 31 | reported, does not warrant the additional complexity. 32 | Hence, we simply check for any inhibitors and inhibit 33 | accordingly. */ 34 | bool inhibited = !wl_list_empty(&server->inhibitors); 35 | wlr_idle_set_enabled(server->idle, NULL, !inhibited); 36 | } 37 | 38 | static void 39 | handle_destroy(struct wl_listener *listener, void *data) 40 | { 41 | struct cg_idle_inhibitor_v1 *inhibitor = wl_container_of(listener, inhibitor, destroy); 42 | struct cg_server *server = inhibitor->server; 43 | 44 | wl_list_remove(&inhibitor->link); 45 | wl_list_remove(&inhibitor->destroy.link); 46 | free(inhibitor); 47 | 48 | idle_inhibit_v1_check_active(server); 49 | } 50 | 51 | void 52 | handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data) 53 | { 54 | struct cg_server *server = wl_container_of(listener, server, new_idle_inhibitor_v1); 55 | struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; 56 | 57 | struct cg_idle_inhibitor_v1 *inhibitor = calloc(1, sizeof(struct cg_idle_inhibitor_v1)); 58 | if (!inhibitor) { 59 | return; 60 | } 61 | 62 | inhibitor->server = server; 63 | wl_list_insert(&server->inhibitors, &inhibitor->link); 64 | 65 | inhibitor->destroy.notify = handle_destroy; 66 | wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy); 67 | 68 | idle_inhibit_v1_check_active(server); 69 | } 70 | -------------------------------------------------------------------------------- /seat.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_SEAT_H 2 | #define CG_SEAT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "server.h" 12 | #include "view.h" 13 | 14 | #define DEFAULT_XCURSOR "left_ptr" 15 | #define XCURSOR_SIZE 24 16 | 17 | struct cg_seat { 18 | struct wlr_seat *seat; 19 | struct cg_server *server; 20 | struct wl_listener destroy; 21 | 22 | struct wl_list keyboards; 23 | struct wl_list keyboard_groups; 24 | struct wl_list pointers; 25 | struct wl_list touch; 26 | struct wl_listener new_input; 27 | 28 | struct wlr_cursor *cursor; 29 | struct wlr_xcursor_manager *xcursor_manager; 30 | struct wl_listener cursor_motion; 31 | struct wl_listener cursor_motion_absolute; 32 | struct wl_listener cursor_button; 33 | struct wl_listener cursor_axis; 34 | struct wl_listener cursor_frame; 35 | 36 | int32_t touch_id; 37 | double touch_lx; 38 | double touch_ly; 39 | struct wl_listener touch_down; 40 | struct wl_listener touch_up; 41 | struct wl_listener touch_motion; 42 | 43 | struct wl_list drag_icons; 44 | struct wl_listener request_start_drag; 45 | struct wl_listener start_drag; 46 | 47 | struct wl_listener request_set_cursor; 48 | struct wl_listener request_set_selection; 49 | struct wl_listener request_set_primary_selection; 50 | }; 51 | 52 | struct cg_keyboard_group { 53 | struct wlr_keyboard_group *wlr_group; 54 | struct cg_seat *seat; 55 | struct wl_listener key; 56 | struct wl_listener modifiers; 57 | struct wl_list link; // cg_seat::keyboard_groups 58 | }; 59 | 60 | struct cg_pointer { 61 | struct wl_list link; // seat::pointers 62 | struct cg_seat *seat; 63 | struct wlr_input_device *device; 64 | 65 | struct wl_listener destroy; 66 | }; 67 | 68 | struct cg_touch { 69 | struct wl_list link; // seat::touch 70 | struct cg_seat *seat; 71 | struct wlr_input_device *device; 72 | 73 | struct wl_listener destroy; 74 | }; 75 | 76 | struct cg_drag_icon { 77 | struct wl_list link; // seat::drag_icons 78 | struct cg_seat *seat; 79 | struct wlr_drag_icon *wlr_drag_icon; 80 | 81 | /* The drag icon has a position in layout coordinates. */ 82 | double lx, ly; 83 | 84 | struct wl_listener destroy; 85 | }; 86 | 87 | struct cg_seat *seat_create(struct cg_server *server, struct wlr_backend *backend); 88 | void seat_destroy(struct cg_seat *seat); 89 | struct cg_view *seat_get_focus(struct cg_seat *seat); 90 | void seat_set_focus(struct cg_seat *seat, struct cg_view *view); 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cage: a Wayland kiosk [![builds.sr.ht status](https://builds.sr.ht/~hjdskes.svg)](https://builds.sr.ht/~hjdskes?) 2 | 3 | Cage's logo 4 | 5 | This is Cage, a Wayland kiosk. A kiosk runs a single, maximized 6 | application. 7 | 8 | This README is only relevant for development resources and instructions. For a 9 | description of Cage and installation instructions for end-users, please see 10 | [its project page](https://www.hjdskes.nl/projects/cage) and [the 11 | Wiki](https://github.com/Hjdskes/cage/wiki/). 12 | 13 | ## Release signatures 14 | 15 | Releases are signed with 16 | [6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1) 17 | and published on [GitHub](https://github.com/Hjdskes/cage/releases). 18 | 19 | ## Building and running Cage 20 | 21 | You can build Cage with the [meson](https://mesonbuild.com/) build system. It 22 | requires wayland, wlroots, and xkbcommon to be installed. Optionally, install 23 | scdoc for manual pages. Note that Cage is developed against the latest tag of 24 | wlroots, in order to not constantly chase breaking changes as soon as they 25 | occur. 26 | 27 | Simply execute the following steps to build Cage: 28 | 29 | ``` 30 | $ meson build 31 | $ ninja -C build 32 | ``` 33 | 34 | By default, this builds a debug build. To build a release build, use `meson 35 | build --buildtype=release`. 36 | 37 | Cage comes with compile-time support for XWayland. To enable this, 38 | first make sure that your version of wlroots is compiled with this 39 | option. Then, add `-Dxwayland=true` to the `meson` command above. Note 40 | that you'll need to have the XWayland binary installed on your system 41 | for this to work. 42 | 43 | You can run Cage by running `./build/cage APPLICATION`. If you run it from 44 | within an existing X11 or Wayland session, it will open in a virtual output as 45 | a window in your existing session. If you run it at a TTY, it'll run with the 46 | KMS+DRM backend. In debug mode (default build type with Meson), press 47 | Alt+Esc to quit. For more configuration options, see 48 | [Configuration](https://github.com/Hjdskes/cage/wiki/Configuration). 49 | 50 | Cage is based on the annotated source of tinywl and rootston. 51 | 52 | ## Bugs 53 | 54 | For any bug, please [create an 55 | issue](https://github.com/Hjdskes/cage/issues/new) on 56 | [GitHub](https://github.com/Hjdskes/cage). 57 | 58 | ## License 59 | 60 | Please see 61 | [LICENSE](https://github.com/Hjdskes/cage/blob/master/LICENSE) on 62 | [GitHub](https://github.com/Hjdskes/cage). 63 | 64 | Copyright © 2018-2020 Jente Hidskes 65 | -------------------------------------------------------------------------------- /view.h: -------------------------------------------------------------------------------- 1 | #ifndef CG_VIEW_H 2 | #define CG_VIEW_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #if CAGE_HAS_XWAYLAND 12 | #include 13 | #endif 14 | 15 | #include "server.h" 16 | 17 | enum cg_view_type { 18 | CAGE_XDG_SHELL_VIEW, 19 | #if CAGE_HAS_XWAYLAND 20 | CAGE_XWAYLAND_VIEW, 21 | #endif 22 | }; 23 | 24 | struct cg_view { 25 | struct cg_server *server; 26 | struct wl_list link; // server::views 27 | struct wl_list children; // cg_view_child::link 28 | struct wlr_surface *wlr_surface; 29 | 30 | /* The view has a position in layout coordinates. */ 31 | int lx, ly; 32 | 33 | enum cg_view_type type; 34 | const struct cg_view_impl *impl; 35 | 36 | struct wl_listener new_subsurface; 37 | }; 38 | 39 | struct cg_view_impl { 40 | char *(*get_title)(struct cg_view *view); 41 | void (*get_geometry)(struct cg_view *view, int *width_out, int *height_out); 42 | bool (*is_primary)(struct cg_view *view); 43 | bool (*is_transient_for)(struct cg_view *child, struct cg_view *parent); 44 | void (*activate)(struct cg_view *view, bool activate); 45 | void (*maximize)(struct cg_view *view, int output_width, int output_height); 46 | void (*destroy)(struct cg_view *view); 47 | void (*for_each_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); 48 | void (*for_each_popup_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); 49 | struct wlr_surface *(*wlr_surface_at)(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y); 50 | }; 51 | 52 | struct cg_view_child { 53 | struct cg_view *view; 54 | struct wlr_surface *wlr_surface; 55 | struct wl_list link; 56 | 57 | struct wl_listener commit; 58 | struct wl_listener new_subsurface; 59 | 60 | void (*destroy)(struct cg_view_child *child); 61 | }; 62 | 63 | struct cg_subsurface { 64 | struct cg_view_child view_child; 65 | struct wlr_subsurface *wlr_subsurface; 66 | 67 | struct wl_listener destroy; 68 | }; 69 | 70 | char *view_get_title(struct cg_view *view); 71 | bool view_is_primary(struct cg_view *view); 72 | bool view_is_transient_for(struct cg_view *child, struct cg_view *parent); 73 | void view_damage_part(struct cg_view *view); 74 | void view_damage_whole(struct cg_view *view); 75 | void view_activate(struct cg_view *view, bool activate); 76 | void view_position(struct cg_view *view); 77 | void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); 78 | void view_for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); 79 | void view_unmap(struct cg_view *view); 80 | void view_map(struct cg_view *view, struct wlr_surface *surface); 81 | void view_destroy(struct cg_view *view); 82 | void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl); 83 | 84 | struct cg_view *view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface); 85 | struct wlr_surface *view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y); 86 | 87 | void view_child_finish(struct cg_view_child *child); 88 | void view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface); 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('cage', 'c', 2 | version: '0.1.4', 3 | license: 'MIT', 4 | default_options: [ 5 | 'c_std=c11', 6 | 'warning_level=3', 7 | ], 8 | ) 9 | 10 | add_project_arguments( 11 | [ 12 | '-DWLR_USE_UNSTABLE', 13 | '-Wall', 14 | '-Wundef', 15 | '-Wno-unused-parameter', 16 | ], 17 | language: 'c', 18 | ) 19 | 20 | if get_option('buildtype').startswith('debug') 21 | add_project_arguments('-DDEBUG', language : 'c') 22 | endif 23 | 24 | cc = meson.get_compiler('c') 25 | 26 | is_freebsd = host_machine.system().startswith('freebsd') 27 | if is_freebsd 28 | add_project_arguments( 29 | [ 30 | '-Wno-format-extra-args', 31 | '-Wno-gnu-zero-variadic-macro-arguments', 32 | ], 33 | language: 'c' 34 | ) 35 | endif 36 | 37 | wlroots = dependency('wlroots', version: '>= 0.14.0', fallback: ['wlroots', 'wlroots']) 38 | wayland_protos = dependency('wayland-protocols', version: '>=1.14') 39 | wayland_server = dependency('wayland-server') 40 | pixman = dependency('pixman-1') 41 | xkbcommon = dependency('xkbcommon') 42 | math = cc.find_library('m') 43 | 44 | wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') 45 | wayland_scanner = find_program('wayland-scanner') 46 | wayland_scanner_server = generator( 47 | wayland_scanner, 48 | output: '@BASENAME@-protocol.h', 49 | arguments: ['server-header', '@INPUT@', '@OUTPUT@'], 50 | ) 51 | 52 | server_protocols = [ 53 | [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], 54 | ] 55 | 56 | server_protos_headers = [] 57 | 58 | foreach p : server_protocols 59 | xml = join_paths(p) 60 | server_protos_headers += wayland_scanner_server.process(xml) 61 | endforeach 62 | 63 | server_protos = declare_dependency( 64 | sources: server_protos_headers, 65 | ) 66 | 67 | if get_option('xwayland') 68 | wlroots_has_xwayland = wlroots.get_variable(pkgconfig: 'have_xwayland', internal: 'have_xwayland') == 'true' 69 | if not wlroots_has_xwayland 70 | error('Cannot build Cage with XWayland support: wlroots has been built without it') 71 | endif 72 | have_xwayland = true 73 | else 74 | have_xwayland = false 75 | endif 76 | 77 | version = '@0@'.format(meson.project_version()) 78 | git = find_program('git', native: true, required: false) 79 | if git.found() 80 | git_commit = run_command([git, 'rev-parse', '--short', 'HEAD']) 81 | git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) 82 | if git_commit.returncode() == 0 and git_branch.returncode() == 0 83 | version = '@0@-@1@ (branch \'@2@\')'.format( 84 | meson.project_version(), 85 | git_commit.stdout().strip(), 86 | git_branch.stdout().strip(), 87 | ) 88 | endif 89 | endif 90 | 91 | conf_data = configuration_data() 92 | conf_data.set10('CAGE_HAS_XWAYLAND', have_xwayland) 93 | conf_data.set_quoted('CAGE_VERSION', version) 94 | 95 | scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) 96 | if scdoc.found() 97 | scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) 98 | sh = find_program('sh', native: true) 99 | mandir = get_option('mandir') 100 | man_files = [ 101 | 'cage.1.scd' 102 | ] 103 | foreach filename : man_files 104 | topic = filename.split('.')[-3].split('/')[-1] 105 | section = filename.split('.')[-2] 106 | output = '@0@.@1@'.format(topic, section) 107 | 108 | custom_target( 109 | output, 110 | input: filename, 111 | output: output, 112 | command: [ 113 | sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output) 114 | ], 115 | install: true, 116 | install_dir: '@0@/man@1@'.format(mandir, section) 117 | ) 118 | endforeach 119 | endif 120 | 121 | cage_sources = [ 122 | 'cage.c', 123 | 'idle_inhibit_v1.c', 124 | 'output.c', 125 | 'render.c', 126 | 'seat.c', 127 | 'util.c', 128 | 'view.c', 129 | 'xdg_shell.c', 130 | ] 131 | 132 | cage_headers = [ 133 | configure_file(input: 'config.h.in', 134 | output: 'config.h', 135 | configuration: conf_data), 136 | 'idle_inhibit_v1.h', 137 | 'output.h', 138 | 'render.h', 139 | 'seat.h', 140 | 'server.h', 141 | 'util.h', 142 | 'view.h', 143 | 'xdg_shell.h', 144 | ] 145 | 146 | if conf_data.get('CAGE_HAS_XWAYLAND', 0) == 1 147 | cage_sources += 'xwayland.c' 148 | cage_headers += 'xwayland.h' 149 | endif 150 | 151 | executable( 152 | meson.project_name(), 153 | cage_sources + cage_headers, 154 | dependencies: [ 155 | server_protos, 156 | wayland_server, 157 | wlroots, 158 | xkbcommon, 159 | pixman, 160 | math, 161 | ], 162 | install: true, 163 | ) 164 | 165 | summary = [ 166 | '', 167 | 'Cage @0@'.format(version), 168 | '', 169 | ' xwayland: @0@'.format(have_xwayland), 170 | '' 171 | ] 172 | message('\n'.join(summary)) 173 | -------------------------------------------------------------------------------- /render.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2020 Jente Hidskes 5 | * Copyright (C) 2019 The Sway authors 6 | * 7 | * See the LICENSE file accompanying this file. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "output.h" 22 | #include "seat.h" 23 | #include "server.h" 24 | #include "util.h" 25 | #include "view.h" 26 | 27 | static void 28 | scissor_output(struct wlr_output *output, pixman_box32_t *rect) 29 | { 30 | struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); 31 | 32 | struct wlr_box box = { 33 | .x = rect->x1, 34 | .y = rect->y1, 35 | .width = rect->x2 - rect->x1, 36 | .height = rect->y2 - rect->y1, 37 | }; 38 | 39 | int output_width, output_height; 40 | wlr_output_transformed_resolution(output, &output_width, &output_height); 41 | enum wl_output_transform transform = wlr_output_transform_invert(output->transform); 42 | wlr_box_transform(&box, &box, transform, output_width, output_height); 43 | 44 | wlr_renderer_scissor(renderer, &box); 45 | } 46 | 47 | struct render_data { 48 | pixman_region32_t *damage; 49 | }; 50 | 51 | static void 52 | render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, struct wlr_texture *texture, 53 | const struct wlr_box *box, const float matrix[static 9]) 54 | { 55 | struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); 56 | 57 | pixman_region32_t damage; 58 | pixman_region32_init(&damage); 59 | pixman_region32_union_rect(&damage, &damage, box->x, box->y, box->width, box->height); 60 | pixman_region32_intersect(&damage, &damage, output_damage); 61 | if (!pixman_region32_not_empty(&damage)) { 62 | goto damage_finish; 63 | } 64 | 65 | int nrects; 66 | pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); 67 | for (int i = 0; i < nrects; i++) { 68 | scissor_output(wlr_output, &rects[i]); 69 | wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f); 70 | } 71 | 72 | damage_finish: 73 | pixman_region32_fini(&damage); 74 | } 75 | 76 | static void 77 | render_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data) 78 | { 79 | struct render_data *data = user_data; 80 | struct wlr_output *wlr_output = output->wlr_output; 81 | pixman_region32_t *output_damage = data->damage; 82 | 83 | struct wlr_texture *texture = wlr_surface_get_texture(surface); 84 | if (!texture) { 85 | wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); 86 | return; 87 | } 88 | 89 | scale_box(box, wlr_output->scale); 90 | 91 | float matrix[9]; 92 | enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); 93 | wlr_matrix_project_box(matrix, box, transform, 0.0f, wlr_output->transform_matrix); 94 | 95 | render_texture(wlr_output, output_damage, texture, box, matrix); 96 | } 97 | 98 | static void 99 | render_drag_icons(struct cg_output *output, pixman_region32_t *damage, struct wl_list *drag_icons) 100 | { 101 | struct render_data data = { 102 | .damage = damage, 103 | }; 104 | output_drag_icons_for_each_surface(output, drag_icons, render_surface_iterator, &data); 105 | } 106 | 107 | /** 108 | * Render all toplevels without descending into popups. 109 | */ 110 | static void 111 | render_view_toplevels(struct cg_view *view, struct cg_output *output, pixman_region32_t *damage) 112 | { 113 | struct render_data data = { 114 | .damage = damage, 115 | }; 116 | double ox = view->lx; 117 | double oy = view->ly; 118 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); 119 | output_surface_for_each_surface(output, view->wlr_surface, ox, oy, render_surface_iterator, &data); 120 | } 121 | 122 | static void 123 | render_view_popups(struct cg_view *view, struct cg_output *output, pixman_region32_t *damage) 124 | { 125 | struct render_data data = { 126 | .damage = damage, 127 | }; 128 | output_view_for_each_popup_surface(output, view, render_surface_iterator, &data); 129 | } 130 | 131 | void 132 | output_render(struct cg_output *output, pixman_region32_t *damage) 133 | { 134 | struct cg_server *server = output->server; 135 | struct wlr_output *wlr_output = output->wlr_output; 136 | 137 | struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); 138 | if (!renderer) { 139 | wlr_log(WLR_DEBUG, "Expected the output backend to have a renderer"); 140 | return; 141 | } 142 | 143 | wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); 144 | 145 | if (!pixman_region32_not_empty(damage)) { 146 | wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer swap"); 147 | goto renderer_end; 148 | } 149 | 150 | #ifdef DEBUG 151 | if (server->debug_damage_tracking) { 152 | wlr_renderer_clear(renderer, (float[]){1.0f, 0.0f, 0.0f, 1.0f}); 153 | } 154 | #endif 155 | 156 | float color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; 157 | int nrects; 158 | pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); 159 | for (int i = 0; i < nrects; i++) { 160 | scissor_output(wlr_output, &rects[i]); 161 | wlr_renderer_clear(renderer, color); 162 | } 163 | 164 | // TODO: render only top view, possibly use focused view for this, see #35. 165 | struct cg_view *view; 166 | wl_list_for_each_reverse (view, &server->views, link) { 167 | render_view_toplevels(view, output, damage); 168 | } 169 | 170 | struct cg_view *focused_view = seat_get_focus(server->seat); 171 | if (focused_view) { 172 | render_view_popups(focused_view, output, damage); 173 | } 174 | 175 | render_drag_icons(output, damage, &server->seat->drag_icons); 176 | 177 | renderer_end: 178 | /* Draw software cursor in case hardware cursors aren't 179 | available. This is a no-op when they are. */ 180 | wlr_output_render_software_cursors(wlr_output, damage); 181 | wlr_renderer_scissor(renderer, NULL); 182 | wlr_renderer_end(renderer); 183 | 184 | int output_width, output_height; 185 | wlr_output_transformed_resolution(wlr_output, &output_width, &output_height); 186 | 187 | pixman_region32_t frame_damage; 188 | pixman_region32_init(&frame_damage); 189 | 190 | enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); 191 | wlr_region_transform(&frame_damage, &output->damage->current, transform, output_width, output_height); 192 | 193 | #ifdef DEBUG 194 | if (server->debug_damage_tracking) { 195 | pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, output_width, output_height); 196 | } 197 | #endif 198 | 199 | wlr_output_set_damage(wlr_output, &frame_damage); 200 | pixman_region32_fini(&frame_damage); 201 | 202 | if (!wlr_output_commit(wlr_output)) { 203 | wlr_log(WLR_ERROR, "Could not commit output"); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /xwayland.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2020 Jente Hidskes 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "server.h" 17 | #include "view.h" 18 | #include "xwayland.h" 19 | 20 | struct cg_xwayland_view * 21 | xwayland_view_from_view(struct cg_view *view) 22 | { 23 | return (struct cg_xwayland_view *) view; 24 | } 25 | 26 | bool 27 | xwayland_view_should_manage(struct cg_view *view) 28 | { 29 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 30 | struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; 31 | return !xwayland_surface->override_redirect; 32 | } 33 | 34 | static char * 35 | get_title(struct cg_view *view) 36 | { 37 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 38 | return xwayland_view->xwayland_surface->title; 39 | } 40 | 41 | static void 42 | get_geometry(struct cg_view *view, int *width_out, int *height_out) 43 | { 44 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 45 | *width_out = xwayland_view->xwayland_surface->surface->current.width; 46 | *height_out = xwayland_view->xwayland_surface->surface->current.height; 47 | } 48 | 49 | static bool 50 | is_primary(struct cg_view *view) 51 | { 52 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 53 | struct wlr_xwayland_surface *parent = xwayland_view->xwayland_surface->parent; 54 | return parent == NULL; 55 | } 56 | 57 | static bool 58 | is_transient_for(struct cg_view *child, struct cg_view *parent) 59 | { 60 | if (parent->type != CAGE_XDG_SHELL_VIEW) { 61 | return false; 62 | } 63 | struct cg_xwayland_view *_child = xwayland_view_from_view(child); 64 | struct wlr_xwayland_surface *xwayland_surface = _child->xwayland_surface; 65 | struct cg_xwayland_view *_parent = xwayland_view_from_view(parent); 66 | struct wlr_xwayland_surface *parent_xwayland_surface = _parent->xwayland_surface; 67 | while (xwayland_surface) { 68 | if (xwayland_surface->parent == parent_xwayland_surface) { 69 | return true; 70 | } 71 | xwayland_surface = xwayland_surface->parent; 72 | } 73 | return false; 74 | } 75 | 76 | static void 77 | activate(struct cg_view *view, bool activate) 78 | { 79 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 80 | wlr_xwayland_surface_activate(xwayland_view->xwayland_surface, activate); 81 | } 82 | 83 | static void 84 | maximize(struct cg_view *view, int output_width, int output_height) 85 | { 86 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 87 | wlr_xwayland_surface_configure(xwayland_view->xwayland_surface, view->lx, view->ly, output_width, 88 | output_height); 89 | wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true); 90 | } 91 | 92 | static void 93 | destroy(struct cg_view *view) 94 | { 95 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 96 | free(xwayland_view); 97 | } 98 | 99 | static void 100 | for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) 101 | { 102 | wlr_surface_for_each_surface(view->wlr_surface, iterator, data); 103 | } 104 | 105 | static struct wlr_surface * 106 | wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) 107 | { 108 | return wlr_surface_surface_at(view->wlr_surface, sx, sy, sub_x, sub_y); 109 | } 110 | 111 | static void 112 | handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, void *data) 113 | { 114 | struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_fullscreen); 115 | struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; 116 | wlr_xwayland_surface_set_fullscreen(xwayland_view->xwayland_surface, xwayland_surface->fullscreen); 117 | } 118 | 119 | static void 120 | handle_xwayland_surface_commit(struct wl_listener *listener, void *data) 121 | { 122 | struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit); 123 | struct cg_view *view = &xwayland_view->view; 124 | view_damage_part(view); 125 | } 126 | 127 | static void 128 | handle_xwayland_surface_unmap(struct wl_listener *listener, void *data) 129 | { 130 | struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap); 131 | struct cg_view *view = &xwayland_view->view; 132 | 133 | view_damage_whole(view); 134 | 135 | wl_list_remove(&xwayland_view->commit.link); 136 | 137 | view_unmap(view); 138 | } 139 | 140 | static void 141 | handle_xwayland_surface_map(struct wl_listener *listener, void *data) 142 | { 143 | struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, map); 144 | struct cg_view *view = &xwayland_view->view; 145 | 146 | if (!xwayland_view_should_manage(view)) { 147 | view->lx = xwayland_view->xwayland_surface->x; 148 | view->ly = xwayland_view->xwayland_surface->y; 149 | } 150 | 151 | xwayland_view->commit.notify = handle_xwayland_surface_commit; 152 | wl_signal_add(&xwayland_view->xwayland_surface->surface->events.commit, &xwayland_view->commit); 153 | 154 | view_map(view, xwayland_view->xwayland_surface->surface); 155 | 156 | view_damage_whole(view); 157 | } 158 | 159 | static void 160 | handle_xwayland_surface_destroy(struct wl_listener *listener, void *data) 161 | { 162 | struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy); 163 | struct cg_view *view = &xwayland_view->view; 164 | 165 | wl_list_remove(&xwayland_view->map.link); 166 | wl_list_remove(&xwayland_view->unmap.link); 167 | wl_list_remove(&xwayland_view->destroy.link); 168 | wl_list_remove(&xwayland_view->request_fullscreen.link); 169 | xwayland_view->xwayland_surface = NULL; 170 | 171 | view_destroy(view); 172 | } 173 | 174 | static const struct cg_view_impl xwayland_view_impl = { 175 | .get_title = get_title, 176 | .get_geometry = get_geometry, 177 | .is_primary = is_primary, 178 | .is_transient_for = is_transient_for, 179 | .activate = activate, 180 | .maximize = maximize, 181 | .destroy = destroy, 182 | .for_each_surface = for_each_surface, 183 | /* XWayland doesn't have a separate popup iterator. */ 184 | .for_each_popup_surface = NULL, 185 | .wlr_surface_at = wlr_surface_at, 186 | }; 187 | 188 | void 189 | handle_xwayland_surface_new(struct wl_listener *listener, void *data) 190 | { 191 | struct cg_server *server = wl_container_of(listener, server, new_xwayland_surface); 192 | struct wlr_xwayland_surface *xwayland_surface = data; 193 | 194 | struct cg_xwayland_view *xwayland_view = calloc(1, sizeof(struct cg_xwayland_view)); 195 | if (!xwayland_view) { 196 | wlr_log(WLR_ERROR, "Failed to allocate XWayland view"); 197 | return; 198 | } 199 | 200 | view_init(&xwayland_view->view, server, CAGE_XWAYLAND_VIEW, &xwayland_view_impl); 201 | xwayland_view->xwayland_surface = xwayland_surface; 202 | 203 | xwayland_view->map.notify = handle_xwayland_surface_map; 204 | wl_signal_add(&xwayland_surface->events.map, &xwayland_view->map); 205 | xwayland_view->unmap.notify = handle_xwayland_surface_unmap; 206 | wl_signal_add(&xwayland_surface->events.unmap, &xwayland_view->unmap); 207 | xwayland_view->destroy.notify = handle_xwayland_surface_destroy; 208 | wl_signal_add(&xwayland_surface->events.destroy, &xwayland_view->destroy); 209 | xwayland_view->request_fullscreen.notify = handle_xwayland_surface_request_fullscreen; 210 | wl_signal_add(&xwayland_surface->events.request_fullscreen, &xwayland_view->request_fullscreen); 211 | } 212 | -------------------------------------------------------------------------------- /view.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2020 Jente Hidskes 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #define _POSIX_C_SOURCE 200809L 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "output.h" 20 | #include "seat.h" 21 | #include "server.h" 22 | #include "view.h" 23 | #if CAGE_HAS_XWAYLAND 24 | #include "xwayland.h" 25 | #endif 26 | 27 | static void 28 | view_child_handle_commit(struct wl_listener *listener, void *data) 29 | { 30 | struct cg_view_child *child = wl_container_of(listener, child, commit); 31 | view_damage_part(child->view); 32 | } 33 | 34 | static void subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface); 35 | 36 | static void 37 | view_child_handle_new_subsurface(struct wl_listener *listener, void *data) 38 | { 39 | struct cg_view_child *child = wl_container_of(listener, child, new_subsurface); 40 | struct wlr_subsurface *wlr_subsurface = data; 41 | subsurface_create(child->view, wlr_subsurface); 42 | } 43 | 44 | void 45 | view_child_finish(struct cg_view_child *child) 46 | { 47 | if (!child) { 48 | return; 49 | } 50 | 51 | view_damage_whole(child->view); 52 | 53 | wl_list_remove(&child->link); 54 | wl_list_remove(&child->commit.link); 55 | wl_list_remove(&child->new_subsurface.link); 56 | } 57 | 58 | void 59 | view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface) 60 | { 61 | child->view = view; 62 | child->wlr_surface = wlr_surface; 63 | 64 | child->commit.notify = view_child_handle_commit; 65 | wl_signal_add(&wlr_surface->events.commit, &child->commit); 66 | child->new_subsurface.notify = view_child_handle_new_subsurface; 67 | wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); 68 | 69 | wl_list_insert(&view->children, &child->link); 70 | } 71 | 72 | static void 73 | subsurface_destroy(struct cg_view_child *child) 74 | { 75 | if (!child) { 76 | return; 77 | } 78 | 79 | struct cg_subsurface *subsurface = (struct cg_subsurface *) child; 80 | wl_list_remove(&subsurface->destroy.link); 81 | view_child_finish(&subsurface->view_child); 82 | free(subsurface); 83 | } 84 | 85 | static void 86 | subsurface_handle_destroy(struct wl_listener *listener, void *data) 87 | { 88 | struct cg_subsurface *subsurface = wl_container_of(listener, subsurface, destroy); 89 | struct cg_view_child *view_child = (struct cg_view_child *) subsurface; 90 | subsurface_destroy(view_child); 91 | } 92 | 93 | static void 94 | subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface) 95 | { 96 | struct cg_subsurface *subsurface = calloc(1, sizeof(struct cg_subsurface)); 97 | if (!subsurface) { 98 | return; 99 | } 100 | 101 | view_child_init(&subsurface->view_child, view, wlr_subsurface->surface); 102 | subsurface->view_child.destroy = subsurface_destroy; 103 | subsurface->wlr_subsurface = wlr_subsurface; 104 | 105 | subsurface->destroy.notify = subsurface_handle_destroy; 106 | wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); 107 | } 108 | 109 | static void 110 | handle_new_subsurface(struct wl_listener *listener, void *data) 111 | { 112 | struct cg_view *view = wl_container_of(listener, view, new_subsurface); 113 | struct wlr_subsurface *wlr_subsurface = data; 114 | subsurface_create(view, wlr_subsurface); 115 | } 116 | 117 | char * 118 | view_get_title(struct cg_view *view) 119 | { 120 | const char *title = view->impl->get_title(view); 121 | if (!title) { 122 | return NULL; 123 | } 124 | return strndup(title, strlen(title)); 125 | } 126 | 127 | bool 128 | view_is_primary(struct cg_view *view) 129 | { 130 | return view->impl->is_primary(view); 131 | } 132 | 133 | bool 134 | view_is_transient_for(struct cg_view *child, struct cg_view *parent) 135 | { 136 | return child->impl->is_transient_for(child, parent); 137 | } 138 | 139 | void 140 | view_damage_part(struct cg_view *view) 141 | { 142 | struct cg_output *output; 143 | wl_list_for_each (output, &view->server->outputs, link) { 144 | output_damage_surface(output, view->wlr_surface, view->lx, view->ly, false); 145 | } 146 | } 147 | 148 | void 149 | view_damage_whole(struct cg_view *view) 150 | { 151 | struct cg_output *output; 152 | wl_list_for_each (output, &view->server->outputs, link) { 153 | output_damage_surface(output, view->wlr_surface, view->lx, view->ly, true); 154 | } 155 | } 156 | 157 | void 158 | view_activate(struct cg_view *view, bool activate) 159 | { 160 | view->impl->activate(view, activate); 161 | } 162 | 163 | static bool 164 | view_extends_output_layout(struct cg_view *view, struct wlr_box *layout_box) 165 | { 166 | int width, height; 167 | view->impl->get_geometry(view, &width, &height); 168 | 169 | return (layout_box->height < height || layout_box->width < width); 170 | } 171 | 172 | static void 173 | view_maximize(struct cg_view *view, struct wlr_box *layout_box) 174 | { 175 | view->lx = layout_box->x; 176 | view->ly = layout_box->y; 177 | view->impl->maximize(view, layout_box->width, layout_box->height); 178 | } 179 | 180 | static void 181 | view_center(struct cg_view *view, struct wlr_box *layout_box) 182 | { 183 | int width, height; 184 | view->impl->get_geometry(view, &width, &height); 185 | 186 | view->lx = (layout_box->width - width) / 2; 187 | view->ly = (layout_box->height - height) / 2; 188 | } 189 | 190 | void 191 | view_position(struct cg_view *view) 192 | { 193 | struct wlr_box *layout_box = wlr_output_layout_get_box(view->server->output_layout, NULL); 194 | 195 | if (view_is_primary(view) || view_extends_output_layout(view, layout_box)) { 196 | view_maximize(view, layout_box); 197 | } else { 198 | view_center(view, layout_box); 199 | } 200 | } 201 | 202 | void 203 | view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) 204 | { 205 | view->impl->for_each_surface(view, iterator, data); 206 | } 207 | 208 | void 209 | view_for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) 210 | { 211 | if (!view->impl->for_each_popup_surface) { 212 | return; 213 | } 214 | view->impl->for_each_popup_surface(view, iterator, data); 215 | } 216 | 217 | void 218 | view_unmap(struct cg_view *view) 219 | { 220 | wl_list_remove(&view->link); 221 | 222 | wl_list_remove(&view->new_subsurface.link); 223 | 224 | struct cg_view_child *child, *tmp; 225 | wl_list_for_each_safe (child, tmp, &view->children, link) { 226 | child->destroy(child); 227 | } 228 | 229 | view->wlr_surface = NULL; 230 | } 231 | 232 | void 233 | view_map(struct cg_view *view, struct wlr_surface *surface) 234 | { 235 | view->wlr_surface = surface; 236 | 237 | struct wlr_subsurface *subsurface; 238 | wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces_below, parent_link) { 239 | subsurface_create(view, subsurface); 240 | } 241 | wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces_above, parent_link) { 242 | subsurface_create(view, subsurface); 243 | } 244 | 245 | view->new_subsurface.notify = handle_new_subsurface; 246 | wl_signal_add(&view->wlr_surface->events.new_subsurface, &view->new_subsurface); 247 | 248 | #if CAGE_HAS_XWAYLAND 249 | /* We shouldn't position override-redirect windows. They set 250 | their own (x,y) coordinates in handle_wayland_surface_map. */ 251 | if (view->type != CAGE_XWAYLAND_VIEW || xwayland_view_should_manage(view)) 252 | #endif 253 | { 254 | view_position(view); 255 | } 256 | 257 | wl_list_insert(&view->server->views, &view->link); 258 | seat_set_focus(view->server->seat, view); 259 | } 260 | 261 | void 262 | view_destroy(struct cg_view *view) 263 | { 264 | struct cg_server *server = view->server; 265 | 266 | if (view->wlr_surface != NULL) { 267 | view_unmap(view); 268 | } 269 | 270 | view->impl->destroy(view); 271 | 272 | /* If there is a previous view in the list, focus that. */ 273 | bool empty = wl_list_empty(&server->views); 274 | if (!empty) { 275 | struct cg_view *prev = wl_container_of(server->views.next, prev, link); 276 | seat_set_focus(server->seat, prev); 277 | } 278 | } 279 | 280 | void 281 | view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl) 282 | { 283 | view->server = server; 284 | view->type = type; 285 | view->impl = impl; 286 | 287 | wl_list_init(&view->children); 288 | } 289 | 290 | struct cg_view * 291 | view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface) 292 | { 293 | struct cg_view *view; 294 | wl_list_for_each (view, &server->views, link) { 295 | if (view->wlr_surface == surface) { 296 | return view; 297 | } 298 | } 299 | return NULL; 300 | } 301 | 302 | struct wlr_surface * 303 | view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) 304 | { 305 | return view->impl->wlr_surface_at(view, sx, sy, sub_x, sub_y); 306 | } 307 | -------------------------------------------------------------------------------- /xdg_shell.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2019 Jente Hidskes 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "server.h" 17 | #include "view.h" 18 | #include "xdg_shell.h" 19 | 20 | static void 21 | xdg_decoration_handle_destroy(struct wl_listener *listener, void *data) 22 | { 23 | struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, destroy); 24 | 25 | wl_list_remove(&xdg_decoration->destroy.link); 26 | wl_list_remove(&xdg_decoration->request_mode.link); 27 | free(xdg_decoration); 28 | } 29 | 30 | static void 31 | xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data) 32 | { 33 | struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, request_mode); 34 | enum wlr_xdg_toplevel_decoration_v1_mode mode; 35 | 36 | if (xdg_decoration->server->xdg_decoration) { 37 | mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; 38 | } else { 39 | mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; 40 | } 41 | wlr_xdg_toplevel_decoration_v1_set_mode(xdg_decoration->wlr_decoration, mode); 42 | } 43 | 44 | static void 45 | xdg_popup_destroy(struct cg_view_child *child) 46 | { 47 | if (!child) { 48 | return; 49 | } 50 | 51 | struct cg_xdg_popup *popup = (struct cg_xdg_popup *) child; 52 | wl_list_remove(&popup->destroy.link); 53 | wl_list_remove(&popup->map.link); 54 | wl_list_remove(&popup->unmap.link); 55 | wl_list_remove(&popup->new_popup.link); 56 | view_child_finish(&popup->view_child); 57 | free(popup); 58 | } 59 | 60 | static void 61 | handle_xdg_popup_map(struct wl_listener *listener, void *data) 62 | { 63 | struct cg_xdg_popup *popup = wl_container_of(listener, popup, map); 64 | view_damage_whole(popup->view_child.view); 65 | } 66 | 67 | static void 68 | handle_xdg_popup_unmap(struct wl_listener *listener, void *data) 69 | { 70 | struct cg_xdg_popup *popup = wl_container_of(listener, popup, unmap); 71 | view_damage_whole(popup->view_child.view); 72 | } 73 | 74 | static void 75 | handle_xdg_popup_destroy(struct wl_listener *listener, void *data) 76 | { 77 | struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy); 78 | struct cg_view_child *view_child = (struct cg_view_child *) popup; 79 | xdg_popup_destroy(view_child); 80 | } 81 | 82 | static void xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup); 83 | 84 | static void 85 | popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) 86 | { 87 | struct cg_xdg_popup *popup = wl_container_of(listener, popup, new_popup); 88 | struct wlr_xdg_popup *wlr_popup = data; 89 | xdg_popup_create(popup->view_child.view, wlr_popup); 90 | } 91 | 92 | static void 93 | popup_unconstrain(struct cg_xdg_popup *popup) 94 | { 95 | struct cg_view *view = popup->view_child.view; 96 | struct cg_server *server = view->server; 97 | struct wlr_box *popup_box = &popup->wlr_popup->geometry; 98 | 99 | struct wlr_output_layout *output_layout = server->output_layout; 100 | struct wlr_output *wlr_output = 101 | wlr_output_layout_output_at(output_layout, view->lx + popup_box->x, view->ly + popup_box->y); 102 | struct wlr_box *output_box = wlr_output_layout_get_box(output_layout, wlr_output); 103 | 104 | struct wlr_box output_toplevel_box = { 105 | .x = output_box->x - view->lx, 106 | .y = output_box->y - view->ly, 107 | .width = output_box->width, 108 | .height = output_box->height, 109 | }; 110 | 111 | wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); 112 | } 113 | 114 | static void 115 | xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup) 116 | { 117 | struct cg_xdg_popup *popup = calloc(1, sizeof(struct cg_xdg_popup)); 118 | if (!popup) { 119 | return; 120 | } 121 | 122 | popup->wlr_popup = wlr_popup; 123 | view_child_init(&popup->view_child, view, wlr_popup->base->surface); 124 | popup->view_child.destroy = xdg_popup_destroy; 125 | popup->destroy.notify = handle_xdg_popup_destroy; 126 | wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); 127 | popup->map.notify = handle_xdg_popup_map; 128 | wl_signal_add(&wlr_popup->base->events.map, &popup->map); 129 | popup->unmap.notify = handle_xdg_popup_unmap; 130 | wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); 131 | popup->new_popup.notify = popup_handle_new_xdg_popup; 132 | wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); 133 | 134 | popup_unconstrain(popup); 135 | } 136 | 137 | static void 138 | handle_new_xdg_popup(struct wl_listener *listener, void *data) 139 | { 140 | struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup); 141 | struct wlr_xdg_popup *wlr_popup = data; 142 | xdg_popup_create(&xdg_shell_view->view, wlr_popup); 143 | } 144 | 145 | static struct cg_xdg_shell_view * 146 | xdg_shell_view_from_view(struct cg_view *view) 147 | { 148 | return (struct cg_xdg_shell_view *) view; 149 | } 150 | 151 | static char * 152 | get_title(struct cg_view *view) 153 | { 154 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 155 | return xdg_shell_view->xdg_surface->toplevel->title; 156 | } 157 | 158 | static void 159 | get_geometry(struct cg_view *view, int *width_out, int *height_out) 160 | { 161 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 162 | struct wlr_box geom; 163 | 164 | wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geom); 165 | *width_out = geom.width; 166 | *height_out = geom.height; 167 | } 168 | 169 | static bool 170 | is_primary(struct cg_view *view) 171 | { 172 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 173 | struct wlr_xdg_surface *parent = xdg_shell_view->xdg_surface->toplevel->parent; 174 | /* FIXME: role is 0? */ 175 | return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ 176 | } 177 | 178 | static bool 179 | is_transient_for(struct cg_view *child, struct cg_view *parent) 180 | { 181 | if (parent->type != CAGE_XDG_SHELL_VIEW) { 182 | return false; 183 | } 184 | struct cg_xdg_shell_view *_child = xdg_shell_view_from_view(child); 185 | struct wlr_xdg_surface *xdg_surface = _child->xdg_surface; 186 | struct cg_xdg_shell_view *_parent = xdg_shell_view_from_view(parent); 187 | struct wlr_xdg_surface *parent_xdg_surface = _parent->xdg_surface; 188 | while (xdg_surface) { 189 | if (xdg_surface->toplevel->parent == parent_xdg_surface) { 190 | return true; 191 | } 192 | xdg_surface = xdg_surface->toplevel->parent; 193 | } 194 | return false; 195 | } 196 | 197 | static void 198 | activate(struct cg_view *view, bool activate) 199 | { 200 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 201 | wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate); 202 | } 203 | 204 | static void 205 | maximize(struct cg_view *view, int output_width, int output_height) 206 | { 207 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 208 | wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, output_width, output_height); 209 | wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true); 210 | } 211 | 212 | static void 213 | destroy(struct cg_view *view) 214 | { 215 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 216 | free(xdg_shell_view); 217 | } 218 | 219 | static void 220 | for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) 221 | { 222 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 223 | wlr_xdg_surface_for_each_surface(xdg_shell_view->xdg_surface, iterator, data); 224 | } 225 | 226 | static void 227 | for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) 228 | { 229 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 230 | wlr_xdg_surface_for_each_popup_surface(xdg_shell_view->xdg_surface, iterator, data); 231 | } 232 | 233 | static struct wlr_surface * 234 | wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) 235 | { 236 | struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); 237 | return wlr_xdg_surface_surface_at(xdg_shell_view->xdg_surface, sx, sy, sub_x, sub_y); 238 | } 239 | 240 | static void 241 | handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, void *data) 242 | { 243 | struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen); 244 | struct wlr_xdg_toplevel_set_fullscreen_event *event = data; 245 | struct wlr_box *layout_box = wlr_output_layout_get_box(xdg_shell_view->view.server->output_layout, NULL); 246 | wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, layout_box->width, layout_box->height); 247 | wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, event->fullscreen); 248 | } 249 | 250 | static void 251 | handle_xdg_shell_surface_commit(struct wl_listener *listener, void *data) 252 | { 253 | struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit); 254 | struct cg_view *view = &xdg_shell_view->view; 255 | view_damage_part(view); 256 | } 257 | 258 | static void 259 | handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data) 260 | { 261 | struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); 262 | struct cg_view *view = &xdg_shell_view->view; 263 | 264 | view_damage_whole(view); 265 | 266 | wl_list_remove(&xdg_shell_view->commit.link); 267 | 268 | view_unmap(view); 269 | } 270 | 271 | static void 272 | handle_xdg_shell_surface_map(struct wl_listener *listener, void *data) 273 | { 274 | struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map); 275 | struct cg_view *view = &xdg_shell_view->view; 276 | 277 | xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; 278 | wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit); 279 | 280 | view_map(view, xdg_shell_view->xdg_surface->surface); 281 | 282 | view_damage_whole(view); 283 | } 284 | 285 | static void 286 | handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *data) 287 | { 288 | struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, destroy); 289 | struct cg_view *view = &xdg_shell_view->view; 290 | 291 | wl_list_remove(&xdg_shell_view->map.link); 292 | wl_list_remove(&xdg_shell_view->unmap.link); 293 | wl_list_remove(&xdg_shell_view->destroy.link); 294 | wl_list_remove(&xdg_shell_view->request_fullscreen.link); 295 | wl_list_remove(&xdg_shell_view->new_popup.link); 296 | xdg_shell_view->xdg_surface = NULL; 297 | 298 | view_destroy(view); 299 | } 300 | 301 | static const struct cg_view_impl xdg_shell_view_impl = { 302 | .get_title = get_title, 303 | .get_geometry = get_geometry, 304 | .is_primary = is_primary, 305 | .is_transient_for = is_transient_for, 306 | .activate = activate, 307 | .maximize = maximize, 308 | .destroy = destroy, 309 | .for_each_surface = for_each_surface, 310 | .for_each_popup_surface = for_each_popup_surface, 311 | .wlr_surface_at = wlr_surface_at, 312 | }; 313 | 314 | void 315 | handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) 316 | { 317 | struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface); 318 | struct wlr_xdg_surface *xdg_surface = data; 319 | 320 | if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 321 | return; 322 | } 323 | 324 | struct cg_xdg_shell_view *xdg_shell_view = calloc(1, sizeof(struct cg_xdg_shell_view)); 325 | if (!xdg_shell_view) { 326 | wlr_log(WLR_ERROR, "Failed to allocate XDG Shell view"); 327 | return; 328 | } 329 | 330 | view_init(&xdg_shell_view->view, server, CAGE_XDG_SHELL_VIEW, &xdg_shell_view_impl); 331 | xdg_shell_view->xdg_surface = xdg_surface; 332 | 333 | xdg_shell_view->map.notify = handle_xdg_shell_surface_map; 334 | wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); 335 | xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap; 336 | wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); 337 | xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy; 338 | wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); 339 | xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen; 340 | wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen); 341 | xdg_shell_view->new_popup.notify = handle_new_xdg_popup; 342 | wl_signal_add(&xdg_surface->events.new_popup, &xdg_shell_view->new_popup); 343 | } 344 | 345 | void 346 | handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) 347 | { 348 | struct cg_server *server = wl_container_of(listener, server, xdg_toplevel_decoration); 349 | struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data; 350 | 351 | struct cg_xdg_decoration *xdg_decoration = calloc(1, sizeof(struct cg_xdg_decoration)); 352 | if (!xdg_decoration) { 353 | return; 354 | } 355 | 356 | xdg_decoration->wlr_decoration = wlr_decoration; 357 | xdg_decoration->server = server; 358 | 359 | xdg_decoration->destroy.notify = xdg_decoration_handle_destroy; 360 | wl_signal_add(&wlr_decoration->events.destroy, &xdg_decoration->destroy); 361 | xdg_decoration->request_mode.notify = xdg_decoration_handle_request_mode; 362 | wl_signal_add(&wlr_decoration->events.request_mode, &xdg_decoration->request_mode); 363 | 364 | xdg_decoration_handle_request_mode(&xdg_decoration->request_mode, wlr_decoration); 365 | } 366 | -------------------------------------------------------------------------------- /cage.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2020 Jente Hidskes 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #define _POSIX_C_SOURCE 200112L 10 | 11 | #include "config.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #if CAGE_HAS_XWAYLAND 33 | #include 34 | #endif 35 | #include 36 | #include 37 | #include 38 | #include 39 | #if CAGE_HAS_XWAYLAND 40 | #include 41 | #endif 42 | 43 | #include "idle_inhibit_v1.h" 44 | #include "output.h" 45 | #include "seat.h" 46 | #include "server.h" 47 | #include "view.h" 48 | #include "xdg_shell.h" 49 | #if CAGE_HAS_XWAYLAND 50 | #include "xwayland.h" 51 | #endif 52 | 53 | static int 54 | sigchld_handler(int fd, uint32_t mask, void *data) 55 | { 56 | struct wl_display *display = data; 57 | 58 | /* Close Cage's read pipe. */ 59 | close(fd); 60 | 61 | if (mask & WL_EVENT_HANGUP) { 62 | wlr_log(WLR_DEBUG, "Child process closed normally"); 63 | } else if (mask & WL_EVENT_ERROR) { 64 | wlr_log(WLR_DEBUG, "Connection closed by server"); 65 | } 66 | 67 | wl_display_terminate(display); 68 | return 0; 69 | } 70 | 71 | static bool 72 | set_cloexec(int fd) 73 | { 74 | int flags = fcntl(fd, F_GETFD); 75 | 76 | if (flags == -1) { 77 | wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed"); 78 | return false; 79 | } 80 | 81 | flags = flags | FD_CLOEXEC; 82 | if (fcntl(fd, F_SETFD, flags) == -1) { 83 | wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed"); 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | static bool 91 | spawn_primary_client(struct wl_display *display, char *argv[], pid_t *pid_out, struct wl_event_source **sigchld_source) 92 | { 93 | int fd[2]; 94 | if (pipe(fd) != 0) { 95 | wlr_log(WLR_ERROR, "Unable to create pipe"); 96 | return false; 97 | } 98 | 99 | pid_t pid = fork(); 100 | if (pid == 0) { 101 | sigset_t set; 102 | sigemptyset(&set); 103 | sigprocmask(SIG_SETMASK, &set, NULL); 104 | /* Close read, we only need write in the primary client process. */ 105 | close(fd[0]); 106 | execvp(argv[0], argv); 107 | _exit(1); 108 | } else if (pid == -1) { 109 | wlr_log_errno(WLR_ERROR, "Unable to fork"); 110 | return false; 111 | } 112 | 113 | /* Set this early so that if we fail, the client process will be cleaned up properly. */ 114 | *pid_out = pid; 115 | 116 | if (!set_cloexec(fd[0]) || !set_cloexec(fd[1])) { 117 | return false; 118 | } 119 | 120 | /* Close write, we only need read in Cage. */ 121 | close(fd[1]); 122 | 123 | struct wl_event_loop *event_loop = wl_display_get_event_loop(display); 124 | uint32_t mask = WL_EVENT_HANGUP | WL_EVENT_ERROR; 125 | *sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, display); 126 | 127 | wlr_log(WLR_DEBUG, "Child process created with pid %d", pid); 128 | return true; 129 | } 130 | 131 | static void 132 | cleanup_primary_client(pid_t pid) 133 | { 134 | int status; 135 | 136 | waitpid(pid, &status, 0); 137 | 138 | if (WIFEXITED(status)) { 139 | wlr_log(WLR_DEBUG, "Child exited normally with exit status %d", WEXITSTATUS(status)); 140 | } else if (WIFSIGNALED(status)) { 141 | wlr_log(WLR_DEBUG, "Child was terminated by a signal (%d)", WTERMSIG(status)); 142 | } 143 | } 144 | 145 | static bool 146 | drop_permissions(void) 147 | { 148 | if (getuid() != geteuid() || getgid() != getegid()) { 149 | // Set the gid and uid in the correct order. 150 | if (setgid(getgid()) != 0 || setuid(getuid()) != 0) { 151 | wlr_log(WLR_ERROR, "Unable to drop root, refusing to start"); 152 | return false; 153 | } 154 | } 155 | 156 | if (setgid(0) != -1 || setuid(0) != -1) { 157 | wlr_log(WLR_ERROR, 158 | "Unable to drop root (we shouldn't be able to restore it after setuid), refusing to start"); 159 | return false; 160 | } 161 | 162 | return true; 163 | } 164 | 165 | static int 166 | handle_signal(int signal, void *data) 167 | { 168 | struct wl_display *display = data; 169 | 170 | switch (signal) { 171 | case SIGINT: 172 | /* Fallthrough */ 173 | case SIGTERM: 174 | wl_display_terminate(display); 175 | return 0; 176 | default: 177 | return 0; 178 | } 179 | } 180 | 181 | static void 182 | usage(FILE *file, const char *cage) 183 | { 184 | fprintf(file, 185 | "Usage: %s [OPTIONS] [--] APPLICATION\n" 186 | "\n" 187 | " -d\t Don't draw client side decorations, when possible\n" 188 | #ifdef DEBUG 189 | " -D\t Turn on damage tracking debugging\n" 190 | #endif 191 | " -h\t Display this help message\n" 192 | " -m extend Extend the display across all connected outputs (default)\n" 193 | " -m last Use only the last connected output\n" 194 | " -r\t Rotate the output 90 degrees clockwise, specify up to three times\n" 195 | " -s\t Allow VT switching\n" 196 | " -v\t Show the version number and exit\n" 197 | "\n" 198 | " Use -- when you want to pass arguments to APPLICATION\n", 199 | cage); 200 | } 201 | 202 | static bool 203 | parse_args(struct cg_server *server, int argc, char *argv[]) 204 | { 205 | int c; 206 | #ifdef DEBUG 207 | while ((c = getopt(argc, argv, "dDhm:rsv")) != -1) { 208 | #else 209 | while ((c = getopt(argc, argv, "dhm:rsv")) != -1) { 210 | #endif 211 | switch (c) { 212 | case 'd': 213 | server->xdg_decoration = true; 214 | break; 215 | #ifdef DEBUG 216 | case 'D': 217 | server->debug_damage_tracking = true; 218 | break; 219 | #endif 220 | case 'h': 221 | usage(stdout, argv[0]); 222 | return false; 223 | case 'm': 224 | if (strcmp(optarg, "last") == 0) { 225 | server->output_mode = CAGE_MULTI_OUTPUT_MODE_LAST; 226 | } else if (strcmp(optarg, "extend") == 0) { 227 | server->output_mode = CAGE_MULTI_OUTPUT_MODE_EXTEND; 228 | } 229 | break; 230 | case 'r': 231 | server->output_transform++; 232 | if (server->output_transform > WL_OUTPUT_TRANSFORM_270) { 233 | server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL; 234 | } 235 | break; 236 | case 's': 237 | server->allow_vt_switch = true; 238 | break; 239 | case 'v': 240 | fprintf(stdout, "Cage version " CAGE_VERSION "\n"); 241 | exit(0); 242 | default: 243 | usage(stderr, argv[0]); 244 | return false; 245 | } 246 | } 247 | 248 | if (optind >= argc) { 249 | usage(stderr, argv[0]); 250 | return false; 251 | } 252 | 253 | return true; 254 | } 255 | 256 | int 257 | main(int argc, char *argv[]) 258 | { 259 | struct cg_server server = {0}; 260 | struct wl_event_loop *event_loop = NULL; 261 | struct wl_event_source *sigint_source = NULL; 262 | struct wl_event_source *sigterm_source = NULL; 263 | struct wl_event_source *sigchld_source = NULL; 264 | struct wlr_renderer *renderer = NULL; 265 | struct wlr_compositor *compositor = NULL; 266 | struct wlr_data_device_manager *data_device_manager = NULL; 267 | struct wlr_server_decoration_manager *server_decoration_manager = NULL; 268 | struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL; 269 | struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager = NULL; 270 | struct wlr_screencopy_manager_v1 *screencopy_manager = NULL; 271 | struct wlr_xdg_output_manager_v1 *output_manager = NULL; 272 | struct wlr_gamma_control_manager_v1 *gamma_control_manager = NULL; 273 | struct wlr_xdg_shell *xdg_shell = NULL; 274 | #if CAGE_HAS_XWAYLAND 275 | struct wlr_xwayland *xwayland = NULL; 276 | struct wlr_xcursor_manager *xcursor_manager = NULL; 277 | #endif 278 | pid_t pid = 0; 279 | int ret = 0; 280 | 281 | if (!parse_args(&server, argc, argv)) { 282 | return 1; 283 | } 284 | 285 | #ifdef DEBUG 286 | wlr_log_init(WLR_DEBUG, NULL); 287 | #else 288 | wlr_log_init(WLR_ERROR, NULL); 289 | #endif 290 | 291 | /* Wayland requires XDG_RUNTIME_DIR to be set. */ 292 | if (!getenv("XDG_RUNTIME_DIR")) { 293 | wlr_log(WLR_ERROR, "XDG_RUNTIME_DIR is not set in the environment"); 294 | return 1; 295 | } 296 | 297 | server.wl_display = wl_display_create(); 298 | if (!server.wl_display) { 299 | wlr_log(WLR_ERROR, "Cannot allocate a Wayland display"); 300 | return 1; 301 | } 302 | 303 | event_loop = wl_display_get_event_loop(server.wl_display); 304 | sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server.wl_display); 305 | sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server.wl_display); 306 | 307 | server.backend = wlr_backend_autocreate(server.wl_display); 308 | if (!server.backend) { 309 | wlr_log(WLR_ERROR, "Unable to create the wlroots backend"); 310 | ret = 1; 311 | goto end; 312 | } 313 | 314 | if (!drop_permissions()) { 315 | ret = 1; 316 | goto end; 317 | } 318 | 319 | renderer = wlr_backend_get_renderer(server.backend); 320 | wlr_renderer_init_wl_display(renderer, server.wl_display); 321 | 322 | wl_list_init(&server.views); 323 | wl_list_init(&server.outputs); 324 | 325 | server.output_layout = wlr_output_layout_create(); 326 | if (!server.output_layout) { 327 | wlr_log(WLR_ERROR, "Unable to create output layout"); 328 | ret = 1; 329 | goto end; 330 | } 331 | 332 | compositor = wlr_compositor_create(server.wl_display, renderer); 333 | if (!compositor) { 334 | wlr_log(WLR_ERROR, "Unable to create the wlroots compositor"); 335 | ret = 1; 336 | goto end; 337 | } 338 | 339 | data_device_manager = wlr_data_device_manager_create(server.wl_display); 340 | if (!data_device_manager) { 341 | wlr_log(WLR_ERROR, "Unable to create the data device manager"); 342 | ret = 1; 343 | goto end; 344 | } 345 | 346 | /* Configure a listener to be notified when new outputs are 347 | * available on the backend. We use this only to detect the 348 | * first output and ignore subsequent outputs. */ 349 | server.new_output.notify = handle_new_output; 350 | wl_signal_add(&server.backend->events.new_output, &server.new_output); 351 | 352 | server.seat = seat_create(&server, server.backend); 353 | if (!server.seat) { 354 | wlr_log(WLR_ERROR, "Unable to create the seat"); 355 | ret = 1; 356 | goto end; 357 | } 358 | 359 | server.idle = wlr_idle_create(server.wl_display); 360 | if (!server.idle) { 361 | wlr_log(WLR_ERROR, "Unable to create the idle tracker"); 362 | ret = 1; 363 | goto end; 364 | } 365 | 366 | server.idle_inhibit_v1 = wlr_idle_inhibit_v1_create(server.wl_display); 367 | if (!server.idle_inhibit_v1) { 368 | wlr_log(WLR_ERROR, "Cannot create the idle inhibitor"); 369 | ret = 1; 370 | goto end; 371 | } 372 | server.new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1_new; 373 | wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, &server.new_idle_inhibitor_v1); 374 | wl_list_init(&server.inhibitors); 375 | 376 | xdg_shell = wlr_xdg_shell_create(server.wl_display); 377 | if (!xdg_shell) { 378 | wlr_log(WLR_ERROR, "Unable to create the XDG shell interface"); 379 | ret = 1; 380 | goto end; 381 | } 382 | server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new; 383 | wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface); 384 | 385 | xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server.wl_display); 386 | if (!xdg_decoration_manager) { 387 | wlr_log(WLR_ERROR, "Unable to create the XDG decoration manager"); 388 | ret = 1; 389 | goto end; 390 | } 391 | wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &server.xdg_toplevel_decoration); 392 | server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; 393 | 394 | server_decoration_manager = wlr_server_decoration_manager_create(server.wl_display); 395 | if (!server_decoration_manager) { 396 | wlr_log(WLR_ERROR, "Unable to create the server decoration manager"); 397 | ret = 1; 398 | goto end; 399 | } 400 | wlr_server_decoration_manager_set_default_mode( 401 | server_decoration_manager, server.xdg_decoration ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER 402 | : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); 403 | 404 | export_dmabuf_manager = wlr_export_dmabuf_manager_v1_create(server.wl_display); 405 | if (!export_dmabuf_manager) { 406 | wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager"); 407 | ret = 1; 408 | goto end; 409 | } 410 | 411 | screencopy_manager = wlr_screencopy_manager_v1_create(server.wl_display); 412 | if (!screencopy_manager) { 413 | wlr_log(WLR_ERROR, "Unable to create the screencopy manager"); 414 | ret = 1; 415 | goto end; 416 | } 417 | 418 | output_manager = wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout); 419 | if (!output_manager) { 420 | wlr_log(WLR_ERROR, "Unable to create the output manager"); 421 | ret = 1; 422 | goto end; 423 | } 424 | 425 | gamma_control_manager = wlr_gamma_control_manager_v1_create(server.wl_display); 426 | if (!gamma_control_manager) { 427 | wlr_log(WLR_ERROR, "Unable to create the gamma control manager"); 428 | ret = 1; 429 | goto end; 430 | } 431 | 432 | #if CAGE_HAS_XWAYLAND 433 | xwayland = wlr_xwayland_create(server.wl_display, compositor, true); 434 | if (!xwayland) { 435 | wlr_log(WLR_ERROR, "Cannot create XWayland server"); 436 | ret = 1; 437 | goto end; 438 | } 439 | server.new_xwayland_surface.notify = handle_xwayland_surface_new; 440 | wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface); 441 | 442 | xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE); 443 | if (!xcursor_manager) { 444 | wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager"); 445 | ret = 1; 446 | goto end; 447 | } 448 | 449 | if (setenv("DISPLAY", xwayland->display_name, true) < 0) { 450 | wlr_log_errno(WLR_ERROR, "Unable to set DISPLAY for XWayland. Clients may not be able to connect"); 451 | } else { 452 | wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name); 453 | } 454 | 455 | if (!wlr_xcursor_manager_load(xcursor_manager, 1)) { 456 | wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme"); 457 | } 458 | struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1); 459 | if (xcursor) { 460 | struct wlr_xcursor_image *image = xcursor->images[0]; 461 | wlr_xwayland_set_cursor(xwayland, image->buffer, image->width * 4, image->width, image->height, 462 | image->hotspot_x, image->hotspot_y); 463 | } 464 | #endif 465 | 466 | const char *socket = wl_display_add_socket_auto(server.wl_display); 467 | if (!socket) { 468 | wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket"); 469 | ret = 1; 470 | goto end; 471 | } 472 | 473 | if (!wlr_backend_start(server.backend)) { 474 | wlr_log(WLR_ERROR, "Unable to start the wlroots backend"); 475 | ret = 1; 476 | goto end; 477 | } 478 | 479 | if (setenv("WAYLAND_DISPLAY", socket, true) < 0) { 480 | wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY. Clients may not be able to connect"); 481 | } else { 482 | wlr_log(WLR_DEBUG, "Cage " CAGE_VERSION " is running on Wayland display %s", socket); 483 | } 484 | 485 | #if CAGE_HAS_XWAYLAND 486 | wlr_xwayland_set_seat(xwayland, server.seat->seat); 487 | #endif 488 | 489 | if (!spawn_primary_client(server.wl_display, argv + optind, &pid, &sigchld_source)) { 490 | ret = 1; 491 | goto end; 492 | } 493 | 494 | /* Place the cursor in the center of the output layout. */ 495 | struct wlr_box *layout_box = wlr_output_layout_get_box(server.output_layout, NULL); 496 | wlr_cursor_warp(server.seat->cursor, NULL, layout_box->width / 2, layout_box->height / 2); 497 | 498 | wl_display_run(server.wl_display); 499 | 500 | #if CAGE_HAS_XWAYLAND 501 | wlr_xwayland_destroy(xwayland); 502 | wlr_xcursor_manager_destroy(xcursor_manager); 503 | #endif 504 | wl_display_destroy_clients(server.wl_display); 505 | 506 | end: 507 | cleanup_primary_client(pid); 508 | 509 | wl_event_source_remove(sigint_source); 510 | wl_event_source_remove(sigterm_source); 511 | if (sigchld_source) { 512 | wl_event_source_remove(sigchld_source); 513 | } 514 | seat_destroy(server.seat); 515 | /* This function is not null-safe, but we only ever get here 516 | with a proper wl_display. */ 517 | wl_display_destroy(server.wl_display); 518 | wlr_output_layout_destroy(server.output_layout); 519 | return ret; 520 | } 521 | -------------------------------------------------------------------------------- /output.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2020 Jente Hidskes 5 | * Copyright (C) 2019 The Sway authors 6 | * 7 | * See the LICENSE file accompanying this file. 8 | */ 9 | 10 | #define _POSIX_C_SOURCE 200112L 11 | 12 | #include "config.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #if WLR_HAS_X11_BACKEND 21 | #include 22 | #endif 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "output.h" 35 | #include "render.h" 36 | #include "seat.h" 37 | #include "server.h" 38 | #include "util.h" 39 | #include "view.h" 40 | #if CAGE_HAS_XWAYLAND 41 | #include "xwayland.h" 42 | #endif 43 | 44 | static void output_for_each_surface(struct cg_output *output, cg_surface_iterator_func_t iterator, void *user_data); 45 | 46 | struct surface_iterator_data { 47 | cg_surface_iterator_func_t user_iterator; 48 | void *user_data; 49 | 50 | struct cg_output *output; 51 | 52 | /* Output-local coordinates. */ 53 | double ox, oy; 54 | }; 55 | 56 | static bool 57 | intersects_with_output(struct cg_output *output, struct wlr_output_layout *output_layout, struct wlr_box *surface_box) 58 | { 59 | /* Since the surface_box's x- and y-coordinates are already output local, 60 | * the x- and y-coordinates of this box need to be 0 for this function to 61 | * work correctly. */ 62 | struct wlr_box output_box = {0}; 63 | wlr_output_effective_resolution(output->wlr_output, &output_box.width, &output_box.height); 64 | 65 | struct wlr_box intersection; 66 | return wlr_box_intersection(&intersection, &output_box, surface_box); 67 | } 68 | 69 | static void 70 | output_for_each_surface_iterator(struct wlr_surface *surface, int sx, int sy, void *user_data) 71 | { 72 | struct surface_iterator_data *data = user_data; 73 | struct cg_output *output = data->output; 74 | 75 | if (!wlr_surface_has_buffer(surface)) { 76 | return; 77 | } 78 | 79 | struct wlr_box surface_box = { 80 | .x = data->ox + sx + surface->sx, 81 | .y = data->oy + sy + surface->sy, 82 | .width = surface->current.width, 83 | .height = surface->current.height, 84 | }; 85 | 86 | if (!intersects_with_output(output, output->server->output_layout, &surface_box)) { 87 | return; 88 | } 89 | 90 | data->user_iterator(data->output, surface, &surface_box, data->user_data); 91 | } 92 | 93 | void 94 | output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy, 95 | cg_surface_iterator_func_t iterator, void *user_data) 96 | { 97 | struct surface_iterator_data data = { 98 | .user_iterator = iterator, 99 | .user_data = user_data, 100 | .output = output, 101 | .ox = ox, 102 | .oy = oy, 103 | }; 104 | 105 | wlr_surface_for_each_surface(surface, output_for_each_surface_iterator, &data); 106 | } 107 | 108 | static void 109 | output_view_for_each_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator, 110 | void *user_data) 111 | { 112 | struct surface_iterator_data data = { 113 | .user_iterator = iterator, 114 | .user_data = user_data, 115 | .output = output, 116 | .ox = view->lx, 117 | .oy = view->ly, 118 | }; 119 | 120 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &data.ox, &data.oy); 121 | view_for_each_surface(view, output_for_each_surface_iterator, &data); 122 | } 123 | 124 | void 125 | output_view_for_each_popup_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator, 126 | void *user_data) 127 | { 128 | struct surface_iterator_data data = { 129 | .user_iterator = iterator, 130 | .user_data = user_data, 131 | .output = output, 132 | .ox = view->lx, 133 | .oy = view->ly, 134 | }; 135 | 136 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &data.ox, &data.oy); 137 | view_for_each_popup_surface(view, output_for_each_surface_iterator, &data); 138 | } 139 | 140 | void 141 | output_drag_icons_for_each_surface(struct cg_output *output, struct wl_list *drag_icons, 142 | cg_surface_iterator_func_t iterator, void *user_data) 143 | { 144 | struct cg_drag_icon *drag_icon; 145 | wl_list_for_each (drag_icon, drag_icons, link) { 146 | if (drag_icon->wlr_drag_icon->mapped) { 147 | double ox = drag_icon->lx; 148 | double oy = drag_icon->ly; 149 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); 150 | output_surface_for_each_surface(output, drag_icon->wlr_drag_icon->surface, ox, oy, iterator, 151 | user_data); 152 | } 153 | } 154 | } 155 | 156 | static void 157 | output_for_each_surface(struct cg_output *output, cg_surface_iterator_func_t iterator, void *user_data) 158 | { 159 | struct cg_view *view; 160 | wl_list_for_each_reverse (view, &output->server->views, link) { 161 | output_view_for_each_surface(output, view, iterator, user_data); 162 | } 163 | 164 | output_drag_icons_for_each_surface(output, &output->server->seat->drag_icons, iterator, user_data); 165 | } 166 | 167 | struct send_frame_done_data { 168 | struct timespec when; 169 | }; 170 | 171 | static void 172 | send_frame_done_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data) 173 | { 174 | struct send_frame_done_data *data = user_data; 175 | wlr_surface_send_frame_done(surface, &data->when); 176 | } 177 | 178 | static void 179 | send_frame_done(struct cg_output *output, struct send_frame_done_data *data) 180 | { 181 | output_for_each_surface(output, send_frame_done_iterator, data); 182 | } 183 | 184 | static void 185 | count_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *_box, void *data) 186 | { 187 | size_t *n = data; 188 | (*n)++; 189 | } 190 | 191 | static bool 192 | scan_out_primary_view(struct cg_output *output) 193 | { 194 | struct cg_server *server = output->server; 195 | struct wlr_output *wlr_output = output->wlr_output; 196 | 197 | struct cg_drag_icon *drag_icon; 198 | wl_list_for_each (drag_icon, &server->seat->drag_icons, link) { 199 | if (drag_icon->wlr_drag_icon->mapped) { 200 | return false; 201 | } 202 | } 203 | 204 | struct cg_view *view = seat_get_focus(server->seat); 205 | if (!view || !view->wlr_surface) { 206 | return false; 207 | } 208 | 209 | size_t n_surfaces = 0; 210 | output_view_for_each_surface(output, view, count_surface_iterator, &n_surfaces); 211 | if (n_surfaces > 1) { 212 | return false; 213 | } 214 | 215 | #if CAGE_HAS_XWAYLAND 216 | if (view->type == CAGE_XWAYLAND_VIEW) { 217 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 218 | if (!wl_list_empty(&xwayland_view->xwayland_surface->children)) { 219 | return false; 220 | } 221 | } 222 | #endif 223 | 224 | struct wlr_surface *surface = view->wlr_surface; 225 | 226 | if (!surface->buffer) { 227 | return false; 228 | } 229 | 230 | if ((float) surface->current.scale != wlr_output->scale || 231 | surface->current.transform != wlr_output->transform) { 232 | return false; 233 | } 234 | 235 | wlr_output_attach_buffer(wlr_output, &surface->buffer->base); 236 | return wlr_output_commit(wlr_output); 237 | } 238 | 239 | static void 240 | output_enable(struct cg_output *output) 241 | { 242 | struct wlr_output *wlr_output = output->wlr_output; 243 | 244 | /* Outputs get enabled by the backend before firing the new_output event, 245 | * so we can't do a check for already enabled outputs here unless we 246 | * duplicate the enabled property in cg_output. */ 247 | wlr_log(WLR_DEBUG, "Enabling output %s", wlr_output->name); 248 | 249 | wlr_output_layout_add_auto(output->server->output_layout, wlr_output); 250 | wlr_output_enable(wlr_output, true); 251 | wlr_output_commit(wlr_output); 252 | } 253 | 254 | static void 255 | output_disable(struct cg_output *output) 256 | { 257 | struct wlr_output *wlr_output = output->wlr_output; 258 | 259 | if (!wlr_output->enabled) { 260 | wlr_log(WLR_DEBUG, "Not disabling already disabled output %s", wlr_output->name); 261 | return; 262 | } 263 | 264 | wlr_log(WLR_DEBUG, "Disabling output %s", wlr_output->name); 265 | wlr_output_enable(wlr_output, false); 266 | wlr_output_layout_remove(output->server->output_layout, wlr_output); 267 | wlr_output_commit(wlr_output); 268 | } 269 | 270 | static void 271 | damage_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data) 272 | { 273 | struct wlr_output *wlr_output = output->wlr_output; 274 | bool whole = *(bool *) user_data; 275 | 276 | scale_box(box, output->wlr_output->scale); 277 | 278 | if (whole) { 279 | wlr_output_damage_add_box(output->damage, box); 280 | } else if (pixman_region32_not_empty(&surface->buffer_damage)) { 281 | pixman_region32_t damage; 282 | pixman_region32_init(&damage); 283 | wlr_surface_get_effective_damage(surface, &damage); 284 | 285 | wlr_region_scale(&damage, &damage, wlr_output->scale); 286 | if (ceil(wlr_output->scale) > surface->current.scale) { 287 | /* When scaling up a surface it'll become 288 | blurry, so we need to expand the damage 289 | region. */ 290 | wlr_region_expand(&damage, &damage, ceil(wlr_output->scale) - surface->current.scale); 291 | } 292 | pixman_region32_translate(&damage, box->x, box->y); 293 | wlr_output_damage_add(output->damage, &damage); 294 | pixman_region32_fini(&damage); 295 | } 296 | } 297 | 298 | void 299 | output_damage_surface(struct cg_output *output, struct wlr_surface *surface, double lx, double ly, bool whole) 300 | { 301 | if (!output->wlr_output->enabled) { 302 | wlr_log(WLR_DEBUG, "Not adding damage for disabled output %s", output->wlr_output->name); 303 | return; 304 | } 305 | 306 | double ox = lx, oy = ly; 307 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); 308 | output_surface_for_each_surface(output, surface, ox, oy, damage_surface_iterator, &whole); 309 | } 310 | 311 | static void 312 | handle_output_damage_frame(struct wl_listener *listener, void *data) 313 | { 314 | struct cg_output *output = wl_container_of(listener, output, damage_frame); 315 | struct send_frame_done_data frame_data = {0}; 316 | 317 | if (!output->wlr_output->enabled) { 318 | return; 319 | } 320 | 321 | /* Check if we can scan-out the primary view. */ 322 | static bool last_scanned_out = false; 323 | bool scanned_out = scan_out_primary_view(output); 324 | 325 | if (scanned_out && !last_scanned_out) { 326 | wlr_log(WLR_DEBUG, "Scanning out primary view"); 327 | } 328 | if (last_scanned_out && !scanned_out) { 329 | wlr_log(WLR_DEBUG, "Stopping primary view scan out"); 330 | } 331 | last_scanned_out = scanned_out; 332 | 333 | if (scanned_out) { 334 | goto frame_done; 335 | } 336 | 337 | bool needs_frame; 338 | pixman_region32_t damage; 339 | pixman_region32_init(&damage); 340 | if (!wlr_output_damage_attach_render(output->damage, &needs_frame, &damage)) { 341 | wlr_log(WLR_ERROR, "Cannot make damage output current"); 342 | goto damage_finish; 343 | } 344 | 345 | if (!needs_frame) { 346 | wlr_output_rollback(output->wlr_output); 347 | goto damage_finish; 348 | } 349 | 350 | output_render(output, &damage); 351 | 352 | damage_finish: 353 | pixman_region32_fini(&damage); 354 | 355 | frame_done: 356 | clock_gettime(CLOCK_MONOTONIC, &frame_data.when); 357 | send_frame_done(output, &frame_data); 358 | } 359 | 360 | static void 361 | handle_output_commit(struct wl_listener *listener, void *data) 362 | { 363 | struct cg_output *output = wl_container_of(listener, output, commit); 364 | struct wlr_output_event_commit *event = data; 365 | 366 | if (!output->wlr_output->enabled) { 367 | return; 368 | } 369 | 370 | if (event->committed & WLR_OUTPUT_STATE_TRANSFORM) { 371 | struct cg_view *view; 372 | wl_list_for_each (view, &output->server->views, link) { 373 | view_position(view); 374 | } 375 | } 376 | } 377 | 378 | static void 379 | handle_output_mode(struct wl_listener *listener, void *data) 380 | { 381 | struct cg_output *output = wl_container_of(listener, output, mode); 382 | 383 | if (!output->wlr_output->enabled) { 384 | return; 385 | } 386 | 387 | struct cg_view *view; 388 | wl_list_for_each (view, &output->server->views, link) { 389 | view_position(view); 390 | } 391 | } 392 | 393 | static void 394 | output_destroy(struct cg_output *output) 395 | { 396 | struct cg_server *server = output->server; 397 | 398 | wl_list_remove(&output->destroy.link); 399 | wl_list_remove(&output->commit.link); 400 | wl_list_remove(&output->mode.link); 401 | wl_list_remove(&output->damage_frame.link); 402 | wl_list_remove(&output->damage_destroy.link); 403 | wl_list_remove(&output->link); 404 | 405 | wlr_output_layout_remove(server->output_layout, output->wlr_output); 406 | 407 | free(output); 408 | 409 | if (wl_list_empty(&server->outputs)) { 410 | wl_display_terminate(server->wl_display); 411 | } else if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST) { 412 | struct cg_output *prev = wl_container_of(server->outputs.next, prev, link); 413 | if (prev) { 414 | output_enable(prev); 415 | 416 | struct cg_view *view; 417 | wl_list_for_each (view, &server->views, link) { 418 | view_position(view); 419 | } 420 | } 421 | } 422 | } 423 | 424 | static void 425 | handle_output_damage_destroy(struct wl_listener *listener, void *data) 426 | { 427 | struct cg_output *output = wl_container_of(listener, output, damage_destroy); 428 | output_destroy(output); 429 | } 430 | 431 | static void 432 | handle_output_destroy(struct wl_listener *listener, void *data) 433 | { 434 | struct cg_output *output = wl_container_of(listener, output, destroy); 435 | wlr_output_damage_destroy(output->damage); 436 | output_destroy(output); 437 | } 438 | 439 | void 440 | handle_new_output(struct wl_listener *listener, void *data) 441 | { 442 | struct cg_server *server = wl_container_of(listener, server, new_output); 443 | struct wlr_output *wlr_output = data; 444 | 445 | struct cg_output *output = calloc(1, sizeof(struct cg_output)); 446 | if (!output) { 447 | wlr_log(WLR_ERROR, "Failed to allocate output"); 448 | return; 449 | } 450 | 451 | output->wlr_output = wlr_output; 452 | output->server = server; 453 | output->damage = wlr_output_damage_create(wlr_output); 454 | wl_list_insert(&server->outputs, &output->link); 455 | 456 | output->commit.notify = handle_output_commit; 457 | wl_signal_add(&wlr_output->events.commit, &output->commit); 458 | output->mode.notify = handle_output_mode; 459 | wl_signal_add(&wlr_output->events.mode, &output->mode); 460 | output->destroy.notify = handle_output_destroy; 461 | wl_signal_add(&wlr_output->events.destroy, &output->destroy); 462 | output->damage_frame.notify = handle_output_damage_frame; 463 | wl_signal_add(&output->damage->events.frame, &output->damage_frame); 464 | output->damage_destroy.notify = handle_output_damage_destroy; 465 | wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); 466 | 467 | struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); 468 | if (preferred_mode) { 469 | wlr_output_set_mode(wlr_output, preferred_mode); 470 | } 471 | wlr_output_set_transform(wlr_output, output->server->output_transform); 472 | 473 | if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST) { 474 | struct cg_output *next = wl_container_of(output->link.next, next, link); 475 | if (next) { 476 | output_disable(next); 477 | } 478 | } 479 | 480 | if (!wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) { 481 | wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f", wlr_output->name, 482 | wlr_output->scale); 483 | } 484 | 485 | output_enable(output); 486 | 487 | struct cg_view *view; 488 | wl_list_for_each (view, &output->server->views, link) { 489 | view_position(view); 490 | } 491 | } 492 | 493 | void 494 | output_set_window_title(struct cg_output *output, const char *title) 495 | { 496 | struct wlr_output *wlr_output = output->wlr_output; 497 | 498 | if (!wlr_output->enabled) { 499 | wlr_log(WLR_DEBUG, "Not setting window title for disabled output %s", wlr_output->name); 500 | return; 501 | } 502 | 503 | if (wlr_output_is_wl(wlr_output)) { 504 | wlr_wl_output_set_title(wlr_output, title); 505 | #if WLR_HAS_X11_BACKEND 506 | } else if (wlr_output_is_x11(wlr_output)) { 507 | wlr_x11_output_set_title(wlr_output, title); 508 | #endif 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /seat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Cage: A Wayland kiosk. 3 | * 4 | * Copyright (C) 2018-2020 Jente Hidskes 5 | * 6 | * See the LICENSE file accompanying this file. 7 | */ 8 | 9 | #include "config.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #if CAGE_HAS_XWAYLAND 27 | #include 28 | #endif 29 | 30 | #include "output.h" 31 | #include "seat.h" 32 | #include "server.h" 33 | #include "view.h" 34 | #if CAGE_HAS_XWAYLAND 35 | #include "xwayland.h" 36 | #endif 37 | 38 | static void drag_icon_update_position(struct cg_drag_icon *drag_icon); 39 | 40 | /* XDG toplevels may have nested surfaces, such as popup windows for context 41 | * menus or tooltips. This function tests if any of those are underneath the 42 | * coordinates lx and ly (in output Layout Coordinates). If so, it sets the 43 | * surface pointer to that wlr_surface and the sx and sy coordinates to the 44 | * coordinates relative to that surface's top-left corner. */ 45 | static bool 46 | view_at(struct cg_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) 47 | { 48 | double view_sx = lx - view->lx; 49 | double view_sy = ly - view->ly; 50 | 51 | double _sx, _sy; 52 | struct wlr_surface *_surface = view_wlr_surface_at(view, view_sx, view_sy, &_sx, &_sy); 53 | if (_surface != NULL) { 54 | *sx = _sx; 55 | *sy = _sy; 56 | *surface = _surface; 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /* This iterates over all of our surfaces and attempts to find one 64 | * under the cursor. This relies on server->views being ordered from 65 | * top-to-bottom. If desktop_view_at returns a view, there is also a 66 | * surface. There cannot be a surface without a view, either. It's 67 | * both or nothing. */ 68 | static struct cg_view * 69 | desktop_view_at(struct cg_server *server, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) 70 | { 71 | struct cg_view *view; 72 | 73 | wl_list_for_each (view, &server->views, link) { 74 | if (view_at(view, lx, ly, surface, sx, sy)) { 75 | return view; 76 | } 77 | } 78 | 79 | return NULL; 80 | } 81 | 82 | static void 83 | press_cursor_button(struct cg_seat *seat, struct wlr_input_device *device, uint32_t time, uint32_t button, 84 | uint32_t state, double lx, double ly) 85 | { 86 | struct cg_server *server = seat->server; 87 | 88 | if (state == WLR_BUTTON_PRESSED) { 89 | double sx, sy; 90 | struct wlr_surface *surface; 91 | struct cg_view *view = desktop_view_at(server, lx, ly, &surface, &sx, &sy); 92 | struct cg_view *current = seat_get_focus(seat); 93 | if (view == current) { 94 | return; 95 | } 96 | 97 | /* Focus that client if the button was pressed and 98 | it has no open dialogs. */ 99 | if (view && !view_is_transient_for(current, view)) { 100 | seat_set_focus(seat, view); 101 | } 102 | } 103 | } 104 | 105 | static void 106 | update_capabilities(struct cg_seat *seat) 107 | { 108 | uint32_t caps = 0; 109 | 110 | if (!wl_list_empty(&seat->keyboard_groups)) { 111 | caps |= WL_SEAT_CAPABILITY_KEYBOARD; 112 | } 113 | if (!wl_list_empty(&seat->pointers)) { 114 | caps |= WL_SEAT_CAPABILITY_POINTER; 115 | } 116 | if (!wl_list_empty(&seat->touch)) { 117 | caps |= WL_SEAT_CAPABILITY_TOUCH; 118 | } 119 | wlr_seat_set_capabilities(seat->seat, caps); 120 | 121 | /* Hide cursor if the seat doesn't have pointer capability. */ 122 | if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { 123 | wlr_cursor_set_image(seat->cursor, NULL, 0, 0, 0, 0, 0, 0); 124 | } else { 125 | wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, DEFAULT_XCURSOR, seat->cursor); 126 | } 127 | } 128 | 129 | static void 130 | map_input_device_to_output(struct cg_seat *seat, struct wlr_input_device *device) 131 | { 132 | if (!device->output_name) { 133 | wlr_log(WLR_INFO, "Input device %s cannot be mapped to an output device\n", device->name); 134 | return; 135 | } 136 | 137 | struct cg_output *output; 138 | wl_list_for_each (output, &seat->server->outputs, link) { 139 | if (strcmp(device->output_name, output->wlr_output->name) == 0) { 140 | wlr_log(WLR_INFO, "Mapping input device %s to output device %s\n", device->name, 141 | output->wlr_output->name); 142 | wlr_cursor_map_input_to_output(seat->cursor, device, output->wlr_output); 143 | return; 144 | } 145 | } 146 | 147 | wlr_log(WLR_INFO, "Couldn't map input device %s to an output\n", device->name); 148 | } 149 | 150 | static void 151 | handle_touch_destroy(struct wl_listener *listener, void *data) 152 | { 153 | struct cg_touch *touch = wl_container_of(listener, touch, destroy); 154 | struct cg_seat *seat = touch->seat; 155 | 156 | wl_list_remove(&touch->link); 157 | wlr_cursor_detach_input_device(seat->cursor, touch->device); 158 | wl_list_remove(&touch->destroy.link); 159 | free(touch); 160 | 161 | update_capabilities(seat); 162 | } 163 | 164 | static void 165 | handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device) 166 | { 167 | struct cg_touch *touch = calloc(1, sizeof(struct cg_touch)); 168 | if (!touch) { 169 | wlr_log(WLR_ERROR, "Cannot allocate touch"); 170 | return; 171 | } 172 | 173 | touch->seat = seat; 174 | touch->device = device; 175 | wlr_cursor_attach_input_device(seat->cursor, device); 176 | 177 | wl_list_insert(&seat->touch, &touch->link); 178 | touch->destroy.notify = handle_touch_destroy; 179 | wl_signal_add(&touch->device->events.destroy, &touch->destroy); 180 | 181 | map_input_device_to_output(seat, device); 182 | } 183 | 184 | static void 185 | handle_pointer_destroy(struct wl_listener *listener, void *data) 186 | { 187 | struct cg_pointer *pointer = wl_container_of(listener, pointer, destroy); 188 | struct cg_seat *seat = pointer->seat; 189 | 190 | wl_list_remove(&pointer->link); 191 | wlr_cursor_detach_input_device(seat->cursor, pointer->device); 192 | wl_list_remove(&pointer->destroy.link); 193 | free(pointer); 194 | 195 | update_capabilities(seat); 196 | } 197 | 198 | static void 199 | handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) 200 | { 201 | struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); 202 | if (!pointer) { 203 | wlr_log(WLR_ERROR, "Cannot allocate pointer"); 204 | return; 205 | } 206 | 207 | pointer->seat = seat; 208 | pointer->device = device; 209 | wlr_cursor_attach_input_device(seat->cursor, device); 210 | 211 | wl_list_insert(&seat->pointers, &pointer->link); 212 | pointer->destroy.notify = handle_pointer_destroy; 213 | wl_signal_add(&device->events.destroy, &pointer->destroy); 214 | 215 | map_input_device_to_output(seat, device); 216 | } 217 | 218 | static void 219 | handle_modifier_event(struct wlr_input_device *device, struct cg_seat *seat) 220 | { 221 | wlr_seat_set_keyboard(seat->seat, device); 222 | wlr_seat_keyboard_notify_modifiers(seat->seat, &device->keyboard->modifiers); 223 | 224 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 225 | } 226 | 227 | static bool 228 | handle_keybinding(struct cg_server *server, xkb_keysym_t sym) 229 | { 230 | #ifdef DEBUG 231 | if (sym == XKB_KEY_Escape) { 232 | wl_display_terminate(server->wl_display); 233 | return true; 234 | } 235 | #endif 236 | if (server->allow_vt_switch && sym >= XKB_KEY_XF86Switch_VT_1 && sym <= XKB_KEY_XF86Switch_VT_12) { 237 | if (wlr_backend_is_multi(server->backend)) { 238 | struct wlr_session *session = wlr_backend_get_session(server->backend); 239 | if (session) { 240 | unsigned vt = sym - XKB_KEY_XF86Switch_VT_1 + 1; 241 | wlr_session_change_vt(session, vt); 242 | } 243 | } 244 | } else { 245 | return false; 246 | } 247 | wlr_idle_notify_activity(server->idle, server->seat->seat); 248 | return true; 249 | } 250 | 251 | static void 252 | handle_key_event(struct wlr_input_device *device, struct cg_seat *seat, void *data) 253 | { 254 | struct wlr_event_keyboard_key *event = data; 255 | 256 | /* Translate from libinput keycode to an xkbcommon keycode. */ 257 | xkb_keycode_t keycode = event->keycode + 8; 258 | 259 | const xkb_keysym_t *syms; 260 | int nsyms = xkb_state_key_get_syms(device->keyboard->xkb_state, keycode, &syms); 261 | 262 | bool handled = false; 263 | uint32_t modifiers = wlr_keyboard_get_modifiers(device->keyboard); 264 | if ((modifiers & WLR_MODIFIER_ALT) && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { 265 | /* If Alt is held down and this button was pressed, we 266 | * attempt to process it as a compositor 267 | * keybinding. */ 268 | for (int i = 0; i < nsyms; i++) { 269 | handled = handle_keybinding(seat->server, syms[i]); 270 | } 271 | } 272 | 273 | if (!handled) { 274 | /* Otherwise, we pass it along to the client. */ 275 | wlr_seat_set_keyboard(seat->seat, device); 276 | wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state); 277 | } 278 | 279 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 280 | } 281 | 282 | static void 283 | handle_keyboard_group_key(struct wl_listener *listener, void *data) 284 | { 285 | struct cg_keyboard_group *cg_group = wl_container_of(listener, cg_group, key); 286 | handle_key_event(cg_group->wlr_group->input_device, cg_group->seat, data); 287 | } 288 | 289 | static void 290 | handle_keyboard_group_modifiers(struct wl_listener *listener, void *data) 291 | { 292 | struct cg_keyboard_group *group = wl_container_of(listener, group, modifiers); 293 | handle_modifier_event(group->wlr_group->input_device, group->seat); 294 | } 295 | 296 | static void 297 | cg_keyboard_group_add(struct wlr_input_device *device, struct cg_seat *seat) 298 | { 299 | struct wlr_keyboard *wlr_keyboard = device->keyboard; 300 | 301 | struct cg_keyboard_group *group; 302 | wl_list_for_each (group, &seat->keyboard_groups, link) { 303 | struct wlr_keyboard_group *wlr_group = group->wlr_group; 304 | if (wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard)) { 305 | wlr_log(WLR_DEBUG, "Added new keyboard to existing group"); 306 | return; 307 | } 308 | } 309 | 310 | /* This is reached if and only if the keyboard could not be inserted into 311 | * any group */ 312 | struct cg_keyboard_group *cg_group = calloc(1, sizeof(struct cg_keyboard_group)); 313 | if (cg_group == NULL) { 314 | wlr_log(WLR_ERROR, "Failed to allocate keyboard group."); 315 | return; 316 | } 317 | cg_group->seat = seat; 318 | cg_group->wlr_group = wlr_keyboard_group_create(); 319 | if (cg_group->wlr_group == NULL) { 320 | wlr_log(WLR_ERROR, "Failed to create wlr keyboard group."); 321 | goto cleanup; 322 | } 323 | 324 | cg_group->wlr_group->data = cg_group; 325 | wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard, device->keyboard->keymap); 326 | 327 | wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, wlr_keyboard->repeat_info.rate, 328 | wlr_keyboard->repeat_info.delay); 329 | 330 | wlr_log(WLR_DEBUG, "Created keyboard group"); 331 | 332 | wlr_keyboard_group_add_keyboard(cg_group->wlr_group, wlr_keyboard); 333 | wl_list_insert(&seat->keyboard_groups, &cg_group->link); 334 | 335 | wl_signal_add(&cg_group->wlr_group->keyboard.events.key, &cg_group->key); 336 | cg_group->key.notify = handle_keyboard_group_key; 337 | wl_signal_add(&cg_group->wlr_group->keyboard.events.modifiers, &cg_group->modifiers); 338 | cg_group->modifiers.notify = handle_keyboard_group_modifiers; 339 | 340 | return; 341 | 342 | cleanup: 343 | if (cg_group && cg_group->wlr_group) { 344 | wlr_keyboard_group_destroy(cg_group->wlr_group); 345 | } 346 | free(cg_group); 347 | } 348 | 349 | static void 350 | handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) 351 | { 352 | struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 353 | if (!context) { 354 | wlr_log(WLR_ERROR, "Unable to create XBK context"); 355 | return; 356 | } 357 | 358 | struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); 359 | if (!keymap) { 360 | wlr_log(WLR_ERROR, "Unable to configure keyboard: keymap does not exist"); 361 | xkb_context_unref(context); 362 | return; 363 | } 364 | 365 | wlr_keyboard_set_keymap(device->keyboard, keymap); 366 | 367 | xkb_keymap_unref(keymap); 368 | xkb_context_unref(context); 369 | wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); 370 | 371 | cg_keyboard_group_add(device, seat); 372 | 373 | wlr_seat_set_keyboard(seat->seat, device); 374 | } 375 | 376 | static void 377 | handle_new_input(struct wl_listener *listener, void *data) 378 | { 379 | struct cg_seat *seat = wl_container_of(listener, seat, new_input); 380 | struct wlr_input_device *device = data; 381 | 382 | switch (device->type) { 383 | case WLR_INPUT_DEVICE_KEYBOARD: 384 | handle_new_keyboard(seat, device); 385 | break; 386 | case WLR_INPUT_DEVICE_POINTER: 387 | handle_new_pointer(seat, device); 388 | break; 389 | case WLR_INPUT_DEVICE_TOUCH: 390 | handle_new_touch(seat, device); 391 | break; 392 | case WLR_INPUT_DEVICE_SWITCH: 393 | wlr_log(WLR_DEBUG, "Switch input is not implemented"); 394 | return; 395 | case WLR_INPUT_DEVICE_TABLET_TOOL: 396 | case WLR_INPUT_DEVICE_TABLET_PAD: 397 | wlr_log(WLR_DEBUG, "Tablet input is not implemented"); 398 | return; 399 | } 400 | 401 | update_capabilities(seat); 402 | } 403 | 404 | static void 405 | handle_request_set_primary_selection(struct wl_listener *listener, void *data) 406 | { 407 | struct cg_seat *seat = wl_container_of(listener, seat, request_set_primary_selection); 408 | struct wlr_seat_request_set_primary_selection_event *event = data; 409 | 410 | wlr_seat_set_primary_selection(seat->seat, event->source, event->serial); 411 | } 412 | 413 | static void 414 | handle_request_set_selection(struct wl_listener *listener, void *data) 415 | { 416 | struct cg_seat *seat = wl_container_of(listener, seat, request_set_selection); 417 | struct wlr_seat_request_set_selection_event *event = data; 418 | 419 | wlr_seat_set_selection(seat->seat, event->source, event->serial); 420 | } 421 | 422 | static void 423 | handle_request_set_cursor(struct wl_listener *listener, void *data) 424 | { 425 | struct cg_seat *seat = wl_container_of(listener, seat, request_set_cursor); 426 | struct wlr_seat_pointer_request_set_cursor_event *event = data; 427 | struct wlr_surface *focused_surface = event->seat_client->seat->pointer_state.focused_surface; 428 | bool has_focused = focused_surface != NULL && focused_surface->resource != NULL; 429 | struct wl_client *focused_client = NULL; 430 | if (has_focused) { 431 | focused_client = wl_resource_get_client(focused_surface->resource); 432 | } 433 | 434 | /* This can be sent by any client, so we check to make sure 435 | * this one actually has pointer focus first. */ 436 | if (focused_client == event->seat_client->client) { 437 | wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, event->hotspot_y); 438 | } 439 | } 440 | 441 | static void 442 | handle_touch_down(struct wl_listener *listener, void *data) 443 | { 444 | struct cg_seat *seat = wl_container_of(listener, seat, touch_down); 445 | struct wlr_event_touch_down *event = data; 446 | 447 | double lx, ly; 448 | wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, event->y, &lx, &ly); 449 | 450 | double sx, sy; 451 | struct wlr_surface *surface; 452 | struct cg_view *view = desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); 453 | 454 | uint32_t serial = 0; 455 | if (view) { 456 | serial = wlr_seat_touch_notify_down(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); 457 | } 458 | 459 | if (serial && wlr_seat_touch_num_points(seat->seat) == 1) { 460 | seat->touch_id = event->touch_id; 461 | seat->touch_lx = lx; 462 | seat->touch_ly = ly; 463 | press_cursor_button(seat, event->device, event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly); 464 | } 465 | 466 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 467 | } 468 | 469 | static void 470 | handle_touch_up(struct wl_listener *listener, void *data) 471 | { 472 | struct cg_seat *seat = wl_container_of(listener, seat, touch_up); 473 | struct wlr_event_touch_up *event = data; 474 | 475 | if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { 476 | return; 477 | } 478 | 479 | if (wlr_seat_touch_num_points(seat->seat) == 1) { 480 | press_cursor_button(seat, event->device, event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED, 481 | seat->touch_lx, seat->touch_ly); 482 | } 483 | 484 | wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id); 485 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 486 | } 487 | 488 | static void 489 | handle_touch_motion(struct wl_listener *listener, void *data) 490 | { 491 | struct cg_seat *seat = wl_container_of(listener, seat, touch_motion); 492 | struct wlr_event_touch_motion *event = data; 493 | 494 | if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { 495 | return; 496 | } 497 | 498 | double lx, ly; 499 | wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, event->y, &lx, &ly); 500 | 501 | double sx, sy; 502 | struct wlr_surface *surface; 503 | struct cg_view *view = desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); 504 | 505 | if (view) { 506 | wlr_seat_touch_point_focus(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); 507 | wlr_seat_touch_notify_motion(seat->seat, event->time_msec, event->touch_id, sx, sy); 508 | } else { 509 | wlr_seat_touch_point_clear_focus(seat->seat, event->time_msec, event->touch_id); 510 | } 511 | 512 | if (event->touch_id == seat->touch_id) { 513 | seat->touch_lx = lx; 514 | seat->touch_ly = ly; 515 | } 516 | 517 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 518 | } 519 | 520 | static void 521 | handle_cursor_frame(struct wl_listener *listener, void *data) 522 | { 523 | struct cg_seat *seat = wl_container_of(listener, seat, cursor_frame); 524 | 525 | wlr_seat_pointer_notify_frame(seat->seat); 526 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 527 | } 528 | 529 | static void 530 | handle_cursor_axis(struct wl_listener *listener, void *data) 531 | { 532 | struct cg_seat *seat = wl_container_of(listener, seat, cursor_axis); 533 | struct wlr_event_pointer_axis *event = data; 534 | 535 | wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, event->orientation, event->delta, 536 | event->delta_discrete, event->source); 537 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 538 | } 539 | 540 | static void 541 | handle_cursor_button(struct wl_listener *listener, void *data) 542 | { 543 | struct cg_seat *seat = wl_container_of(listener, seat, cursor_button); 544 | struct wlr_event_pointer_button *event = data; 545 | 546 | wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->state); 547 | press_cursor_button(seat, event->device, event->time_msec, event->button, event->state, seat->cursor->x, 548 | seat->cursor->y); 549 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 550 | } 551 | 552 | static void 553 | process_cursor_motion(struct cg_seat *seat, uint32_t time) 554 | { 555 | double sx, sy; 556 | struct wlr_seat *wlr_seat = seat->seat; 557 | struct wlr_surface *surface = NULL; 558 | 559 | struct cg_view *view = desktop_view_at(seat->server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy); 560 | 561 | if (!view) { 562 | wlr_seat_pointer_clear_focus(wlr_seat); 563 | } else { 564 | wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); 565 | 566 | wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); 567 | } 568 | 569 | struct cg_drag_icon *drag_icon; 570 | wl_list_for_each (drag_icon, &seat->drag_icons, link) { 571 | drag_icon_update_position(drag_icon); 572 | } 573 | 574 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 575 | } 576 | 577 | static void 578 | handle_cursor_motion_absolute(struct wl_listener *listener, void *data) 579 | { 580 | struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_absolute); 581 | struct wlr_event_pointer_motion_absolute *event = data; 582 | 583 | wlr_cursor_warp_absolute(seat->cursor, event->device, event->x, event->y); 584 | process_cursor_motion(seat, event->time_msec); 585 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 586 | } 587 | 588 | static void 589 | handle_cursor_motion(struct wl_listener *listener, void *data) 590 | { 591 | struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion); 592 | struct wlr_event_pointer_motion *event = data; 593 | 594 | wlr_cursor_move(seat->cursor, event->device, event->delta_x, event->delta_y); 595 | process_cursor_motion(seat, event->time_msec); 596 | wlr_idle_notify_activity(seat->server->idle, seat->seat); 597 | } 598 | 599 | static void 600 | drag_icon_damage(struct cg_drag_icon *drag_icon) 601 | { 602 | struct cg_output *output; 603 | wl_list_for_each (output, &drag_icon->seat->server->outputs, link) { 604 | output_damage_surface(output, drag_icon->wlr_drag_icon->surface, drag_icon->lx, drag_icon->ly, true); 605 | } 606 | } 607 | 608 | static void 609 | drag_icon_update_position(struct cg_drag_icon *drag_icon) 610 | { 611 | struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon; 612 | struct cg_seat *seat = drag_icon->seat; 613 | struct wlr_touch_point *point; 614 | 615 | drag_icon_damage(drag_icon); 616 | 617 | switch (wlr_icon->drag->grab_type) { 618 | case WLR_DRAG_GRAB_KEYBOARD: 619 | return; 620 | case WLR_DRAG_GRAB_KEYBOARD_POINTER: 621 | drag_icon->lx = seat->cursor->x; 622 | drag_icon->ly = seat->cursor->y; 623 | break; 624 | case WLR_DRAG_GRAB_KEYBOARD_TOUCH: 625 | point = wlr_seat_touch_get_point(seat->seat, wlr_icon->drag->touch_id); 626 | if (!point) { 627 | return; 628 | } 629 | drag_icon->lx = seat->touch_lx; 630 | drag_icon->ly = seat->touch_ly; 631 | break; 632 | } 633 | 634 | drag_icon_damage(drag_icon); 635 | } 636 | 637 | static void 638 | handle_drag_icon_destroy(struct wl_listener *listener, void *data) 639 | { 640 | struct cg_drag_icon *drag_icon = wl_container_of(listener, drag_icon, destroy); 641 | 642 | drag_icon_damage(drag_icon); 643 | wl_list_remove(&drag_icon->link); 644 | wl_list_remove(&drag_icon->destroy.link); 645 | free(drag_icon); 646 | } 647 | 648 | static void 649 | handle_request_start_drag(struct wl_listener *listener, void *data) 650 | { 651 | struct cg_seat *seat = wl_container_of(listener, seat, request_start_drag); 652 | struct wlr_seat_request_start_drag_event *event = data; 653 | 654 | if (wlr_seat_validate_pointer_grab_serial(seat->seat, event->origin, event->serial)) { 655 | wlr_seat_start_pointer_drag(seat->seat, event->drag, event->serial); 656 | return; 657 | } 658 | 659 | struct wlr_touch_point *point; 660 | if (wlr_seat_validate_touch_grab_serial(seat->seat, event->origin, event->serial, &point)) { 661 | wlr_seat_start_touch_drag(seat->seat, event->drag, event->serial, point); 662 | return; 663 | } 664 | 665 | // TODO: tablet grabs 666 | wlr_log(WLR_DEBUG, "Ignoring start_drag request: could not validate pointer/touch serial %" PRIu32, 667 | event->serial); 668 | wlr_data_source_destroy(event->drag->source); 669 | } 670 | 671 | static void 672 | handle_start_drag(struct wl_listener *listener, void *data) 673 | { 674 | struct cg_seat *seat = wl_container_of(listener, seat, start_drag); 675 | struct wlr_drag *wlr_drag = data; 676 | struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; 677 | if (wlr_drag_icon == NULL) { 678 | return; 679 | } 680 | 681 | struct cg_drag_icon *drag_icon = calloc(1, sizeof(struct cg_drag_icon)); 682 | if (!drag_icon) { 683 | return; 684 | } 685 | drag_icon->seat = seat; 686 | drag_icon->wlr_drag_icon = wlr_drag_icon; 687 | 688 | drag_icon->destroy.notify = handle_drag_icon_destroy; 689 | wl_signal_add(&wlr_drag_icon->events.destroy, &drag_icon->destroy); 690 | 691 | wl_list_insert(&seat->drag_icons, &drag_icon->link); 692 | 693 | drag_icon_update_position(drag_icon); 694 | } 695 | 696 | static void 697 | handle_destroy(struct wl_listener *listener, void *data) 698 | { 699 | struct cg_seat *seat = wl_container_of(listener, seat, destroy); 700 | wl_list_remove(&seat->destroy.link); 701 | wl_list_remove(&seat->cursor_motion.link); 702 | wl_list_remove(&seat->cursor_motion_absolute.link); 703 | wl_list_remove(&seat->cursor_button.link); 704 | wl_list_remove(&seat->cursor_axis.link); 705 | wl_list_remove(&seat->cursor_frame.link); 706 | wl_list_remove(&seat->touch_down.link); 707 | wl_list_remove(&seat->touch_up.link); 708 | wl_list_remove(&seat->touch_motion.link); 709 | wl_list_remove(&seat->request_set_cursor.link); 710 | wl_list_remove(&seat->request_set_selection.link); 711 | wl_list_remove(&seat->request_set_primary_selection.link); 712 | 713 | struct cg_keyboard_group *group, *group_tmp; 714 | wl_list_for_each_safe (group, group_tmp, &seat->keyboard_groups, link) { 715 | wlr_keyboard_group_destroy(group->wlr_group); 716 | free(group); 717 | } 718 | struct cg_pointer *pointer, *pointer_tmp; 719 | wl_list_for_each_safe (pointer, pointer_tmp, &seat->pointers, link) { 720 | handle_pointer_destroy(&pointer->destroy, NULL); 721 | } 722 | struct cg_touch *touch, *touch_tmp; 723 | wl_list_for_each_safe (touch, touch_tmp, &seat->touch, link) { 724 | handle_touch_destroy(&touch->destroy, NULL); 725 | } 726 | wl_list_remove(&seat->new_input.link); 727 | 728 | wlr_xcursor_manager_destroy(seat->xcursor_manager); 729 | if (seat->cursor) { 730 | wlr_cursor_destroy(seat->cursor); 731 | } 732 | free(seat); 733 | } 734 | 735 | struct cg_seat * 736 | seat_create(struct cg_server *server, struct wlr_backend *backend) 737 | { 738 | struct cg_seat *seat = calloc(1, sizeof(struct cg_seat)); 739 | if (!seat) { 740 | wlr_log(WLR_ERROR, "Cannot allocate seat"); 741 | return NULL; 742 | } 743 | 744 | seat->seat = wlr_seat_create(server->wl_display, "seat0"); 745 | if (!seat->seat) { 746 | wlr_log(WLR_ERROR, "Cannot allocate seat0"); 747 | free(seat); 748 | return NULL; 749 | } 750 | seat->server = server; 751 | seat->destroy.notify = handle_destroy; 752 | wl_signal_add(&seat->seat->events.destroy, &seat->destroy); 753 | 754 | seat->cursor = wlr_cursor_create(); 755 | if (!seat->cursor) { 756 | wlr_log(WLR_ERROR, "Unable to create cursor"); 757 | wl_list_remove(&seat->destroy.link); 758 | free(seat); 759 | return NULL; 760 | } 761 | wlr_cursor_attach_output_layout(seat->cursor, server->output_layout); 762 | 763 | if (!seat->xcursor_manager) { 764 | seat->xcursor_manager = wlr_xcursor_manager_create(NULL, XCURSOR_SIZE); 765 | if (!seat->xcursor_manager) { 766 | wlr_log(WLR_ERROR, "Cannot create XCursor manager"); 767 | wlr_cursor_destroy(seat->cursor); 768 | wl_list_remove(&seat->destroy.link); 769 | free(seat); 770 | return NULL; 771 | } 772 | } 773 | 774 | seat->cursor_motion.notify = handle_cursor_motion; 775 | wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); 776 | seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute; 777 | wl_signal_add(&seat->cursor->events.motion_absolute, &seat->cursor_motion_absolute); 778 | seat->cursor_button.notify = handle_cursor_button; 779 | wl_signal_add(&seat->cursor->events.button, &seat->cursor_button); 780 | seat->cursor_axis.notify = handle_cursor_axis; 781 | wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis); 782 | seat->cursor_frame.notify = handle_cursor_frame; 783 | wl_signal_add(&seat->cursor->events.frame, &seat->cursor_frame); 784 | 785 | seat->touch_down.notify = handle_touch_down; 786 | wl_signal_add(&seat->cursor->events.touch_down, &seat->touch_down); 787 | seat->touch_up.notify = handle_touch_up; 788 | wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up); 789 | seat->touch_motion.notify = handle_touch_motion; 790 | wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion); 791 | 792 | seat->request_set_cursor.notify = handle_request_set_cursor; 793 | wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_set_cursor); 794 | seat->request_set_selection.notify = handle_request_set_selection; 795 | wl_signal_add(&seat->seat->events.request_set_selection, &seat->request_set_selection); 796 | seat->request_set_primary_selection.notify = handle_request_set_primary_selection; 797 | wl_signal_add(&seat->seat->events.request_set_primary_selection, &seat->request_set_primary_selection); 798 | 799 | wl_list_init(&seat->keyboards); 800 | wl_list_init(&seat->keyboard_groups); 801 | wl_list_init(&seat->pointers); 802 | wl_list_init(&seat->touch); 803 | 804 | seat->new_input.notify = handle_new_input; 805 | wl_signal_add(&backend->events.new_input, &seat->new_input); 806 | 807 | wl_list_init(&seat->drag_icons); 808 | seat->request_start_drag.notify = handle_request_start_drag; 809 | wl_signal_add(&seat->seat->events.request_start_drag, &seat->request_start_drag); 810 | seat->start_drag.notify = handle_start_drag; 811 | wl_signal_add(&seat->seat->events.start_drag, &seat->start_drag); 812 | 813 | return seat; 814 | } 815 | 816 | void 817 | seat_destroy(struct cg_seat *seat) 818 | { 819 | if (!seat) { 820 | return; 821 | } 822 | 823 | wl_list_remove(&seat->request_start_drag.link); 824 | wl_list_remove(&seat->start_drag.link); 825 | 826 | // Destroying the wlr seat will trigger the destroy handler on our seat, 827 | // which will in turn free it. 828 | wlr_seat_destroy(seat->seat); 829 | } 830 | 831 | struct cg_view * 832 | seat_get_focus(struct cg_seat *seat) 833 | { 834 | struct wlr_surface *prev_surface = seat->seat->keyboard_state.focused_surface; 835 | return view_from_wlr_surface(seat->server, prev_surface); 836 | } 837 | 838 | void 839 | seat_set_focus(struct cg_seat *seat, struct cg_view *view) 840 | { 841 | struct cg_server *server = seat->server; 842 | struct wlr_seat *wlr_seat = seat->seat; 843 | struct cg_view *prev_view = seat_get_focus(seat); 844 | 845 | if (!view || prev_view == view) { 846 | return; 847 | } 848 | 849 | #if CAGE_HAS_XWAYLAND 850 | if (view->type == CAGE_XWAYLAND_VIEW) { 851 | struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); 852 | if (!wlr_xwayland_or_surface_wants_focus(xwayland_view->xwayland_surface)) { 853 | return; 854 | } 855 | } 856 | #endif 857 | 858 | if (prev_view) { 859 | view_activate(prev_view, false); 860 | } 861 | 862 | /* Move the view to the front, but only if it isn't a 863 | fullscreen view. */ 864 | if (!view_is_primary(view)) { 865 | wl_list_remove(&view->link); 866 | wl_list_insert(&server->views, &view->link); 867 | } 868 | 869 | view_activate(view, true); 870 | char *title = view_get_title(view); 871 | struct cg_output *output; 872 | wl_list_for_each (output, &server->outputs, link) { 873 | output_set_window_title(output, title); 874 | } 875 | free(title); 876 | 877 | struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); 878 | if (keyboard) { 879 | wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, keyboard->keycodes, keyboard->num_keycodes, 880 | &keyboard->modifiers); 881 | } else { 882 | wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, NULL); 883 | } 884 | 885 | process_cursor_motion(seat, -1); 886 | } 887 | --------------------------------------------------------------------------------