diff --git a/.builds/alpine.yml b/.builds/alpine.yml deleted file mode 100644 index 2c6d089..0000000 --- a/.builds/alpine.yml +++ /dev/null @@ -1,28 +0,0 @@ -image: alpine/edge -packages: - - eudev-dev - - mesa-dev - - meson - - libinput-dev - - libxkbcommon-dev - - pixman-dev - - wayland-dev - - wayland-protocols - - xorg-server-xwayland -sources: - - https://github.com/swaywm/wlroots - - https://github.com/Hjdskes/cage -tasks: - # Install wlroots, which is required by Cage. Note that we compile a tagged - # version, instead of master, to avoid any breaking changes in wlroots. - - wlroots: | - cd wlroots - # This corresponds to the tag of 0.6.0 - git checkout c0305f4f864543f8c3fea6f302e91c9b1d3396f3 - meson --prefix=/usr build -Drootston=false -Dexamples=false - ninja -C build - sudo ninja -C build install - - build: | - cd cage - meson build -Dxwayland=true - ninja -C build diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml deleted file mode 100644 index e052118..0000000 --- a/.builds/archlinux.yml +++ /dev/null @@ -1,30 +0,0 @@ -image: archlinux -packages: - - clang - - meson - - libinput - - libxkbcommon - - wayland - - wayland-protocols - - xorg-server-xwayland -sources: - - https://github.com/swaywm/wlroots - - https://github.com/Hjdskes/cage -tasks: - # Install wlroots, which is required by Cage. Note that we compile a tagged - # version, instead of master, to avoid any breaking changes in wlroots. - - wlroots: | - cd wlroots - # This corresponds to the tag of 0.6.0 - git checkout c0305f4f864543f8c3fea6f302e91c9b1d3396f3 - meson --prefix=/usr build -Drootston=false -Dexamples=false - ninja -C build - sudo ninja -C build install - - build: | - cd cage - meson build -Dxwayland=true - ninja -C build - - scan-build: | - cd cage - CC=clang meson build -Dxwayland=true - CC=clang ninja -C build scan-build diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml deleted file mode 100644 index 274eaaa..0000000 --- a/.builds/freebsd.yml +++ /dev/null @@ -1,29 +0,0 @@ -image: freebsd/latest -packages: - - devel/evdev-proto - - devel/meson - - devel/libepoll-shim - - devel/pkgconf - - graphics/mesa-libs - - graphics/wayland - - graphics/wayland-protocols - - x11/libinput - - x11/libxkbcommon - - x11/pixman -sources: - - https://github.com/swaywm/wlroots - - https://github.com/Hjdskes/cage -tasks: - # Install wlroots, which is required by Cage. Note that we compile a tagged - # version, instead of master, to avoid any breaking changes in wlroots. - - wlroots: | - cd wlroots - # This corresponds to the tag of 0.6.0 - git checkout c0305f4f864543f8c3fea6f302e91c9b1d3396f3 - meson --prefix=/usr/local build -Drootston=false -Dexamples=false - ninja -C build - sudo ninja -C build install - - build: | - cd cage - PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build -Dxwayland=true - PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..49b64c2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +AlignAfterOpenBracket: 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 diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000..60dd059 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +subprojects/**/* \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e9e7800 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = 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 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..31693bf --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,81 @@ +name: Continuous integration build +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + WLROOTS_VERSION: 0.19 + +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 libdisplay-info-dev + + - 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 libdisplay-info + + - name: Fetch wlroots as a subproject + run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b $WLROOTS_VERSION + + - name: Compile Cage (XWayland=${{ matrix.xwayland }}) + run: | + meson --fatal-meson-warnings --wrap-mode=nodownload \ + 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 libdisplay-info + - name: Fetch wlroots as a subproject + run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b $WLROOTS_VERSION + - name: Check for formatting changes + run: | + meson --wrap-mode=nodownload 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 libdisplay-info + - name: Fetch wlroots as a subproject + run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b $WLROOTS_VERSION + - name: Run scan-build + run: | + meson --wrap-mode=nodownload build-scan-build -Dwlroots:xwayland=enabled + ninja -C build-scan-build scan-build diff --git a/LICENSE b/LICENSE index 41a8d9f..b047cf6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Copyright (c) 2018-2019 Jente Hidskes +Copyright (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 diff --git a/README.md b/README.md index 280034e..d285219 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,61 @@ -# Cage: a Wayland kiosk [![builds.sr.ht status](https://builds.sr.ht/~hjdskes.svg)](https://builds.sr.ht/~hjdskes?) +# Cage: a Wayland kiosk Cage's logo This is Cage, a Wayland kiosk. A kiosk runs a single, maximized application. -This README is only relevant for development resources and -instructions. For a description of Cage and installation instructions -for end-users, please see [its project -page](https://hjdskes.nl/projects/cage). +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/). +See [the man page](./cage.1.scd) for a list of possible environment variables and run options. ## Release signatures -Releases are signed with -[6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1) -and published on [GitHub](https://github.com/Hjdskes/cage/releases). +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. Note that Cage is -developed against the latest tag of wlroots, in order to not constantly chase -breaking changes as soon as they occur. +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 build -$ ninja -C build +$ meson setup build +$ meson compile -C build ``` -Cage comes with compile-time support for XWayland. To enable this, -first make sure that your version of wlroots is compiled with this -option. Then, add `-Dxwayland=true` to the `meson` command above. Note -that you'll need to have the XWayland binary installed on your system -for this to work. +By default, this builds a debug build. To build a release build, use `meson +setup build --buildtype=release`. -You can run Cage by running `./build/cage APPLICATION`. If you run it -from within an existing X11 or Wayland session, it will open in a -virtual output as a window in your existing session. If you run it at -a TTY, it'll run with the KMS+DRM backend. In debug mode (default -build type with Meson), press Alt+Esc to quit. To build a release -build, use `meson 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 +Alt+Esc 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/Hjdskes/cage/issues/new) on -[GitHub](https://github.com/Hjdskes/cage). +issue](https://github.com/cage-kiosk/cage/issues/new) on +[GitHub](https://github.com/cage-kiosk/cage). ## License Please see -[LICENSE](https://github.com/Hjdskes/cage/blob/master/LICENSE) on -[GitHub](https://github.com/Hjdskes/cage). +[LICENSE](https://github.com/cage-kiosk/cage/blob/master/LICENSE) on +[GitHub](https://github.com/cage-kiosk/cage). -Copyright © 2018-2019 Jente Hidskes +Copyright © 2018-2020 Jente Hidskes diff --git a/cage.1.scd b/cage.1.scd new file mode 100644 index 0000000..ed1f518 --- /dev/null +++ b/cage.1.scd @@ -0,0 +1,71 @@ +cage(1) + +# NAME + +cage - a Wayland kiosk compositor + +# SYNOPSIS + +*cage* [options...] [--] [_application_...] + +# 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. + +*-D* + Enable debug logging. + +*-h* + Show the help message. + +*-m* + Set the multi-monitor behavior. Supported modes are: + *last* Cage uses only the last connected monitor. + *extend* Cage extends the display across all connected monitors. + +*-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 diff --git a/cage.c b/cage.c index 9c7098a..9b7c510 100644 --- a/cage.c +++ b/cage.c @@ -1,7 +1,7 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018-2019 Jente Hidskes + * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ @@ -10,25 +10,41 @@ #include "config.h" +#include #include #include #include #include #include #include -#include +#include #include +#include #include #include #include -#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #if CAGE_HAS_XWAYLAND #include #endif #include +#include #include #include #if CAGE_HAS_XWAYLAND @@ -45,39 +61,143 @@ #include "xwayland.h" #endif -static bool -spawn_primary_client(char *argv[], pid_t *pid_out) +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()) { - if (setuid(getuid()) != 0 || setgid(getgid()) != 0) { + 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 (setuid(0) != -1) { - wlr_log(WLR_ERROR, "Unable to drop root (we shouldn't be able to " - "restore it after setuid), refusing to start"); + 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; } @@ -87,30 +207,32 @@ drop_permissions(void) static int handle_signal(int signal, void *data) { - struct wl_display *display = data; + struct cg_server *server = data; switch (signal) { case SIGINT: /* Fallthrough */ case SIGTERM: - wl_display_terminate(display); + server_terminate(server); return 0; default: - return 1; + return 0; } } static void usage(FILE *file, const char *cage) { - fprintf(file, "Usage: %s [OPTIONS] [--] APPLICATION\n" + fprintf(file, + "Usage: %s [OPTIONS] [--] [APPLICATION...]\n" "\n" " -d\t Don't draw client side decorations, when possible\n" - " -r\t Rotate the output 90 degrees clockwise, specify up to three times\n" -#ifdef DEBUG - " -D\t Turn on damage tracking debugging\n" -#endif + " -D\t Enable debug logging\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); @@ -120,71 +242,62 @@ static bool parse_args(struct cg_server *server, int argc, char *argv[]) { int c; -#ifdef DEBUG - while ((c = getopt(argc, argv, "drDh")) != -1) { -#else - while ((c = getopt(argc, argv, "drh")) != -1) { -#endif + while ((c = getopt(argc, argv, "dDhm:sv")) != -1) { switch (c) { case 'd': server->xdg_decoration = true; break; - case 'r': - server->output_transform++; - if (server->output_transform > WL_OUTPUT_TRANSFORM_270) { - server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL; - } - break; -#ifdef DEBUG case 'D': - server->debug_damage_tracking = true; + server->log_level = WLR_DEBUG; break; -#endif case 'h': usage(stdout, argv[0]); return false; + case 'm': + if (strcmp(optarg, "last") == 0) { + server->output_mode = CAGE_MULTI_OUTPUT_MODE_LAST; + } else if (strcmp(optarg, "extend") == 0) { + server->output_mode = CAGE_MULTI_OUTPUT_MODE_EXTEND; + } + break; + case 's': + server->allow_vt_switch = true; + break; + case 'v': + fprintf(stdout, "Cage version " CAGE_VERSION "\n"); + exit(0); default: usage(stderr, argv[0]); return false; } } - if (optind >= argc) { - usage(stderr, argv[0]); - return false; - } - return true; } int main(int argc, char *argv[]) { - struct cg_server server = {0}; - struct wl_event_loop *event_loop = NULL; - struct wl_event_source *sigint_source = NULL; - struct wl_event_source *sigterm_source = NULL; - struct wlr_renderer *renderer = NULL; - struct wlr_compositor *compositor = NULL; - struct wlr_data_device_manager *data_device_mgr = NULL; - struct wlr_server_decoration_manager *server_decoration_manager = NULL; - struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL; - struct wlr_xdg_shell *xdg_shell = NULL; -#if CAGE_HAS_XWAYLAND - struct wlr_xwayland *xwayland = NULL; - struct wlr_xcursor_manager *xcursor_manager = NULL; + struct cg_server server = {.log_level = WLR_INFO}; + struct wl_event_source *sigchld_source = NULL; + pid_t pid = 0; + int ret = 0, app_ret = 0; + +#ifdef DEBUG + server.log_level = WLR_DEBUG; #endif - int ret = 0; if (!parse_args(&server, argc, argv)) { return 1; } -#ifdef DEBUG - wlr_log_init(WLR_DEBUG, NULL); -#else - wlr_log_init(WLR_ERROR, NULL); -#endif + wlr_log_init(server.log_level, NULL); + + /* 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) { @@ -192,11 +305,14 @@ main(int argc, char *argv[]) return 1; } - event_loop = wl_display_get_event_loop(server.wl_display); - sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server.wl_display); - sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server.wl_display); + server.display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(server.wl_display, &server.display_destroy); - server.backend = wlr_backend_autocreate(server.wl_display, NULL); + 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; @@ -208,46 +324,82 @@ main(int argc, char *argv[]) goto end; } - renderer = wlr_backend_get_renderer(server.backend); - wlr_renderer_init_wl_display(renderer, server.wl_display); + 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.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); - compositor = wlr_compositor_create(server.wl_display, renderer); + 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; } - data_device_mgr = wlr_data_device_manager_create(server.wl_display); - if (!data_device_mgr) { + 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.seat = seat_create(&server, server.backend); if (!server.seat) { wlr_log(WLR_ERROR, "Unable to create the seat"); ret = 1; goto end; } - server.idle = wlr_idle_create(server.wl_display); + 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; @@ -264,16 +416,19 @@ main(int argc, char *argv[]) wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, &server.new_idle_inhibitor_v1); wl_list_init(&server.inhibitors); - xdg_shell = wlr_xdg_shell_create(server.wl_display); + struct wlr_xdg_shell *xdg_shell = wlr_xdg_shell_create(server.wl_display, 5); if (!xdg_shell) { wlr_log(WLR_ERROR, "Unable to create the XDG shell interface"); ret = 1; goto end; } - server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new; - wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface); + 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); - xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server.wl_display); + 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; @@ -282,51 +437,128 @@ main(int argc, char *argv[]) wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &server.xdg_toplevel_decoration); server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; - server_decoration_manager = wlr_server_decoration_manager_create(server.wl_display); + 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); + 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 CAGE_HAS_XWAYLAND - xwayland = wlr_xwayland_create(server.wl_display, compositor, true); - if (!xwayland) { - wlr_log(WLR_ERROR, "Cannot create XWayland server"); + if (!wlr_viewporter_create(server.wl_display)) { + wlr_log(WLR_ERROR, "Unable to create the viewporter interface"); ret = 1; goto end; } - server.new_xwayland_surface.notify = handle_xwayland_surface_new; - wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface); - xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE); - if (!xcursor_manager) { - wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager"); - ret = 1; + struct wlr_presentation *presentation = wlr_presentation_create(server.wl_display, server.backend, 2); + if (!presentation) { + wlr_log(WLR_ERROR, "Unable to create the presentation interface"); + 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_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_xcursor_manager_load(xcursor_manager, 1)) { - wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme"); + if (!wlr_screencopy_manager_v1_create(server.wl_display)) { + wlr_log(WLR_ERROR, "Unable to create the screencopy manager"); + ret = 1; + goto end; } - 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); + + 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 @@ -334,7 +566,7 @@ main(int argc, char *argv[]) if (!socket) { wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket"); ret = 1; - goto end; + goto end; } if (!wlr_backend_start(server.backend)) { @@ -344,48 +576,62 @@ main(int argc, char *argv[]) } if (setenv("WAYLAND_DISPLAY", socket, true) < 0) { - wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY.", - "Clients may not be able to connect"); + wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY. Clients may not be able to connect"); } else { - wlr_log(WLR_DEBUG, "Cage is running on Wayland display %s", socket); + wlr_log(WLR_DEBUG, "Cage " CAGE_VERSION " is running on Wayland display %s", socket); } #if CAGE_HAS_XWAYLAND - wlr_xwayland_set_seat(xwayland, server.seat->seat); + if (xwayland) { + wlr_xwayland_set_seat(xwayland, server.seat->seat); + } #endif - pid_t pid; - if (!spawn_primary_client(argv + optind, &pid)) { + if (optind < argc && !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 + if (xwayland) { + wl_list_remove(&server.new_xwayland_surface.link); + } wlr_xwayland_destroy(xwayland); wlr_xcursor_manager_destroy(xcursor_manager); #endif wl_display_destroy_clients(server.wl_display); - waitpid(pid, NULL, 0); + wl_list_remove(&server.new_virtual_pointer.link); + wl_list_remove(&server.new_virtual_keyboard.link); + wl_list_remove(&server.output_manager_apply.link); + wl_list_remove(&server.output_manager_test.link); + wl_list_remove(&server.xdg_toplevel_decoration.link); + wl_list_remove(&server.new_xdg_toplevel.link); + wl_list_remove(&server.new_xdg_popup.link); + wl_list_remove(&server.new_idle_inhibitor_v1.link); + wl_list_remove(&server.new_output.link); + wl_list_remove(&server.output_layout_change.link); end: + if (pid != 0) + 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); - seat_destroy(server.seat); - wlr_server_decoration_manager_destroy(server_decoration_manager); - wlr_xdg_decoration_manager_v1_destroy(xdg_decoration_manager); - wlr_xdg_shell_destroy(xdg_shell); - wlr_idle_inhibit_v1_destroy(server.idle_inhibit_v1); - if (server.idle) { - wlr_idle_destroy(server.idle); + if (sigchld_source) { + wl_event_source_remove(sigchld_source); } - wlr_data_device_manager_destroy(data_device_mgr); - wlr_compositor_destroy(compositor); - wlr_output_layout_destroy(server.output_layout); + seat_destroy(server.seat); /* This function is not null-safe, but we only ever get here with a proper wl_display. */ wl_display_destroy(server.wl_display); + wlr_scene_node_destroy(&server.scene->tree.node); + wlr_allocator_destroy(server.allocator); + wlr_renderer_destroy(server.renderer); return ret; } diff --git a/config.h.in b/config.h.in index 3305da9..51137ec 100644 --- a/config.h.in +++ b/config.h.in @@ -3,4 +3,6 @@ #mesondefine CAGE_HAS_XWAYLAND +#mesondefine CAGE_VERSION + #endif diff --git a/contrib/increment-version b/contrib/increment-version new file mode 100755 index 0000000..11a3354 --- /dev/null +++ b/contrib/increment-version @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +if [ "$#" -ne 1 ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +new_version="$1" + +if [ "$new_version" != "${new_version#v}" ]; then + echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2 + exit 1 +fi + +set -x + +sed -i meson.build -e "s/^ version: '.*'/ version: '$new_version'/" + +echo -n "Minimum wlroots version? " +read -r wlr_version_min + +sed -i meson.build -e "s/'wlroots', version: '.*'/'wlroots', version: '>= $wlr_version_min'/" + +git add meson.build +git commit -m "Update version to $new_version" \ No newline at end of file diff --git a/contrib/release b/contrib/release new file mode 100755 index 0000000..514d370 --- /dev/null +++ b/contrib/release @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +if [ "$#" -ne 1 ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +new_version="$1" + +if [ "$new_version" != "${new_version#v}" ]; then + echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2 + exit 1 +fi + +set -x + +./increment_version "$new_version" +./tag-release "$new_version" +./sign-release + +git push --tags \ No newline at end of file diff --git a/contrib/sign-release b/contrib/sign-release new file mode 100755 index 0000000..b11fd02 --- /dev/null +++ b/contrib/sign-release @@ -0,0 +1,12 @@ +#!/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" diff --git a/contrib/tag-release b/contrib/tag-release new file mode 100755 index 0000000..4452964 --- /dev/null +++ b/contrib/tag-release @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -x + +if [ "$#" -ne 1 ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +last=$(git describe --tags --abbrev=0) +echo "Last release was $last" + +next="v$1" + +shortlog="$(git shortlog --no-merges "$last"..)" + +printf "Shortlog: \n\n%s\n\nRelease $next? [y/N] " "$shortlog" +read -r answer + +if [ "$answer" != "y" ]; then + exit 0 +fi + +project="$(basename "$(pwd)")" + +(echo "$project $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F - diff --git a/idle_inhibit_v1.c b/idle_inhibit_v1.c index 3b950ad..683cfe2 100644 --- a/idle_inhibit_v1.c +++ b/idle_inhibit_v1.c @@ -1,15 +1,15 @@ /* * Cage: A Wayland kiosk. - * + * * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include -#include -#include +#include #include +#include #include "idle_inhibit_v1.h" #include "server.h" @@ -32,7 +32,7 @@ idle_inhibit_v1_check_active(struct cg_server *server) Hence, we simply check for any inhibitors and inhibit accordingly. */ bool inhibited = !wl_list_empty(&server->inhibitors); - wlr_idle_set_enabled(server->idle, NULL, !inhibited); + wlr_idle_notifier_v1_set_inhibited(server->idle, inhibited); } static void diff --git a/idle_inhibit_v1.h b/idle_inhibit_v1.h index 5cde94f..e9bb25b 100644 --- a/idle_inhibit_v1.h +++ b/idle_inhibit_v1.h @@ -1,7 +1,7 @@ #ifndef CG_IDLE_INHIBIT_H #define CG_IDLE_INHIBIT_H -#include +#include void handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data); diff --git a/meson.build b/meson.build index 0f2c9ff..f8ccd76 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,10 @@ project('cage', 'c', - version: '0.1.1', + version: '0.2.1', license: 'MIT', + meson_version: '>=0.58.1', default_options: [ 'c_std=c11', - 'warning_level=3', + 'warning_level=2', 'werror=true', ], ) @@ -11,7 +12,6 @@ project('cage', 'c', add_project_arguments( [ '-DWLR_USE_UNSTABLE', - '-Wall', '-Wundef', '-Wno-unused-parameter', ], @@ -35,14 +35,13 @@ if is_freebsd ) endif -wlroots = dependency('wlroots', version: '>= 0.6.0') +wlroots = dependency('wlroots-0.19', fallback: ['wlroots', 'wlroots']) wayland_protos = dependency('wayland-protocols', version: '>=1.14') wayland_server = dependency('wayland-server') -pixman = dependency('pixman-1') xkbcommon = dependency('xkbcommon') math = cc.find_library('m') -wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') +wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') wayland_scanner_server = generator( wayland_scanner, @@ -65,19 +64,51 @@ server_protos = declare_dependency( sources: server_protos_headers, ) -if get_option('xwayland') - wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' - if not wlroots_has_xwayland - error('Cannot build Cage with XWayland support: wlroots has been built without it') - else - have_xwayland = true +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 -else - have_xwayland = false 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', @@ -90,8 +121,8 @@ cage_sources = [ cage_headers = [ configure_file(input: 'config.h.in', - output: 'config.h', - configuration: conf_data), + output: 'config.h', + configuration: conf_data), 'idle_inhibit_v1.h', 'output.h', 'seat.h', @@ -113,17 +144,16 @@ executable( wayland_server, wlroots, xkbcommon, - pixman, math, ], install: true, ) summary = [ - '', - 'Cage @0@'.format(meson.project_version()), - '', - ' xwayland: @0@'.format(conf_data.get('CAGE_HAS_XWAYLAND', false)), - '' + '', + 'Cage @0@'.format(version), + '', + ' xwayland: @0@'.format(have_xwayland), + '' ] message('\n'.join(summary)) diff --git a/meson_options.txt b/meson_options.txt index 87763ff..e40a23d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1 @@ -option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications') +option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') diff --git a/output.c b/output.c index 658f422..093836b 100644 --- a/output.c +++ b/output.c @@ -1,7 +1,8 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018-2019 Jente Hidskes + * Copyright (C) 2018-2021 Jente Hidskes + * Copyright (C) 2019 The Sway authors * * See the LICENSE file accompanying this file. */ @@ -9,336 +10,224 @@ #define _POSIX_C_SOURCE 200112L #include "config.h" -#include +#include #include #include -#include +#include #include #include +#include #if WLR_HAS_X11_BACKEND #include #endif +#include #include +#include #include -#include #include -#include #include -#include +#include +#include +#include #include #include #include #include "output.h" +#include "seat.h" #include "server.h" #include "view.h" +#if CAGE_HAS_XWAYLAND +#include "xwayland.h" +#endif + +#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 -scissor_output(struct wlr_output *output, pixman_box32_t *rect) +update_output_manager_config(struct cg_server *server) { - struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); - struct wlr_box box = { - .x = rect->x1, - .y = rect->y1, - .width = rect->x2 - rect->x1, - .height = rect->y2 - rect->y1, - }; - - int output_width, output_height; - wlr_output_transformed_resolution(output, &output_width, &output_height); - enum wl_output_transform transform = wlr_output_transform_invert(output->transform); - wlr_box_transform(&box, &box, transform, output_width, output_height); - - wlr_renderer_scissor(renderer, &box); -} - -static void -send_frame_done(struct wlr_surface *surface, int _unused, int _not_used, void *data) -{ - struct timespec *now = data; - wlr_surface_send_frame_done(surface, now); -} - -/* Used to move all of the data necessary to damage a surface. */ -struct damage_data { struct cg_output *output; - double x; - double y; - bool whole; -}; + 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 -damage_surface(struct wlr_surface *surface, int sx, int sy, void *data) +output_enable(struct cg_output *output) { - struct damage_data *ddata = data; - struct cg_output *output = ddata->output; struct wlr_output *wlr_output = output->wlr_output; - if (!wlr_surface_has_buffer(surface)) { - return; + /* 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); } - double x = ddata->x + sx, y = ddata->y + sy; - wlr_output_layout_output_coords(output->server->output_layout, wlr_output, &x, &y); - - struct wlr_box box = { - .x = x * wlr_output->scale, - .y = y * wlr_output->scale, - .width = surface->current.width * wlr_output->scale, - .height = surface->current.height * wlr_output->scale, - }; - - if (ddata->whole) { - wlr_output_damage_add_box(output->damage, &box); - } else if (pixman_region32_not_empty(&surface->buffer_damage)) { - pixman_region32_t damage; - pixman_region32_init(&damage); - wlr_surface_get_effective_damage(surface, &damage); - - wlr_region_scale(&damage, &damage, wlr_output->scale); - if (ceil(wlr_output->scale) > surface->current.scale) { - /* When scaling up a surface it'll become - blurry, so we need to expand the damage - region. */ - wlr_region_expand(&damage, &damage, - ceil(wlr_output->scale) - surface->current.scale); - } - pixman_region32_translate(&damage, box.x, box.y); - wlr_output_damage_add(output->damage, &damage); - pixman_region32_fini(&damage); - } -} - -/* Used to move all of the data necessary to render a surface from the - * top-level frame handler to the per-surface render function. */ -struct render_data { - struct wlr_output_layout *output_layout; - struct wlr_output *output; - struct timespec *when; - pixman_region32_t *damage; - double x, y; -}; - -static void -render_surface(struct wlr_surface *surface, int sx, int sy, void *data) -{ - struct render_data *rdata = data; - struct wlr_output *output = rdata->output; - - if (!wlr_surface_has_buffer(surface)) { - return; - } - - struct wlr_texture *texture = wlr_surface_get_texture(surface); - if (!texture) { - wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); - return; - } - - double x = rdata->x + sx, y = rdata->y + sy; - wlr_output_layout_output_coords(rdata->output_layout, output, &x, &y); - - struct wlr_box box = { - .x = x * output->scale, - .y = y * output->scale, - .width = surface->current.width * output->scale, - .height = surface->current.height * output->scale, - }; - - pixman_region32_t damage; - pixman_region32_init(&damage); - pixman_region32_union_rect(&damage, &damage, box.x, box.y, box.width, box.height); - pixman_region32_intersect(&damage, &damage, rdata->damage); - if (!pixman_region32_not_empty(&damage)) { - goto buffer_damage_finish; - } - - float matrix[9]; - enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); - wlr_matrix_project_box(matrix, &box, transform, 0, output->transform_matrix); - - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); - for (int i = 0; i < nrects; i++) { - scissor_output(output, &rects[i]); - wlr_render_texture_with_matrix(surface->renderer, texture, matrix, 1); - } - - buffer_damage_finish: - pixman_region32_fini(&damage); + update_output_manager_config(output->server); } static void -drag_icons_for_each_surface(struct cg_server *server, wlr_surface_iterator_func_t iterator, - void *data) +output_disable(struct cg_output *output) { - struct render_data *rdata = data; - - struct cg_drag_icon *drag_icon; - wl_list_for_each(drag_icon, &server->seat->drag_icons, link) { - if (!drag_icon->wlr_drag_icon->mapped) { - continue; - } - rdata->x = drag_icon->x; - rdata->y = drag_icon->y; - wlr_surface_for_each_surface(drag_icon->wlr_drag_icon->surface, - iterator, - data); + 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 void -handle_output_damage_frame(struct wl_listener *listener, void *data) +handle_output_frame(struct wl_listener *listener, void *data) { - struct cg_output *output = wl_container_of(listener, output, damage_frame); - struct wlr_renderer *renderer = wlr_backend_get_renderer(output->server->backend); + struct cg_output *output = wl_container_of(listener, output, frame); - struct timespec now; + 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); - - bool needs_frame; - pixman_region32_t buffer_damage; - pixman_region32_init(&buffer_damage); - if (!wlr_output_damage_attach_render(output->damage, &needs_frame, &buffer_damage)) { - wlr_log(WLR_ERROR, "Cannot make damage output current"); - goto buffer_damage_finish; - } - - if (!needs_frame) { - wlr_log(WLR_DEBUG, "Output doesn't need frame and isn't damaged"); - goto buffer_damage_finish; - } - - wlr_renderer_begin(renderer, output->wlr_output->width, output->wlr_output->height); - - if (!pixman_region32_not_empty(&buffer_damage)) { - wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer frame"); - goto renderer_end; - } - -#ifdef DEBUG - if (output->server->debug_damage_tracking) { - wlr_renderer_clear(renderer, (float[]){1, 0, 0, 1}); - } -#endif - - float color[4] = {0.3, 0.3, 0.3, 1.0}; - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&buffer_damage, &nrects); - for (int i = 0; i < nrects; i++) { - scissor_output(output->wlr_output, &rects[i]); - wlr_renderer_clear(renderer, color); - } - - struct render_data rdata = { - .output_layout = output->server->output_layout, - .output = output->wlr_output, - .when = &now, - .damage = &buffer_damage, - }; - - struct cg_view *view; - wl_list_for_each_reverse(view, &output->server->views, link) { - rdata.x = view->x; - rdata.y = view->y; - view_for_each_surface(view, render_surface, &rdata); - } - - drag_icons_for_each_surface(output->server, render_surface, &rdata); - - renderer_end: - /* Draw software cursor in case hardware cursors aren't - available. This is a no-op when they are. */ - wlr_output_render_software_cursors(output->wlr_output, &buffer_damage); - wlr_renderer_scissor(renderer, NULL); - wlr_renderer_end(renderer); - - int output_width, output_height; - wlr_output_transformed_resolution(output->wlr_output, &output_width, &output_height); - - pixman_region32_t frame_damage; - pixman_region32_init(&frame_damage); - - enum wl_output_transform transform = wlr_output_transform_invert(output->wlr_output->transform); - wlr_region_transform(&frame_damage, &output->damage->current, transform, output_width, output_height); - -#ifdef DEBUG - if (output->server->debug_damage_tracking) { - pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, output_width, output_height); - } -#endif - - wlr_output_set_damage(output->wlr_output, &frame_damage); - pixman_region32_fini(&frame_damage); - - if (!wlr_output_commit(output->wlr_output)) { - wlr_log(WLR_ERROR, "Could not commit output"); - goto buffer_damage_finish; - } - - buffer_damage_finish: - pixman_region32_fini(&buffer_damage); - - wl_list_for_each_reverse(view, &output->server->views, link) { - view_for_each_surface(view, send_frame_done, &now); - } - drag_icons_for_each_surface(output->server, send_frame_done, &now); + wlr_scene_output_send_frame_done(output->scene_output, &now); } static void -handle_output_transform(struct wl_listener *listener, void *data) +handle_output_commit(struct wl_listener *listener, void *data) { - struct cg_output *output = wl_container_of(listener, output, transform); + struct cg_output *output = wl_container_of(listener, output, commit); + struct wlr_output_event_commit *event = data; - struct cg_view *view; - wl_list_for_each(view, &output->server->views, link) { - view_position(view); + /* 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_mode(struct wl_listener *listener, void *data) +handle_output_request_state(struct wl_listener *listener, void *data) { - struct cg_output *output = wl_container_of(listener, output, mode); + struct cg_output *output = wl_container_of(listener, output, request_state); + struct wlr_output_event_request_state *event = data; - struct cg_view *view; - wl_list_for_each(view, &output->server->views, link) { - view_position(view); + 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->mode.link); - wl_list_remove(&output->transform.link); - wl_list_remove(&output->damage_frame.link); - wl_list_remove(&output->damage_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); - server->output = NULL; - /* Since there is no use in continuing without our (single) - * output, terminate. */ - wl_display_terminate(server->wl_display); -} - -static void -handle_output_damage_destroy(struct wl_listener *listener, void *data) -{ - struct cg_output *output = wl_container_of(listener, output, damage_destroy); - output_destroy(output); + 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); - wlr_output_damage_destroy(output->damage); output_destroy(output); } @@ -347,83 +236,78 @@ handle_new_output(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; - struct wlr_output_mode *preferred_mode; - preferred_mode = wlr_output_preferred_mode(wlr_output); - if (preferred_mode) { - wlr_output_set_mode(wlr_output, preferred_mode); + if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) { + wlr_log(WLR_ERROR, "Failed to initialize output rendering"); + return; } - server->output = calloc(1, sizeof(struct cg_output)); - server->output->wlr_output = wlr_output; - server->output->server = server; - server->output->damage = wlr_output_damage_create(wlr_output); + struct cg_output *output = calloc(1, sizeof(struct cg_output)); + if (!output) { + wlr_log(WLR_ERROR, "Failed to allocate output"); + return; + } - server->output->mode.notify = handle_output_mode; - wl_signal_add(&wlr_output->events.mode, &server->output->mode); - server->output->transform.notify = handle_output_transform; - wl_signal_add(&wlr_output->events.transform, &server->output->transform); - server->output->destroy.notify = handle_output_destroy; - wl_signal_add(&wlr_output->events.destroy, &server->output->destroy); - server->output->damage_frame.notify = handle_output_damage_frame; - wl_signal_add(&server->output->damage->events.frame, &server->output->damage_frame); - server->output->damage_destroy.notify = handle_output_damage_destroy; - wl_signal_add(&server->output->damage->events.destroy, &server->output->damage_destroy); + output->wlr_output = wlr_output; + wlr_output->data = output; + output->server = server; - wlr_output_set_transform(wlr_output, server->output_transform); + wl_list_insert(&server->outputs, &output->link); - wlr_output_layout_add_auto(server->output_layout, wlr_output); + 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); - /* Disconnect the signal now, because we only use one static output. */ - wl_list_remove(&server->new_output.link); + 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; + } - 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, + 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); } - /* Place the cursor in the center of the screen. */ - wlr_cursor_warp(server->seat->cursor, NULL, wlr_output->width / 2, wlr_output->height / 2); - wlr_output_damage_add_whole(server->output->damage); -} + wlr_log(WLR_DEBUG, "Enabling new output %s", wlr_output->name); + if (wlr_output_commit_state(wlr_output, &state)) { + output_layout_add_auto(output); + } -void -output_damage_view_surface(struct cg_output *cg_output, struct cg_view *view) -{ - struct damage_data data = { - .output = cg_output, - .x = view->x, - .y = view->y, - .whole = false, - }; - view_for_each_surface(view, damage_surface, &data); -} - -void -output_damage_view_whole(struct cg_output *cg_output, struct cg_view *view) -{ - struct damage_data data = { - .output = cg_output, - .x = view->x, - .y = view->y, - .whole = true, - }; - view_for_each_surface(view, damage_surface, &data); -} - -void -output_damage_drag_icon(struct cg_output *cg_output, struct cg_drag_icon *drag_icon) -{ - struct damage_data data = { - .output = cg_output, - .x = drag_icon->x, - .y = drag_icon->y, - .whole = true, - }; - wlr_surface_for_each_surface(drag_icon->wlr_drag_icon->surface, - damage_surface, - &data); + view_position_all(output->server); + update_output_manager_config(output->server); } void @@ -431,6 +315,11 @@ 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 @@ -439,3 +328,95 @@ output_set_window_title(struct cg_output *output, const char *title) #endif } } + +static bool +output_config_apply(struct cg_server *server, struct wlr_output_configuration_v1 *config, bool test_only) +{ + bool ok = false; + + size_t states_len; + struct wlr_backend_output_state *states = wlr_output_configuration_v1_build_state(config, &states_len); + if (states == NULL) { + return false; + } + + struct wlr_output_swapchain_manager swapchain_manager; + wlr_output_swapchain_manager_init(&swapchain_manager, server->backend); + + ok = wlr_output_swapchain_manager_prepare(&swapchain_manager, states, states_len); + if (!ok || test_only) { + goto out; + } + + for (size_t i = 0; i < states_len; i++) { + struct wlr_backend_output_state *backend_state = &states[i]; + struct cg_output *output = backend_state->output->data; + + struct wlr_swapchain *swapchain = + wlr_output_swapchain_manager_get_swapchain(&swapchain_manager, backend_state->output); + struct wlr_scene_output_state_options options = { + .swapchain = swapchain, + }; + struct wlr_output_state *state = &backend_state->base; + if (!wlr_scene_output_build_state(output->scene_output, state, &options)) { + ok = false; + goto out; + } + } + + ok = wlr_backend_commit(server->backend, states, states_len); + if (!ok) { + goto out; + } + + wlr_output_swapchain_manager_apply(&swapchain_manager); + + struct wlr_output_configuration_head_v1 *head; + wl_list_for_each (head, &config->heads, link) { + struct cg_output *output = head->state.output->data; + + if (head->state.enabled) { + output_layout_add(output, head->state.x, head->state.y); + } else { + output_layout_remove(output); + } + } + +out: + wlr_output_swapchain_manager_finish(&swapchain_manager); + for (size_t i = 0; i < states_len; i++) { + wlr_output_state_finish(&states[i].base); + } + free(states); + return ok; +} + +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); +} diff --git a/output.h b/output.h index 9c052f2..fa72545 100644 --- a/output.h +++ b/output.h @@ -1,30 +1,29 @@ #ifndef CG_OUTPUT_H #define CG_OUTPUT_H -#include +#include #include -#include -#include "seat.h" #include "server.h" #include "view.h" struct cg_output { struct cg_server *server; struct wlr_output *wlr_output; - struct wlr_output_damage *damage; + struct wlr_scene_output *scene_output; - struct wl_listener mode; - struct wl_listener transform; + struct wl_listener commit; + struct wl_listener request_state; struct wl_listener destroy; - struct wl_listener damage_frame; - struct wl_listener damage_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_damage_view_surface(struct cg_output *output, struct cg_view *view); -void output_damage_view_whole(struct cg_output *cg_output, struct cg_view *view); -void output_damage_drag_icon(struct cg_output *output, struct cg_drag_icon *icon); void output_set_window_title(struct cg_output *output, const char *title); #endif diff --git a/seat.c b/seat.c index 980adf3..5f659a4 100644 --- a/seat.c +++ b/seat.c @@ -1,23 +1,34 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018-2019 Jente Hidskes + * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ +#define _POSIX_C_SOURCE 200809L + #include "config.h" -#include +#include #include -#include +#include +#include +#include #include +#include +#include #include #include -#include +#include +#include #include +#include +#include #include -#include +#include +#include +#include #include #include #if CAGE_HAS_XWAYLAND @@ -38,58 +49,54 @@ static void drag_icon_update_position(struct cg_drag_icon *drag_icon); * menus or tooltips. This function tests if any of those are underneath the * coordinates lx and ly (in output Layout Coordinates). If so, it sets the * surface pointer to that wlr_surface and the sx and sy coordinates to the - * coordinates relative to that surface's top-left corner. */ -static bool -view_at(struct cg_view *view, double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy) -{ - double view_sx = lx - view->x; - double view_sy = ly - view->y; - - double _sx, _sy; - struct wlr_surface *_surface = view_wlr_surface_at(view, view_sx, view_sy, &_sx, &_sy); - if (_surface != NULL) { - *sx = _sx; - *sy = _sy; - *surface = _surface; - return true; - } - - return false; -} - -/* This iterates over all of our surfaces and attempts to find one - * under the cursor. This relies on server->views being ordered from - * top-to-bottom. If desktop_view_at returns a view, there is also a - * surface. There cannot be a surface without a view, either. It's - * both or nothing. */ + * 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) +desktop_view_at(struct cg_server *server, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { - struct cg_view *view; - - wl_list_for_each(view, &server->views, link) { - if (view_at(view, lx, ly, surface, sx, sy)) { - return view; - } + 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; } - 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) +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 *view = desktop_view_at(server, lx, ly, &surface, &sx, &sy); struct cg_view *current = seat_get_focus(seat); if (view == current) { return; @@ -104,10 +111,11 @@ press_cursor_button(struct cg_seat *seat, struct wlr_input_device *device, } static void -update_capabilities(struct cg_seat *seat) { +update_capabilities(struct cg_seat *seat) +{ uint32_t caps = 0; - if (!wl_list_empty(&seat->keyboards)) { + if (!wl_list_empty(&seat->keyboard_groups)) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } if (!wl_list_empty(&seat->pointers)) { @@ -120,21 +128,41 @@ update_capabilities(struct cg_seat *seat) { /* Hide cursor if the seat doesn't have pointer capability. */ if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { - wlr_cursor_set_image(seat->cursor, NULL, 0, 0, 0, 0, 0, 0); + wlr_cursor_unset_image(seat->cursor); } else { - wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, - DEFAULT_XCURSOR, - seat->cursor); + wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, DEFAULT_XCURSOR); } } static void -handle_touch_destroy(struct wl_listener *listener, void *data) { +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->device); + wlr_cursor_detach_input_device(seat->cursor, &touch->touch->base); wl_list_remove(&touch->destroy.link); free(touch); @@ -142,7 +170,7 @@ handle_touch_destroy(struct wl_listener *listener, void *data) { } static void -handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device) +handle_new_touch(struct cg_seat *seat, struct wlr_touch *wlr_touch) { struct cg_touch *touch = calloc(1, sizeof(struct cg_touch)); if (!touch) { @@ -151,14 +179,14 @@ handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device) } touch->seat = seat; - touch->device = device; - wlr_cursor_attach_input_device(seat->cursor, device); + 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(&touch->device->events.destroy, &touch->destroy); + wl_signal_add(&wlr_touch->base.events.destroy, &touch->destroy); - wlr_cursor_map_input_to_output(seat->cursor, device, seat->server->output->wlr_output); + map_input_device_to_output(seat, &wlr_touch->base, wlr_touch->output_name); } static void @@ -168,7 +196,7 @@ handle_pointer_destroy(struct wl_listener *listener, void *data) struct cg_seat *seat = pointer->seat; wl_list_remove(&pointer->link); - wlr_cursor_detach_input_device(seat->cursor, pointer->device); + wlr_cursor_detach_input_device(seat->cursor, &pointer->pointer->base); wl_list_remove(&pointer->destroy.link); free(pointer); @@ -176,7 +204,7 @@ handle_pointer_destroy(struct wl_listener *listener, void *data) } static void -handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) +handle_new_pointer(struct cg_seat *seat, struct wlr_pointer *wlr_pointer) { struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); if (!pointer) { @@ -185,60 +213,83 @@ handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) } pointer->seat = seat; - pointer->device = device; - wlr_cursor_attach_input_device(seat->cursor, device); + 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(&device->events.destroy, &pointer->destroy); + wl_signal_add(&wlr_pointer->base.events.destroy, &pointer->destroy); - wlr_cursor_map_input_to_output(seat->cursor, device, seat->server->output->wlr_output); + map_input_device_to_output(seat, &wlr_pointer->base, wlr_pointer->output_name); } static void -handle_keyboard_modifiers(struct wl_listener *listener, void *data) +handle_virtual_pointer(struct wl_listener *listener, void *data) { - struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, modifiers); + 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; - wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device); - wlr_seat_keyboard_notify_modifiers(keyboard->seat->seat, - &keyboard->device->keyboard->modifiers); + /* 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); +} - wlr_idle_notify_activity(keyboard->seat->server->idle, keyboard->seat->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) { - switch (sym) { #ifdef DEBUG - case XKB_KEY_Escape: - wl_display_terminate(server->wl_display); - break; + if (sym == XKB_KEY_Escape) { + server_terminate(server); + return true; + } #endif - default: + 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_notify_activity(server->idle, server->seat->seat); + wlr_idle_notifier_v1_notify_activity(server->idle, server->seat->seat); return true; } static void -handle_keyboard_key(struct wl_listener *listener, void *data) +handle_key_event(struct wlr_keyboard *keyboard, struct cg_seat *seat, void *data) { - struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, key); - struct cg_seat *seat = keyboard->seat; - struct wlr_event_keyboard_key *event = 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->device->keyboard->xkb_state, keycode, &syms); + int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms); bool handled = false; - uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard); - if ((modifiers & WLR_MODIFIER_ALT) && event->state == WLR_KEY_PRESSED) { + 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. */ @@ -249,77 +300,136 @@ handle_keyboard_key(struct wl_listener *listener, void *data) if (!handled) { /* Otherwise, we pass it along to the client. */ - wlr_seat_set_keyboard(seat->seat, keyboard->device); - wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, - event->keycode, event->state); + wlr_seat_set_keyboard(seat->seat, keyboard); + wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state); } - wlr_idle_notify_activity(seat->server->idle, seat->seat); + wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void -handle_keyboard_destroy(struct wl_listener *listener, void *data) +handle_keyboard_group_key(struct wl_listener *listener, void *data) { - struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); - struct cg_seat *seat = keyboard->seat; - - wl_list_remove(&keyboard->destroy.link); - wl_list_remove(&keyboard->modifiers.link); - wl_list_remove(&keyboard->key.link); - wl_list_remove(&keyboard->link); - free(keyboard); - - update_capabilities(seat); + 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_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) +handle_keyboard_group_modifiers(struct wl_listener *listener, void *data) { - struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard)); - if (!keyboard) { - wlr_log(WLR_ERROR, "Cannot allocate keyboard"); + 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 +keyboard_group_destroy(struct cg_keyboard_group *keyboard_group) +{ + wl_list_remove(&keyboard_group->key.link); + wl_list_remove(&keyboard_group->modifiers.link); + wlr_keyboard_group_destroy(keyboard_group->wlr_group); + wl_list_remove(&keyboard_group->link); + free(keyboard_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 XBK context"); - free(keyboard); + wlr_log(WLR_ERROR, "Unable to create XKB context"); return; } - struct xkb_rule_names rules = { 0 }; - rules.rules = getenv("XKB_DEFAULT_RULES"); - rules.model = getenv("XKB_DEFAULT_MODEL"); - rules.layout = getenv("XKB_DEFAULT_LAYOUT"); - rules.variant = getenv("XKB_DEFAULT_VARIANT"); - rules.options = getenv("XKB_DEFAULT_OPTIONS"); - struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, - XKB_KEYMAP_COMPILE_NO_FLAGS); + 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"); - free(keyboard); xkb_context_unref(context); return; } - keyboard->seat = seat; - keyboard->device = device; - wlr_keyboard_set_keymap(device->keyboard, keymap); + wlr_keyboard_set_keymap(keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); + wlr_keyboard_set_repeat_info(keyboard, 25, 600); - wl_list_insert(&seat->keyboards, &keyboard->link); - keyboard->destroy.notify = handle_keyboard_destroy; - wl_signal_add(&device->events.destroy, &keyboard->destroy); - keyboard->key.notify = handle_keyboard_key; - wl_signal_add(&device->keyboard->events.key, &keyboard->key); - keyboard->modifiers.notify = handle_keyboard_modifiers; - wl_signal_add(&device->keyboard->events.modifiers, &keyboard->modifiers); + cg_keyboard_group_add(keyboard, seat, virtual); - wlr_seat_set_keyboard(seat->seat, device); + 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 @@ -330,18 +440,18 @@ handle_new_input(struct wl_listener *listener, void *data) switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: - handle_new_keyboard(seat, device); + handle_new_keyboard(seat, wlr_keyboard_from_input_device(device), false); break; case WLR_INPUT_DEVICE_POINTER: - handle_new_pointer(seat, device); + handle_new_pointer(seat, wlr_pointer_from_input_device(device)); break; case WLR_INPUT_DEVICE_TOUCH: - handle_new_touch(seat, device); + 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_TOOL: + case WLR_INPUT_DEVICE_TABLET: case WLR_INPUT_DEVICE_TABLET_PAD: wlr_log(WLR_DEBUG, "Tablet input is not implemented"); return; @@ -383,8 +493,7 @@ handle_request_set_cursor(struct wl_listener *listener, void *data) /* 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); + wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, event->hotspot_y); } } @@ -392,90 +501,88 @@ static void handle_touch_down(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_down); - struct wlr_event_touch_down *event = data; + struct wlr_touch_down_event *event = data; double lx, ly; - wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, - event->x, event->y, &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); + 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); + 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_x = lx; - seat->touch_y = ly; - press_cursor_button(seat, event->device, event->time_msec, - BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly); + 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_notify_activity(seat->server->idle, seat->seat); + 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_event_touch_up *event = data; + 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->device, event->time_msec, - BTN_LEFT, WLR_BUTTON_RELEASED, - seat->touch_x, seat->touch_y); + 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_notify_activity(seat->server->idle, seat->seat); + 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_event_touch_motion *event = data; + 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->device, - event->x, event->y, &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); + 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); + 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); + wlr_seat_touch_point_clear_focus(seat->seat, event->time_msec, event->touch_id); } if (event->touch_id == seat->touch_id) { - seat->touch_x = lx; - seat->touch_y = ly; + seat->touch_lx = lx; + seat->touch_ly = ly; } - wlr_idle_notify_activity(seat->server->idle, seat->seat); + 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 @@ -484,91 +591,89 @@ handle_cursor_frame(struct wl_listener *listener, void *data) struct cg_seat *seat = wl_container_of(listener, seat, cursor_frame); wlr_seat_pointer_notify_frame(seat->seat); - wlr_idle_notify_activity(seat->server->idle, seat->seat); + 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_event_pointer_axis *event = data; + 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); - wlr_idle_notify_activity(seat->server->idle, seat->seat); + 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_event_pointer_button *event = data; + 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->device, event->time_msec, - event->button, event->state, - seat->cursor->x, seat->cursor->y); - wlr_idle_notify_activity(seat->server->idle, seat->seat); + 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) +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); - + 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); + } - bool focus_changed = wlr_seat->pointer_state.focused_surface != surface; - if (!focus_changed && time > 0) { - wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); - } + 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) { + wl_list_for_each (drag_icon, &seat->drag_icons, link) { drag_icon_update_position(drag_icon); } - wlr_idle_notify_activity(seat->server->idle, seat->seat); + 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_event_pointer_motion_absolute *event = data; + struct wlr_pointer_motion_absolute_event *event = data; - wlr_cursor_warp_absolute(seat->cursor, event->device, event->x, event->y); - process_cursor_motion(seat, event->time_msec); - wlr_idle_notify_activity(seat->server->idle, seat->seat); + 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(struct wl_listener *listener, void *data) +handle_cursor_motion_relative(struct wl_listener *listener, void *data) { - struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion); - struct wlr_event_pointer_motion *event = 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->device, event->delta_x, event->delta_y); - process_cursor_motion(seat, event->time_msec); - wlr_idle_notify_activity(seat->server->idle, seat->seat); -} - -static void -drag_icon_damage(struct cg_drag_icon *drag_icon) -{ - output_damage_drag_icon(drag_icon->seat->server->output, drag_icon); + 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 @@ -578,26 +683,24 @@ drag_icon_update_position(struct cg_drag_icon *drag_icon) struct cg_seat *seat = drag_icon->seat; struct wlr_touch_point *point; - drag_icon_damage(drag_icon); - switch (wlr_icon->drag->grab_type) { case WLR_DRAG_GRAB_KEYBOARD: return; case WLR_DRAG_GRAB_KEYBOARD_POINTER: - drag_icon->x = seat->cursor->x; - drag_icon->y = seat->cursor->y; + 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->x = seat->touch_x; - drag_icon->y = seat->touch_y; + drag_icon->lx = seat->touch_lx; + drag_icon->ly = seat->touch_ly; break; } - drag_icon_damage(drag_icon); + wlr_scene_node_set_position(&drag_icon->scene_tree->node, drag_icon->lx, drag_icon->ly); } static void @@ -607,6 +710,7 @@ handle_drag_icon_destroy(struct wl_listener *listener, void *data) 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); } @@ -616,23 +720,20 @@ 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)) { + 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); + 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_log(WLR_DEBUG, "Ignoring start_drag request: could not validate pointer/touch serial %" PRIu32, + event->serial); wlr_data_source_destroy(event->drag->source); } @@ -652,6 +753,11 @@ handle_start_drag(struct wl_listener *listener, void *data) } 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); @@ -666,17 +772,30 @@ 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 *keyboard, *keyboard_tmp; - wl_list_for_each_safe(keyboard, keyboard_tmp, &seat->keyboards, link) { - handle_keyboard_destroy(&keyboard->destroy, NULL); + 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) { + 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) { + wl_list_for_each_safe (touch, touch_tmp, &seat->touch, link) { handle_touch_destroy(&touch->destroy, NULL); } wl_list_remove(&seat->new_input.link); @@ -685,22 +804,11 @@ handle_destroy(struct wl_listener *listener, void *data) if (seat->cursor) { wlr_cursor_destroy(seat->cursor); } - wl_list_remove(&seat->cursor_motion.link); - wl_list_remove(&seat->cursor_motion_absolute.link); - wl_list_remove(&seat->cursor_button.link); - wl_list_remove(&seat->cursor_axis.link); - wl_list_remove(&seat->cursor_frame.link); - wl_list_remove(&seat->touch_down.link); - wl_list_remove(&seat->touch_up.link); - wl_list_remove(&seat->touch_motion.link); - wl_list_remove(&seat->request_set_cursor.link); - wl_list_remove(&seat->request_set_selection.link); - wl_list_remove(&seat->request_set_primary_selection.link); free(seat); } struct cg_seat * -seat_create(struct cg_server *server) +seat_create(struct cg_server *server, struct wlr_backend *backend) { struct cg_seat *seat = calloc(1, sizeof(struct cg_seat)); if (!seat) { @@ -738,8 +846,8 @@ seat_create(struct cg_server *server) } } - seat->cursor_motion.notify = handle_cursor_motion; - wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); + 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; @@ -755,6 +863,8 @@ seat_create(struct cg_server *server) 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); @@ -764,16 +874,19 @@ seat_create(struct cg_server *server) 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(&server->backend->events.new_input, &seat->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); + 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); @@ -790,6 +903,11 @@ seat_destroy(struct cg_seat *seat) wl_list_remove(&seat->request_start_drag.link); wl_list_remove(&seat->start_drag.link); + struct cg_keyboard_group *keyboard_group, *keyboard_group_tmp; + wl_list_for_each_safe (keyboard_group, keyboard_group_tmp, &seat->keyboard_groups, link) { + keyboard_group_destroy(keyboard_group); + } + // Destroying the wlr seat will trigger the destroy handler on our seat, // which will in turn free it. wlr_seat_destroy(seat->seat); @@ -799,7 +917,10 @@ struct cg_view * seat_get_focus(struct cg_seat *seat) { struct wlr_surface *prev_surface = seat->seat->keyboard_state.focused_surface; - return view_from_wlr_surface(seat->server, prev_surface); + if (!prev_surface) { + return NULL; + } + return view_from_wlr_surface(prev_surface); } void @@ -816,7 +937,7 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view) #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)) { + if (!wlr_xwayland_surface_override_redirect_wants_focus(xwayland_view->xwayland_surface)) { return; } } @@ -835,19 +956,28 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view) view_activate(view, true); char *title = view_get_title(view); - output_set_window_title(server->output, title); + 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, + 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); + wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, NULL); } - process_cursor_motion(seat, -1); + 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); } diff --git a/seat.h b/seat.h index 7e5d6b6..4b7bfda 100644 --- a/seat.h +++ b/seat.h @@ -1,7 +1,7 @@ #ifndef CG_SEAT_H #define CG_SEAT_H -#include +#include #include #include #include @@ -20,24 +20,26 @@ struct cg_seat { struct wl_listener destroy; struct wl_list keyboards; + struct wl_list keyboard_groups; struct wl_list pointers; struct wl_list touch; struct wl_listener new_input; struct wlr_cursor *cursor; struct wlr_xcursor_manager *xcursor_manager; - struct wl_listener cursor_motion; + struct wl_listener cursor_motion_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_x; - double touch_y; + 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; @@ -48,20 +50,19 @@ struct cg_seat { struct wl_listener request_set_primary_selection; }; -struct cg_keyboard { - struct wl_list link; // seat::keyboards +struct cg_keyboard_group { + struct wlr_keyboard_group *wlr_group; struct cg_seat *seat; - struct wlr_input_device *device; - - struct wl_listener modifiers; struct wl_listener key; - struct wl_listener destroy; + 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_input_device *device; + struct wlr_pointer *pointer; struct wl_listener destroy; }; @@ -69,7 +70,7 @@ struct cg_pointer { struct cg_touch { struct wl_list link; // seat::touch struct cg_seat *seat; - struct wlr_input_device *device; + struct wlr_touch *touch; struct wl_listener destroy; }; @@ -78,15 +79,18 @@ struct cg_drag_icon { struct wl_list link; // seat::drag_icons struct cg_seat *seat; struct wlr_drag_icon *wlr_drag_icon; - double x; - double y; + 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 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 diff --git a/server.h b/server.h index 80e9951..00c2a61 100644 --- a/server.h +++ b/server.h @@ -3,46 +3,71 @@ #include "config.h" -#include -#include -#include +#include #include +#include #include +#include #include +#include + #if CAGE_HAS_XWAYLAND #include #endif -#include "output.h" -#include "seat.h" -#include "view.h" +enum cg_multi_output_mode { + CAGE_MULTI_OUTPUT_MODE_EXTEND, + CAGE_MULTI_OUTPUT_MODE_LAST, +}; struct cg_server { struct wl_display *wl_display; - struct wlr_backend *backend; 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 *idle; + 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 cg_output *output; + 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_shell_surface; + 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; - enum wl_output_transform output_transform; -#ifdef DEBUG - bool debug_damage_tracking; -#endif + bool allow_vt_switch; + bool return_app_code; + bool terminated; + enum wlr_log_importance log_level; }; +void server_terminate(struct cg_server *server); + #endif diff --git a/view.c b/view.c index 6b55606..8cbeb5e 100644 --- a/view.c +++ b/view.c @@ -1,20 +1,20 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018-2019 Jente Hidskes + * Copyright (C) 2018-2021 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200809L +#include #include #include #include -#include -#include +#include #include -#include +#include #include "output.h" #include "seat.h" @@ -24,96 +24,6 @@ #include "xwayland.h" #endif -static void -view_child_handle_commit(struct wl_listener *listener, void *data) -{ - struct cg_view_child *child = wl_container_of(listener, child, commit); - view_damage_surface(child->view); -} - -static void subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface); - -static void -view_child_handle_new_subsurface(struct wl_listener *listener, void *data) -{ - struct cg_view_child *child = wl_container_of(listener, child, new_subsurface); - struct wlr_subsurface *wlr_subsurface = data; - subsurface_create(child->view, wlr_subsurface); -} - -void -view_child_finish(struct cg_view_child *child) -{ - if (!child) { - return; - } - - view_damage_whole(child->view); - - wl_list_remove(&child->link); - wl_list_remove(&child->commit.link); - wl_list_remove(&child->new_subsurface.link); -} - -void -view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface) -{ - child->view = view; - child->wlr_surface = wlr_surface; - - child->commit.notify = view_child_handle_commit; - wl_signal_add(&wlr_surface->events.commit, &child->commit); - child->new_subsurface.notify = view_child_handle_new_subsurface; - wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); - - wl_list_insert(&view->children, &child->link); -} - -static void -subsurface_destroy(struct cg_view_child *child) -{ - if (!child) { - return; - } - - struct cg_subsurface *subsurface = (struct cg_subsurface *) child; - wl_list_remove(&subsurface->destroy.link); - view_child_finish(&subsurface->view_child); - free(subsurface); -} - -static void -subsurface_handle_destroy(struct wl_listener *listener, void *data) -{ - struct cg_subsurface *subsurface = wl_container_of(listener, subsurface, destroy); - struct cg_view_child *view_child = (struct cg_view_child *) subsurface; - subsurface_destroy(view_child); -} - -static void -subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface) -{ - struct cg_subsurface *subsurface = calloc(1, sizeof(struct cg_subsurface)); - if (!subsurface) { - return; - } - - view_child_init(&subsurface->view_child, view, wlr_subsurface->surface); - subsurface->view_child.destroy = subsurface_destroy; - subsurface->wlr_subsurface = wlr_subsurface; - - subsurface->destroy.notify = subsurface_handle_destroy; - wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); -} - -static void -handle_new_subsurface(struct wl_listener *listener, void *data) -{ - struct cg_view *view = wl_container_of(listener, view, new_subsurface); - struct wlr_subsurface *wlr_subsurface = data; - subsurface_create(view, wlr_subsurface); -} - char * view_get_title(struct cg_view *view) { @@ -131,67 +41,73 @@ view_is_primary(struct cg_view *view) } bool -view_is_transient_for(struct cg_view *child, struct cg_view *parent) { +view_is_transient_for(struct cg_view *child, struct cg_view *parent) +{ return child->impl->is_transient_for(child, parent); } -void -view_damage_surface(struct cg_view *view) -{ - output_damage_view_surface(view->server->output, view); -} - -void -view_damage_whole(struct cg_view *view) -{ - output_damage_view_whole(view->server->output, view); -} - void view_activate(struct cg_view *view, bool activate) { view->impl->activate(view, activate); } -static void -view_maximize(struct cg_view *view) +static bool +view_extends_output_layout(struct cg_view *view, struct wlr_box *layout_box) { - struct cg_output *output = view->server->output; - int output_width, output_height; - - wlr_output_transformed_resolution(output->wlr_output, &output_width, &output_height); - view->impl->maximize(view, output_width, output_height); -} - -static void -view_center(struct cg_view *view) -{ - struct wlr_output *output = view->server->output->wlr_output; - - int output_width, output_height; - wlr_output_transformed_resolution(output, &output_width, &output_height); - int width, height; view->impl->get_geometry(view, &width, &height); - view->x = (output_width - width) / 2; - view->y = (output_height - height) / 2; + 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) { - if (view_is_primary(view)) { - view_maximize(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); + view_center(view, &layout_box); } } void -view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) +view_position_all(struct cg_server *server) { - view->impl->for_each_surface(view, iterator, data); + struct cg_view *view; + wl_list_for_each (view, &server->views, link) { + view_position(view); + } } void @@ -199,28 +115,24 @@ view_unmap(struct cg_view *view) { wl_list_remove(&view->link); - wl_list_remove(&view->new_subsurface.link); - - struct cg_view_child *child, *tmp; - wl_list_for_each_safe(child, tmp, &view->children, link) { - child->destroy(child); - } + 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->wlr_surface = surface; - - struct wlr_subsurface *subsurface; - wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces, parent_link) { - subsurface_create(view, subsurface); + 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->new_subsurface.notify = handle_new_subsurface; - wl_signal_add(&view->wlr_surface->events.new_subsurface, &view->new_subsurface); + view->wlr_surface = surface; + surface->data = view; #if CAGE_HAS_XWAYLAND /* We shouldn't position override-redirect windows. They set @@ -239,14 +151,6 @@ void view_destroy(struct cg_view *view) { struct cg_server *server = view->server; - bool ever_been_mapped = true; - -#if CAGE_HAS_XWAYLAND - if (view->type == CAGE_XWAYLAND_VIEW) { - struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); - ever_been_mapped = xwayland_view->ever_been_mapped; - } -#endif if (view->wlr_surface != NULL) { view_unmap(view); @@ -259,38 +163,20 @@ view_destroy(struct cg_view *view) if (!empty) { struct cg_view *prev = wl_container_of(server->views.next, prev, link); seat_set_focus(server->seat, prev); - } else if (ever_been_mapped) { - /* The list is empty and the last view has been - mapped, so we can safely exit. */ - wl_display_terminate(server->wl_display); } } void -view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, - const struct cg_view_impl *impl) +view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl) { view->server = server; view->type = type; view->impl = impl; - - wl_list_init(&view->children); } struct cg_view * -view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface) +view_from_wlr_surface(struct wlr_surface *surface) { - struct cg_view *view; - wl_list_for_each(view, &server->views, link) { - if (view->wlr_surface == surface) { - return view; - } - } - return NULL; -} - -struct wlr_surface * -view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) -{ - return view->impl->wlr_surface_at(view, sx, sy, sub_x, sub_y); + assert(surface); + return surface->data; } diff --git a/view.h b/view.h index 03e41ca..9b2ab75 100644 --- a/view.h +++ b/view.h @@ -4,10 +4,10 @@ #include "config.h" #include -#include -#include -#include +#include +#include #include +#include #if CAGE_HAS_XWAYLAND #include #endif @@ -24,14 +24,14 @@ enum cg_view_type { struct cg_view { struct cg_server *server; struct wl_list link; // server::views - struct wl_list children; // cg_view_child::link struct wlr_surface *wlr_surface; - int x, y; + 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 wl_listener new_subsurface; }; struct cg_view_impl { @@ -42,49 +42,19 @@ struct cg_view_impl { void (*activate)(struct cg_view *view, bool activate); void (*maximize)(struct cg_view *view, int output_width, int output_height); void (*destroy)(struct cg_view *view); - void (*for_each_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, - void *data); - struct wlr_surface *(*wlr_surface_at)(struct cg_view *view, double sx, double sy, - double *sub_x, double *sub_y); -}; - -struct cg_view_child { - struct cg_view *view; - struct wlr_surface *wlr_surface; - struct wl_list link; - - struct wl_listener commit; - struct wl_listener new_subsurface; - - void (*destroy)(struct cg_view_child *child); -}; - -struct cg_subsurface { - struct cg_view_child view_child; - struct wlr_subsurface *wlr_subsurface; - - struct wl_listener destroy; }; char *view_get_title(struct cg_view *view); bool view_is_primary(struct cg_view *view); bool view_is_transient_for(struct cg_view *child, struct cg_view *parent); -void view_damage_surface(struct cg_view *view); -void view_damage_whole(struct cg_view *view); void view_activate(struct cg_view *view, bool activate); void view_position(struct cg_view *view); -void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); +void view_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); +void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl); -struct cg_view *view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface); -struct wlr_surface *view_wlr_surface_at(struct cg_view *view, double sx, double sy, - double *sub_x, double *sub_y); - -void view_child_finish(struct cg_view_child *child); -void view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface); +struct cg_view *view_from_wlr_surface(struct wlr_surface *surface); #endif diff --git a/xdg_shell.c b/xdg_shell.c index f964f76..5493918 100644 --- a/xdg_shell.c +++ b/xdg_shell.c @@ -6,10 +6,11 @@ * See the LICENSE file accompanying this file. */ +#include #include #include -#include -#include +#include +#include #include #include @@ -18,21 +19,9 @@ #include "xdg_shell.h" static void -xdg_decoration_handle_destroy(struct wl_listener *listener, void *data) +xdg_decoration_set_mode(struct cg_xdg_decoration *xdg_decoration) { - struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, destroy); - - wl_list_remove(&xdg_decoration->destroy.link); - wl_list_remove(&xdg_decoration->request_mode.link); - free(xdg_decoration); -} - -static void -xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data) -{ - struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, request_mode); enum wlr_xdg_toplevel_decoration_v1_mode mode; - if (xdg_decoration->server->xdg_decoration) { mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; } else { @@ -42,101 +31,84 @@ xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data) } static void -xdg_popup_destroy(struct cg_view_child *child) +xdg_decoration_handle_destroy(struct wl_listener *listener, void *data) { - if (!child) { + 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_xdg_popup *popup = (struct cg_xdg_popup *) child; - wl_list_remove(&popup->destroy.link); - wl_list_remove(&popup->map.link); - wl_list_remove(&popup->unmap.link); - wl_list_remove(&popup->new_popup.link); - view_child_finish(&popup->view_child); - free(popup); -} + struct cg_server *server = view->server; + struct wlr_box *popup_box = &popup->current.geometry; -static void -handle_xdg_popup_map(struct wl_listener *listener, void *data) -{ - struct cg_xdg_popup *popup = wl_container_of(listener, popup, map); - view_damage_whole(popup->view_child.view); -} - -static void -handle_xdg_popup_unmap(struct wl_listener *listener, void *data) -{ - struct cg_xdg_popup *popup = wl_container_of(listener, popup, unmap); - view_damage_whole(popup->view_child.view); -} - -static void -handle_xdg_popup_destroy(struct wl_listener *listener, void *data) -{ - struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy); - struct cg_view_child *view_child = (struct cg_view_child *) popup; - xdg_popup_destroy(view_child); -} - -static void xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup); - -static void -popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) -{ - struct cg_xdg_popup *popup = wl_container_of(listener, popup, new_popup); - struct wlr_xdg_popup *wlr_popup = data; - xdg_popup_create(popup->view_child.view, wlr_popup); -} - -static void -popup_unconstrain(struct cg_xdg_popup *popup) -{ - struct cg_view *view = popup->view_child.view; - struct wlr_output *output = view->server->output->wlr_output; - struct wlr_output_layout *output_layout = view->server->output_layout; - - struct wlr_box *output_box = wlr_output_layout_get_box(output_layout, output); + 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->x, - .y = output_box->y - view->y, - .width = output_box->width, - .height = output_box->height + .x = output_box.x - view->lx, + .y = output_box.y - view->ly, + .width = output_box.width, + .height = output_box.height, }; - wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); -} - -static void -xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup) -{ - struct cg_xdg_popup *popup = calloc(1, sizeof(struct cg_xdg_popup)); - if (!popup) { - return; - } - - popup->wlr_popup = wlr_popup; - view_child_init(&popup->view_child, view, wlr_popup->base->surface); - popup->view_child.destroy = xdg_popup_destroy; - popup->destroy.notify = handle_xdg_popup_destroy; - wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); - popup->map.notify = handle_xdg_popup_map; - wl_signal_add(&wlr_popup->base->events.map, &popup->map); - popup->unmap.notify = handle_xdg_popup_unmap; - wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); - popup->new_popup.notify = popup_handle_new_xdg_popup; - wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); - - popup_unconstrain(popup); -} - -static void -handle_new_xdg_popup(struct wl_listener *listener, void *data) -{ - struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup); - struct wlr_xdg_popup *wlr_popup = data; - xdg_popup_create(&xdg_shell_view->view, wlr_popup); + wlr_xdg_popup_unconstrain_from_box(popup, &output_toplevel_box); } static struct cg_xdg_shell_view * @@ -149,27 +121,26 @@ static char * get_title(struct cg_view *view) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); - return xdg_shell_view->xdg_surface->toplevel->title; + 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; + struct wlr_xdg_surface *xdg_surface = xdg_shell_view->xdg_toplevel->base; - wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geom); - *width_out = geom.width; - *height_out = geom.height; + *width_out = xdg_surface->geometry.width; + *height_out = xdg_surface->geometry.height; } static bool is_primary(struct cg_view *view) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); - struct wlr_xdg_surface *parent = xdg_shell_view->xdg_surface->toplevel->parent; - /* FIXME: role is 0? */ - return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ + struct wlr_xdg_toplevel *parent = xdg_shell_view->xdg_toplevel->parent; + + return parent == NULL; } static bool @@ -179,14 +150,13 @@ is_transient_for(struct cg_view *child, struct cg_view *parent) return false; } struct cg_xdg_shell_view *_child = xdg_shell_view_from_view(child); - struct wlr_xdg_surface *xdg_surface = _child->xdg_surface; + struct wlr_xdg_toplevel *xdg_toplevel = _child->xdg_toplevel; struct cg_xdg_shell_view *_parent = xdg_shell_view_from_view(parent); - struct wlr_xdg_surface *parent_xdg_surface = _parent->xdg_surface; - while (xdg_surface) { - if (xdg_surface->toplevel->parent == parent_xdg_surface) { + while (xdg_toplevel) { + if (xdg_toplevel->parent == _parent->xdg_toplevel) { return true; } - xdg_surface = xdg_surface->toplevel->parent; + xdg_toplevel = xdg_toplevel->parent; } return false; } @@ -195,15 +165,15 @@ static void activate(struct cg_view *view, bool activate) { struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); - wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate); + 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_surface, output_width, output_height); - wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true); + 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 @@ -214,74 +184,68 @@ destroy(struct cg_view *view) } static void -for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) -{ - struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); - wlr_xdg_surface_for_each_surface(xdg_shell_view->xdg_surface, iterator, data); -} - -static struct wlr_surface * -wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) -{ - struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); - return wlr_xdg_surface_surface_at(xdg_shell_view->xdg_surface, sx, sy, sub_x, sub_y); -} - -static void -handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, void *data) +handle_xdg_toplevel_request_fullscreen(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen); - struct wlr_xdg_toplevel_set_fullscreen_event *event = data; - wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, event->fullscreen); + + /** + * 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_commit(struct wl_listener *listener, void *data) -{ - struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit); - struct cg_view *view = &xdg_shell_view->view; - view_damage_surface(view); -} - -static void -handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data) +handle_xdg_toplevel_unmap(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); struct cg_view *view = &xdg_shell_view->view; - view_damage_whole(view); - - wl_list_remove(&xdg_shell_view->commit.link); - view_unmap(view); } static void -handle_xdg_shell_surface_map(struct wl_listener *listener, void *data) +handle_xdg_toplevel_map(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map); struct cg_view *view = &xdg_shell_view->view; - xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; - wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit); - - view_map(view, xdg_shell_view->xdg_surface->surface); - - view_damage_whole(view); + view_map(view, xdg_shell_view->xdg_toplevel->base->surface); } static void -handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *data) +handle_xdg_toplevel_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; + } + + wlr_xdg_toplevel_set_wm_capabilities(xdg_shell_view->xdg_toplevel, XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + + /* 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_toplevel_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); - wl_list_remove(&xdg_shell_view->new_popup.link); - xdg_shell_view->xdg_surface = NULL; + xdg_shell_view->xdg_toplevel = NULL; view_destroy(view); } @@ -294,19 +258,13 @@ static const struct cg_view_impl xdg_shell_view_impl = { .activate = activate, .maximize = maximize, .destroy = destroy, - .for_each_surface = for_each_surface, - .wlr_surface_at = wlr_surface_at, }; void -handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) +handle_new_xdg_toplevel(struct wl_listener *listener, void *data) { - struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface); - struct wlr_xdg_surface *xdg_surface = data; - - if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - return; - } + struct cg_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) { @@ -315,18 +273,105 @@ handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) } view_init(&xdg_shell_view->view, server, CAGE_XDG_SHELL_VIEW, &xdg_shell_view_impl); - xdg_shell_view->xdg_surface = xdg_surface; + xdg_shell_view->xdg_toplevel = toplevel; - xdg_shell_view->map.notify = handle_xdg_shell_surface_map; - wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); - xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap; - wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); - xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy; - wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); - xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen; - wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen); - xdg_shell_view->new_popup.notify = handle_new_xdg_popup; - wl_signal_add(&xdg_surface->events.new_popup, &xdg_shell_view->new_popup); + xdg_shell_view->commit.notify = handle_xdg_toplevel_commit; + wl_signal_add(&toplevel->base->surface->events.commit, &xdg_shell_view->commit); + xdg_shell_view->map.notify = handle_xdg_toplevel_map; + wl_signal_add(&toplevel->base->surface->events.map, &xdg_shell_view->map); + xdg_shell_view->unmap.notify = handle_xdg_toplevel_unmap; + wl_signal_add(&toplevel->base->surface->events.unmap, &xdg_shell_view->unmap); + xdg_shell_view->destroy.notify = handle_xdg_toplevel_destroy; + wl_signal_add(&toplevel->events.destroy, &xdg_shell_view->destroy); + xdg_shell_view->request_fullscreen.notify = handle_xdg_toplevel_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); + wl_list_remove(&popup->reposition.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); + } +} + +static void +popup_handle_reposition(struct wl_listener *listener, void *data) +{ + struct cg_xdg_popup *popup = wl_container_of(listener, popup, reposition); + + 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); + + popup->reposition.notify = popup_handle_reposition; + wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); + + 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 @@ -345,8 +390,8 @@ handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) 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); - - xdg_decoration_handle_request_mode(&xdg_decoration->request_mode, wlr_decoration); } diff --git a/xdg_shell.h b/xdg_shell.h index 60ba535..f819549 100644 --- a/xdg_shell.h +++ b/xdg_shell.h @@ -1,7 +1,7 @@ #ifndef CG_XDG_SHELL_H #define CG_XDG_SHELL_H -#include +#include #include #include @@ -9,34 +9,33 @@ struct cg_xdg_shell_view { struct cg_view view; - struct wlr_xdg_surface *xdg_surface; + struct wlr_xdg_toplevel *xdg_toplevel; struct wl_listener destroy; - struct wl_listener unmap; - struct wl_listener map; struct wl_listener commit; - struct wl_listener request_fullscreen; - struct wl_listener new_popup; -}; - -struct cg_xdg_popup { - struct cg_view_child view_child; - struct wlr_xdg_popup *wlr_popup; - - struct wl_listener destroy; - struct wl_listener map; struct wl_listener unmap; - struct wl_listener new_popup; + struct 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; }; -void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data); +struct cg_xdg_popup { + struct wlr_xdg_popup *xdg_popup; + + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener reposition; +}; + +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); diff --git a/xwayland.c b/xwayland.c index 0cbe412..3df7a08 100644 --- a/xwayland.c +++ b/xwayland.c @@ -1,17 +1,16 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018-2019 Jente Hidskes + * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #include #include -#include -#include -#include +#include #include +#include #include "server.h" #include "view.h" @@ -42,8 +41,15 @@ static void get_geometry(struct cg_view *view, int *width_out, int *height_out) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); - *width_out = xwayland_view->xwayland_surface->surface->current.width; - *height_out = xwayland_view->xwayland_surface->surface->current.height; + 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 @@ -84,8 +90,9 @@ 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, 0, 0, output_width, output_height); - wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true); + 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, true); } static void @@ -95,18 +102,6 @@ destroy(struct cg_view *view) free(xwayland_view); } -static void -for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data) -{ - wlr_surface_for_each_surface(view->wlr_surface, iterator, data); -} - -static struct wlr_surface * -wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y) -{ - return wlr_surface_surface_at(view->wlr_surface, sx, sy, sub_x, sub_y); -} - static void handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, void *data) { @@ -115,24 +110,12 @@ handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, void *d wlr_xwayland_surface_set_fullscreen(xwayland_view->xwayland_surface, xwayland_surface->fullscreen); } -static void -handle_xwayland_surface_commit(struct wl_listener *listener, void *data) -{ - struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit); - struct cg_view *view = &xwayland_view->view; - view_damage_surface(view); -} - static void handle_xwayland_surface_unmap(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap); struct cg_view *view = &xwayland_view->view; - view_damage_whole(view); - - wl_list_remove(&xwayland_view->commit.link); - view_unmap(view); } @@ -143,17 +126,11 @@ handle_xwayland_surface_map(struct wl_listener *listener, void *data) struct cg_view *view = &xwayland_view->view; if (!xwayland_view_should_manage(view)) { - view->x = xwayland_view->xwayland_surface->x; - view->y = xwayland_view->xwayland_surface->y; + view->lx = xwayland_view->xwayland_surface->x; + view->ly = xwayland_view->xwayland_surface->y; } - xwayland_view->commit.notify = handle_xwayland_surface_commit; - wl_signal_add(&xwayland_view->xwayland_surface->surface->events.commit, &xwayland_view->commit); - - xwayland_view->ever_been_mapped = true; view_map(view, xwayland_view->xwayland_surface->surface); - - view_damage_whole(view); } static void @@ -162,8 +139,6 @@ handle_xwayland_surface_destroy(struct wl_listener *listener, void *data) struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy); struct cg_view *view = &xwayland_view->view; - wl_list_remove(&xwayland_view->map.link); - wl_list_remove(&xwayland_view->unmap.link); wl_list_remove(&xwayland_view->destroy.link); wl_list_remove(&xwayland_view->request_fullscreen.link); xwayland_view->xwayland_surface = NULL; @@ -179,10 +154,28 @@ static const struct cg_view_impl xwayland_view_impl = { .activate = activate, .maximize = maximize, .destroy = destroy, - .for_each_surface = for_each_surface, - .wlr_surface_at = wlr_surface_at, }; +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) { @@ -198,10 +191,10 @@ handle_xwayland_surface_new(struct wl_listener *listener, void *data) view_init(&xwayland_view->view, server, CAGE_XWAYLAND_VIEW, &xwayland_view_impl); xwayland_view->xwayland_surface = xwayland_surface; - xwayland_view->map.notify = handle_xwayland_surface_map; - wl_signal_add(&xwayland_surface->events.map, &xwayland_view->map); - xwayland_view->unmap.notify = handle_xwayland_surface_unmap; - wl_signal_add(&xwayland_surface->events.unmap, &xwayland_view->unmap); + xwayland_view->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; diff --git a/xwayland.h b/xwayland.h index fa9c62e..bca4b02 100644 --- a/xwayland.h +++ b/xwayland.h @@ -1,7 +1,7 @@ #ifndef CG_XWAYLAND_H #define CG_XWAYLAND_H -#include +#include #include #include "view.h" @@ -9,25 +9,11 @@ struct cg_xwayland_view { struct cg_view view; struct wlr_xwayland_surface *xwayland_surface; - - /* Some applications that aren't yet Wayland-native or - otherwise "special" (e.g. Firefox Nightly and Google - Chrome/Chromium) spawn an XWayland surface upon startup - that is almost immediately closed again. This makes Cage - think there are no views left, which results in it - exiting. However, after this initial (unmapped) surface, - the "real" application surface is opened. This leads to - these applications' startup sequences being interrupted by - Cage exiting. Hence, to work around this issue, Cage checks - whether an XWayland surface has ever been mapped and exits - only if 1) the XWayland surface has ever been mapped and 2) - this was the last surface Cage manages. */ - bool ever_been_mapped; - struct wl_listener destroy; + struct wl_listener associate; + struct wl_listener dissociate; struct wl_listener unmap; struct wl_listener map; - struct wl_listener commit; struct wl_listener request_fullscreen; };