pax_global_header00006660000000000000000000000064140655546740014532gustar00rootroot0000000000000052 comment=646b3e80b45a011676eb4190652b8c506d56e7a4 cage-0.1.4/000077500000000000000000000000001406555467400124335ustar00rootroot00000000000000cage-0.1.4/.builds/000077500000000000000000000000001406555467400137735ustar00rootroot00000000000000cage-0.1.4/.builds/alpine.yml000066400000000000000000000015051406555467400157670ustar00rootroot00000000000000image: alpine/edge packages: - eudev-dev - mesa-dev - meson - libinput-dev - libxkbcommon-dev - pixman-dev - scdoc - wayland-dev - wayland-protocols - xcb-util-wm-dev - xwayland sources: - https://github.com/swaywm/wlroots - https://github.com/Hjdskes/cage tasks: # Install wlroots, which is required by Cage. Note that we compile a tagged # version, instead of master, to avoid any breaking changes in wlroots. - wlroots: | cd wlroots git checkout 0.14.0 meson --prefix=/usr build -Dexamples=false ninja -C build sudo ninja -C build install - build: | cd cage meson build --werror -Dxwayland=true ninja -C build rm -rf build - build-no-xwayland: | cd cage meson build --werror -Dxwayland=false ninja -C build rm -rf build cage-0.1.4/.builds/archlinux.yml000066400000000000000000000021161406555467400165130ustar00rootroot00000000000000image: archlinux packages: - clang - meson - libinput - libxkbcommon - mesa - scdoc - wayland - wayland-protocols - xcb-util-wm - xorg-xwayland sources: - https://github.com/swaywm/wlroots - https://github.com/Hjdskes/cage tasks: # Install wlroots, which is required by Cage. Note that we compile a tagged # version, instead of master, to avoid any breaking changes in wlroots. - wlroots: | cd wlroots git checkout 0.14.0 meson --prefix=/usr build -Dexamples=false ninja -C build sudo ninja -C build install - build: | cd cage meson build --werror -Dxwayland=true ninja -C build rm -rf build - build-no-xwayland: | cd cage meson build --werror -Dxwayland=false ninja -C build rm -rf build - scan-build: | cd cage CC=clang meson build --werror -Dxwayland=true CC=clang ninja -C build scan-build rm -rf build - clang-format: | cd cage meson build --werror -Dxwayland=true ninja -C build clang-format rm -rf build git diff --exit-code cage-0.1.4/.builds/freebsd.yml000066400000000000000000000021271406555467400161320ustar00rootroot00000000000000image: freebsd/latest packages: - devel/evdev-proto - devel/meson - devel/libepoll-shim - devel/pkgconf - graphics/mesa-libs - graphics/wayland - graphics/wayland-protocols - textproc/scdoc - x11/libinput - x11/libxkbcommon - x11/pixman - x11/xcb-util-wm - x11-servers/xwayland sources: - https://github.com/swaywm/wlroots - https://github.com/Hjdskes/cage tasks: # Install wlroots, which is required by Cage. Note that we compile a tagged # version, instead of master, to avoid any breaking changes in wlroots. - wlroots: | cd wlroots git checkout 0.14.0 meson --prefix=/usr/local build -Dexamples=false ninja -C build sudo ninja -C build install - build: | cd cage PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build --werror -Dxwayland=true PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build rm -rf build - build-no-xwayland: | cd cage PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build --werror -Dxwayland=false PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build rm -rf build cage-0.1.4/.clang-format000066400000000000000000000005571406555467400150150ustar00rootroot00000000000000AlignAfterOpenBracket: Align AlignTrailingComments: false AlwaysBreakAfterReturnType: TopLevelDefinitions BreakBeforeBraces: Linux ColumnLimit: 120 ContinuationIndentWidth: 8 ForEachMacros: [wl_list_for_each, wl_list_for_each_safe, wl_list_for_each_reverse] IndentWidth: 8 ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: true TabWidth: 8 UseTab: Always cage-0.1.4/LICENSE000066400000000000000000000021121406555467400134340ustar00rootroot00000000000000Copyright (c) 2018-2020 Jente Hidskes Copyright (c) 2019 The Sway authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cage-0.1.4/README.md000066400000000000000000000046321406555467400137170ustar00rootroot00000000000000# Cage: a Wayland kiosk [![builds.sr.ht status](https://builds.sr.ht/~hjdskes.svg)](https://builds.sr.ht/~hjdskes?) Cage's logo This is Cage, a Wayland kiosk. A kiosk runs a single, maximized application. This README is only relevant for development resources and instructions. For a description of Cage and installation instructions for end-users, please see [its project page](https://www.hjdskes.nl/projects/cage) and [the Wiki](https://github.com/Hjdskes/cage/wiki/). ## Release signatures Releases are signed with [6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1) and published on [GitHub](https://github.com/Hjdskes/cage/releases). ## Building and running Cage You can build Cage with the [meson](https://mesonbuild.com/) build system. It requires wayland, wlroots, and xkbcommon to be installed. Optionally, install scdoc for manual pages. Note that Cage is developed against the latest tag of wlroots, in order to not constantly chase breaking changes as soon as they occur. Simply execute the following steps to build Cage: ``` $ meson build $ ninja -C build ``` By default, this builds a debug build. To build a release build, use `meson build --buildtype=release`. Cage comes with compile-time support for XWayland. To enable this, first make sure that your version of wlroots is compiled with this option. Then, add `-Dxwayland=true` to the `meson` command above. Note that you'll need to have the XWayland binary installed on your system for this to work. You can run Cage by running `./build/cage APPLICATION`. If you run it from within an existing X11 or Wayland session, it will open in a virtual output as a window in your existing session. If you run it at a TTY, it'll run with the KMS+DRM backend. In debug mode (default build type with Meson), press Alt+Esc to quit. For more configuration options, see [Configuration](https://github.com/Hjdskes/cage/wiki/Configuration). Cage is based on the annotated source of tinywl and rootston. ## Bugs For any bug, please [create an issue](https://github.com/Hjdskes/cage/issues/new) on [GitHub](https://github.com/Hjdskes/cage). ## License Please see [LICENSE](https://github.com/Hjdskes/cage/blob/master/LICENSE) on [GitHub](https://github.com/Hjdskes/cage). Copyright © 2018-2020 Jente Hidskes cage-0.1.4/cage.1.scd000066400000000000000000000030731406555467400141670ustar00rootroot00000000000000cage(1) # NAME cage - a Wayland kiosk compositor # SYNOPSIS *cage* [-dhmrsv] [--] _application_ [application argument ...] # DESCRIPTION Cage runs a single, maximized application. Cage can run multiple applications, but only a single one is visible at any point in time. User interaction and activities outside the scope of the running application are prevented. # OPTIONS *-d* Don't draw client side decorations when possible. *-h* Show the help message. *-m* Set the multi-monitor behavior. Supported modes are: *last* Cage uses only the last connected monitor. *extend* Cage extends the display across all connected monitors. *-r* Rotate the output 90 degrees clockwise. This can be specified up to three times, each resulting in an additional 90 degrees clockwise rotation. *-s* Allow VT switching *-v* Show the version number and exit. # ENVIRONMENT _DISPLAY_ If compiled with Xwayland support, this will be set to the name of the X display used for Xwayland. Otherwise, probe the X11 backend. _WAYLAND_DISPLAY_ Specifies the name of the Wayland display that Cage is running on. _XCURSOR_PATH_ Directory where cursors are located. _XCURSOR_SIZE_ Specifies the configured cursor size. _XCURSOR_THEME_ Specifies the configured cursor theme. _XKB_DEFAULT_RULES_, _XKB_DEFAULT_MODEL_, _XKB_DEFAULT_LAYOUT_, _XKB_DEFAULT_VARIANT_, _XKB_DEFAULT_OPTIONS_ Configures the xkb keyboard settings. See *xkeyboard-config*(7). # SEE ALSO *xkeyboard-config(7)* # BUGS Report bugs at https://github.com/Hjdskes/cage # AUTHORS Jente Hidskes cage-0.1.4/cage.c000066400000000000000000000337011406555467400135020ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200112L #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CAGE_HAS_XWAYLAND #include #endif #include #include #include #include #if CAGE_HAS_XWAYLAND #include #endif #include "idle_inhibit_v1.h" #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #include "xdg_shell.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif static int sigchld_handler(int fd, uint32_t mask, void *data) { struct wl_display *display = data; /* Close Cage's read pipe. */ close(fd); if (mask & WL_EVENT_HANGUP) { wlr_log(WLR_DEBUG, "Child process closed normally"); } else if (mask & WL_EVENT_ERROR) { wlr_log(WLR_DEBUG, "Connection closed by server"); } wl_display_terminate(display); return 0; } static bool set_cloexec(int fd) { int flags = fcntl(fd, F_GETFD); if (flags == -1) { wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed"); return false; } flags = flags | FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) { wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed"); return false; } return true; } static bool spawn_primary_client(struct wl_display *display, char *argv[], pid_t *pid_out, struct wl_event_source **sigchld_source) { int fd[2]; if (pipe(fd) != 0) { wlr_log(WLR_ERROR, "Unable to create pipe"); return false; } pid_t pid = fork(); if (pid == 0) { sigset_t set; sigemptyset(&set); sigprocmask(SIG_SETMASK, &set, NULL); /* Close read, we only need write in the primary client process. */ close(fd[0]); execvp(argv[0], argv); _exit(1); } else if (pid == -1) { wlr_log_errno(WLR_ERROR, "Unable to fork"); return false; } /* Set this early so that if we fail, the client process will be cleaned up properly. */ *pid_out = pid; if (!set_cloexec(fd[0]) || !set_cloexec(fd[1])) { return false; } /* Close write, we only need read in Cage. */ close(fd[1]); struct wl_event_loop *event_loop = wl_display_get_event_loop(display); uint32_t mask = WL_EVENT_HANGUP | WL_EVENT_ERROR; *sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, display); wlr_log(WLR_DEBUG, "Child process created with pid %d", pid); return true; } static void cleanup_primary_client(pid_t pid) { int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { wlr_log(WLR_DEBUG, "Child exited normally with exit status %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { wlr_log(WLR_DEBUG, "Child was terminated by a signal (%d)", WTERMSIG(status)); } } static bool drop_permissions(void) { if (getuid() != geteuid() || getgid() != getegid()) { // Set the gid and uid in the correct order. if (setgid(getgid()) != 0 || setuid(getuid()) != 0) { wlr_log(WLR_ERROR, "Unable to drop root, refusing to start"); return false; } } if (setgid(0) != -1 || setuid(0) != -1) { wlr_log(WLR_ERROR, "Unable to drop root (we shouldn't be able to restore it after setuid), refusing to start"); return false; } return true; } static int handle_signal(int signal, void *data) { struct wl_display *display = data; switch (signal) { case SIGINT: /* Fallthrough */ case SIGTERM: wl_display_terminate(display); return 0; default: return 0; } } static void usage(FILE *file, const char *cage) { fprintf(file, "Usage: %s [OPTIONS] [--] APPLICATION\n" "\n" " -d\t Don't draw client side decorations, when possible\n" #ifdef DEBUG " -D\t Turn on damage tracking debugging\n" #endif " -h\t Display this help message\n" " -m extend Extend the display across all connected outputs (default)\n" " -m last Use only the last connected output\n" " -r\t Rotate the output 90 degrees clockwise, specify up to three times\n" " -s\t Allow VT switching\n" " -v\t Show the version number and exit\n" "\n" " Use -- when you want to pass arguments to APPLICATION\n", cage); } static bool parse_args(struct cg_server *server, int argc, char *argv[]) { int c; #ifdef DEBUG while ((c = getopt(argc, argv, "dDhm:rsv")) != -1) { #else while ((c = getopt(argc, argv, "dhm:rsv")) != -1) { #endif switch (c) { case 'd': server->xdg_decoration = true; break; #ifdef DEBUG case 'D': server->debug_damage_tracking = true; break; #endif case 'h': usage(stdout, argv[0]); return false; case 'm': if (strcmp(optarg, "last") == 0) { server->output_mode = CAGE_MULTI_OUTPUT_MODE_LAST; } else if (strcmp(optarg, "extend") == 0) { server->output_mode = CAGE_MULTI_OUTPUT_MODE_EXTEND; } break; case 'r': server->output_transform++; if (server->output_transform > WL_OUTPUT_TRANSFORM_270) { server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL; } break; case 's': server->allow_vt_switch = true; break; case 'v': fprintf(stdout, "Cage version " CAGE_VERSION "\n"); exit(0); default: usage(stderr, argv[0]); return false; } } if (optind >= argc) { usage(stderr, argv[0]); return false; } return true; } int main(int argc, char *argv[]) { struct cg_server server = {0}; struct wl_event_loop *event_loop = NULL; struct wl_event_source *sigint_source = NULL; struct wl_event_source *sigterm_source = NULL; struct wl_event_source *sigchld_source = NULL; struct wlr_renderer *renderer = NULL; struct wlr_compositor *compositor = NULL; struct wlr_data_device_manager *data_device_manager = NULL; struct wlr_server_decoration_manager *server_decoration_manager = NULL; struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL; struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager = NULL; struct wlr_screencopy_manager_v1 *screencopy_manager = NULL; struct wlr_xdg_output_manager_v1 *output_manager = NULL; struct wlr_gamma_control_manager_v1 *gamma_control_manager = NULL; struct wlr_xdg_shell *xdg_shell = NULL; #if CAGE_HAS_XWAYLAND struct wlr_xwayland *xwayland = NULL; struct wlr_xcursor_manager *xcursor_manager = NULL; #endif pid_t pid = 0; int ret = 0; if (!parse_args(&server, argc, argv)) { return 1; } #ifdef DEBUG wlr_log_init(WLR_DEBUG, NULL); #else wlr_log_init(WLR_ERROR, NULL); #endif /* Wayland requires XDG_RUNTIME_DIR to be set. */ if (!getenv("XDG_RUNTIME_DIR")) { wlr_log(WLR_ERROR, "XDG_RUNTIME_DIR is not set in the environment"); return 1; } server.wl_display = wl_display_create(); if (!server.wl_display) { wlr_log(WLR_ERROR, "Cannot allocate a Wayland display"); return 1; } event_loop = wl_display_get_event_loop(server.wl_display); sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server.wl_display); sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server.wl_display); server.backend = wlr_backend_autocreate(server.wl_display); if (!server.backend) { wlr_log(WLR_ERROR, "Unable to create the wlroots backend"); ret = 1; goto end; } if (!drop_permissions()) { ret = 1; goto end; } renderer = wlr_backend_get_renderer(server.backend); wlr_renderer_init_wl_display(renderer, server.wl_display); wl_list_init(&server.views); wl_list_init(&server.outputs); server.output_layout = wlr_output_layout_create(); if (!server.output_layout) { wlr_log(WLR_ERROR, "Unable to create output layout"); ret = 1; goto end; } compositor = wlr_compositor_create(server.wl_display, renderer); if (!compositor) { wlr_log(WLR_ERROR, "Unable to create the wlroots compositor"); ret = 1; goto end; } data_device_manager = wlr_data_device_manager_create(server.wl_display); if (!data_device_manager) { wlr_log(WLR_ERROR, "Unable to create the data device manager"); ret = 1; goto end; } /* Configure a listener to be notified when new outputs are * available on the backend. We use this only to detect the * first output and ignore subsequent outputs. */ server.new_output.notify = handle_new_output; wl_signal_add(&server.backend->events.new_output, &server.new_output); server.seat = seat_create(&server, server.backend); if (!server.seat) { wlr_log(WLR_ERROR, "Unable to create the seat"); ret = 1; goto end; } server.idle = wlr_idle_create(server.wl_display); if (!server.idle) { wlr_log(WLR_ERROR, "Unable to create the idle tracker"); ret = 1; goto end; } server.idle_inhibit_v1 = wlr_idle_inhibit_v1_create(server.wl_display); if (!server.idle_inhibit_v1) { wlr_log(WLR_ERROR, "Cannot create the idle inhibitor"); ret = 1; goto end; } server.new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1_new; wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, &server.new_idle_inhibitor_v1); wl_list_init(&server.inhibitors); xdg_shell = wlr_xdg_shell_create(server.wl_display); if (!xdg_shell) { wlr_log(WLR_ERROR, "Unable to create the XDG shell interface"); ret = 1; goto end; } server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new; wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface); xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server.wl_display); if (!xdg_decoration_manager) { wlr_log(WLR_ERROR, "Unable to create the XDG decoration manager"); ret = 1; goto end; } wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &server.xdg_toplevel_decoration); server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; server_decoration_manager = wlr_server_decoration_manager_create(server.wl_display); if (!server_decoration_manager) { wlr_log(WLR_ERROR, "Unable to create the server decoration manager"); ret = 1; goto end; } wlr_server_decoration_manager_set_default_mode( server_decoration_manager, server.xdg_decoration ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); export_dmabuf_manager = wlr_export_dmabuf_manager_v1_create(server.wl_display); if (!export_dmabuf_manager) { wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager"); ret = 1; goto end; } screencopy_manager = wlr_screencopy_manager_v1_create(server.wl_display); if (!screencopy_manager) { wlr_log(WLR_ERROR, "Unable to create the screencopy manager"); ret = 1; goto end; } output_manager = wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout); if (!output_manager) { wlr_log(WLR_ERROR, "Unable to create the output manager"); ret = 1; goto end; } gamma_control_manager = wlr_gamma_control_manager_v1_create(server.wl_display); if (!gamma_control_manager) { wlr_log(WLR_ERROR, "Unable to create the gamma control manager"); ret = 1; goto end; } #if CAGE_HAS_XWAYLAND xwayland = wlr_xwayland_create(server.wl_display, compositor, true); if (!xwayland) { wlr_log(WLR_ERROR, "Cannot create XWayland server"); ret = 1; goto end; } server.new_xwayland_surface.notify = handle_xwayland_surface_new; wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface); xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE); if (!xcursor_manager) { wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager"); ret = 1; goto end; } if (setenv("DISPLAY", xwayland->display_name, true) < 0) { wlr_log_errno(WLR_ERROR, "Unable to set DISPLAY for XWayland. Clients may not be able to connect"); } else { wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name); } if (!wlr_xcursor_manager_load(xcursor_manager, 1)) { wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme"); } struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1); if (xcursor) { struct wlr_xcursor_image *image = xcursor->images[0]; wlr_xwayland_set_cursor(xwayland, image->buffer, image->width * 4, image->width, image->height, image->hotspot_x, image->hotspot_y); } #endif const char *socket = wl_display_add_socket_auto(server.wl_display); if (!socket) { wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket"); ret = 1; goto end; } if (!wlr_backend_start(server.backend)) { wlr_log(WLR_ERROR, "Unable to start the wlroots backend"); ret = 1; goto end; } if (setenv("WAYLAND_DISPLAY", socket, true) < 0) { wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY. Clients may not be able to connect"); } else { wlr_log(WLR_DEBUG, "Cage " CAGE_VERSION " is running on Wayland display %s", socket); } #if CAGE_HAS_XWAYLAND wlr_xwayland_set_seat(xwayland, server.seat->seat); #endif if (!spawn_primary_client(server.wl_display, argv + optind, &pid, &sigchld_source)) { ret = 1; goto end; } /* Place the cursor in the center of the output layout. */ struct wlr_box *layout_box = wlr_output_layout_get_box(server.output_layout, NULL); wlr_cursor_warp(server.seat->cursor, NULL, layout_box->width / 2, layout_box->height / 2); wl_display_run(server.wl_display); #if CAGE_HAS_XWAYLAND wlr_xwayland_destroy(xwayland); wlr_xcursor_manager_destroy(xcursor_manager); #endif wl_display_destroy_clients(server.wl_display); end: cleanup_primary_client(pid); wl_event_source_remove(sigint_source); wl_event_source_remove(sigterm_source); if (sigchld_source) { wl_event_source_remove(sigchld_source); } seat_destroy(server.seat); /* This function is not null-safe, but we only ever get here with a proper wl_display. */ wl_display_destroy(server.wl_display); wlr_output_layout_destroy(server.output_layout); return ret; } cage-0.1.4/config.h.in000066400000000000000000000001531406555467400144550ustar00rootroot00000000000000#ifndef CG_CONFIG_H #define CG_CONFIG_H #mesondefine CAGE_HAS_XWAYLAND #mesondefine CAGE_VERSION #endif cage-0.1.4/contrib/000077500000000000000000000000001406555467400140735ustar00rootroot00000000000000cage-0.1.4/contrib/increment-version000077500000000000000000000010451406555467400174700ustar00rootroot00000000000000#!/usr/bin/env bash if [ "$#" -ne 1 ]; then echo "usage: $0 " >&2 exit 1 fi new_version="$1" if [ "$new_version" != "${new_version#v}" ]; then echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2 exit 1 fi set -x sed -i meson.build -e "s/^ version: '.*'/ version: '$new_version'/" echo -n "Minimum wlroots version? " read -r wlr_version_min sed -i meson.build -e "s/'wlroots', version: '.*'/'wlroots', version: '>= $wlr_version_min'/" git add meson.build git commit -m "Update version to $new_version"cage-0.1.4/contrib/release000077500000000000000000000005371406555467400154460ustar00rootroot00000000000000#!/usr/bin/env bash if [ "$#" -ne 1 ]; then echo "usage: $0 " >&2 exit 1 fi new_version="$1" if [ "$new_version" != "${new_version#v}" ]; then echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2 exit 1 fi set -x ./increment_version "$new_version" ./tag-release "$new_version" ./sign-release git push --tagscage-0.1.4/contrib/sign-release000077500000000000000000000004041406555467400163750ustar00rootroot00000000000000#!/usr/bin/env bash set -x project="$(basename "$(pwd)")" last=$(git describe --tags --abbrev=0) prefix="$project-${last#v}" archive="$prefix.tar.gz" git archive --prefix="$prefix/" -o "$archive" "$last" gpg --output "$archive".sig --detach-sig "$archive" cage-0.1.4/contrib/tag-release000077500000000000000000000007241406555467400162150ustar00rootroot00000000000000#!/usr/bin/env bash set -x if [ "$#" -ne 1 ]; then echo "usage: $0 " >&2 exit 1 fi last=$(git describe --tags --abbrev=0) echo "Last release was $last" next="v$1" shortlog="$(git shortlog --no-merges "$last"..)" printf "Shortlog: \n\n%s\n\nRelease $next? [y/N] " "$shortlog" read -r answer if [ "$answer" != "y" ]; then exit 0 fi project="$(basename "$(pwd)")" (echo "$project $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F - cage-0.1.4/idle_inhibit_v1.c000066400000000000000000000036101406555467400156300ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include #include #include #include #include "idle_inhibit_v1.h" #include "server.h" struct cg_idle_inhibitor_v1 { struct cg_server *server; struct wl_list link; // server::inhibitors struct wl_listener destroy; }; static void idle_inhibit_v1_check_active(struct cg_server *server) { /* Due to Cage's unique window management, we don't need to check for visibility. In the worst cage, the inhibitor is spawned by a dialog that _may_ be obscured by another dialog, but this is really an edge case that, until reported, does not warrant the additional complexity. Hence, we simply check for any inhibitors and inhibit accordingly. */ bool inhibited = !wl_list_empty(&server->inhibitors); wlr_idle_set_enabled(server->idle, NULL, !inhibited); } static void handle_destroy(struct wl_listener *listener, void *data) { struct cg_idle_inhibitor_v1 *inhibitor = wl_container_of(listener, inhibitor, destroy); struct cg_server *server = inhibitor->server; wl_list_remove(&inhibitor->link); wl_list_remove(&inhibitor->destroy.link); free(inhibitor); idle_inhibit_v1_check_active(server); } void handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_idle_inhibitor_v1); struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; struct cg_idle_inhibitor_v1 *inhibitor = calloc(1, sizeof(struct cg_idle_inhibitor_v1)); if (!inhibitor) { return; } inhibitor->server = server; wl_list_insert(&server->inhibitors, &inhibitor->link); inhibitor->destroy.notify = handle_destroy; wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy); idle_inhibit_v1_check_active(server); } cage-0.1.4/idle_inhibit_v1.h000066400000000000000000000002541406555467400156360ustar00rootroot00000000000000#ifndef CG_IDLE_INHIBIT_H #define CG_IDLE_INHIBIT_H #include void handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data); #endif cage-0.1.4/meson.build000066400000000000000000000077221406555467400146050ustar00rootroot00000000000000project('cage', 'c', version: '0.1.4', license: 'MIT', default_options: [ 'c_std=c11', 'warning_level=3', ], ) add_project_arguments( [ '-DWLR_USE_UNSTABLE', '-Wall', '-Wundef', '-Wno-unused-parameter', ], language: 'c', ) if get_option('buildtype').startswith('debug') add_project_arguments('-DDEBUG', language : 'c') endif cc = meson.get_compiler('c') is_freebsd = host_machine.system().startswith('freebsd') if is_freebsd add_project_arguments( [ '-Wno-format-extra-args', '-Wno-gnu-zero-variadic-macro-arguments', ], language: 'c' ) endif wlroots = dependency('wlroots', version: '>= 0.14.0') wayland_protos = dependency('wayland-protocols', version: '>=1.14') wayland_server = dependency('wayland-server') pixman = dependency('pixman-1') xkbcommon = dependency('xkbcommon') math = cc.find_library('m') wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') wayland_scanner_server = generator( wayland_scanner, output: '@BASENAME@-protocol.h', arguments: ['server-header', '@INPUT@', '@OUTPUT@'], ) server_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], ] server_protos_headers = [] foreach p : server_protocols xml = join_paths(p) server_protos_headers += wayland_scanner_server.process(xml) endforeach server_protos = declare_dependency( sources: server_protos_headers, ) if get_option('xwayland') wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' if not wlroots_has_xwayland error('Cannot build Cage with XWayland support: wlroots has been built without it') else have_xwayland = true endif else have_xwayland = false endif version = '@0@'.format(meson.project_version()) git = find_program('git', native: true, required: false) if git.found() git_commit = run_command([git, 'rev-parse', '--short', 'HEAD']) git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) if git_commit.returncode() == 0 and git_branch.returncode() == 0 version = '@0@-@1@ (branch \'@2@\')'.format( meson.project_version(), git_commit.stdout().strip(), git_branch.stdout().strip(), ) endif endif conf_data = configuration_data() conf_data.set10('CAGE_HAS_XWAYLAND', have_xwayland) conf_data.set_quoted('CAGE_VERSION', version) scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) if scdoc.found() scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) sh = find_program('sh', native: true) mandir = get_option('mandir') man_files = [ 'cage.1.scd' ] foreach filename : man_files topic = filename.split('.')[-3].split('/')[-1] section = filename.split('.')[-2] output = '@0@.@1@'.format(topic, section) custom_target( output, input: filename, output: output, command: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output) ], install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif cage_sources = [ 'cage.c', 'idle_inhibit_v1.c', 'output.c', 'render.c', 'seat.c', 'util.c', 'view.c', 'xdg_shell.c', ] cage_headers = [ configure_file(input: 'config.h.in', output: 'config.h', configuration: conf_data), 'idle_inhibit_v1.h', 'output.h', 'render.h', 'seat.h', 'server.h', 'util.h', 'view.h', 'xdg_shell.h', ] if conf_data.get('CAGE_HAS_XWAYLAND', 0) == 1 cage_sources += 'xwayland.c' cage_headers += 'xwayland.h' endif executable( meson.project_name(), cage_sources + cage_headers, dependencies: [ server_protos, wayland_server, wlroots, xkbcommon, pixman, math, ], install: true, ) summary = [ '', 'Cage @0@'.format(version), '', ' xwayland: @0@'.format(have_xwayland), '' ] message('\n'.join(summary)) cage-0.1.4/meson_options.txt000066400000000000000000000003131406555467400160650ustar00rootroot00000000000000option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications') cage-0.1.4/output.c000066400000000000000000000340141406555467400141410ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * Copyright (C) 2019 The Sway authors * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200112L #include "config.h" #include #include #include #include #include #include #if WLR_HAS_X11_BACKEND #include #endif #include #include #include #include #include #include #include #include #include #include #include "output.h" #include "render.h" #include "seat.h" #include "server.h" #include "util.h" #include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif static void output_for_each_surface(struct cg_output *output, cg_surface_iterator_func_t iterator, void *user_data); struct surface_iterator_data { cg_surface_iterator_func_t user_iterator; void *user_data; struct cg_output *output; /* Output-local coordinates. */ double ox, oy; }; static bool intersects_with_output(struct cg_output *output, struct wlr_output_layout *output_layout, struct wlr_box *surface_box) { /* Since the surface_box's x- and y-coordinates are already output local, * the x- and y-coordinates of this box need to be 0 for this function to * work correctly. */ struct wlr_box output_box = {0}; wlr_output_effective_resolution(output->wlr_output, &output_box.width, &output_box.height); struct wlr_box intersection; return wlr_box_intersection(&intersection, &output_box, surface_box); } static void output_for_each_surface_iterator(struct wlr_surface *surface, int sx, int sy, void *user_data) { struct surface_iterator_data *data = user_data; struct cg_output *output = data->output; if (!wlr_surface_has_buffer(surface)) { return; } struct wlr_box surface_box = { .x = data->ox + sx + surface->sx, .y = data->oy + sy + surface->sy, .width = surface->current.width, .height = surface->current.height, }; if (!intersects_with_output(output, output->server->output_layout, &surface_box)) { return; } data->user_iterator(data->output, surface, &surface_box, data->user_data); } void output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy, cg_surface_iterator_func_t iterator, void *user_data) { struct surface_iterator_data data = { .user_iterator = iterator, .user_data = user_data, .output = output, .ox = ox, .oy = oy, }; wlr_surface_for_each_surface(surface, output_for_each_surface_iterator, &data); } static void output_view_for_each_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator, void *user_data) { struct surface_iterator_data data = { .user_iterator = iterator, .user_data = user_data, .output = output, .ox = view->lx, .oy = view->ly, }; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &data.ox, &data.oy); view_for_each_surface(view, output_for_each_surface_iterator, &data); } void output_view_for_each_popup_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator, void *user_data) { struct surface_iterator_data data = { .user_iterator = iterator, .user_data = user_data, .output = output, .ox = view->lx, .oy = view->ly, }; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &data.ox, &data.oy); view_for_each_popup_surface(view, output_for_each_surface_iterator, &data); } void output_drag_icons_for_each_surface(struct cg_output *output, struct wl_list *drag_icons, cg_surface_iterator_func_t iterator, void *user_data) { struct cg_drag_icon *drag_icon; wl_list_for_each (drag_icon, drag_icons, link) { if (drag_icon->wlr_drag_icon->mapped) { double ox = drag_icon->lx; double oy = drag_icon->ly; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); output_surface_for_each_surface(output, drag_icon->wlr_drag_icon->surface, ox, oy, iterator, user_data); } } } static void output_for_each_surface(struct cg_output *output, cg_surface_iterator_func_t iterator, void *user_data) { struct cg_view *view; wl_list_for_each_reverse (view, &output->server->views, link) { output_view_for_each_surface(output, view, iterator, user_data); } output_drag_icons_for_each_surface(output, &output->server->seat->drag_icons, iterator, user_data); } struct send_frame_done_data { struct timespec when; }; static void send_frame_done_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data) { struct send_frame_done_data *data = user_data; wlr_surface_send_frame_done(surface, &data->when); } static void send_frame_done(struct cg_output *output, struct send_frame_done_data *data) { output_for_each_surface(output, send_frame_done_iterator, data); } static void count_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *_box, void *data) { size_t *n = data; n++; } static bool scan_out_primary_view(struct cg_output *output) { struct cg_server *server = output->server; struct wlr_output *wlr_output = output->wlr_output; struct cg_drag_icon *drag_icon; wl_list_for_each (drag_icon, &server->seat->drag_icons, link) { if (drag_icon->wlr_drag_icon->mapped) { return false; } } struct cg_view *view = seat_get_focus(server->seat); if (!view || !view->wlr_surface) { return false; } size_t n_surfaces = 0; output_view_for_each_surface(output, view, count_surface_iterator, &n_surfaces); if (n_surfaces > 1) { return false; } #if CAGE_HAS_XWAYLAND if (view->type == CAGE_XWAYLAND_VIEW) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); if (!wl_list_empty(&xwayland_view->xwayland_surface->children)) { return false; } } #endif struct wlr_surface *surface = view->wlr_surface; if (!surface->buffer) { return false; } if ((float) surface->current.scale != wlr_output->scale || surface->current.transform != wlr_output->transform) { return false; } wlr_output_attach_buffer(wlr_output, &surface->buffer->base); return wlr_output_commit(wlr_output); } static void output_enable(struct cg_output *output) { struct wlr_output *wlr_output = output->wlr_output; /* Outputs get enabled by the backend before firing the new_output event, * so we can't do a check for already enabled outputs here unless we * duplicate the enabled property in cg_output. */ wlr_log(WLR_DEBUG, "Enabling output %s", wlr_output->name); wlr_output_layout_add_auto(output->server->output_layout, wlr_output); wlr_output_enable(wlr_output, true); wlr_output_commit(wlr_output); } static void output_disable(struct cg_output *output) { struct wlr_output *wlr_output = output->wlr_output; if (!wlr_output->enabled) { wlr_log(WLR_DEBUG, "Not disabling already disabled output %s", wlr_output->name); return; } wlr_log(WLR_DEBUG, "Disabling output %s", wlr_output->name); wlr_output_enable(wlr_output, false); wlr_output_layout_remove(output->server->output_layout, wlr_output); wlr_output_commit(wlr_output); } static void damage_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data) { struct wlr_output *wlr_output = output->wlr_output; bool whole = *(bool *) user_data; scale_box(box, output->wlr_output->scale); if (whole) { wlr_output_damage_add_box(output->damage, box); } else if (pixman_region32_not_empty(&surface->buffer_damage)) { pixman_region32_t damage; pixman_region32_init(&damage); wlr_surface_get_effective_damage(surface, &damage); wlr_region_scale(&damage, &damage, wlr_output->scale); if (ceil(wlr_output->scale) > surface->current.scale) { /* When scaling up a surface it'll become blurry, so we need to expand the damage region. */ wlr_region_expand(&damage, &damage, ceil(wlr_output->scale) - surface->current.scale); } pixman_region32_translate(&damage, box->x, box->y); wlr_output_damage_add(output->damage, &damage); pixman_region32_fini(&damage); } } void output_damage_surface(struct cg_output *output, struct wlr_surface *surface, double lx, double ly, bool whole) { if (!output->wlr_output->enabled) { wlr_log(WLR_DEBUG, "Not adding damage for disabled output %s", output->wlr_output->name); return; } double ox = lx, oy = ly; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); output_surface_for_each_surface(output, surface, ox, oy, damage_surface_iterator, &whole); } static void handle_output_damage_frame(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, damage_frame); struct send_frame_done_data frame_data = {0}; if (!output->wlr_output->enabled) { return; } /* Check if we can scan-out the primary view. */ static bool last_scanned_out = false; bool scanned_out = scan_out_primary_view(output); if (scanned_out && !last_scanned_out) { wlr_log(WLR_DEBUG, "Scanning out primary view"); } if (last_scanned_out && !scanned_out) { wlr_log(WLR_DEBUG, "Stopping primary view scan out"); } last_scanned_out = scanned_out; if (scanned_out) { goto frame_done; } bool needs_frame; pixman_region32_t damage; pixman_region32_init(&damage); if (!wlr_output_damage_attach_render(output->damage, &needs_frame, &damage)) { wlr_log(WLR_ERROR, "Cannot make damage output current"); goto damage_finish; } if (!needs_frame) { wlr_output_rollback(output->wlr_output); goto damage_finish; } output_render(output, &damage); damage_finish: pixman_region32_fini(&damage); frame_done: clock_gettime(CLOCK_MONOTONIC, &frame_data.when); send_frame_done(output, &frame_data); } static void handle_output_commit(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, commit); struct wlr_output_event_commit *event = data; if (!output->wlr_output->enabled) { return; } if (event->committed & WLR_OUTPUT_STATE_TRANSFORM) { struct cg_view *view; wl_list_for_each (view, &output->server->views, link) { view_position(view); } } } static void handle_output_mode(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, mode); if (!output->wlr_output->enabled) { return; } struct cg_view *view; wl_list_for_each (view, &output->server->views, link) { view_position(view); } } static void output_destroy(struct cg_output *output) { struct cg_server *server = output->server; wl_list_remove(&output->destroy.link); wl_list_remove(&output->commit.link); wl_list_remove(&output->mode.link); wl_list_remove(&output->damage_frame.link); wl_list_remove(&output->damage_destroy.link); wl_list_remove(&output->link); wlr_output_layout_remove(server->output_layout, output->wlr_output); free(output); if (wl_list_empty(&server->outputs)) { wl_display_terminate(server->wl_display); } else if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST) { struct cg_output *prev = wl_container_of(server->outputs.next, prev, link); if (prev) { output_enable(prev); struct cg_view *view; wl_list_for_each (view, &server->views, link) { view_position(view); } } } } static void handle_output_damage_destroy(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, damage_destroy); output_destroy(output); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, destroy); wlr_output_damage_destroy(output->damage); output_destroy(output); } void handle_new_output(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; struct cg_output *output = calloc(1, sizeof(struct cg_output)); if (!output) { wlr_log(WLR_ERROR, "Failed to allocate output"); return; } output->wlr_output = wlr_output; output->server = server; output->damage = wlr_output_damage_create(wlr_output); wl_list_insert(&server->outputs, &output->link); output->commit.notify = handle_output_commit; wl_signal_add(&wlr_output->events.commit, &output->commit); output->mode.notify = handle_output_mode; wl_signal_add(&wlr_output->events.mode, &output->mode); output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->damage_frame.notify = handle_output_damage_frame; wl_signal_add(&output->damage->events.frame, &output->damage_frame); output->damage_destroy.notify = handle_output_damage_destroy; wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); if (preferred_mode) { wlr_output_set_mode(wlr_output, preferred_mode); } wlr_output_set_transform(wlr_output, output->server->output_transform); if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST) { struct cg_output *next = wl_container_of(output->link.next, next, link); if (next) { output_disable(next); } } if (!wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) { wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f", wlr_output->name, wlr_output->scale); } output_enable(output); struct cg_view *view; wl_list_for_each (view, &output->server->views, link) { view_position(view); } } void output_set_window_title(struct cg_output *output, const char *title) { struct wlr_output *wlr_output = output->wlr_output; if (!wlr_output->enabled) { wlr_log(WLR_DEBUG, "Not setting window title for disabled output %s", wlr_output->name); return; } if (wlr_output_is_wl(wlr_output)) { wlr_wl_output_set_title(wlr_output, title); #if WLR_HAS_X11_BACKEND } else if (wlr_output_is_x11(wlr_output)) { wlr_x11_output_set_title(wlr_output, title); #endif } } cage-0.1.4/output.h000066400000000000000000000025651406555467400141540ustar00rootroot00000000000000#ifndef CG_OUTPUT_H #define CG_OUTPUT_H #include #include #include #include "server.h" #include "view.h" struct cg_output { struct cg_server *server; struct wlr_output *wlr_output; struct wlr_output_damage *damage; struct wl_listener commit; struct wl_listener mode; struct wl_listener destroy; struct wl_listener damage_frame; struct wl_listener damage_destroy; struct wl_list link; // cg_server::outputs }; typedef void (*cg_surface_iterator_func_t)(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data); void handle_new_output(struct wl_listener *listener, void *data); void output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy, cg_surface_iterator_func_t iterator, void *user_data); void output_view_for_each_popup_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator, void *user_data); void output_drag_icons_for_each_surface(struct cg_output *output, struct wl_list *drag_icons, cg_surface_iterator_func_t iterator, void *user_data); void output_damage_surface(struct cg_output *output, struct wlr_surface *surface, double lx, double ly, bool whole); void output_set_window_title(struct cg_output *output, const char *title); #endif cage-0.1.4/render.c000066400000000000000000000137131406555467400140630ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * Copyright (C) 2019 The Sway authors * * See the LICENSE file accompanying this file. */ #include #include #include #include #include #include #include #include #include #include #include "output.h" #include "seat.h" #include "server.h" #include "util.h" #include "view.h" static void scissor_output(struct wlr_output *output, pixman_box32_t *rect) { struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); struct wlr_box box = { .x = rect->x1, .y = rect->y1, .width = rect->x2 - rect->x1, .height = rect->y2 - rect->y1, }; int output_width, output_height; wlr_output_transformed_resolution(output, &output_width, &output_height); enum wl_output_transform transform = wlr_output_transform_invert(output->transform); wlr_box_transform(&box, &box, transform, output_width, output_height); wlr_renderer_scissor(renderer, &box); } struct render_data { pixman_region32_t *damage; }; static void render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, struct wlr_texture *texture, const struct wlr_box *box, const float matrix[static 9]) { struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); pixman_region32_t damage; pixman_region32_init(&damage); pixman_region32_union_rect(&damage, &damage, box->x, box->y, box->width, box->height); pixman_region32_intersect(&damage, &damage, output_damage); if (!pixman_region32_not_empty(&damage)) { goto damage_finish; } int nrects; pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); for (int i = 0; i < nrects; i++) { scissor_output(wlr_output, &rects[i]); wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f); } damage_finish: pixman_region32_fini(&damage); } static void render_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data) { struct render_data *data = user_data; struct wlr_output *wlr_output = output->wlr_output; pixman_region32_t *output_damage = data->damage; struct wlr_texture *texture = wlr_surface_get_texture(surface); if (!texture) { wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); return; } scale_box(box, wlr_output->scale); float matrix[9]; enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); wlr_matrix_project_box(matrix, box, transform, 0.0f, wlr_output->transform_matrix); render_texture(wlr_output, output_damage, texture, box, matrix); } static void render_drag_icons(struct cg_output *output, pixman_region32_t *damage, struct wl_list *drag_icons) { struct render_data data = { .damage = damage, }; output_drag_icons_for_each_surface(output, drag_icons, render_surface_iterator, &data); } /** * Render all toplevels without descending into popups. */ static void render_view_toplevels(struct cg_view *view, struct cg_output *output, pixman_region32_t *damage) { struct render_data data = { .damage = damage, }; double ox = view->lx; double oy = view->ly; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); output_surface_for_each_surface(output, view->wlr_surface, ox, oy, render_surface_iterator, &data); } static void render_view_popups(struct cg_view *view, struct cg_output *output, pixman_region32_t *damage) { struct render_data data = { .damage = damage, }; output_view_for_each_popup_surface(output, view, render_surface_iterator, &data); } void output_render(struct cg_output *output, pixman_region32_t *damage) { struct cg_server *server = output->server; struct wlr_output *wlr_output = output->wlr_output; struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); if (!renderer) { wlr_log(WLR_DEBUG, "Expected the output backend to have a renderer"); return; } wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); if (!pixman_region32_not_empty(damage)) { wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer swap"); goto renderer_end; } #ifdef DEBUG if (server->debug_damage_tracking) { wlr_renderer_clear(renderer, (float[]){1.0f, 0.0f, 0.0f, 1.0f}); } #endif float color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; int nrects; pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); for (int i = 0; i < nrects; i++) { scissor_output(wlr_output, &rects[i]); wlr_renderer_clear(renderer, color); } // TODO: render only top view, possibly use focused view for this, see #35. struct cg_view *view; wl_list_for_each_reverse (view, &server->views, link) { render_view_toplevels(view, output, damage); } struct cg_view *focused_view = seat_get_focus(server->seat); if (focused_view) { render_view_popups(focused_view, output, damage); } render_drag_icons(output, damage, &server->seat->drag_icons); renderer_end: /* Draw software cursor in case hardware cursors aren't available. This is a no-op when they are. */ wlr_output_render_software_cursors(wlr_output, damage); wlr_renderer_scissor(renderer, NULL); wlr_renderer_end(renderer); int output_width, output_height; wlr_output_transformed_resolution(wlr_output, &output_width, &output_height); pixman_region32_t frame_damage; pixman_region32_init(&frame_damage); enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); wlr_region_transform(&frame_damage, &output->damage->current, transform, output_width, output_height); #ifdef DEBUG if (server->debug_damage_tracking) { pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, output_width, output_height); } #endif wlr_output_set_damage(wlr_output, &frame_damage); pixman_region32_fini(&frame_damage); if (!wlr_output_commit(wlr_output)) { wlr_log(WLR_ERROR, "Could not commit output"); } } cage-0.1.4/render.h000066400000000000000000000002171406555467400140630ustar00rootroot00000000000000#ifndef CG_RENDER_H #define CG_RENDER_H #include "output.h" void output_render(struct cg_output *output, pixman_region32_t *damage); #endif cage-0.1.4/seat.c000066400000000000000000000655301406555467400135440ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CAGE_HAS_XWAYLAND #include #endif #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif static void drag_icon_update_position(struct cg_drag_icon *drag_icon); /* XDG toplevels may have nested surfaces, such as popup windows for context * menus or tooltips. This function tests if any of those are underneath the * coordinates lx and ly (in output Layout Coordinates). If so, it sets the * surface pointer to that wlr_surface and the sx and sy coordinates to the * coordinates relative to that surface's top-left corner. */ static bool view_at(struct cg_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { double view_sx = lx - view->lx; double view_sy = ly - view->ly; double _sx, _sy; struct wlr_surface *_surface = view_wlr_surface_at(view, view_sx, view_sy, &_sx, &_sy); if (_surface != NULL) { *sx = _sx; *sy = _sy; *surface = _surface; return true; } return false; } /* This iterates over all of our surfaces and attempts to find one * under the cursor. This relies on server->views being ordered from * top-to-bottom. If desktop_view_at returns a view, there is also a * surface. There cannot be a surface without a view, either. It's * both or nothing. */ static struct cg_view * desktop_view_at(struct cg_server *server, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { struct cg_view *view; wl_list_for_each (view, &server->views, link) { if (view_at(view, lx, ly, surface, sx, sy)) { return view; } } return NULL; } static void press_cursor_button(struct cg_seat *seat, struct wlr_input_device *device, uint32_t time, uint32_t button, uint32_t state, double lx, double ly) { struct cg_server *server = seat->server; if (state == WLR_BUTTON_PRESSED) { double sx, sy; struct wlr_surface *surface; struct cg_view *view = desktop_view_at(server, lx, ly, &surface, &sx, &sy); struct cg_view *current = seat_get_focus(seat); if (view == current) { return; } /* Focus that client if the button was pressed and it has no open dialogs. */ if (view && !view_is_transient_for(current, view)) { seat_set_focus(seat, view); } } } static void update_capabilities(struct cg_seat *seat) { uint32_t caps = 0; if (!wl_list_empty(&seat->keyboard_groups)) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } if (!wl_list_empty(&seat->pointers)) { caps |= WL_SEAT_CAPABILITY_POINTER; } if (!wl_list_empty(&seat->touch)) { caps |= WL_SEAT_CAPABILITY_TOUCH; } wlr_seat_set_capabilities(seat->seat, caps); /* Hide cursor if the seat doesn't have pointer capability. */ if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { wlr_cursor_set_image(seat->cursor, NULL, 0, 0, 0, 0, 0, 0); } else { wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, DEFAULT_XCURSOR, seat->cursor); } } static void map_input_device_to_output(struct cg_seat *seat, struct wlr_input_device *device) { if (!device->output_name) { wlr_log(WLR_INFO, "Input device %s cannot be mapped to an output device\n", device->name); return; } struct cg_output *output; wl_list_for_each (output, &seat->server->outputs, link) { if (strcmp(device->output_name, output->wlr_output->name) == 0) { wlr_log(WLR_INFO, "Mapping input device %s to output device %s\n", device->name, output->wlr_output->name); wlr_cursor_map_input_to_output(seat->cursor, device, output->wlr_output); return; } } wlr_log(WLR_INFO, "Couldn't map input device %s to an output\n", device->name); } static void handle_touch_destroy(struct wl_listener *listener, void *data) { struct cg_touch *touch = wl_container_of(listener, touch, destroy); struct cg_seat *seat = touch->seat; wl_list_remove(&touch->link); wlr_cursor_detach_input_device(seat->cursor, touch->device); wl_list_remove(&touch->destroy.link); free(touch); update_capabilities(seat); } static void handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device) { struct cg_touch *touch = calloc(1, sizeof(struct cg_touch)); if (!touch) { wlr_log(WLR_ERROR, "Cannot allocate touch"); return; } touch->seat = seat; touch->device = device; wlr_cursor_attach_input_device(seat->cursor, device); wl_list_insert(&seat->touch, &touch->link); touch->destroy.notify = handle_touch_destroy; wl_signal_add(&touch->device->events.destroy, &touch->destroy); map_input_device_to_output(seat, device); } static void handle_pointer_destroy(struct wl_listener *listener, void *data) { struct cg_pointer *pointer = wl_container_of(listener, pointer, destroy); struct cg_seat *seat = pointer->seat; wl_list_remove(&pointer->link); wlr_cursor_detach_input_device(seat->cursor, pointer->device); wl_list_remove(&pointer->destroy.link); free(pointer); update_capabilities(seat); } static void handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) { struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); if (!pointer) { wlr_log(WLR_ERROR, "Cannot allocate pointer"); return; } pointer->seat = seat; pointer->device = device; wlr_cursor_attach_input_device(seat->cursor, device); wl_list_insert(&seat->pointers, &pointer->link); pointer->destroy.notify = handle_pointer_destroy; wl_signal_add(&device->events.destroy, &pointer->destroy); map_input_device_to_output(seat, device); } static void handle_modifier_event(struct wlr_input_device *device, struct cg_seat *seat) { wlr_seat_set_keyboard(seat->seat, device); wlr_seat_keyboard_notify_modifiers(seat->seat, &device->keyboard->modifiers); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static bool handle_keybinding(struct cg_server *server, xkb_keysym_t sym) { #ifdef DEBUG if (sym == XKB_KEY_Escape) { wl_display_terminate(server->wl_display); return true; } #endif if (server->allow_vt_switch && sym >= XKB_KEY_XF86Switch_VT_1 && sym <= XKB_KEY_XF86Switch_VT_12) { if (wlr_backend_is_multi(server->backend)) { struct wlr_session *session = wlr_backend_get_session(server->backend); if (session) { unsigned vt = sym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(session, vt); } } } else { return false; } wlr_idle_notify_activity(server->idle, server->seat->seat); return true; } static void handle_key_event(struct wlr_input_device *device, struct cg_seat *seat, void *data) { struct wlr_event_keyboard_key *event = data; /* Translate from libinput keycode to an xkbcommon keycode. */ xkb_keycode_t keycode = event->keycode + 8; const xkb_keysym_t *syms; int nsyms = xkb_state_key_get_syms(device->keyboard->xkb_state, keycode, &syms); bool handled = false; uint32_t modifiers = wlr_keyboard_get_modifiers(device->keyboard); if ((modifiers & WLR_MODIFIER_ALT) && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { /* If Alt is held down and this button was pressed, we * attempt to process it as a compositor * keybinding. */ for (int i = 0; i < nsyms; i++) { handled = handle_keybinding(seat->server, syms[i]); } } if (!handled) { /* Otherwise, we pass it along to the client. */ wlr_seat_set_keyboard(seat->seat, device); wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state); } wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_keyboard_group_key(struct wl_listener *listener, void *data) { struct cg_keyboard_group *cg_group = wl_container_of(listener, cg_group, key); handle_key_event(cg_group->wlr_group->input_device, cg_group->seat, data); } static void handle_keyboard_group_modifiers(struct wl_listener *listener, void *data) { struct cg_keyboard_group *group = wl_container_of(listener, group, modifiers); handle_modifier_event(group->wlr_group->input_device, group->seat); } static void cg_keyboard_group_add(struct wlr_input_device *device, struct cg_seat *seat) { struct wlr_keyboard *wlr_keyboard = device->keyboard; struct cg_keyboard_group *group; wl_list_for_each (group, &seat->keyboard_groups, link) { struct wlr_keyboard_group *wlr_group = group->wlr_group; if (wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard)) { wlr_log(WLR_DEBUG, "Added new keyboard to existing group"); return; } } /* This is reached if and only if the keyboard could not be inserted into * any group */ struct cg_keyboard_group *cg_group = calloc(1, sizeof(struct cg_keyboard_group)); if (cg_group == NULL) { wlr_log(WLR_ERROR, "Failed to allocate keyboard group."); return; } cg_group->seat = seat; cg_group->wlr_group = wlr_keyboard_group_create(); if (cg_group->wlr_group == NULL) { wlr_log(WLR_ERROR, "Failed to create wlr keyboard group."); goto cleanup; } cg_group->wlr_group->data = cg_group; wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard, device->keyboard->keymap); wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, wlr_keyboard->repeat_info.rate, wlr_keyboard->repeat_info.delay); wlr_log(WLR_DEBUG, "Created keyboard group"); wlr_keyboard_group_add_keyboard(cg_group->wlr_group, wlr_keyboard); wl_list_insert(&seat->keyboard_groups, &cg_group->link); wl_signal_add(&cg_group->wlr_group->keyboard.events.key, &cg_group->key); cg_group->key.notify = handle_keyboard_group_key; wl_signal_add(&cg_group->wlr_group->keyboard.events.modifiers, &cg_group->modifiers); cg_group->modifiers.notify = handle_keyboard_group_modifiers; return; cleanup: if (cg_group && cg_group->wlr_group) { wlr_keyboard_group_destroy(cg_group->wlr_group); } free(cg_group); } static void handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) { struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!context) { wlr_log(WLR_ERROR, "Unable to create XBK context"); return; } struct xkb_rule_names rules = {0}; rules.rules = getenv("XKB_DEFAULT_RULES"); rules.model = getenv("XKB_DEFAULT_MODEL"); rules.layout = getenv("XKB_DEFAULT_LAYOUT"); rules.variant = getenv("XKB_DEFAULT_VARIANT"); rules.options = getenv("XKB_DEFAULT_OPTIONS"); struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!keymap) { wlr_log(WLR_ERROR, "Unable to configure keyboard: keymap does not exist"); xkb_context_unref(context); return; } wlr_keyboard_set_keymap(device->keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); cg_keyboard_group_add(device, seat); wlr_seat_set_keyboard(seat->seat, device); } static void handle_new_input(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, new_input); struct wlr_input_device *device = data; switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: handle_new_keyboard(seat, device); break; case WLR_INPUT_DEVICE_POINTER: handle_new_pointer(seat, device); break; case WLR_INPUT_DEVICE_TOUCH: handle_new_touch(seat, device); break; case WLR_INPUT_DEVICE_SWITCH: wlr_log(WLR_DEBUG, "Switch input is not implemented"); return; case WLR_INPUT_DEVICE_TABLET_TOOL: case WLR_INPUT_DEVICE_TABLET_PAD: wlr_log(WLR_DEBUG, "Tablet input is not implemented"); return; } update_capabilities(seat); } static void handle_request_set_primary_selection(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_set_primary_selection); struct wlr_seat_request_set_primary_selection_event *event = data; wlr_seat_set_primary_selection(seat->seat, event->source, event->serial); } static void handle_request_set_selection(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_set_selection); struct wlr_seat_request_set_selection_event *event = data; wlr_seat_set_selection(seat->seat, event->source, event->serial); } static void handle_request_set_cursor(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_set_cursor); struct wlr_seat_pointer_request_set_cursor_event *event = data; struct wlr_surface *focused_surface = event->seat_client->seat->pointer_state.focused_surface; bool has_focused = focused_surface != NULL && focused_surface->resource != NULL; struct wl_client *focused_client = NULL; if (has_focused) { focused_client = wl_resource_get_client(focused_surface->resource); } /* This can be sent by any client, so we check to make sure * this one actually has pointer focus first. */ if (focused_client == event->seat_client->client) { wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, event->hotspot_y); } } static void handle_touch_down(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_down); struct wlr_event_touch_down *event = data; double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, event->y, &lx, &ly); double sx, sy; struct wlr_surface *surface; struct cg_view *view = desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); uint32_t serial = 0; if (view) { serial = wlr_seat_touch_notify_down(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); } if (serial && wlr_seat_touch_num_points(seat->seat) == 1) { seat->touch_id = event->touch_id; seat->touch_lx = lx; seat->touch_ly = ly; press_cursor_button(seat, event->device, event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly); } wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_up(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_up); struct wlr_event_touch_up *event = data; if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { return; } if (wlr_seat_touch_num_points(seat->seat) == 1) { press_cursor_button(seat, event->device, event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED, seat->touch_lx, seat->touch_ly); } wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_motion(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_motion); struct wlr_event_touch_motion *event = data; if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { return; } double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, event->y, &lx, &ly); double sx, sy; struct wlr_surface *surface; struct cg_view *view = desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); if (view) { wlr_seat_touch_point_focus(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); wlr_seat_touch_notify_motion(seat->seat, event->time_msec, event->touch_id, sx, sy); } else { wlr_seat_touch_point_clear_focus(seat->seat, event->time_msec, event->touch_id); } if (event->touch_id == seat->touch_id) { seat->touch_lx = lx; seat->touch_ly = ly; } wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_frame(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_frame); wlr_seat_pointer_notify_frame(seat->seat); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_axis(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_axis); struct wlr_event_pointer_axis *event = data; wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, event->orientation, event->delta, event->delta_discrete, event->source); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_button(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_button); struct wlr_event_pointer_button *event = data; wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->state); press_cursor_button(seat, event->device, event->time_msec, event->button, event->state, seat->cursor->x, seat->cursor->y); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void process_cursor_motion(struct cg_seat *seat, uint32_t time) { double sx, sy; struct wlr_seat *wlr_seat = seat->seat; struct wlr_surface *surface = NULL; struct cg_view *view = desktop_view_at(seat->server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy); if (!view) { wlr_seat_pointer_clear_focus(wlr_seat); } else { wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); bool focus_changed = wlr_seat->pointer_state.focused_surface != surface; if (!focus_changed && time > 0) { wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); } } struct cg_drag_icon *drag_icon; wl_list_for_each (drag_icon, &seat->drag_icons, link) { drag_icon_update_position(drag_icon); } wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_motion_absolute(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_absolute); struct wlr_event_pointer_motion_absolute *event = data; wlr_cursor_warp_absolute(seat->cursor, event->device, event->x, event->y); process_cursor_motion(seat, event->time_msec); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion); struct wlr_event_pointer_motion *event = data; wlr_cursor_move(seat->cursor, event->device, event->delta_x, event->delta_y); process_cursor_motion(seat, event->time_msec); wlr_idle_notify_activity(seat->server->idle, seat->seat); } static void drag_icon_damage(struct cg_drag_icon *drag_icon) { struct cg_output *output; wl_list_for_each (output, &drag_icon->seat->server->outputs, link) { output_damage_surface(output, drag_icon->wlr_drag_icon->surface, drag_icon->lx, drag_icon->ly, true); } } static void drag_icon_update_position(struct cg_drag_icon *drag_icon) { struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon; struct cg_seat *seat = drag_icon->seat; struct wlr_touch_point *point; drag_icon_damage(drag_icon); switch (wlr_icon->drag->grab_type) { case WLR_DRAG_GRAB_KEYBOARD: return; case WLR_DRAG_GRAB_KEYBOARD_POINTER: drag_icon->lx = seat->cursor->x; drag_icon->ly = seat->cursor->y; break; case WLR_DRAG_GRAB_KEYBOARD_TOUCH: point = wlr_seat_touch_get_point(seat->seat, wlr_icon->drag->touch_id); if (!point) { return; } drag_icon->lx = seat->touch_lx; drag_icon->ly = seat->touch_ly; break; } drag_icon_damage(drag_icon); } static void handle_drag_icon_destroy(struct wl_listener *listener, void *data) { struct cg_drag_icon *drag_icon = wl_container_of(listener, drag_icon, destroy); wl_list_remove(&drag_icon->link); wl_list_remove(&drag_icon->destroy.link); free(drag_icon); } static void handle_request_start_drag(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_start_drag); struct wlr_seat_request_start_drag_event *event = data; if (wlr_seat_validate_pointer_grab_serial(seat->seat, event->origin, event->serial)) { wlr_seat_start_pointer_drag(seat->seat, event->drag, event->serial); return; } struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial(seat->seat, event->origin, event->serial, &point)) { wlr_seat_start_touch_drag(seat->seat, event->drag, event->serial, point); return; } // TODO: tablet grabs wlr_log(WLR_DEBUG, "Ignoring start_drag request: could not validate pointer/touch serial %" PRIu32, event->serial); wlr_data_source_destroy(event->drag->source); } static void handle_start_drag(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, start_drag); struct wlr_drag *wlr_drag = data; struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; if (wlr_drag_icon == NULL) { return; } struct cg_drag_icon *drag_icon = calloc(1, sizeof(struct cg_drag_icon)); if (!drag_icon) { return; } drag_icon->seat = seat; drag_icon->wlr_drag_icon = wlr_drag_icon; drag_icon->destroy.notify = handle_drag_icon_destroy; wl_signal_add(&wlr_drag_icon->events.destroy, &drag_icon->destroy); wl_list_insert(&seat->drag_icons, &drag_icon->link); drag_icon_update_position(drag_icon); } static void handle_destroy(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, destroy); wl_list_remove(&seat->destroy.link); wl_list_remove(&seat->cursor_motion.link); wl_list_remove(&seat->cursor_motion_absolute.link); wl_list_remove(&seat->cursor_button.link); wl_list_remove(&seat->cursor_axis.link); wl_list_remove(&seat->cursor_frame.link); wl_list_remove(&seat->touch_down.link); wl_list_remove(&seat->touch_up.link); wl_list_remove(&seat->touch_motion.link); wl_list_remove(&seat->request_set_cursor.link); wl_list_remove(&seat->request_set_selection.link); wl_list_remove(&seat->request_set_primary_selection.link); struct cg_keyboard_group *group, *group_tmp; wl_list_for_each_safe (group, group_tmp, &seat->keyboard_groups, link) { wlr_keyboard_group_destroy(group->wlr_group); free(group); } struct cg_pointer *pointer, *pointer_tmp; wl_list_for_each_safe (pointer, pointer_tmp, &seat->pointers, link) { handle_pointer_destroy(&pointer->destroy, NULL); } struct cg_touch *touch, *touch_tmp; wl_list_for_each_safe (touch, touch_tmp, &seat->touch, link) { handle_touch_destroy(&touch->destroy, NULL); } wl_list_remove(&seat->new_input.link); wlr_xcursor_manager_destroy(seat->xcursor_manager); if (seat->cursor) { wlr_cursor_destroy(seat->cursor); } free(seat); } struct cg_seat * seat_create(struct cg_server *server, struct wlr_backend *backend) { struct cg_seat *seat = calloc(1, sizeof(struct cg_seat)); if (!seat) { wlr_log(WLR_ERROR, "Cannot allocate seat"); return NULL; } seat->seat = wlr_seat_create(server->wl_display, "seat0"); if (!seat->seat) { wlr_log(WLR_ERROR, "Cannot allocate seat0"); free(seat); return NULL; } seat->server = server; seat->destroy.notify = handle_destroy; wl_signal_add(&seat->seat->events.destroy, &seat->destroy); seat->cursor = wlr_cursor_create(); if (!seat->cursor) { wlr_log(WLR_ERROR, "Unable to create cursor"); wl_list_remove(&seat->destroy.link); free(seat); return NULL; } wlr_cursor_attach_output_layout(seat->cursor, server->output_layout); if (!seat->xcursor_manager) { seat->xcursor_manager = wlr_xcursor_manager_create(NULL, XCURSOR_SIZE); if (!seat->xcursor_manager) { wlr_log(WLR_ERROR, "Cannot create XCursor manager"); wlr_cursor_destroy(seat->cursor); wl_list_remove(&seat->destroy.link); free(seat); return NULL; } } seat->cursor_motion.notify = handle_cursor_motion; wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute; wl_signal_add(&seat->cursor->events.motion_absolute, &seat->cursor_motion_absolute); seat->cursor_button.notify = handle_cursor_button; wl_signal_add(&seat->cursor->events.button, &seat->cursor_button); seat->cursor_axis.notify = handle_cursor_axis; wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis); seat->cursor_frame.notify = handle_cursor_frame; wl_signal_add(&seat->cursor->events.frame, &seat->cursor_frame); seat->touch_down.notify = handle_touch_down; wl_signal_add(&seat->cursor->events.touch_down, &seat->touch_down); seat->touch_up.notify = handle_touch_up; wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up); seat->touch_motion.notify = handle_touch_motion; wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion); seat->request_set_cursor.notify = handle_request_set_cursor; wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_set_cursor); seat->request_set_selection.notify = handle_request_set_selection; wl_signal_add(&seat->seat->events.request_set_selection, &seat->request_set_selection); seat->request_set_primary_selection.notify = handle_request_set_primary_selection; wl_signal_add(&seat->seat->events.request_set_primary_selection, &seat->request_set_primary_selection); wl_list_init(&seat->keyboards); wl_list_init(&seat->keyboard_groups); wl_list_init(&seat->pointers); wl_list_init(&seat->touch); seat->new_input.notify = handle_new_input; wl_signal_add(&backend->events.new_input, &seat->new_input); wl_list_init(&seat->drag_icons); seat->request_start_drag.notify = handle_request_start_drag; wl_signal_add(&seat->seat->events.request_start_drag, &seat->request_start_drag); seat->start_drag.notify = handle_start_drag; wl_signal_add(&seat->seat->events.start_drag, &seat->start_drag); return seat; } void seat_destroy(struct cg_seat *seat) { if (!seat) { return; } wl_list_remove(&seat->request_start_drag.link); wl_list_remove(&seat->start_drag.link); // Destroying the wlr seat will trigger the destroy handler on our seat, // which will in turn free it. wlr_seat_destroy(seat->seat); } struct cg_view * seat_get_focus(struct cg_seat *seat) { struct wlr_surface *prev_surface = seat->seat->keyboard_state.focused_surface; return view_from_wlr_surface(seat->server, prev_surface); } void seat_set_focus(struct cg_seat *seat, struct cg_view *view) { struct cg_server *server = seat->server; struct wlr_seat *wlr_seat = seat->seat; struct cg_view *prev_view = seat_get_focus(seat); if (!view || prev_view == view) { return; } #if CAGE_HAS_XWAYLAND if (view->type == CAGE_XWAYLAND_VIEW) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); if (!wlr_xwayland_or_surface_wants_focus(xwayland_view->xwayland_surface)) { return; } } #endif if (prev_view) { view_activate(prev_view, false); } /* Move the view to the front, but only if it isn't a fullscreen view. */ if (!view_is_primary(view)) { wl_list_remove(&view->link); wl_list_insert(&server->views, &view->link); } view_activate(view, true); char *title = view_get_title(view); struct cg_output *output; wl_list_for_each (output, &server->outputs, link) { output_set_window_title(output, title); } free(title); struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); if (keyboard) { wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } else { wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, NULL); } process_cursor_motion(seat, -1); } cage-0.1.4/seat.h000066400000000000000000000042631406555467400135450ustar00rootroot00000000000000#ifndef CG_SEAT_H #define CG_SEAT_H #include #include #include #include #include #include #include "server.h" #include "view.h" #define DEFAULT_XCURSOR "left_ptr" #define XCURSOR_SIZE 24 struct cg_seat { struct wlr_seat *seat; struct cg_server *server; struct wl_listener destroy; struct wl_list keyboards; struct wl_list keyboard_groups; struct wl_list pointers; struct wl_list touch; struct wl_listener new_input; struct wlr_cursor *cursor; struct wlr_xcursor_manager *xcursor_manager; struct wl_listener cursor_motion; struct wl_listener cursor_motion_absolute; struct wl_listener cursor_button; struct wl_listener cursor_axis; struct wl_listener cursor_frame; int32_t touch_id; double touch_lx; double touch_ly; struct wl_listener touch_down; struct wl_listener touch_up; struct wl_listener touch_motion; struct wl_list drag_icons; struct wl_listener request_start_drag; struct wl_listener start_drag; struct wl_listener request_set_cursor; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; }; struct cg_keyboard_group { struct wlr_keyboard_group *wlr_group; struct cg_seat *seat; struct wl_listener key; struct wl_listener modifiers; struct wl_list link; // cg_seat::keyboard_groups }; struct cg_pointer { struct wl_list link; // seat::pointers struct cg_seat *seat; struct wlr_input_device *device; struct wl_listener destroy; }; struct cg_touch { struct wl_list link; // seat::touch struct cg_seat *seat; struct wlr_input_device *device; struct wl_listener destroy; }; struct cg_drag_icon { struct wl_list link; // seat::drag_icons struct cg_seat *seat; struct wlr_drag_icon *wlr_drag_icon; /* The drag icon has a position in layout coordinates. */ double lx, ly; struct wl_listener destroy; }; struct cg_seat *seat_create(struct cg_server *server, struct wlr_backend *backend); void seat_destroy(struct cg_seat *seat); struct cg_view *seat_get_focus(struct cg_seat *seat); void seat_set_focus(struct cg_seat *seat, struct cg_view *view); #endif cage-0.1.4/server.h000066400000000000000000000024321406555467400141130ustar00rootroot00000000000000#ifndef CG_SERVER_H #define CG_SERVER_H #include "config.h" #include #include #include #include #include #if CAGE_HAS_XWAYLAND #include #endif #include "output.h" #include "seat.h" #include "view.h" enum cg_multi_output_mode { CAGE_MULTI_OUTPUT_MODE_EXTEND, CAGE_MULTI_OUTPUT_MODE_LAST, }; struct cg_server { struct wl_display *wl_display; struct wl_list views; struct wlr_backend *backend; struct cg_seat *seat; struct wlr_idle *idle; struct wlr_idle_inhibit_manager_v1 *idle_inhibit_v1; struct wl_listener new_idle_inhibitor_v1; struct wl_list inhibitors; enum cg_multi_output_mode output_mode; struct wlr_output_layout *output_layout; /* Includes disabled outputs; depending on the output_mode * some outputs may be disabled. */ struct wl_list outputs; // cg_output::link struct wl_listener new_output; struct wl_listener xdg_toplevel_decoration; struct wl_listener new_xdg_shell_surface; #if CAGE_HAS_XWAYLAND struct wl_listener new_xwayland_surface; #endif bool xdg_decoration; bool allow_vt_switch; enum wl_output_transform output_transform; #ifdef DEBUG bool debug_damage_tracking; #endif }; #endif cage-0.1.4/util.c000066400000000000000000000017201406555467400135540ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2019 The Sway authors * * See the LICENSE file accompanying this file. */ #include #include "util.h" int scale_length(int length, int offset, float scale) { /** * One does not simply multiply the width by the scale. We allow fractional * scaling, which means the resulting scaled width might be a decimal. * So we round it. * * But even this can produce undesirable results depending on the X or Y * offset of the box. For example, with a scale of 1.5, a box with * width=1 should not scale to 2px if its X coordinate is 1, because the * X coordinate would have scaled to 2px. */ return round((offset + length) * scale) - round(offset * scale); } void scale_box(struct wlr_box *box, float scale) { box->width = scale_length(box->width, box->x, scale); box->height = scale_length(box->height, box->y, scale); box->x = round(box->x * scale); box->y = round(box->y * scale); } cage-0.1.4/util.h000066400000000000000000000003401406555467400135560ustar00rootroot00000000000000#ifndef CG_UTIL_H #define CG_UTIL_H #include /** Apply scale to a width or height. */ int scale_length(int length, int offset, float scale); void scale_box(struct wlr_box *box, float scale); #endif cage-0.1.4/view.c000066400000000000000000000165671406555467400135700ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif static void view_child_handle_commit(struct wl_listener *listener, void *data) { struct cg_view_child *child = wl_container_of(listener, child, commit); view_damage_part(child->view); } static void subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface); static void view_child_handle_new_subsurface(struct wl_listener *listener, void *data) { struct cg_view_child *child = wl_container_of(listener, child, new_subsurface); struct wlr_subsurface *wlr_subsurface = data; subsurface_create(child->view, wlr_subsurface); } void view_child_finish(struct cg_view_child *child) { if (!child) { return; } view_damage_whole(child->view); wl_list_remove(&child->link); wl_list_remove(&child->commit.link); wl_list_remove(&child->new_subsurface.link); } void view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface) { child->view = view; child->wlr_surface = wlr_surface; child->commit.notify = view_child_handle_commit; wl_signal_add(&wlr_surface->events.commit, &child->commit); child->new_subsurface.notify = view_child_handle_new_subsurface; wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); wl_list_insert(&view->children, &child->link); } static void subsurface_destroy(struct cg_view_child *child) { if (!child) { return; } struct cg_subsurface *subsurface = (struct cg_subsurface *) child; wl_list_remove(&subsurface->destroy.link); view_child_finish(&subsurface->view_child); free(subsurface); } static void subsurface_handle_destroy(struct wl_listener *listener, void *data) { struct cg_subsurface *subsurface = wl_container_of(listener, subsurface, destroy); struct cg_view_child *view_child = (struct cg_view_child *) subsurface; subsurface_destroy(view_child); } static void subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface) { struct cg_subsurface *subsurface = calloc(1, sizeof(struct cg_subsurface)); if (!subsurface) { return; } view_child_init(&subsurface->view_child, view, wlr_subsurface->surface); subsurface->view_child.destroy = subsurface_destroy; subsurface->wlr_subsurface = wlr_subsurface; subsurface->destroy.notify = subsurface_handle_destroy; wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); } static void handle_new_subsurface(struct wl_listener *listener, void *data) { struct cg_view *view = wl_container_of(listener, view, new_subsurface); struct wlr_subsurface *wlr_subsurface = data; subsurface_create(view, wlr_subsurface); } char * view_get_title(struct cg_view *view) { const char *title = view->impl->get_title(view); if (!title) { return NULL; } return strndup(title, strlen(title)); } bool view_is_primary(struct cg_view *view) { return view->impl->is_primary(view); } bool view_is_transient_for(struct cg_view *child, struct cg_view *parent) { return child->impl->is_transient_for(child, parent); } void view_damage_part(struct cg_view *view) { struct cg_output *output; wl_list_for_each (output, &view->server->outputs, link) { output_damage_surface(output, view->wlr_surface, view->lx, view->ly, false); } } void view_damage_whole(struct cg_view *view) { struct cg_output *output; wl_list_for_each (output, &view->server->outputs, link) { output_damage_surface(output, view->wlr_surface, view->lx, view->ly, true); } } void view_activate(struct cg_view *view, bool activate) { view->impl->activate(view, activate); } static bool view_extends_output_layout(struct cg_view *view, struct wlr_box *layout_box) { int width, height; view->impl->get_geometry(view, &width, &height); return (layout_box->height < height || layout_box->width < width); } static void view_maximize(struct cg_view *view, struct wlr_box *layout_box) { view->lx = layout_box->x; view->ly = layout_box->y; view->impl->maximize(view, layout_box->width, layout_box->height); } static void view_center(struct cg_view *view, struct wlr_box *layout_box) { int width, height; view->impl->get_geometry(view, &width, &height); view->lx = (layout_box->width - width) / 2; view->ly = (layout_box->height - height) / 2; } void view_position(struct cg_view *view) { struct wlr_box *layout_box = wlr_output_layout_get_box(view->server->output_layout, NULL); if (view_is_primary(view) || view_extends_output_layout(view, layout_box)) { view_maximize(view, layout_box); } else { view_center(view, layout_box); } } void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) { view->impl->for_each_surface(view, iterator, data); } void view_for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) { if (!view->impl->for_each_popup_surface) { return; } view->impl->for_each_popup_surface(view, iterator, data); } void view_unmap(struct cg_view *view) { wl_list_remove(&view->link); wl_list_remove(&view->new_subsurface.link); struct cg_view_child *child, *tmp; wl_list_for_each_safe (child, tmp, &view->children, link) { child->destroy(child); } view->wlr_surface = NULL; } void view_map(struct cg_view *view, struct wlr_surface *surface) { view->wlr_surface = surface; struct wlr_subsurface *subsurface; wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces_below, parent_link) { subsurface_create(view, subsurface); } wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces_above, parent_link) { subsurface_create(view, subsurface); } view->new_subsurface.notify = handle_new_subsurface; wl_signal_add(&view->wlr_surface->events.new_subsurface, &view->new_subsurface); #if CAGE_HAS_XWAYLAND /* We shouldn't position override-redirect windows. They set their own (x,y) coordinates in handle_wayland_surface_map. */ if (view->type != CAGE_XWAYLAND_VIEW || xwayland_view_should_manage(view)) #endif { view_position(view); } wl_list_insert(&view->server->views, &view->link); seat_set_focus(view->server->seat, view); } void view_destroy(struct cg_view *view) { struct cg_server *server = view->server; if (view->wlr_surface != NULL) { view_unmap(view); } view->impl->destroy(view); /* If there is a previous view in the list, focus that. */ bool empty = wl_list_empty(&server->views); if (!empty) { struct cg_view *prev = wl_container_of(server->views.next, prev, link); seat_set_focus(server->seat, prev); } } void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl) { view->server = server; view->type = type; view->impl = impl; wl_list_init(&view->children); } struct cg_view * view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface) { struct cg_view *view; wl_list_for_each (view, &server->views, link) { if (view->wlr_surface == surface) { return view; } } return NULL; } struct wlr_surface * view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) { return view->impl->wlr_surface_at(view, sx, sy, sub_x, sub_y); } cage-0.1.4/view.h000066400000000000000000000057311406555467400135640ustar00rootroot00000000000000#ifndef CG_VIEW_H #define CG_VIEW_H #include "config.h" #include #include #include #include #include #if CAGE_HAS_XWAYLAND #include #endif #include "server.h" enum cg_view_type { CAGE_XDG_SHELL_VIEW, #if CAGE_HAS_XWAYLAND CAGE_XWAYLAND_VIEW, #endif }; struct cg_view { struct cg_server *server; struct wl_list link; // server::views struct wl_list children; // cg_view_child::link struct wlr_surface *wlr_surface; /* The view has a position in layout coordinates. */ int lx, ly; enum cg_view_type type; const struct cg_view_impl *impl; struct wl_listener new_subsurface; }; struct cg_view_impl { char *(*get_title)(struct cg_view *view); void (*get_geometry)(struct cg_view *view, int *width_out, int *height_out); bool (*is_primary)(struct cg_view *view); bool (*is_transient_for)(struct cg_view *child, struct cg_view *parent); void (*activate)(struct cg_view *view, bool activate); void (*maximize)(struct cg_view *view, int output_width, int output_height); void (*destroy)(struct cg_view *view); void (*for_each_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); void (*for_each_popup_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); struct wlr_surface *(*wlr_surface_at)(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y); }; struct cg_view_child { struct cg_view *view; struct wlr_surface *wlr_surface; struct wl_list link; struct wl_listener commit; struct wl_listener new_subsurface; void (*destroy)(struct cg_view_child *child); }; struct cg_subsurface { struct cg_view_child view_child; struct wlr_subsurface *wlr_subsurface; struct wl_listener destroy; }; char *view_get_title(struct cg_view *view); bool view_is_primary(struct cg_view *view); bool view_is_transient_for(struct cg_view *child, struct cg_view *parent); void view_damage_part(struct cg_view *view); void view_damage_whole(struct cg_view *view); void view_activate(struct cg_view *view, bool activate); void view_position(struct cg_view *view); void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); void view_for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); void view_unmap(struct cg_view *view); void view_map(struct cg_view *view, struct wlr_surface *surface); void view_destroy(struct cg_view *view); void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl); struct cg_view *view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface); struct wlr_surface *view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y); void view_child_finish(struct cg_view_child *child); void view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface); #endif cage-0.1.4/xdg_shell.c000066400000000000000000000270651406555467400145620ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include #include #include #include #include #include #include "server.h" #include "view.h" #include "xdg_shell.h" static void xdg_decoration_handle_destroy(struct wl_listener *listener, void *data) { struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, destroy); wl_list_remove(&xdg_decoration->destroy.link); wl_list_remove(&xdg_decoration->request_mode.link); free(xdg_decoration); } static void xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data) { struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, request_mode); enum wlr_xdg_toplevel_decoration_v1_mode mode; if (xdg_decoration->server->xdg_decoration) { mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; } else { mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; } wlr_xdg_toplevel_decoration_v1_set_mode(xdg_decoration->wlr_decoration, mode); } static void xdg_popup_destroy(struct cg_view_child *child) { if (!child) { return; } struct cg_xdg_popup *popup = (struct cg_xdg_popup *) child; wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->map.link); wl_list_remove(&popup->unmap.link); wl_list_remove(&popup->new_popup.link); view_child_finish(&popup->view_child); free(popup); } static void handle_xdg_popup_map(struct wl_listener *listener, void *data) { struct cg_xdg_popup *popup = wl_container_of(listener, popup, map); view_damage_whole(popup->view_child.view); } static void handle_xdg_popup_unmap(struct wl_listener *listener, void *data) { struct cg_xdg_popup *popup = wl_container_of(listener, popup, unmap); view_damage_whole(popup->view_child.view); } static void handle_xdg_popup_destroy(struct wl_listener *listener, void *data) { struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy); struct cg_view_child *view_child = (struct cg_view_child *) popup; xdg_popup_destroy(view_child); } static void xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup); static void popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) { struct cg_xdg_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(popup->view_child.view, wlr_popup); } static void popup_unconstrain(struct cg_xdg_popup *popup) { struct cg_view *view = popup->view_child.view; struct cg_server *server = view->server; struct wlr_box *popup_box = &popup->wlr_popup->geometry; struct wlr_output_layout *output_layout = server->output_layout; struct wlr_output *wlr_output = wlr_output_layout_output_at(output_layout, view->lx + popup_box->x, view->ly + popup_box->y); struct wlr_box *output_box = wlr_output_layout_get_box(output_layout, wlr_output); struct wlr_box output_toplevel_box = { .x = output_box->x - view->lx, .y = output_box->y - view->ly, .width = output_box->width, .height = output_box->height, }; wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); } static void xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup) { struct cg_xdg_popup *popup = calloc(1, sizeof(struct cg_xdg_popup)); if (!popup) { return; } popup->wlr_popup = wlr_popup; view_child_init(&popup->view_child, view, wlr_popup->base->surface); popup->view_child.destroy = xdg_popup_destroy; popup->destroy.notify = handle_xdg_popup_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); popup->map.notify = handle_xdg_popup_map; wl_signal_add(&wlr_popup->base->events.map, &popup->map); popup->unmap.notify = handle_xdg_popup_unmap; wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); popup->new_popup.notify = popup_handle_new_xdg_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup_unconstrain(popup); } static void handle_new_xdg_popup(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup); struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(&xdg_shell_view->view, wlr_popup); } static struct cg_xdg_shell_view * xdg_shell_view_from_view(struct cg_view *view) { return (struct cg_xdg_shell_view *) view; } static char * get_title(struct cg_view *view) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); return xdg_shell_view->xdg_surface->toplevel->title; } static void get_geometry(struct cg_view *view, int *width_out, int *height_out) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); struct wlr_box geom; wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geom); *width_out = geom.width; *height_out = geom.height; } static bool is_primary(struct cg_view *view) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); struct wlr_xdg_surface *parent = xdg_shell_view->xdg_surface->toplevel->parent; /* FIXME: role is 0? */ return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ } static bool is_transient_for(struct cg_view *child, struct cg_view *parent) { if (parent->type != CAGE_XDG_SHELL_VIEW) { return false; } struct cg_xdg_shell_view *_child = xdg_shell_view_from_view(child); struct wlr_xdg_surface *xdg_surface = _child->xdg_surface; struct cg_xdg_shell_view *_parent = xdg_shell_view_from_view(parent); struct wlr_xdg_surface *parent_xdg_surface = _parent->xdg_surface; while (xdg_surface) { if (xdg_surface->toplevel->parent == parent_xdg_surface) { return true; } xdg_surface = xdg_surface->toplevel->parent; } return false; } static void activate(struct cg_view *view, bool activate) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate); } static void maximize(struct cg_view *view, int output_width, int output_height) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, output_width, output_height); wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true); } static void destroy(struct cg_view *view) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); free(xdg_shell_view); } static void for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); wlr_xdg_surface_for_each_surface(xdg_shell_view->xdg_surface, iterator, data); } static void for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); wlr_xdg_surface_for_each_popup_surface(xdg_shell_view->xdg_surface, iterator, data); } static struct wlr_surface * wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); return wlr_xdg_surface_surface_at(xdg_shell_view->xdg_surface, sx, sy, sub_x, sub_y); } static void handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen); struct wlr_xdg_toplevel_set_fullscreen_event *event = data; wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, event->fullscreen); } static void handle_xdg_shell_surface_commit(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit); struct cg_view *view = &xdg_shell_view->view; view_damage_part(view); } static void handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); struct cg_view *view = &xdg_shell_view->view; view_damage_whole(view); wl_list_remove(&xdg_shell_view->commit.link); view_unmap(view); } static void handle_xdg_shell_surface_map(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map); struct cg_view *view = &xdg_shell_view->view; xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit); view_map(view, xdg_shell_view->xdg_surface->surface); view_damage_whole(view); } static void handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, destroy); struct cg_view *view = &xdg_shell_view->view; wl_list_remove(&xdg_shell_view->map.link); wl_list_remove(&xdg_shell_view->unmap.link); wl_list_remove(&xdg_shell_view->destroy.link); wl_list_remove(&xdg_shell_view->request_fullscreen.link); wl_list_remove(&xdg_shell_view->new_popup.link); xdg_shell_view->xdg_surface = NULL; view_destroy(view); } static const struct cg_view_impl xdg_shell_view_impl = { .get_title = get_title, .get_geometry = get_geometry, .is_primary = is_primary, .is_transient_for = is_transient_for, .activate = activate, .maximize = maximize, .destroy = destroy, .for_each_surface = for_each_surface, .for_each_popup_surface = for_each_popup_surface, .wlr_surface_at = wlr_surface_at, }; void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface); struct wlr_xdg_surface *xdg_surface = data; if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } struct cg_xdg_shell_view *xdg_shell_view = calloc(1, sizeof(struct cg_xdg_shell_view)); if (!xdg_shell_view) { wlr_log(WLR_ERROR, "Failed to allocate XDG Shell view"); return; } view_init(&xdg_shell_view->view, server, CAGE_XDG_SHELL_VIEW, &xdg_shell_view_impl); xdg_shell_view->xdg_surface = xdg_surface; xdg_shell_view->map.notify = handle_xdg_shell_surface_map; wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap; wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy; wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen; wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen); xdg_shell_view->new_popup.notify = handle_new_xdg_popup; wl_signal_add(&xdg_surface->events.new_popup, &xdg_shell_view->new_popup); } void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, xdg_toplevel_decoration); struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data; struct cg_xdg_decoration *xdg_decoration = calloc(1, sizeof(struct cg_xdg_decoration)); if (!xdg_decoration) { return; } xdg_decoration->wlr_decoration = wlr_decoration; xdg_decoration->server = server; xdg_decoration->destroy.notify = xdg_decoration_handle_destroy; wl_signal_add(&wlr_decoration->events.destroy, &xdg_decoration->destroy); xdg_decoration->request_mode.notify = xdg_decoration_handle_request_mode; wl_signal_add(&wlr_decoration->events.request_mode, &xdg_decoration->request_mode); xdg_decoration_handle_request_mode(&xdg_decoration->request_mode, wlr_decoration); } cage-0.1.4/xdg_shell.h000066400000000000000000000017531406555467400145630ustar00rootroot00000000000000#ifndef CG_XDG_SHELL_H #define CG_XDG_SHELL_H #include #include #include #include "view.h" struct cg_xdg_shell_view { struct cg_view view; struct wlr_xdg_surface *xdg_surface; struct wl_listener destroy; struct wl_listener unmap; struct wl_listener map; struct wl_listener commit; struct wl_listener request_fullscreen; struct wl_listener new_popup; }; struct cg_xdg_popup { struct cg_view_child view_child; struct wlr_xdg_popup *wlr_popup; struct wl_listener destroy; struct wl_listener map; struct wl_listener unmap; struct wl_listener new_popup; }; struct cg_xdg_decoration { struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; struct cg_server *server; struct wl_listener destroy; struct wl_listener request_mode; }; void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data); void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data); #endif cage-0.1.4/xwayland.c000066400000000000000000000151051406555467400144300ustar00rootroot00000000000000/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include #include #include #include #include #include #include "server.h" #include "view.h" #include "xwayland.h" struct cg_xwayland_view * xwayland_view_from_view(struct cg_view *view) { return (struct cg_xwayland_view *) view; } bool xwayland_view_should_manage(struct cg_view *view) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; return !xwayland_surface->override_redirect; } static char * get_title(struct cg_view *view) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); return xwayland_view->xwayland_surface->title; } static void get_geometry(struct cg_view *view, int *width_out, int *height_out) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); *width_out = xwayland_view->xwayland_surface->surface->current.width; *height_out = xwayland_view->xwayland_surface->surface->current.height; } static bool is_primary(struct cg_view *view) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); struct wlr_xwayland_surface *parent = xwayland_view->xwayland_surface->parent; return parent == NULL; } static bool is_transient_for(struct cg_view *child, struct cg_view *parent) { if (parent->type != CAGE_XDG_SHELL_VIEW) { return false; } struct cg_xwayland_view *_child = xwayland_view_from_view(child); struct wlr_xwayland_surface *xwayland_surface = _child->xwayland_surface; struct cg_xwayland_view *_parent = xwayland_view_from_view(parent); struct wlr_xwayland_surface *parent_xwayland_surface = _parent->xwayland_surface; while (xwayland_surface) { if (xwayland_surface->parent == parent_xwayland_surface) { return true; } xwayland_surface = xwayland_surface->parent; } return false; } static void activate(struct cg_view *view, bool activate) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); wlr_xwayland_surface_activate(xwayland_view->xwayland_surface, activate); } static void maximize(struct cg_view *view, int output_width, int output_height) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); wlr_xwayland_surface_configure(xwayland_view->xwayland_surface, view->lx, view->ly, output_width, output_height); wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true); } static void destroy(struct cg_view *view) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); free(xwayland_view); } static void for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) { wlr_surface_for_each_surface(view->wlr_surface, iterator, data); } static struct wlr_surface * wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) { return wlr_surface_surface_at(view->wlr_surface, sx, sy, sub_x, sub_y); } static void handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_fullscreen); struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; wlr_xwayland_surface_set_fullscreen(xwayland_view->xwayland_surface, xwayland_surface->fullscreen); } static void handle_xwayland_surface_commit(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit); struct cg_view *view = &xwayland_view->view; view_damage_part(view); } static void handle_xwayland_surface_unmap(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap); struct cg_view *view = &xwayland_view->view; view_damage_whole(view); wl_list_remove(&xwayland_view->commit.link); view_unmap(view); } static void handle_xwayland_surface_map(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, map); struct cg_view *view = &xwayland_view->view; if (!xwayland_view_should_manage(view)) { view->lx = xwayland_view->xwayland_surface->x; view->ly = xwayland_view->xwayland_surface->y; } xwayland_view->commit.notify = handle_xwayland_surface_commit; wl_signal_add(&xwayland_view->xwayland_surface->surface->events.commit, &xwayland_view->commit); view_map(view, xwayland_view->xwayland_surface->surface); view_damage_whole(view); } static void handle_xwayland_surface_destroy(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy); struct cg_view *view = &xwayland_view->view; wl_list_remove(&xwayland_view->map.link); wl_list_remove(&xwayland_view->unmap.link); wl_list_remove(&xwayland_view->destroy.link); wl_list_remove(&xwayland_view->request_fullscreen.link); xwayland_view->xwayland_surface = NULL; view_destroy(view); } static const struct cg_view_impl xwayland_view_impl = { .get_title = get_title, .get_geometry = get_geometry, .is_primary = is_primary, .is_transient_for = is_transient_for, .activate = activate, .maximize = maximize, .destroy = destroy, .for_each_surface = for_each_surface, /* XWayland doesn't have a separate popup iterator. */ .for_each_popup_surface = NULL, .wlr_surface_at = wlr_surface_at, }; void handle_xwayland_surface_new(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_xwayland_surface); struct wlr_xwayland_surface *xwayland_surface = data; struct cg_xwayland_view *xwayland_view = calloc(1, sizeof(struct cg_xwayland_view)); if (!xwayland_view) { wlr_log(WLR_ERROR, "Failed to allocate XWayland view"); return; } view_init(&xwayland_view->view, server, CAGE_XWAYLAND_VIEW, &xwayland_view_impl); xwayland_view->xwayland_surface = xwayland_surface; xwayland_view->map.notify = handle_xwayland_surface_map; wl_signal_add(&xwayland_surface->events.map, &xwayland_view->map); xwayland_view->unmap.notify = handle_xwayland_surface_unmap; wl_signal_add(&xwayland_surface->events.unmap, &xwayland_view->unmap); xwayland_view->destroy.notify = handle_xwayland_surface_destroy; wl_signal_add(&xwayland_surface->events.destroy, &xwayland_view->destroy); xwayland_view->request_fullscreen.notify = handle_xwayland_surface_request_fullscreen; wl_signal_add(&xwayland_surface->events.request_fullscreen, &xwayland_view->request_fullscreen); } cage-0.1.4/xwayland.h000066400000000000000000000011111406555467400144250ustar00rootroot00000000000000#ifndef CG_XWAYLAND_H #define CG_XWAYLAND_H #include #include #include "view.h" struct cg_xwayland_view { struct cg_view view; struct wlr_xwayland_surface *xwayland_surface; struct wl_listener destroy; struct wl_listener unmap; struct wl_listener map; struct wl_listener commit; struct wl_listener request_fullscreen; }; struct cg_xwayland_view *xwayland_view_from_view(struct cg_view *view); bool xwayland_view_should_manage(struct cg_view *view); void handle_xwayland_surface_new(struct wl_listener *listener, void *data); #endif