Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
Please login to access the resource
openSUSE:Factory
cage
cage-0.2.0+0.e128a9f.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File cage-0.2.0+0.e128a9f.obscpio of Package cage
07070100000000000081A400000000000000000000000167028F860000016F000000000000000000000000000000000000002300000000cage-0.2.0+0.e128a9f/.clang-formatAlignAfterOpenBracket: 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 07070100000001000081A400000000000000000000000167028F8600000010000000000000000000000000000000000000002A00000000cage-0.2.0+0.e128a9f/.clang-format-ignoresubprojects/**/*07070100000002000081A400000000000000000000000167028F86000000A7000000000000000000000000000000000000002300000000cage-0.2.0+0.e128a9f/.editorconfigroot = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true indent_style = tab indent_size = 8 max_line_length = 120 07070100000003000041ED00000000000000000000000267028F8600000000000000000000000000000000000000000000001D00000000cage-0.2.0+0.e128a9f/.github07070100000004000041ED00000000000000000000000267028F8600000000000000000000000000000000000000000000002700000000cage-0.2.0+0.e128a9f/.github/workflows07070100000005000081A400000000000000000000000167028F8600000B6C000000000000000000000000000000000000003000000000cage-0.2.0+0.e128a9f/.github/workflows/main.ymlname: Continuous integration build on: push: branches: [ master ] pull_request: branches: [ master ] jobs: compile: runs-on: ubuntu-latest strategy: fail-fast: false matrix: CC: [ gcc, clang ] OS: [ "alpine:edge", "archlinux:base-devel" ] xwayland: [ enabled, disabled ] container: ${{ matrix.OS }} env: CC: ${{ matrix.CC }} steps: - name: Checkout Cage uses: actions/checkout@v2 - name: Install dependencies (Alpine) if: "matrix.OS == 'alpine:edge'" run: apk add build-base xcb-util-wm-dev libseat-dev clang git eudev-dev mesa-dev libdrm-dev libinput-dev libxkbcommon-dev pixman-dev wayland-dev meson wayland-protocols xwayland-dev scdoc-doc hwdata - name: Install dependencies (Arch) if: "matrix.OS == 'archlinux:base-devel'" run: | pacman-key --init pacman -Syu --noconfirm xcb-util-wm seatd git clang meson libinput libdrm mesa libxkbcommon wayland wayland-protocols xorg-server-xwayland scdoc - name: Fetch wlroots as a subproject run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b 0.18 - name: Compile Cage (XWayland=${{ matrix.xwayland }}) run: | meson --fatal-meson-warnings \ build-${{ matrix.CC }}-${{matrix.xwayland }} \ -Dwlroots:xwayland=${{ matrix.xwayland }} ninja -C build-${{ matrix.CC }}-${{matrix.xwayland }} format: runs-on: ubuntu-latest container: "archlinux:base-devel" steps: - name: Checkout Cage uses: actions/checkout@v2 - name: Install dependencies run: | pacman-key --init pacman -Syu --noconfirm xcb-util-wm seatd git clang meson libinput libdrm mesa libxkbcommon wayland wayland-protocols xorg-server-xwayland scdoc hwdata - name: Fetch wlroots as a subproject run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b 0.18 - name: Check for formatting changes run: | meson build-clang-format -Dwlroots:xwayland=enabled ninja -C build-clang-format clang-format-check scan-build: runs-on: ubuntu-latest container: "archlinux:base-devel" env: CC: clang steps: - name: Checkout Cage uses: actions/checkout@v2 - name: Install dependencies run: | pacman-key --init pacman -Syu --noconfirm xcb-util-wm seatd git clang meson libinput libdrm mesa libxkbcommon wayland wayland-protocols xorg-server-xwayland scdoc hwdata - name: Fetch wlroots as a subproject run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b 0.18 - name: Run scan-build run: | meson build-scan-build -Dwlroots:xwayland=enabled ninja -C build-scan-build scan-build 07070100000006000081A400000000000000000000000167028F860000044A000000000000000000000000000000000000001D00000000cage-0.2.0+0.e128a9f/LICENSECopyright (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. 07070100000007000081A400000000000000000000000167028F8600000977000000000000000000000000000000000000001F00000000cage-0.2.0+0.e128a9f/README.md# Cage: a Wayland kiosk <img src="https://www.hjdskes.nl/img/projects/cage/cage.svg" alt="Cage's logo" width="150px" align="right"> 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/cage-kiosk/cage/wiki/). ## Release signatures Releases up to version 0.1.4 are signed with [6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1). Releases from 0.1.5 onwards are signed with [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) All releases are published on [GitHub](https://github.com/cage-kiosk/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. Cage is currently based on branch 0.18 of wlroots. Simply execute the following steps to build Cage: ``` $ meson setup build $ meson compile -C build ``` By default, this builds a debug build. To build a release build, use `meson setup build --buildtype=release`. Cage comes with compile-time support for XWayland. To enable this, make sure that your version of wlroots is compiled with this option. 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 <kbd>Alt</kbd>+<kbd>Esc</kbd> to quit. For more configuration options, see [Configuration](https://github.com/cage-kiosk/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/cage-kiosk/cage/issues/new) on [GitHub](https://github.com/cage-kiosk/cage). ## License Please see [LICENSE](https://github.com/cage-kiosk/cage/blob/master/LICENSE) on [GitHub](https://github.com/cage-kiosk/cage). Copyright © 2018-2020 Jente Hidskes <dev@hjdskes.nl> 07070100000008000081A400000000000000000000000167028F86000005A6000000000000000000000000000000000000002000000000cage-0.2.0+0.e128a9f/cage.1.scdcage(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* <mode> 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. *-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/cage-kiosk/cage # AUTHORS Jente Hidskes <dev@hjdskes.nl> 07070100000009000081A400000000000000000000000167028F8600004425000000000000000000000000000000000000001C00000000cage-0.2.0+0.e128a9f/cage.c/* * 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 <fcntl.h> #include <getopt.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> #include <wayland-server-core.h> #include <wlr/backend.h> #include <wlr/render/allocator.h> #include <wlr/render/wlr_renderer.h> #include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_export_dmabuf_v1.h> #include <wlr/types/wlr_gamma_control_v1.h> #include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_idle_notify_v1.h> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_management_v1.h> #include <wlr/types/wlr_presentation_time.h> #include <wlr/types/wlr_primary_selection_v1.h> #include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_screencopy_v1.h> #include <wlr/types/wlr_server_decoration.h> #include <wlr/types/wlr_single_pixel_buffer_v1.h> #include <wlr/types/wlr_subcompositor.h> #include <wlr/types/wlr_viewporter.h> #include <wlr/types/wlr_virtual_keyboard_v1.h> #include <wlr/types/wlr_virtual_pointer_v1.h> #if CAGE_HAS_XWAYLAND #include <wlr/types/wlr_xcursor_manager.h> #endif #include <wlr/types/wlr_xdg_decoration_v1.h> #include <wlr/types/wlr_xdg_output_v1.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/log.h> #if CAGE_HAS_XWAYLAND #include <wlr/xwayland.h> #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 void server_terminate(struct cg_server *server) { // Workaround for https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/421 if (server->terminated) { return; } wl_display_terminate(server->wl_display); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, display_destroy); server->terminated = true; } static int sigchld_handler(int fd, uint32_t mask, void *data) { struct cg_server *server = 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"); } server->return_app_code = true; server_terminate(server); 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 cg_server *server, 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); /* execvp() returns only on failure */ wlr_log_errno(WLR_ERROR, "Failed to spawn client"); _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(server->wl_display); uint32_t mask = WL_EVENT_HANGUP | WL_EVENT_ERROR; *sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, server); wlr_log(WLR_DEBUG, "Child process created with pid %d", pid); return true; } static int 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)); return WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { /* Mimic Bash and other shells for the exit status */ wlr_log(WLR_DEBUG, "Child was terminated by a signal (%d)", WTERMSIG(status)); return 128 + WTERMSIG(status); } return 0; } static bool drop_permissions(void) { if (getuid() == 0 || getgid() == 0) { wlr_log(WLR_INFO, "Running as root user, this is dangerous"); return true; } if (getuid() != geteuid() || getgid() != getegid()) { wlr_log(WLR_INFO, "setuid/setgid bit detected, dropping permissions"); // 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 cg_server *server = data; switch (signal) { case SIGINT: /* Fallthrough */ case SIGTERM: server_terminate(server); 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" " -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" " -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; while ((c = getopt(argc, argv, "dhm:sv")) != -1) { switch (c) { case 'd': server->xdg_decoration = true; break; 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 '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_source *sigchld_source = NULL; pid_t pid = 0; int ret = 0, app_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; } server.display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server.wl_display, &server.display_destroy); struct wl_event_loop *event_loop = wl_display_get_event_loop(server.wl_display); struct wl_event_source *sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server); struct wl_event_source *sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server); server.backend = wlr_backend_autocreate(event_loop, &server.session); if (!server.backend) { wlr_log(WLR_ERROR, "Unable to create the wlroots backend"); ret = 1; goto end; } if (!drop_permissions()) { ret = 1; goto end; } server.renderer = wlr_renderer_autocreate(server.backend); if (!server.renderer) { wlr_log(WLR_ERROR, "Unable to create the wlroots renderer"); ret = 1; goto end; } server.allocator = wlr_allocator_autocreate(server.backend, server.renderer); if (!server.allocator) { wlr_log(WLR_ERROR, "Unable to create the wlroots allocator"); ret = 1; goto end; } wlr_renderer_init_wl_display(server.renderer, server.wl_display); wl_list_init(&server.views); wl_list_init(&server.outputs); server.output_layout = wlr_output_layout_create(server.wl_display); if (!server.output_layout) { wlr_log(WLR_ERROR, "Unable to create output layout"); ret = 1; goto end; } server.output_layout_change.notify = handle_output_layout_change; wl_signal_add(&server.output_layout->events.change, &server.output_layout_change); server.scene = wlr_scene_create(); if (!server.scene) { wlr_log(WLR_ERROR, "Unable to create scene"); ret = 1; goto end; } server.scene_output_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout); struct wlr_compositor *compositor = wlr_compositor_create(server.wl_display, 6, server.renderer); if (!compositor) { wlr_log(WLR_ERROR, "Unable to create the wlroots compositor"); ret = 1; goto end; } if (!wlr_subcompositor_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the wlroots subcompositor"); ret = 1; goto end; } if (!wlr_data_device_manager_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the data device manager"); ret = 1; goto end; } if (!wlr_primary_selection_v1_device_manager_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create primary selection 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_notifier_v1_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); struct wlr_xdg_shell *xdg_shell = wlr_xdg_shell_create(server.wl_display, 4); if (!xdg_shell) { wlr_log(WLR_ERROR, "Unable to create the XDG shell interface"); ret = 1; goto end; } server.new_xdg_toplevel.notify = handle_new_xdg_toplevel; wl_signal_add(&xdg_shell->events.new_toplevel, &server.new_xdg_toplevel); server.new_xdg_popup.notify = handle_new_xdg_popup; wl_signal_add(&xdg_shell->events.new_popup, &server.new_xdg_popup); struct wlr_xdg_decoration_manager_v1 *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; struct wlr_server_decoration_manager *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); if (!wlr_viewporter_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the viewporter interface"); ret = 1; goto end; } struct wlr_presentation *presentation = wlr_presentation_create(server.wl_display, server.backend); if (!presentation) { wlr_log(WLR_ERROR, "Unable to create the presentation interface"); ret = 1; goto end; } if (!wlr_export_dmabuf_manager_v1_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager"); ret = 1; goto end; } if (!wlr_screencopy_manager_v1_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the screencopy manager"); ret = 1; goto end; } if (!wlr_single_pixel_buffer_manager_v1_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the single pixel buffer manager"); ret = 1; goto end; } if (!wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout)) { wlr_log(WLR_ERROR, "Unable to create the output manager"); ret = 1; goto end; } server.output_manager_v1 = wlr_output_manager_v1_create(server.wl_display); if (!server.output_manager_v1) { wlr_log(WLR_ERROR, "Unable to create the output manager"); ret = 1; goto end; } server.output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&server.output_manager_v1->events.apply, &server.output_manager_apply); server.output_manager_test.notify = handle_output_manager_test; wl_signal_add(&server.output_manager_v1->events.test, &server.output_manager_test); if (!wlr_gamma_control_manager_v1_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the gamma control manager"); ret = 1; goto end; } struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard = wlr_virtual_keyboard_manager_v1_create(server.wl_display); if (!virtual_keyboard) { wlr_log(WLR_ERROR, "Unable to create the virtual keyboard manager"); ret = 1; goto end; } wl_signal_add(&virtual_keyboard->events.new_virtual_keyboard, &server.new_virtual_keyboard); struct wlr_virtual_pointer_manager_v1 *virtual_pointer = wlr_virtual_pointer_manager_v1_create(server.wl_display); if (!virtual_pointer) { wlr_log(WLR_ERROR, "Unable to create the virtual pointer manager"); ret = 1; goto end; } wl_signal_add(&virtual_pointer->events.new_virtual_pointer, &server.new_virtual_pointer); server.relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server.wl_display); if (!server.relative_pointer_manager) { wlr_log(WLR_ERROR, "Unable to create the relative pointer manager"); ret = 1; goto end; } #if CAGE_HAS_XWAYLAND struct wlr_xcursor_manager *xcursor_manager = NULL; struct wlr_xwayland *xwayland = wlr_xwayland_create(server.wl_display, compositor, true); if (!xwayland) { wlr_log(WLR_ERROR, "Cannot create XWayland server"); } else { 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 if (xwayland) { wlr_xwayland_set_seat(xwayland, server.seat->seat); } #endif if (!spawn_primary_client(&server, argv + optind, &pid, &sigchld_source)) { ret = 1; goto end; } seat_center_cursor(server.seat); 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: app_ret = cleanup_primary_client(pid); if (!ret && server.return_app_code) ret = app_ret; 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); return ret; } 0707010000000A000081A400000000000000000000000167028F860000006B000000000000000000000000000000000000002100000000cage-0.2.0+0.e128a9f/config.h.in#ifndef CG_CONFIG_H #define CG_CONFIG_H #mesondefine CAGE_HAS_XWAYLAND #mesondefine CAGE_VERSION #endif 0707010000000B000041ED00000000000000000000000267028F8600000000000000000000000000000000000000000000001D00000000cage-0.2.0+0.e128a9f/contrib0707010000000C000081ED00000000000000000000000167028F8600000225000000000000000000000000000000000000002F00000000cage-0.2.0+0.e128a9f/contrib/increment-version#!/usr/bin/env bash if [ "$#" -ne 1 ]; then echo "usage: $0 <new-version>" >&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"0707010000000D000081ED00000000000000000000000167028F860000015F000000000000000000000000000000000000002500000000cage-0.2.0+0.e128a9f/contrib/release#!/usr/bin/env bash if [ "$#" -ne 1 ]; then echo "usage: $0 <new-version>" >&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 --tags0707010000000E000081ED00000000000000000000000167028F8600000104000000000000000000000000000000000000002A00000000cage-0.2.0+0.e128a9f/contrib/sign-release#!/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" 0707010000000F000081ED00000000000000000000000167028F86000001D4000000000000000000000000000000000000002900000000cage-0.2.0+0.e128a9f/contrib/tag-release#!/usr/bin/env bash set -x if [ "$#" -ne 1 ]; then echo "usage: $0 <new-version>" >&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 - 07070100000010000081A400000000000000000000000167028F8600000799000000000000000000000000000000000000002700000000cage-0.2.0+0.e128a9f/idle_inhibit_v1.c/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include <stdlib.h> #include <wayland-server-core.h> #include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_idle_notify_v1.h> #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_notifier_v1_set_inhibited(server->idle, 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); } 07070100000011000081A400000000000000000000000167028F86000000AC000000000000000000000000000000000000002700000000cage-0.2.0+0.e128a9f/idle_inhibit_v1.h#ifndef CG_IDLE_INHIBIT_H #define CG_IDLE_INHIBIT_H #include <wayland-server-core.h> void handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data); #endif 07070100000012000081A400000000000000000000000167028F8600000EBA000000000000000000000000000000000000002100000000cage-0.2.0+0.e128a9f/meson.buildproject('cage', 'c', version: '0.2.0', license: 'MIT', meson_version: '>=0.58.1', default_options: [ 'c_std=c11', 'warning_level=2', 'werror=true', ], ) add_project_arguments( [ '-DWLR_USE_UNSTABLE', '-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-0.18', fallback: ['wlroots', 'wlroots']) wayland_protos = dependency('wayland-protocols', version: '>=1.14') wayland_server = dependency('wayland-server') xkbcommon = dependency('xkbcommon') math = cc.find_library('m') wl_protocol_dir = wayland_protos.get_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, ) have_xwayland = wlroots.get_variable(pkgconfig: 'have_xwayland', internal: 'have_xwayland') == 'true' 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'], check: false) git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) 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_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.full_path(), output) ], install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif cage_sources = [ 'cage.c', 'idle_inhibit_v1.c', 'output.c', 'seat.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', 'seat.h', 'server.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, math, ], install: true, ) summary = [ '', 'Cage @0@'.format(version), '', ' xwayland: @0@'.format(have_xwayland), '' ] message('\n'.join(summary)) 07070100000013000081A400000000000000000000000167028F8600000063000000000000000000000000000000000000002700000000cage-0.2.0+0.e128a9f/meson_options.txtoption('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 07070100000014000081A400000000000000000000000167028F8600002CDB000000000000000000000000000000000000001E00000000cage-0.2.0+0.e128a9f/output.c/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2021 Jente Hidskes * Copyright (C) 2019 The Sway authors * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200112L #include "config.h" #include <assert.h> #include <stdlib.h> #include <unistd.h> #include <wayland-server-core.h> #include <wlr/backend.h> #include <wlr/backend/wayland.h> #include <wlr/config.h> #if WLR_HAS_X11_BACKEND #include <wlr/backend/x11.h> #endif #include <wlr/render/wlr_renderer.h> #include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_matrix.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_management_v1.h> #include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/log.h> #include <wlr/util/region.h> #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif #define OUTPUT_CONFIG_UPDATED \ (WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM | \ WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) static void update_output_manager_config(struct cg_server *server) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); struct cg_output *output; wl_list_for_each (output, &server->outputs, link) { struct wlr_output *wlr_output = output->wlr_output; struct wlr_output_configuration_head_v1 *config_head = wlr_output_configuration_head_v1_create(config, wlr_output); struct wlr_box output_box; wlr_output_layout_get_box(server->output_layout, wlr_output, &output_box); if (!wlr_box_empty(&output_box)) { config_head->state.x = output_box.x; config_head->state.y = output_box.y; } } wlr_output_manager_v1_set_configuration(server->output_manager_v1, config); } static inline void output_layout_add_auto(struct cg_output *output) { assert(output->scene_output != NULL); struct wlr_output_layout_output *layout_output = wlr_output_layout_add_auto(output->server->output_layout, output->wlr_output); wlr_scene_output_layout_add_output(output->server->scene_output_layout, layout_output, output->scene_output); } static inline void output_layout_add(struct cg_output *output, int32_t x, int32_t y) { assert(output->scene_output != NULL); bool exists = wlr_output_layout_get(output->server->output_layout, output->wlr_output); struct wlr_output_layout_output *layout_output = wlr_output_layout_add(output->server->output_layout, output->wlr_output, x, y); if (exists) { return; } wlr_scene_output_layout_add_output(output->server->scene_output_layout, layout_output, output->scene_output); } static inline void output_layout_remove(struct cg_output *output) { wlr_output_layout_remove(output->server->output_layout, output->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); struct wlr_output_state state = {0}; wlr_output_state_set_enabled(&state, true); if (wlr_output_commit_state(wlr_output, &state)) { output_layout_add_auto(output); } update_output_manager_config(output->server); } 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); struct wlr_output_state state = {0}; wlr_output_state_set_enabled(&state, false); wlr_output_commit_state(wlr_output, &state); output_layout_remove(output); } static bool output_apply_config(struct cg_output *output, struct wlr_output_configuration_head_v1 *head, bool test_only) { struct wlr_output_state state = {0}; wlr_output_head_v1_state_apply(&head->state, &state); if (test_only) { bool ret = wlr_output_test_state(output->wlr_output, &state); return ret; } /* Apply output configuration */ if (!wlr_output_commit_state(output->wlr_output, &state)) { return false; } if (head->state.enabled) { output_layout_add(output, head->state.x, head->state.y); } else { output_layout_remove(output); } return true; } static void handle_output_frame(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, frame); if (!output->wlr_output->enabled || !output->scene_output) { return; } wlr_scene_output_commit(output->scene_output, NULL); struct timespec now = {0}; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(output->scene_output, &now); } 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; /* Notes: * - output layout change will also be called if needed to position the views * - always update output manager configuration even if the output is now disabled */ if (event->state->committed & OUTPUT_CONFIG_UPDATED) { update_output_manager_config(output->server); } } static void handle_output_request_state(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, request_state); struct wlr_output_event_request_state *event = data; if (wlr_output_commit_state(output->wlr_output, event->state)) { update_output_manager_config(output->server); } } void handle_output_layout_change(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, output_layout_change); view_position_all(server); update_output_manager_config(server); } static bool is_nested_output(struct cg_output *output) { if (wlr_output_is_wl(output->wlr_output)) { return true; } #if WLR_HAS_X11_BACKEND if (wlr_output_is_x11(output->wlr_output)) { return true; } #endif return false; } static void output_destroy(struct cg_output *output) { struct cg_server *server = output->server; bool was_nested_output = is_nested_output(output); output->wlr_output->data = NULL; wl_list_remove(&output->destroy.link); wl_list_remove(&output->commit.link); wl_list_remove(&output->request_state.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->link); output_layout_remove(output); free(output); if (wl_list_empty(&server->outputs) && was_nested_output) { server_terminate(server); } else if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST && !wl_list_empty(&server->outputs)) { struct cg_output *prev = wl_container_of(server->outputs.next, prev, link); output_enable(prev); view_position_all(server); } } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, destroy); 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; if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) { wlr_log(WLR_ERROR, "Failed to initialize output rendering"); return; } 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; wlr_output->data = output; output->server = server; wl_list_insert(&server->outputs, &output->link); output->commit.notify = handle_output_commit; wl_signal_add(&wlr_output->events.commit, &output->commit); output->request_state.notify = handle_output_request_state; wl_signal_add(&wlr_output->events.request_state, &output->request_state); output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->frame.notify = handle_output_frame; wl_signal_add(&wlr_output->events.frame, &output->frame); output->scene_output = wlr_scene_output_create(server->scene, wlr_output); if (!output->scene_output) { wlr_log(WLR_ERROR, "Failed to allocate scene output"); return; } struct wlr_output_state state = {0}; wlr_output_state_set_enabled(&state, true); if (!wl_list_empty(&wlr_output->modes)) { struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); if (preferred_mode) { wlr_output_state_set_mode(&state, preferred_mode); } if (!wlr_output_test_state(wlr_output, &state)) { struct wlr_output_mode *mode; wl_list_for_each (mode, &wlr_output->modes, link) { if (mode == preferred_mode) { continue; } wlr_output_state_set_mode(&state, mode); if (wlr_output_test_state(wlr_output, &state)) { break; } } } } if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST && wl_list_length(&server->outputs) > 1) { struct cg_output *next = wl_container_of(output->link.next, next, link); 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); } wlr_log(WLR_DEBUG, "Enabling new output %s", wlr_output->name); if (wlr_output_commit_state(wlr_output, &state)) { output_layout_add_auto(output); } view_position_all(output->server); update_output_manager_config(output->server); } 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 } } static bool output_config_apply(struct cg_server *server, struct wlr_output_configuration_v1 *config, bool test_only) { struct wlr_output_configuration_head_v1 *head; wl_list_for_each (head, &config->heads, link) { struct cg_output *output = head->state.output->data; if (!output_apply_config(output, head, test_only)) { return false; } } return true; } void handle_output_manager_apply(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, output_manager_apply); struct wlr_output_configuration_v1 *config = data; if (output_config_apply(server, config, false)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); } wlr_output_configuration_v1_destroy(config); } void handle_output_manager_test(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, output_manager_test); struct wlr_output_configuration_v1 *config = data; if (output_config_apply(server, config, true)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); } wlr_output_configuration_v1_destroy(config); } 07070100000015000081A400000000000000000000000167028F860000032B000000000000000000000000000000000000001E00000000cage-0.2.0+0.e128a9f/output.h#ifndef CG_OUTPUT_H #define CG_OUTPUT_H #include <wayland-server-core.h> #include <wlr/types/wlr_output.h> #include "server.h" #include "view.h" struct cg_output { struct cg_server *server; struct wlr_output *wlr_output; struct wlr_scene_output *scene_output; struct wl_listener commit; struct wl_listener request_state; struct wl_listener destroy; struct wl_listener frame; struct wl_list link; // cg_server::outputs }; void handle_output_manager_apply(struct wl_listener *listener, void *data); void handle_output_manager_test(struct wl_listener *listener, void *data); void handle_output_layout_change(struct wl_listener *listener, void *data); void handle_new_output(struct wl_listener *listener, void *data); void output_set_window_title(struct cg_output *output, const char *title); #endif 07070100000016000081A400000000000000000000000167028F86000077FB000000000000000000000000000000000000001C00000000cage-0.2.0+0.e128a9f/seat.c/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200809L #include "config.h" #include <assert.h> #include <linux/input-event-codes.h> #include <stdlib.h> #include <string.h> #include <wayland-server-core.h> #include <wlr/backend.h> #include <wlr/backend/multi.h> #include <wlr/backend/session.h> #include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_idle_notify_v1.h> #include <wlr/types/wlr_keyboard_group.h> #include <wlr/types/wlr_primary_selection.h> #include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_seat.h> #include <wlr/types/wlr_touch.h> #include <wlr/types/wlr_virtual_keyboard_v1.h> #include <wlr/types/wlr_virtual_pointer_v1.h> #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/util/log.h> #if CAGE_HAS_XWAYLAND #include <wlr/xwayland.h> #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. * * This function iterates over all of our surfaces and attempts to find one * under the cursor. 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 wlr_scene_node *node = wlr_scene_node_at(&server->scene->tree.node, lx, ly, sx, sy); if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) { return NULL; } struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer); if (!scene_surface) { return NULL; } *surface = scene_surface->surface; /* Walk up the tree until we find a node with a data pointer. When done, * we've found the node representing the view. */ while (!node->data) { if (!node->parent) { node = NULL; break; } node = &node->parent->node; } assert(node != NULL); return node->data; } 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_unset_image(seat->cursor); } else { wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, DEFAULT_XCURSOR); } } static void map_input_device_to_output(struct cg_seat *seat, struct wlr_input_device *device, const char *output_name) { if (!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(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->touch->base); wl_list_remove(&touch->destroy.link); free(touch); update_capabilities(seat); } static void handle_new_touch(struct cg_seat *seat, struct wlr_touch *wlr_touch) { struct cg_touch *touch = calloc(1, sizeof(struct cg_touch)); if (!touch) { wlr_log(WLR_ERROR, "Cannot allocate touch"); return; } touch->seat = seat; touch->touch = wlr_touch; wlr_cursor_attach_input_device(seat->cursor, &wlr_touch->base); wl_list_insert(&seat->touch, &touch->link); touch->destroy.notify = handle_touch_destroy; wl_signal_add(&wlr_touch->base.events.destroy, &touch->destroy); map_input_device_to_output(seat, &wlr_touch->base, wlr_touch->output_name); } 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->pointer->base); wl_list_remove(&pointer->destroy.link); free(pointer); update_capabilities(seat); } static void handle_new_pointer(struct cg_seat *seat, struct wlr_pointer *wlr_pointer) { struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); if (!pointer) { wlr_log(WLR_ERROR, "Cannot allocate pointer"); return; } pointer->seat = seat; pointer->pointer = wlr_pointer; wlr_cursor_attach_input_device(seat->cursor, &wlr_pointer->base); wl_list_insert(&seat->pointers, &pointer->link); pointer->destroy.notify = handle_pointer_destroy; wl_signal_add(&wlr_pointer->base.events.destroy, &pointer->destroy); map_input_device_to_output(seat, &wlr_pointer->base, wlr_pointer->output_name); } static void handle_virtual_pointer(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_virtual_pointer); struct cg_seat *seat = server->seat; struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; struct wlr_pointer *wlr_pointer = &pointer->pointer; /* We'll want to map the device back to an output later, this is a bit * sub-optimal (we could just keep the suggested_output), but just copy * its name so we do like other devices */ if (event->suggested_output != NULL) { wlr_pointer->output_name = strdup(event->suggested_output->name); } /* TODO: event->suggested_seat should be checked if we handle multiple seats */ handle_new_pointer(seat, wlr_pointer); update_capabilities(seat); } static void handle_modifier_event(struct wlr_keyboard *keyboard, struct cg_seat *seat) { wlr_seat_set_keyboard(seat->seat, keyboard); wlr_seat_keyboard_notify_modifiers(seat->seat, &keyboard->modifiers); wlr_idle_notifier_v1_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) { server_terminate(server); 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)) { if (server->session) { unsigned vt = sym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(server->session, vt); } } } else { return false; } wlr_idle_notifier_v1_notify_activity(server->idle, server->seat->seat); return true; } static void handle_key_event(struct wlr_keyboard *keyboard, struct cg_seat *seat, void *data) { struct wlr_keyboard_key_event *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(keyboard->xkb_state, keycode, &syms); bool handled = false; uint32_t modifiers = wlr_keyboard_get_modifiers(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, keyboard); wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state); } wlr_idle_notifier_v1_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->keyboard, 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->keyboard, group->seat); } static void cg_keyboard_group_add(struct wlr_keyboard *keyboard, struct cg_seat *seat, bool virtual) { /* We apparently should not group virtual keyboards, * so create a new group with it */ if (!virtual) { struct cg_keyboard_group *group; wl_list_for_each (group, &seat->keyboard_groups, link) { if (group->is_virtual) continue; struct wlr_keyboard_group *wlr_group = group->wlr_group; if (wlr_keyboard_group_add_keyboard(wlr_group, 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->is_virtual = virtual; 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, keyboard->keymap); wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, keyboard->repeat_info.rate, keyboard->repeat_info.delay); wlr_log(WLR_DEBUG, "Created keyboard group"); wlr_keyboard_group_add_keyboard(cg_group->wlr_group, 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_keyboard *keyboard, bool virtual) { struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!context) { wlr_log(WLR_ERROR, "Unable to create XKB context"); return; } struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, 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(keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(keyboard, 25, 600); cg_keyboard_group_add(keyboard, seat, virtual); wlr_seat_set_keyboard(seat->seat, keyboard); } static void handle_virtual_keyboard(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_virtual_keyboard); struct cg_seat *seat = server->seat; struct wlr_virtual_keyboard_v1 *keyboard = data; struct wlr_keyboard *wlr_keyboard = &keyboard->keyboard; /* TODO: If multiple seats are supported, check keyboard->seat * to select the appropriate one */ handle_new_keyboard(seat, wlr_keyboard, true); update_capabilities(seat); } 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, wlr_keyboard_from_input_device(device), false); break; case WLR_INPUT_DEVICE_POINTER: handle_new_pointer(seat, wlr_pointer_from_input_device(device)); break; case WLR_INPUT_DEVICE_TOUCH: handle_new_touch(seat, wlr_touch_from_input_device(device)); break; case WLR_INPUT_DEVICE_SWITCH: wlr_log(WLR_DEBUG, "Switch input is not implemented"); return; case WLR_INPUT_DEVICE_TABLET: 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_touch_down_event *event = data; double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, 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->touch->base, event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly); } wlr_idle_notifier_v1_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_touch_up_event *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->touch->base, 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_notifier_v1_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_touch_motion_event *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->touch->base, 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_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_frame(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_frame); wlr_seat_touch_notify_frame(seat->seat); wlr_idle_notifier_v1_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_notifier_v1_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_pointer_axis_event *event = data; wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, event->orientation, event->delta, event->delta_discrete, event->source, event->relative_direction); wlr_idle_notifier_v1_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_pointer_button_event *event = data; wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->state); press_cursor_button(seat, &event->pointer->base, event->time_msec, event->button, event->state, seat->cursor->x, seat->cursor->y); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, double dy, double dx_unaccel, double dy_unaccel) { 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); wlr_seat_pointer_notify_motion(wlr_seat, time_msec, sx, sy); } if (dx != 0 || dy != 0) { wlr_relative_pointer_manager_v1_send_relative_motion(seat->server->relative_pointer_manager, wlr_seat, (uint64_t) time_msec * 1000, dx, dy, dx_unaccel, dy_unaccel); } struct cg_drag_icon *drag_icon; wl_list_for_each (drag_icon, &seat->drag_icons, link) { drag_icon_update_position(drag_icon); } wlr_idle_notifier_v1_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_pointer_motion_absolute_event *event = data; double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->pointer->base, event->x, event->y, &lx, &ly); double dx = lx - seat->cursor->x; double dy = ly - seat->cursor->y; wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x, event->y); process_cursor_motion(seat, event->time_msec, dx, dy, dx, dy); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_motion_relative(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_relative); struct wlr_pointer_motion_event *event = data; wlr_cursor_move(seat->cursor, &event->pointer->base, event->delta_x, event->delta_y); process_cursor_motion(seat, event->time_msec, event->delta_x, event->delta_y, event->unaccel_dx, event->unaccel_dy); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } 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; 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; } wlr_scene_node_set_position(&drag_icon->scene_tree->node, drag_icon->lx, drag_icon->ly); } 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); wlr_scene_node_destroy(&drag_icon->scene_tree->node); 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->scene_tree = wlr_scene_subsurface_tree_create(&seat->server->scene->tree, wlr_drag_icon->surface); if (!drag_icon->scene_tree) { free(drag_icon); return; } 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_relative.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->touch_frame.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_relative.notify = handle_cursor_motion_relative; wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion_relative); 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->touch_frame.notify = handle_touch_frame; wl_signal_add(&seat->cursor->events.touch_frame, &seat->touch_frame); 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); server->new_virtual_keyboard.notify = handle_virtual_keyboard; server->new_virtual_pointer.notify = handle_virtual_pointer; 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; if (!prev_surface) { return NULL; } return view_from_wlr_surface(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, 0, 0, 0, 0); } void seat_center_cursor(struct cg_seat *seat) { /* Place the cursor in the center of the output layout. */ struct wlr_box layout_box; wlr_output_layout_get_box(seat->server->output_layout, NULL, &layout_box); wlr_cursor_warp(seat->cursor, NULL, layout_box.width / 2, layout_box.height / 2); } 07070100000017000081A400000000000000000000000167028F8600000936000000000000000000000000000000000000001C00000000cage-0.2.0+0.e128a9f/seat.h#ifndef CG_SEAT_H #define CG_SEAT_H #include <wayland-server-core.h> #include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_seat.h> #include <wlr/types/wlr_xcursor_manager.h> #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_relative; 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_listener touch_frame; 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 bool is_virtual; }; struct cg_pointer { struct wl_list link; // seat::pointers struct cg_seat *seat; struct wlr_pointer *pointer; struct wl_listener destroy; }; struct cg_touch { struct wl_list link; // seat::touch struct cg_seat *seat; struct wlr_touch *touch; 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; struct wlr_scene_tree *scene_tree; /* 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); void seat_center_cursor(struct cg_seat *seat); #endif 07070100000018000081A400000000000000000000000167028F860000075F000000000000000000000000000000000000001E00000000cage-0.2.0+0.e128a9f/server.h#ifndef CG_SERVER_H #define CG_SERVER_H #include "config.h" #include <wayland-server-core.h> #include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_idle_notify_v1.h> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_xdg_decoration_v1.h> #if CAGE_HAS_XWAYLAND #include <wlr/xwayland.h> #endif 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 wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_session *session; struct wl_listener display_destroy; struct cg_seat *seat; struct wlr_idle_notifier_v1 *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; struct wlr_scene_output_layout *scene_output_layout; struct wlr_scene *scene; /* 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 output_layout_change; struct wl_listener xdg_toplevel_decoration; struct wl_listener new_xdg_toplevel; struct wl_listener new_xdg_popup; struct wl_listener new_virtual_keyboard; struct wl_listener new_virtual_pointer; #if CAGE_HAS_XWAYLAND struct wl_listener new_xwayland_surface; #endif struct wlr_output_manager_v1 *output_manager_v1; struct wl_listener output_manager_apply; struct wl_listener output_manager_test; struct wlr_relative_pointer_manager_v1 *relative_pointer_manager; bool xdg_decoration; bool allow_vt_switch; bool return_app_code; bool terminated; }; void server_terminate(struct cg_server *server); #endif 07070100000019000081A400000000000000000000000167028F8600000F48000000000000000000000000000000000000001C00000000cage-0.2.0+0.e128a9f/view.c/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2021 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <wayland-server-core.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_scene.h> #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif 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_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; if (view->scene_tree) { wlr_scene_node_set_position(&view->scene_tree->node, view->lx, view->ly); } 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; if (view->scene_tree) { wlr_scene_node_set_position(&view->scene_tree->node, view->lx, view->ly); } } void view_position(struct cg_view *view) { struct wlr_box layout_box; wlr_output_layout_get_box(view->server->output_layout, NULL, &layout_box); 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_position_all(struct cg_server *server) { struct cg_view *view; wl_list_for_each (view, &server->views, link) { view_position(view); } } void view_unmap(struct cg_view *view) { wl_list_remove(&view->link); wlr_scene_node_destroy(&view->scene_tree->node); view->wlr_surface->data = NULL; view->wlr_surface = NULL; } void view_map(struct cg_view *view, struct wlr_surface *surface) { view->scene_tree = wlr_scene_subsurface_tree_create(&view->server->scene->tree, surface); if (!view->scene_tree) { wl_resource_post_no_memory(surface->resource); return; } view->scene_tree->node.data = view; view->wlr_surface = surface; surface->data = view; #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; } struct cg_view * view_from_wlr_surface(struct wlr_surface *surface) { assert(surface); return surface->data; } 0707010000001A000081A400000000000000000000000167028F86000006E5000000000000000000000000000000000000001C00000000cage-0.2.0+0.e128a9f/view.h#ifndef CG_VIEW_H #define CG_VIEW_H #include "config.h" #include <stdbool.h> #include <wayland-server-core.h> #include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/box.h> #if CAGE_HAS_XWAYLAND #include <wlr/xwayland.h> #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 wlr_surface *wlr_surface; struct wlr_scene_tree *scene_tree; /* The view has a position in layout coordinates. */ int lx, ly; enum cg_view_type type; const struct cg_view_impl *impl; }; 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); }; 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_activate(struct cg_view *view, bool activate); void view_position(struct cg_view *view); void view_position_all(struct cg_server *server); 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 wlr_surface *surface); #endif 0707010000001B000081A400000000000000000000000167028F8600002D63000000000000000000000000000000000000002100000000cage-0.2.0+0.e128a9f/xdg_shell.c/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include <assert.h> #include <stdbool.h> #include <stdlib.h> #include <wayland-server-core.h> #include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/log.h> #include "server.h" #include "view.h" #include "xdg_shell.h" static void xdg_decoration_set_mode(struct cg_xdg_decoration *xdg_decoration) { 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_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->commit.link); wl_list_remove(&xdg_decoration->request_mode.link); free(xdg_decoration); } static void xdg_decoration_handle_commit(struct wl_listener *listener, void *data) { struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, commit); if (xdg_decoration->wlr_decoration->toplevel->base->initial_commit) { xdg_decoration_set_mode(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); if (xdg_decoration->wlr_decoration->toplevel->base->initialized) { xdg_decoration_set_mode(xdg_decoration); } } static struct cg_view * popup_get_view(struct wlr_xdg_popup *popup) { while (true) { if (popup->parent == NULL) { return NULL; } struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(popup->parent); if (xdg_surface == NULL) { return NULL; } switch (xdg_surface->role) { case WLR_XDG_SURFACE_ROLE_TOPLEVEL: return xdg_surface->data; case WLR_XDG_SURFACE_ROLE_POPUP: popup = xdg_surface->popup; break; case WLR_XDG_SURFACE_ROLE_NONE: return NULL; } } } static void popup_unconstrain(struct wlr_xdg_popup *popup) { struct cg_view *view = popup_get_view(popup); if (view == NULL) { return; } struct cg_server *server = view->server; struct wlr_box *popup_box = &popup->current.geometry; struct wlr_output_layout *output_layout = server->output_layout; struct wlr_output *wlr_output = wlr_output_layout_output_at(output_layout, view->lx + popup_box->x, view->ly + popup_box->y); struct wlr_box output_box; wlr_output_layout_get_box(output_layout, wlr_output, &output_box); 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, &output_toplevel_box); } 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_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_toplevel->base, &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_toplevel *parent = xdg_shell_view->xdg_toplevel->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_xdg_shell_view *_child = xdg_shell_view_from_view(child); struct wlr_xdg_toplevel *xdg_toplevel = _child->xdg_toplevel; struct cg_xdg_shell_view *_parent = xdg_shell_view_from_view(parent); while (xdg_toplevel) { if (xdg_toplevel->parent == _parent->xdg_toplevel) { return true; } xdg_toplevel = xdg_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_toplevel, 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_toplevel, output_width, output_height); wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_toplevel, 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 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); /** * Certain clients do not like figuring out their own window geometry if they * display in fullscreen mode, so we set it here. */ struct wlr_box layout_box; wlr_output_layout_get_box(xdg_shell_view->view.server->output_layout, NULL, &layout_box); wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_toplevel, layout_box.width, layout_box.height); wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_toplevel, xdg_shell_view->xdg_toplevel->requested.fullscreen); } 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_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; view_map(view, xdg_shell_view->xdg_toplevel->base->surface); } 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); if (!xdg_shell_view->xdg_toplevel->base->initial_commit) { return; } /* When an xdg_surface performs an initial commit, the compositor must * reply with a configure so the client can map the surface. */ view_position(&xdg_shell_view->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->commit.link); 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); xdg_shell_view->xdg_toplevel = 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, }; void handle_new_xdg_toplevel(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_xdg_toplevel); struct wlr_xdg_toplevel *toplevel = data; 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_toplevel = toplevel; xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; wl_signal_add(&toplevel->base->surface->events.commit, &xdg_shell_view->commit); xdg_shell_view->map.notify = handle_xdg_shell_surface_map; wl_signal_add(&toplevel->base->surface->events.map, &xdg_shell_view->map); xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap; wl_signal_add(&toplevel->base->surface->events.unmap, &xdg_shell_view->unmap); xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy; wl_signal_add(&toplevel->events.destroy, &xdg_shell_view->destroy); xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen; wl_signal_add(&toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen); toplevel->base->data = xdg_shell_view; } static void popup_handle_destroy(struct wl_listener *listener, void *data) { struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->commit.link); free(popup); } static void popup_handle_commit(struct wl_listener *listener, void *data) { struct cg_xdg_popup *popup = wl_container_of(listener, popup, commit); if (popup->xdg_popup->base->initial_commit) { popup_unconstrain(popup->xdg_popup); } } void handle_new_xdg_popup(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_xdg_popup); struct wlr_xdg_popup *wlr_popup = data; struct cg_view *view = popup_get_view(wlr_popup); if (view == NULL) { return; } struct wlr_scene_tree *parent_scene_tree = NULL; struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(wlr_popup->parent); if (parent == NULL) { return; } switch (parent->role) { case WLR_XDG_SURFACE_ROLE_TOPLEVEL:; parent_scene_tree = view->scene_tree; break; case WLR_XDG_SURFACE_ROLE_POPUP: parent_scene_tree = parent->data; break; case WLR_XDG_SURFACE_ROLE_NONE: break; } if (parent_scene_tree == NULL) { return; } struct cg_xdg_popup *popup = calloc(1, sizeof(*popup)); if (popup == NULL) { wlr_log(WLR_ERROR, "Failed to allocate popup"); return; } popup->xdg_popup = wlr_popup; popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); popup->commit.notify = popup_handle_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); struct wlr_scene_tree *popup_scene_tree = wlr_scene_xdg_surface_create(parent_scene_tree, wlr_popup->base); if (popup_scene_tree == NULL) { wlr_log(WLR_ERROR, "Failed to allocate scene-graph node for XDG popup"); free(popup); return; } wlr_popup->base->data = popup_scene_tree; } 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->commit.notify = xdg_decoration_handle_commit; wl_signal_add(&wlr_decoration->toplevel->base->surface->events.commit, &xdg_decoration->commit); xdg_decoration->request_mode.notify = xdg_decoration_handle_request_mode; wl_signal_add(&wlr_decoration->events.request_mode, &xdg_decoration->request_mode); } 0707010000001C000081A400000000000000000000000167028F86000003D1000000000000000000000000000000000000002100000000cage-0.2.0+0.e128a9f/xdg_shell.h#ifndef CG_XDG_SHELL_H #define CG_XDG_SHELL_H #include <wayland-server-core.h> #include <wlr/types/wlr_xdg_decoration_v1.h> #include <wlr/types/wlr_xdg_shell.h> #include "view.h" struct cg_xdg_shell_view { struct cg_view view; struct wlr_xdg_toplevel *xdg_toplevel; struct wl_listener destroy; struct wl_listener commit; struct wl_listener unmap; struct wl_listener map; struct wl_listener request_fullscreen; }; struct cg_xdg_decoration { struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; struct cg_server *server; struct wl_listener destroy; struct wl_listener commit; struct wl_listener request_mode; }; struct cg_xdg_popup { struct wlr_xdg_popup *xdg_popup; struct wl_listener destroy; struct wl_listener commit; }; void handle_new_xdg_toplevel(struct wl_listener *listener, void *data); void handle_new_xdg_popup(struct wl_listener *listener, void *data); void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data); #endif 0707010000001D000081A400000000000000000000000167028F8600001949000000000000000000000000000000000000002000000000cage-0.2.0+0.e128a9f/xwayland.c/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include <stdbool.h> #include <stdlib.h> #include <wayland-server-core.h> #include <wlr/util/log.h> #include <wlr/xwayland.h> #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); struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface; if (xsurface->surface == NULL) { *width_out = 0; *height_out = 0; return; } *width_out = xsurface->surface->current.width; *height_out = xsurface->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 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_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_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; } view_map(view, xwayland_view->xwayland_surface->surface); } 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->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, }; void handle_xwayland_associate(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, associate); struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface; xwayland_view->map.notify = handle_xwayland_surface_map; wl_signal_add(&xsurface->surface->events.map, &xwayland_view->map); xwayland_view->unmap.notify = handle_xwayland_surface_unmap; wl_signal_add(&xsurface->surface->events.unmap, &xwayland_view->unmap); } void handle_xwayland_dissociate(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, dissociate); wl_list_remove(&xwayland_view->map.link); wl_list_remove(&xwayland_view->unmap.link); } 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->associate.notify = handle_xwayland_associate; wl_signal_add(&xwayland_surface->events.associate, &xwayland_view->associate); xwayland_view->dissociate.notify = handle_xwayland_dissociate; wl_signal_add(&xwayland_surface->events.dissociate, &xwayland_view->dissociate); 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); } 0707010000001E000081A400000000000000000000000167028F860000026C000000000000000000000000000000000000002000000000cage-0.2.0+0.e128a9f/xwayland.h#ifndef CG_XWAYLAND_H #define CG_XWAYLAND_H #include <wayland-server-core.h> #include <wlr/xwayland.h> #include "view.h" struct cg_xwayland_view { struct cg_view view; struct wlr_xwayland_surface *xwayland_surface; struct wl_listener destroy; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener unmap; struct wl_listener map; 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 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!217 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor