Compare commits

..

No commits in common. "master" and "v0.1" have entirely different histories.
master ... v0.1

31 changed files with 1352 additions and 1848 deletions

28
.builds/alpine.yml Normal file
View file

@ -0,0 +1,28 @@
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.5.0
git checkout c9137cba691b57c3eaf3ff94f9bf8e623f66ccc5
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

30
.builds/archlinux.yml Normal file
View file

@ -0,0 +1,30 @@
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.5.0
git checkout c9137cba691b57c3eaf3ff94f9bf8e623f66ccc5
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

45
.builds/freebsd.yml Normal file
View file

@ -0,0 +1,45 @@
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:
# Taken from sway
- fixup_epoll: |
cat << 'EOF' | sudo tee /usr/local/libdata/pkgconfig/epoll-shim.pc
prefix=/usr/local
exec_prefix=\$\{\$prefix\}
libdir=${prefix}/lib
sharedlibdir=${prefix}/lib
includedir=${prefix}/include/libepoll-shim
Name: epoll-shim
Description: epoll shim implemented using kevent
Version: 0
Requires:
Libs: -L${libdir} -L${sharedlibdir} -lepoll-shim
Libs.private: -pthread -lrt
Cflags: -I${includedir}
EOF
# 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.5.0
git checkout c9137cba691b57c3eaf3ff94f9bf8e623f66ccc5
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

View file

@ -1,13 +0,0 @@
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

View file

@ -1 +0,0 @@
subprojects/**/*

View file

@ -1,10 +0,0 @@
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

View file

@ -1,81 +0,0 @@
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

View file

@ -1,5 +1,4 @@
Copyright (c) 2018-2020 Jente Hidskes
Copyright (c) 2019 The Sway authors
Copyright (c) 2018-2019 Jente Hidskes
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

View file

@ -1,61 +1,52 @@
# Cage: a Wayland kiosk
<img src="https://www.hjdskes.nl/img/projects/cage/cage.svg" alt="Cage's logo" width="150px" align="right">
# Cage: a Wayland kiosk [![builds.sr.ht status](https://builds.sr.ht/~hjdskes.svg)](https://builds.sr.ht/~hjdskes?)
This is Cage, a Wayland kiosk. A kiosk runs a single, maximized
application.
This README is only relevant for development resources and instructions. For a
description of Cage and installation instructions for end-users, please see
[its project page](https://www.hjdskes.nl/projects/cage) and [the
Wiki](https://github.com/cage-kiosk/cage/wiki/).
See [the man page](./cage.1.scd) for a list of possible environment variables and run options.
## Release signatures
Releases up to version 0.1.4 are signed with [6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1). Releases from 0.1.5 onwards are signed with
[E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
All releases are published on [GitHub](https://github.com/cage-kiosk/cage/releases).
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).
## Building and running Cage
You can build Cage with the [meson](https://mesonbuild.com/) build system. It
requires wayland, wlroots, and xkbcommon to be installed. Optionally, install
scdoc for manual pages. Cage is currently based on branch 0.18 of wlroots.
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.
Simply execute the following steps to build Cage:
```
$ meson setup build
$ meson compile -C build
$ meson build
$ ninja -C build
```
By default, this builds a debug build. To build a release build, use `meson
setup build --buildtype=release`.
Cage comes with compile-time support for XWayland. To enable this,
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.
Cage comes with compile-time support for XWayland. To enable this, make sure
that your version of wlroots is compiled with this option. Note that you'll
need to have the XWayland binary installed on your system for this to work.
You can run Cage by running `./build/cage APPLICATION`. If you run it from
within an existing X11 or Wayland session, it will open in a virtual output as
a window in your existing session. If you run it at a TTY, it'll run with the
KMS+DRM backend. In debug mode (default build type with Meson), press
<kbd>Alt</kbd>+<kbd>Esc</kbd> to quit. For more configuration options, see
[Configuration](https://github.com/cage-kiosk/cage/wiki/Configuration).
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 is based on the annotated source of tinywl and rootston.
## Bugs
For any bug, please [create an
issue](https://github.com/cage-kiosk/cage/issues/new) on
[GitHub](https://github.com/cage-kiosk/cage).
issue](https://github.com/Hjdskes/cage/issues/new) on
[GitHub](https://github.com/Hjdskes/cage).
## License
Please see
[LICENSE](https://github.com/cage-kiosk/cage/blob/master/LICENSE) on
[GitHub](https://github.com/cage-kiosk/cage).
[LICENSE](https://github.com/Hjdskes/cage/blob/master/LICENSE) on
[GitHub](https://github.com/Hjdskes/cage).
Copyright © 2018-2020 Jente Hidskes <dev@hjdskes.nl>
Copyright © 2018-2019 Jente Hidskes <hjdskes@gmail.com>

View file

@ -1,71 +0,0 @@
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* <mode>
Set the multi-monitor behavior. Supported modes are:
*last* Cage uses only the last connected monitor.
*extend* Cage extends the display across all connected monitors.
*-s*
Allow VT switching
*-v*
Show the version number and exit.
# ENVIRONMENT
_DISPLAY_
If compiled with Xwayland support, this will be set to the name of the
X display used for Xwayland. Otherwise, probe the X11 backend.
_WAYLAND_DISPLAY_
Specifies the name of the Wayland display that Cage is running on.
_XCURSOR_PATH_
Directory where cursors are located.
_XCURSOR_SIZE_
Specifies the configured cursor size.
_XCURSOR_THEME_
Specifies the configured cursor theme.
_XKB_DEFAULT_RULES_, _XKB_DEFAULT_MODEL_, _XKB_DEFAULT_LAYOUT_,
_XKB_DEFAULT_VARIANT_, _XKB_DEFAULT_OPTIONS_
Configures the xkb keyboard settings. See *xkeyboard-config*(7).
# SEE ALSO
*xkeyboard-config(7)*
# BUGS
Report bugs at https://github.com/cage-kiosk/cage
# AUTHORS
Jente Hidskes <dev@hjdskes.nl>

508
cage.c
View file

@ -1,7 +1,7 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2020 Jente Hidskes
* Copyright (C) 2018-2019 Jente Hidskes
*
* See the LICENSE file accompanying this file.
*/
@ -10,41 +10,25 @@
#include "config.h"
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/types/wlr_xcursor_manager.h>
#endif
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#if CAGE_HAS_XWAYLAND
@ -61,178 +45,53 @@
#include "xwayland.h"
#endif
void
server_terminate(struct cg_server *server)
{
// Workaround for https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/421
if (server->terminated) {
return;
}
wl_display_terminate(server->wl_display);
}
static void
handle_display_destroy(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, display_destroy);
server->terminated = true;
}
static int
sigchld_handler(int fd, uint32_t mask, void *data)
{
struct cg_server *server = data;
/* Close Cage's read pipe. */
close(fd);
if (mask & WL_EVENT_HANGUP) {
wlr_log(WLR_DEBUG, "Child process closed normally");
} else if (mask & WL_EVENT_ERROR) {
wlr_log(WLR_DEBUG, "Connection closed by server");
}
server->return_app_code = true;
server_terminate(server);
return 0;
}
static bool
set_cloexec(int fd)
spawn_primary_client(char *argv[], pid_t *pid_out)
{
int flags = fcntl(fd, F_GETFD);
if (flags == -1) {
wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed");
return false;
}
flags = flags | FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed");
return false;
}
return true;
}
static bool
spawn_primary_client(struct cg_server *server, char *argv[], pid_t *pid_out, struct wl_event_source **sigchld_source)
{
int fd[2];
if (pipe(fd) != 0) {
wlr_log(WLR_ERROR, "Unable to create pipe");
return false;
}
pid_t pid = fork();
if (pid == 0) {
sigset_t set;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
/* Close read, we only need write in the primary client process. */
close(fd[0]);
execvp(argv[0], argv);
/* execvp() returns only on failure */
wlr_log_errno(WLR_ERROR, "Failed to spawn client");
_exit(1);
} else if (pid == -1) {
wlr_log_errno(WLR_ERROR, "Unable to fork");
return false;
}
/* Set this early so that if we fail, the client process will be cleaned up properly. */
*pid_out = pid;
if (!set_cloexec(fd[0]) || !set_cloexec(fd[1])) {
return false;
}
/* Close write, we only need read in Cage. */
close(fd[1]);
struct wl_event_loop *event_loop = wl_display_get_event_loop(server->wl_display);
uint32_t mask = WL_EVENT_HANGUP | WL_EVENT_ERROR;
*sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, server);
wlr_log(WLR_DEBUG, "Child process created with pid %d", pid);
return true;
}
static int
cleanup_primary_client(pid_t pid)
{
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
wlr_log(WLR_DEBUG, "Child exited normally with exit status %d", WEXITSTATUS(status));
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
/* Mimic Bash and other shells for the exit status */
wlr_log(WLR_DEBUG, "Child was terminated by a signal (%d)", WTERMSIG(status));
return 128 + WTERMSIG(status);
}
return 0;
}
static bool
drop_permissions(void)
{
if (getuid() == 0 || getgid() == 0) {
wlr_log(WLR_INFO, "Running as root user, this is dangerous");
return true;
}
if (getuid() != geteuid() || getgid() != getegid()) {
wlr_log(WLR_INFO, "setuid/setgid bit detected, dropping permissions");
// Set the gid and uid in the correct order.
if (setgid(getgid()) != 0 || setuid(getuid()) != 0) {
wlr_log(WLR_ERROR, "Unable to drop root, refusing to start");
return false;
}
}
if (setgid(0) != -1 || setuid(0) != -1) {
wlr_log(WLR_ERROR,
"Unable to drop root (we shouldn't be able to restore it after setuid), refusing to start");
return false;
}
return true;
}
static int
handle_signal(int signal, void *data)
{
struct cg_server *server = data;
struct wl_display *display = data;
switch (signal) {
case SIGINT:
/* Fallthrough */
case SIGTERM:
server_terminate(server);
wl_display_terminate(display);
return 0;
default:
return 0;
return 1;
}
}
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"
" -D\t Enable debug logging\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
" -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);
@ -242,62 +101,71 @@ static bool
parse_args(struct cg_server *server, int argc, char *argv[])
{
int c;
while ((c = getopt(argc, argv, "dDhm:sv")) != -1) {
#ifdef DEBUG
while ((c = getopt(argc, argv, "drDh")) != -1) {
#else
while ((c = getopt(argc, argv, "drh")) != -1) {
#endif
switch (c) {
case 'd':
server->xdg_decoration = true;
break;
case 'D':
server->log_level = WLR_DEBUG;
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;
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 = {.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;
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;
#endif
int ret = 0;
if (!parse_args(&server, argc, argv)) {
return 1;
}
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;
}
#ifdef DEBUG
wlr_log_init(WLR_DEBUG, NULL);
#else
wlr_log_init(WLR_ERROR, NULL);
#endif
server.wl_display = wl_display_create();
if (!server.wl_display) {
@ -305,101 +173,57 @@ main(int argc, char *argv[])
return 1;
}
server.display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(server.wl_display, &server.display_destroy);
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);
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);
server.backend = wlr_backend_autocreate(server.wl_display, NULL);
if (!server.backend) {
wlr_log(WLR_ERROR, "Unable to create the wlroots backend");
ret = 1;
goto end;
}
if (!drop_permissions()) {
ret = 1;
goto end;
}
server.renderer = wlr_renderer_autocreate(server.backend);
if (!server.renderer) {
wlr_log(WLR_ERROR, "Unable to create the wlroots renderer");
ret = 1;
goto end;
}
server.allocator = wlr_allocator_autocreate(server.backend, server.renderer);
if (!server.allocator) {
wlr_log(WLR_ERROR, "Unable to create the wlroots allocator");
ret = 1;
goto end;
}
wlr_renderer_init_wl_display(server.renderer, server.wl_display);
renderer = wlr_backend_get_renderer(server.backend);
wlr_renderer_init_wl_display(renderer, server.wl_display);
wl_list_init(&server.views);
wl_list_init(&server.outputs);
server.output_layout = wlr_output_layout_create(server.wl_display);
server.output_layout = wlr_output_layout_create();
if (!server.output_layout) {
wlr_log(WLR_ERROR, "Unable to create output layout");
ret = 1;
goto end;
}
server.output_layout_change.notify = handle_output_layout_change;
wl_signal_add(&server.output_layout->events.change, &server.output_layout_change);
server.scene = wlr_scene_create();
if (!server.scene) {
wlr_log(WLR_ERROR, "Unable to create scene");
ret = 1;
goto end;
}
server.scene_output_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout);
struct wlr_compositor *compositor = wlr_compositor_create(server.wl_display, 6, server.renderer);
compositor = wlr_compositor_create(server.wl_display, renderer);
if (!compositor) {
wlr_log(WLR_ERROR, "Unable to create the wlroots compositor");
ret = 1;
goto end;
}
if (!wlr_subcompositor_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the wlroots subcompositor");
ret = 1;
goto end;
}
if (!wlr_data_device_manager_create(server.wl_display)) {
data_device_mgr = wlr_data_device_manager_create(server.wl_display);
if (!data_device_mgr) {
wlr_log(WLR_ERROR, "Unable to create the data device manager");
ret = 1;
goto end;
}
if (!wlr_primary_selection_v1_device_manager_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create primary selection device manager");
ret = 1;
goto end;
}
/* Configure a listener to be notified when new outputs are
* available on the backend. We use this only to detect the
* first output and ignore subsequent outputs. */
server.new_output.notify = handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);
server.seat = seat_create(&server, server.backend);
server.seat = seat_create(&server);
if (!server.seat) {
wlr_log(WLR_ERROR, "Unable to create the seat");
ret = 1;
goto end;
}
server.idle = wlr_idle_notifier_v1_create(server.wl_display);
server.idle = wlr_idle_create(server.wl_display);
if (!server.idle) {
wlr_log(WLR_ERROR, "Unable to create the idle tracker");
ret = 1;
@ -416,19 +240,16 @@ 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);
struct wlr_xdg_shell *xdg_shell = wlr_xdg_shell_create(server.wl_display, 5);
xdg_shell = wlr_xdg_shell_create(server.wl_display);
if (!xdg_shell) {
wlr_log(WLR_ERROR, "Unable to create the XDG shell interface");
ret = 1;
goto end;
}
server.new_xdg_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);
server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new;
wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface);
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager =
wlr_xdg_decoration_manager_v1_create(server.wl_display);
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;
@ -437,128 +258,51 @@ 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;
struct wlr_server_decoration_manager *server_decoration_manager =
wlr_server_decoration_manager_create(server.wl_display);
server_decoration_manager = wlr_server_decoration_manager_create(server.wl_display);
if (!server_decoration_manager) {
wlr_log(WLR_ERROR, "Unable to create the server decoration manager");
ret = 1;
goto end;
}
wlr_server_decoration_manager_set_default_mode(
server_decoration_manager, server.xdg_decoration ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER
: WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
if (!wlr_viewporter_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the viewporter interface");
ret = 1;
goto end;
}
struct wlr_presentation *presentation = wlr_presentation_create(server.wl_display, server.backend, 2);
if (!presentation) {
wlr_log(WLR_ERROR, "Unable to create the presentation interface");
ret = 1;
goto end;
}
if (!wlr_export_dmabuf_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager");
ret = 1;
goto end;
}
if (!wlr_screencopy_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the screencopy manager");
ret = 1;
goto end;
}
if (!wlr_single_pixel_buffer_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the single pixel buffer manager");
ret = 1;
goto end;
}
if (!wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout)) {
wlr_log(WLR_ERROR, "Unable to create the output manager");
ret = 1;
goto end;
}
server.output_manager_v1 = wlr_output_manager_v1_create(server.wl_display);
if (!server.output_manager_v1) {
wlr_log(WLR_ERROR, "Unable to create the output manager");
ret = 1;
goto end;
}
server.output_manager_apply.notify = handle_output_manager_apply;
wl_signal_add(&server.output_manager_v1->events.apply, &server.output_manager_apply);
server.output_manager_test.notify = handle_output_manager_test;
wl_signal_add(&server.output_manager_v1->events.test, &server.output_manager_test);
if (!wlr_gamma_control_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the gamma control manager");
ret = 1;
goto end;
}
struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard =
wlr_virtual_keyboard_manager_v1_create(server.wl_display);
if (!virtual_keyboard) {
wlr_log(WLR_ERROR, "Unable to create the virtual keyboard manager");
ret = 1;
goto end;
}
wl_signal_add(&virtual_keyboard->events.new_virtual_keyboard, &server.new_virtual_keyboard);
struct wlr_virtual_pointer_manager_v1 *virtual_pointer =
wlr_virtual_pointer_manager_v1_create(server.wl_display);
if (!virtual_pointer) {
wlr_log(WLR_ERROR, "Unable to create the virtual pointer manager");
ret = 1;
goto end;
}
wl_signal_add(&virtual_pointer->events.new_virtual_pointer, &server.new_virtual_pointer);
server.relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server.wl_display);
if (!server.relative_pointer_manager) {
wlr_log(WLR_ERROR, "Unable to create the relative pointer manager");
ret = 1;
goto end;
}
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
struct wlr_xcursor_manager *xcursor_manager = NULL;
struct wlr_xwayland *xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
if (!xwayland) {
wlr_log(WLR_ERROR, "Cannot create XWayland server");
ret = 1;
goto end;
}
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE);
if (!xcursor_manager) {
wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager");
ret = 1;
goto end;
}
if (setenv("DISPLAY", xwayland->display_name, true) < 0) {
wlr_log_errno(WLR_ERROR, "Unable to set DISPLAY for XWayland.",
"Clients may not be able to connect");
} else {
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name);
}
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);
}
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
@ -566,7 +310,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)) {
@ -576,62 +320,48 @@ 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 " CAGE_VERSION " is running on Wayland display %s", socket);
wlr_log(WLR_DEBUG, "Cage is running on Wayland display %s", socket);
}
#if CAGE_HAS_XWAYLAND
if (xwayland) {
wlr_xwayland_set_seat(xwayland, server.seat->seat);
}
wlr_xwayland_set_seat(xwayland, server.seat->seat);
#endif
if (optind < argc && !spawn_primary_client(&server, argv + optind, &pid, &sigchld_source)) {
pid_t pid;
if (!spawn_primary_client(argv + optind, &pid)) {
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);
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);
waitpid(pid, NULL, 0);
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);
if (sigchld_source) {
wl_event_source_remove(sigchld_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);
}
wlr_data_device_manager_destroy(data_device_mgr);
wlr_compositor_destroy(compositor);
wlr_output_layout_destroy(server.output_layout);
/* 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;
}

View file

@ -3,6 +3,4 @@
#mesondefine CAGE_HAS_XWAYLAND
#mesondefine CAGE_VERSION
#endif

View file

@ -1,25 +0,0 @@
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
new_version="$1"
if [ "$new_version" != "${new_version#v}" ]; then
echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2
exit 1
fi
set -x
sed -i meson.build -e "s/^ version: '.*'/ version: '$new_version'/"
echo -n "Minimum wlroots version? "
read -r wlr_version_min
sed -i meson.build -e "s/'wlroots', version: '.*'/'wlroots', version: '>= $wlr_version_min'/"
git add meson.build
git commit -m "Update version to $new_version"

View file

@ -1,21 +0,0 @@
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
new_version="$1"
if [ "$new_version" != "${new_version#v}" ]; then
echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2
exit 1
fi
set -x
./increment_version "$new_version"
./tag-release "$new_version"
./sign-release
git push --tags

View file

@ -1,12 +0,0 @@
#!/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"

View file

@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -x
if [ "$#" -ne 1 ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
last=$(git describe --tags --abbrev=0)
echo "Last release was $last"
next="v$1"
shortlog="$(git shortlog --no-merges "$last"..)"
printf "Shortlog: \n\n%s\n\nRelease $next? [y/N] " "$shortlog"
read -r answer
if [ "$answer" != "y" ]; then
exit 0
fi
project="$(basename "$(pwd)")"
(echo "$project $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F -

View file

@ -1,15 +1,15 @@
/*
* Cage: A Wayland kiosk.
*
*
* Copyright (C) 2018-2019 Jente Hidskes
*
* See the LICENSE file accompanying this file.
*/
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include "idle_inhibit_v1.h"
#include "server.h"
@ -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_notifier_v1_set_inhibited(server->idle, inhibited);
wlr_idle_set_enabled(server->idle, NULL, !inhibited);
}
static void

View file

@ -1,7 +1,7 @@
#ifndef CG_IDLE_INHIBIT_H
#define CG_IDLE_INHIBIT_H
#include <wayland-server-core.h>
#include <wayland-server.h>
void handle_idle_inhibitor_v1_new(struct wl_listener *listener, void *data);

View file

@ -1,10 +1,9 @@
project('cage', 'c',
version: '0.2.1',
version: '0.0.1',
license: 'MIT',
meson_version: '>=0.58.1',
default_options: [
'c_std=c11',
'warning_level=2',
'warning_level=3',
'werror=true',
],
)
@ -12,6 +11,7 @@ project('cage', 'c',
add_project_arguments(
[
'-DWLR_USE_UNSTABLE',
'-Wall',
'-Wundef',
'-Wno-unused-parameter',
],
@ -35,13 +35,14 @@ if is_freebsd
)
endif
wlroots = dependency('wlroots-0.19', fallback: ['wlroots', 'wlroots'])
wlroots = dependency('wlroots', version: '>= 0.5.0')
wayland_protos = dependency('wayland-protocols', version: '>=1.14')
wayland_server = dependency('wayland-server')
pixman = dependency('pixman-1')
xkbcommon = dependency('xkbcommon')
math = cc.find_library('m')
wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
wayland_scanner = find_program('wayland-scanner')
wayland_scanner_server = generator(
wayland_scanner,
@ -64,51 +65,19 @@ server_protos = declare_dependency(
sources: server_protos_headers,
)
have_xwayland = wlroots.get_variable(pkgconfig: 'have_xwayland', internal: 'have_xwayland') == 'true'
version = '@0@'.format(meson.project_version())
git = find_program('git', native: true, required: false)
if git.found()
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
if git_commit.returncode() == 0 and git_branch.returncode() == 0
version = '@0@-@1@ (branch \'@2@\')'.format(
meson.project_version(),
git_commit.stdout().strip(),
git_branch.stdout().strip(),
)
if get_option('xwayland')
wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include <wlr/config.h>', dependencies: wlroots) == '1'
if not wlroots_has_xwayland
error('Cannot build Cage with XWayland support: wlroots has been built without it')
else
have_xwayland = true
endif
else
have_xwayland = false
endif
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',
@ -121,8 +90,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',
@ -144,16 +113,17 @@ executable(
wayland_server,
wlroots,
xkbcommon,
pixman,
math,
],
install: true,
)
summary = [
'',
'Cage @0@'.format(version),
'',
' xwayland: @0@'.format(have_xwayland),
''
'',
'Cage @0@'.format(meson.project_version()),
'',
' xwayland: @0@'.format(conf_data.get('CAGE_HAS_XWAYLAND', false)),
''
]
message('\n'.join(summary))

View file

@ -1 +1 @@
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications')

643
output.c
View file

@ -1,8 +1,7 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2021 Jente Hidskes
* Copyright (C) 2019 The Sway authors
* Copyright (C) 2018-2019 Jente Hidskes
*
* See the LICENSE file accompanying this file.
*/
@ -10,224 +9,329 @@
#define _POSIX_C_SOURCE 200112L
#include "config.h"
#include <wlr/config.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/backend/wayland.h>
#include <wlr/config.h>
#if WLR_HAS_X11_BACKEND
#include <wlr/backend/x11.h>
#endif
#include <wlr/render/swapchain.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_output_swapchain_manager.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#include "output.h"
#include "seat.h"
#include "server.h"
#include "view.h"
#if CAGE_HAS_XWAYLAND
#include "xwayland.h"
#endif
#define OUTPUT_CONFIG_UPDATED \
(WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM | \
WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED)
static void
update_output_manager_config(struct cg_server *server)
scissor_output(struct wlr_output *output, pixman_box32_t *rect)
{
struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create();
struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
struct wlr_box box = {
.x = rect->x1,
.y = rect->y1,
.width = rect->x2 - rect->x1,
.height = rect->y2 - rect->y1,
};
int output_width, output_height;
wlr_output_transformed_resolution(output, &output_width, &output_height);
enum wl_output_transform transform = wlr_output_transform_invert(output->transform);
wlr_box_transform(&box, &box, transform, output_width, output_height);
wlr_renderer_scissor(renderer, &box);
}
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;
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;
double x;
double y;
bool whole;
};
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;
static void
damage_surface(struct wlr_surface *surface, int sx, int sy, void *data)
{
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;
}
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);
}
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);
}
/* 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
output_enable(struct cg_output *output)
render_surface(struct wlr_surface *surface, int sx, int sy, void *data)
{
struct wlr_output *wlr_output = output->wlr_output;
struct render_data *rdata = data;
struct wlr_output *output = rdata->output;
/* Outputs get enabled by the backend before firing the new_output event,
* so we can't do a check for already enabled outputs here unless we
* duplicate the enabled property in cg_output. */
wlr_log(WLR_DEBUG, "Enabling output %s", wlr_output->name);
struct wlr_output_state state = {0};
wlr_output_state_set_enabled(&state, true);
if (wlr_output_commit_state(wlr_output, &state)) {
output_layout_add_auto(output);
}
update_output_manager_config(output->server);
}
static void
output_disable(struct cg_output *output)
{
struct wlr_output *wlr_output = output->wlr_output;
if (!wlr_output->enabled) {
wlr_log(WLR_DEBUG, "Not disabling already disabled output %s", wlr_output->name);
if (!wlr_surface_has_buffer(surface)) {
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_frame(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, frame);
if (!output->wlr_output->enabled || !output->scene_output) {
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (!texture) {
wlr_log(WLR_DEBUG, "Cannot obtain surface texture");
return;
}
wlr_scene_output_commit(output->scene_output, NULL);
double x = rdata->x + sx, y = rdata->y + sy;
wlr_output_layout_output_coords(rdata->output_layout, output, &x, &y);
struct timespec now = {0};
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 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);
}
damage_finish:
pixman_region32_fini(&damage);
}
static void
drag_icons_for_each_surface(struct cg_server *server, wlr_surface_iterator_func_t iterator,
void *data)
{
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);
}
}
static void
handle_output_damage_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 timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_scene_output_send_frame_done(output->scene_output, &now);
}
static void
handle_output_commit(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, commit);
struct wlr_output_event_commit *event = data;
/* Notes:
* - output layout change will also be called if needed to position the views
* - always update output manager configuration even if the output is now disabled */
if (event->state->committed & OUTPUT_CONFIG_UPDATED) {
update_output_manager_config(output->server);
bool needs_swap;
pixman_region32_t damage;
pixman_region32_init(&damage);
if (!wlr_output_damage_make_current(output->damage, &needs_swap, &damage)) {
wlr_log(WLR_ERROR, "Cannot make damage output current");
goto damage_finish;
}
}
static void
handle_output_request_state(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, request_state);
struct wlr_output_event_request_state *event = data;
if (wlr_output_commit_state(output->wlr_output, event->state)) {
update_output_manager_config(output->server);
if (!needs_swap) {
wlr_log(WLR_DEBUG, "Output doesn't need swap and isn't damaged");
goto damage_finish;
}
}
void
handle_output_layout_change(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, output_layout_change);
wlr_renderer_begin(renderer, output->wlr_output->width, output->wlr_output->height);
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 (!pixman_region32_not_empty(&damage)) {
wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer swap");
goto renderer_end;
}
#if WLR_HAS_X11_BACKEND
if (wlr_output_is_x11(output->wlr_output)) {
return true;
#ifdef DEBUG
if (output->server->debug_damage_tracking) {
wlr_renderer_clear(renderer, (float[]){1, 0, 0, 1});
}
#endif
return false;
float color[4] = {0.3, 0.3, 0.3, 1.0};
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(&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 = &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, &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);
#ifdef DEBUG
if (output->server->debug_damage_tracking) {
pixman_region32_union_rect(&damage, &damage, 0, 0, output_width, output_height);
}
#endif
enum wl_output_transform transform = wlr_output_transform_invert(output->wlr_output->transform);
wlr_region_transform(&damage, &damage, transform, output_width, output_height);
if (!wlr_output_damage_swap_buffers(output->damage, &now, &damage)) {
wlr_log(WLR_ERROR, "Could not swap buffers");
}
damage_finish:
pixman_region32_fini(&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);
}
static void
handle_output_transform(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, transform);
struct cg_view *view;
wl_list_for_each(view, &output->server->views, link) {
view_position(view);
}
}
static void
handle_output_mode(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, mode);
struct cg_view *view;
wl_list_for_each(view, &output->server->views, link) {
view_position(view);
}
}
static void
output_destroy(struct cg_output *output)
{
struct cg_server *server = output->server;
bool was_nested_output = is_nested_output(output);
output->wlr_output->data = NULL;
wl_list_remove(&output->destroy.link);
wl_list_remove(&output->commit.link);
wl_list_remove(&output->request_state.link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->link);
output_layout_remove(output);
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);
free(output);
server->output = NULL;
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);
}
/* 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);
}
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);
}
@ -237,77 +341,85 @@ handle_new_output(struct wl_listener *listener, void *data)
struct cg_server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) {
wlr_log(WLR_ERROR, "Failed to initialize output rendering");
return;
}
struct cg_output *output = calloc(1, sizeof(struct cg_output));
if (!output) {
wlr_log(WLR_ERROR, "Failed to allocate output");
return;
}
output->wlr_output = wlr_output;
wlr_output->data = output;
output->server = server;
wl_list_insert(&server->outputs, &output->link);
output->commit.notify = handle_output_commit;
wl_signal_add(&wlr_output->events.commit, &output->commit);
output->request_state.notify = handle_output_request_state;
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
output->destroy.notify = handle_output_destroy;
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
output->frame.notify = handle_output_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
if (!output->scene_output) {
wlr_log(WLR_ERROR, "Failed to allocate scene output");
return;
}
struct wlr_output_state state = {0};
wlr_output_state_set_enabled(&state, true);
/* On outputs that have modes, we need to set one before we
* can use it. Each monitor supports only a specific set of
* modes. We just pick the last, in the future we could pick
* the mode the display advertises as preferred. */
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;
}
}
}
struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.prev, mode, link);
wlr_output_set_mode(wlr_output, mode);
}
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);
}
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);
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,
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);
wlr_output_set_transform(wlr_output, server->output_transform);
wlr_output_layout_add_auto(server->output_layout, wlr_output);
/* Disconnect the signal now, because we only use one static output. */
wl_list_remove(&server->new_output.link);
if (wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) {
wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f",
wlr_output->name,
wlr_output->scale);
}
wlr_log(WLR_DEBUG, "Enabling new output %s", wlr_output->name);
if (wlr_output_commit_state(wlr_output, &state)) {
output_layout_add_auto(output);
}
/* 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);
}
view_position_all(output->server);
update_output_manager_config(output->server);
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);
}
void
@ -315,11 +427,6 @@ 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
@ -328,95 +435,3 @@ 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);
}

View file

@ -1,29 +1,30 @@
#ifndef CG_OUTPUT_H
#define CG_OUTPUT_H
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_damage.h>
#include "seat.h"
#include "server.h"
#include "view.h"
struct cg_output {
struct cg_server *server;
struct wlr_output *wlr_output;
struct wlr_scene_output *scene_output;
struct wlr_output_damage *damage;
struct wl_listener commit;
struct wl_listener request_state;
struct wl_listener mode;
struct wl_listener transform;
struct wl_listener destroy;
struct wl_listener frame;
struct wl_list link; // cg_server::outputs
struct wl_listener damage_frame;
struct wl_listener damage_destroy;
};
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

613
seat.c

File diff suppressed because it is too large Load diff

34
seat.h
View file

@ -1,7 +1,7 @@
#ifndef CG_SEAT_H
#define CG_SEAT_H
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_input_device.h>
@ -20,26 +20,24 @@ 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_relative;
struct wl_listener cursor_motion;
struct wl_listener cursor_motion_absolute;
struct wl_listener cursor_button;
struct wl_listener cursor_axis;
struct wl_listener cursor_frame;
int32_t touch_id;
double touch_lx;
double touch_ly;
double touch_x;
double touch_y;
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;
@ -50,19 +48,20 @@ struct cg_seat {
struct wl_listener request_set_primary_selection;
};
struct cg_keyboard_group {
struct wlr_keyboard_group *wlr_group;
struct cg_keyboard {
struct wl_list link; // seat::keyboards
struct cg_seat *seat;
struct wl_listener key;
struct wlr_input_device *device;
struct wl_listener modifiers;
struct wl_list link; // cg_seat::keyboard_groups
bool is_virtual;
struct wl_listener key;
struct wl_listener destroy;
};
struct cg_pointer {
struct wl_list link; // seat::pointers
struct cg_seat *seat;
struct wlr_pointer *pointer;
struct wlr_input_device *device;
struct wl_listener destroy;
};
@ -70,7 +69,7 @@ struct cg_pointer {
struct cg_touch {
struct wl_list link; // seat::touch
struct cg_seat *seat;
struct wlr_touch *touch;
struct wlr_input_device *device;
struct wl_listener destroy;
};
@ -79,18 +78,15 @@ struct cg_drag_icon {
struct wl_list link; // seat::drag_icons
struct cg_seat *seat;
struct wlr_drag_icon *wlr_drag_icon;
struct wlr_scene_tree *scene_tree;
/* The drag icon has a position in layout coordinates. */
double lx, ly;
double x;
double y;
struct wl_listener destroy;
};
struct cg_seat *seat_create(struct cg_server *server, struct wlr_backend *backend);
struct cg_seat *seat_create(struct cg_server *server);
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

View file

@ -3,71 +3,46 @@
#include "config.h"
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/util/log.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/xwayland.h>
#endif
enum cg_multi_output_mode {
CAGE_MULTI_OUTPUT_MODE_EXTEND,
CAGE_MULTI_OUTPUT_MODE_LAST,
};
#include "output.h"
#include "seat.h"
#include "view.h"
struct cg_server {
struct wl_display *wl_display;
struct wl_list views;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_session *session;
struct wl_listener display_destroy;
struct wl_list views;
struct cg_seat *seat;
struct wlr_idle_notifier_v1 *idle;
struct wlr_idle *idle;
struct wlr_idle_inhibit_manager_v1 *idle_inhibit_v1;
struct wl_listener new_idle_inhibitor_v1;
struct wl_list inhibitors;
enum cg_multi_output_mode output_mode;
struct wlr_output_layout *output_layout;
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 cg_output *output;
struct wl_listener new_output;
struct wl_listener output_layout_change;
struct wl_listener xdg_toplevel_decoration;
struct wl_listener new_xdg_toplevel;
struct wl_listener new_xdg_popup;
struct wl_listener new_virtual_keyboard;
struct wl_listener new_virtual_pointer;
struct wl_listener new_xdg_shell_surface;
#if CAGE_HAS_XWAYLAND
struct wl_listener new_xwayland_surface;
#endif
struct wlr_output_manager_v1 *output_manager_v1;
struct wl_listener output_manager_apply;
struct wl_listener output_manager_test;
struct wlr_relative_pointer_manager_v1 *relative_pointer_manager;
bool xdg_decoration;
bool allow_vt_switch;
bool return_app_code;
bool terminated;
enum wlr_log_importance log_level;
enum wl_output_transform output_transform;
#ifdef DEBUG
bool debug_damage_tracking;
#endif
};
void server_terminate(struct cg_server *server);
#endif

222
view.c
View file

@ -1,20 +1,20 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2021 Jente Hidskes
* Copyright (C) 2018-2019 Jente Hidskes
*
* See the LICENSE file accompanying this file.
*/
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_surface.h>
#include "output.h"
#include "seat.h"
@ -24,6 +24,96 @@
#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)
{
@ -41,73 +131,67 @@ 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 bool
view_extends_output_layout(struct cg_view *view, struct wlr_box *layout_box)
static void
view_maximize(struct cg_view *view)
{
int width, height;
view->impl->get_geometry(view, &width, &height);
struct cg_output *output = view->server->output;
int output_width, output_height;
return (layout_box->height < height || layout_box->width < width);
wlr_output_transformed_resolution(output->wlr_output, &output_width, &output_height);
view->impl->maximize(view, output_width, output_height);
}
static void
view_maximize(struct cg_view *view, struct wlr_box *layout_box)
view_center(struct cg_view *view)
{
view->lx = layout_box->x;
view->ly = layout_box->y;
struct wlr_output *output = view->server->output->wlr_output;
if (view->scene_tree) {
wlr_scene_node_set_position(&view->scene_tree->node, view->lx, view->ly);
}
int output_width, output_height;
wlr_output_transformed_resolution(output, &output_width, &output_height);
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);
}
view->x = (output_width - width) / 2;
view->y = (output_height - height) / 2;
}
void
view_position(struct cg_view *view)
{
struct wlr_box layout_box;
wlr_output_layout_get_box(view->server->output_layout, NULL, &layout_box);
if (view_is_primary(view) || view_extends_output_layout(view, &layout_box)) {
view_maximize(view, &layout_box);
if (view_is_primary(view)) {
view_maximize(view);
} else {
view_center(view, &layout_box);
view_center(view);
}
}
void
view_position_all(struct cg_server *server)
view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
{
struct cg_view *view;
wl_list_for_each (view, &server->views, link) {
view_position(view);
}
view->impl->for_each_surface(view, iterator, data);
}
void
@ -115,24 +199,28 @@ view_unmap(struct cg_view *view)
{
wl_list_remove(&view->link);
wlr_scene_node_destroy(&view->scene_tree->node);
wl_list_remove(&view->new_subsurface.link);
struct cg_view_child *child, *tmp;
wl_list_for_each_safe(child, tmp, &view->children, link) {
child->destroy(child);
}
view->wlr_surface->data = NULL;
view->wlr_surface = NULL;
}
void
view_map(struct cg_view *view, struct wlr_surface *surface)
{
view->scene_tree = wlr_scene_subsurface_tree_create(&view->server->scene->tree, surface);
if (!view->scene_tree) {
wl_resource_post_no_memory(surface->resource);
return;
}
view->scene_tree->node.data = view;
view->wlr_surface = surface;
surface->data = view;
struct wlr_subsurface *subsurface;
wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces, parent_link) {
subsurface_create(view, subsurface);
}
view->new_subsurface.notify = handle_new_subsurface;
wl_signal_add(&view->wlr_surface->events.new_subsurface, &view->new_subsurface);
#if CAGE_HAS_XWAYLAND
/* We shouldn't position override-redirect windows. They set
@ -151,6 +239,14 @@ 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);
@ -163,20 +259,38 @@ 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 wlr_surface *surface)
view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface)
{
assert(surface);
return surface->data;
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);
}

50
view.h
View file

@ -4,10 +4,10 @@
#include "config.h"
#include <stdbool.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_compositor.h>
#include <wayland-server.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/box.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/xwayland.h>
#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;
struct wlr_scene_tree *scene_tree;
/* The view has a position in layout coordinates. */
int lx, ly;
int x, y;
enum cg_view_type type;
const struct cg_view_impl *impl;
struct wl_listener new_subsurface;
};
struct cg_view_impl {
@ -42,19 +42,49 @@ 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_position_all(struct cg_server *server);
void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data);
void view_unmap(struct cg_view *view);
void view_map(struct cg_view *view, struct wlr_surface *surface);
void view_destroy(struct cg_view *view);
void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl);
void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type,
const struct cg_view_impl *impl);
struct cg_view *view_from_wlr_surface(struct wlr_surface *surface);
struct cg_view *view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface);
struct wlr_surface *view_wlr_surface_at(struct cg_view *view, double sx, double sy,
double *sub_x, double *sub_y);
void view_child_finish(struct cg_view_child *child);
void view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface);
#endif

View file

@ -6,11 +6,10 @@
* See the LICENSE file accompanying this file.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_scene.h>
#include <wayland-server.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
@ -19,9 +18,21 @@
#include "xdg_shell.h"
static void
xdg_decoration_set_mode(struct cg_xdg_decoration *xdg_decoration)
xdg_decoration_handle_destroy(struct wl_listener *listener, void *data)
{
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, destroy);
wl_list_remove(&xdg_decoration->destroy.link);
wl_list_remove(&xdg_decoration->request_mode.link);
free(xdg_decoration);
}
static void
xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data)
{
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, request_mode);
enum wlr_xdg_toplevel_decoration_v1_mode mode;
if (xdg_decoration->server->xdg_decoration) {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
} else {
@ -31,84 +42,101 @@ xdg_decoration_set_mode(struct cg_xdg_decoration *xdg_decoration)
}
static void
xdg_decoration_handle_destroy(struct wl_listener *listener, void *data)
xdg_popup_destroy(struct cg_view_child *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) {
if (!child) {
return;
}
struct cg_server *server = view->server;
struct wlr_box *popup_box = &popup->current.geometry;
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 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);
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;
int width, height;
wlr_output_effective_resolution(output, &width, &height);
struct wlr_box output_toplevel_box = {
.x = output_box.x - view->lx,
.y = output_box.y - view->ly,
.width = output_box.width,
.height = output_box.height,
.x = output->lx - view->x,
.y = output->ly - view->y,
.width = width,
.height = height
};
wlr_xdg_popup_unconstrain_from_box(popup, &output_toplevel_box);
wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box);
}
static void
xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup)
{
struct cg_xdg_popup *popup = calloc(1, sizeof(struct cg_xdg_popup));
if (!popup) {
return;
}
popup->wlr_popup = wlr_popup;
view_child_init(&popup->view_child, view, wlr_popup->base->surface);
popup->view_child.destroy = xdg_popup_destroy;
popup->destroy.notify = handle_xdg_popup_destroy;
wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
popup->map.notify = handle_xdg_popup_map;
wl_signal_add(&wlr_popup->base->events.map, &popup->map);
popup->unmap.notify = handle_xdg_popup_unmap;
wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
popup->new_popup.notify = popup_handle_new_xdg_popup;
wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
popup_unconstrain(popup);
}
static void
handle_new_xdg_popup(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
xdg_popup_create(&xdg_shell_view->view, wlr_popup);
}
static struct cg_xdg_shell_view *
@ -121,26 +149,27 @@ static char *
get_title(struct cg_view *view)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
return xdg_shell_view->xdg_toplevel->title;
return xdg_shell_view->xdg_surface->toplevel->title;
}
static void
get_geometry(struct cg_view *view, int *width_out, int *height_out)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
struct wlr_xdg_surface *xdg_surface = xdg_shell_view->xdg_toplevel->base;
struct wlr_box geom;
*width_out = xdg_surface->geometry.width;
*height_out = xdg_surface->geometry.height;
wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geom);
*width_out = geom.width;
*height_out = geom.height;
}
static bool
is_primary(struct cg_view *view)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
struct wlr_xdg_toplevel *parent = xdg_shell_view->xdg_toplevel->parent;
return parent == NULL;
struct wlr_xdg_surface *parent = xdg_shell_view->xdg_surface->toplevel->parent;
/* FIXME: role is 0? */
return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */
}
static bool
@ -150,13 +179,14 @@ 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_toplevel *xdg_toplevel = _child->xdg_toplevel;
struct wlr_xdg_surface *xdg_surface = _child->xdg_surface;
struct cg_xdg_shell_view *_parent = xdg_shell_view_from_view(parent);
while (xdg_toplevel) {
if (xdg_toplevel->parent == _parent->xdg_toplevel) {
struct wlr_xdg_surface *parent_xdg_surface = _parent->xdg_surface;
while (xdg_surface) {
if (xdg_surface->toplevel->parent == parent_xdg_surface) {
return true;
}
xdg_toplevel = xdg_toplevel->parent;
xdg_surface = xdg_surface->toplevel->parent;
}
return false;
}
@ -165,15 +195,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_toplevel, activate);
wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate);
}
static void
maximize(struct cg_view *view, int output_width, int output_height)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_toplevel, output_width, output_height);
wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_toplevel, true);
wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, output_width, output_height);
wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true);
}
static void
@ -184,68 +214,74 @@ destroy(struct cg_view *view)
}
static void
handle_xdg_toplevel_request_fullscreen(struct wl_listener *listener, void *data)
for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen);
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);
}
/**
* 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 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_toplevel_unmap(struct wl_listener *listener, void *data)
handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen);
struct wlr_xdg_toplevel_set_fullscreen_event *event = data;
wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, event->fullscreen);
}
static void
handle_xdg_shell_surface_commit(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit);
struct cg_view *view = &xdg_shell_view->view;
view_damage_surface(view);
}
static void
handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap);
struct cg_view *view = &xdg_shell_view->view;
view_damage_whole(view);
wl_list_remove(&xdg_shell_view->commit.link);
view_unmap(view);
}
static void
handle_xdg_toplevel_map(struct wl_listener *listener, void *data)
handle_xdg_shell_surface_map(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map);
struct cg_view *view = &xdg_shell_view->view;
view_map(view, xdg_shell_view->xdg_toplevel->base->surface);
xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit;
wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit);
view_map(view, xdg_shell_view->xdg_surface->surface);
view_damage_whole(view);
}
static void
handle_xdg_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)
handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, destroy);
struct cg_view *view = &xdg_shell_view->view;
wl_list_remove(&xdg_shell_view->commit.link);
wl_list_remove(&xdg_shell_view->map.link);
wl_list_remove(&xdg_shell_view->unmap.link);
wl_list_remove(&xdg_shell_view->destroy.link);
wl_list_remove(&xdg_shell_view->request_fullscreen.link);
xdg_shell_view->xdg_toplevel = NULL;
wl_list_remove(&xdg_shell_view->new_popup.link);
xdg_shell_view->xdg_surface = NULL;
view_destroy(view);
}
@ -258,13 +294,19 @@ 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_new_xdg_toplevel(struct wl_listener *listener, void *data)
handle_xdg_shell_surface_new(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, new_xdg_toplevel);
struct wlr_xdg_toplevel *toplevel = data;
struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface);
struct wlr_xdg_surface *xdg_surface = data;
if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
return;
}
struct cg_xdg_shell_view *xdg_shell_view = calloc(1, sizeof(struct cg_xdg_shell_view));
if (!xdg_shell_view) {
@ -273,105 +315,18 @@ handle_new_xdg_toplevel(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_toplevel = toplevel;
xdg_shell_view->xdg_surface = xdg_surface;
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;
xdg_shell_view->map.notify = handle_xdg_shell_surface_map;
wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map);
xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap;
wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap);
xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy;
wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy);
xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen;
wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen);
xdg_shell_view->new_popup.notify = handle_new_xdg_popup;
wl_signal_add(&xdg_surface->events.new_popup, &xdg_shell_view->new_popup);
}
void
@ -390,8 +345,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);
}

View file

@ -1,7 +1,7 @@
#ifndef CG_XDG_SHELL_H
#define CG_XDG_SHELL_H
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
@ -9,33 +9,34 @@
struct cg_xdg_shell_view {
struct cg_view view;
struct wlr_xdg_toplevel *xdg_toplevel;
struct wlr_xdg_surface *xdg_surface;
struct wl_listener destroy;
struct wl_listener commit;
struct wl_listener unmap;
struct wl_listener map;
struct wl_listener commit;
struct wl_listener request_fullscreen;
struct wl_listener new_popup;
};
struct cg_xdg_popup {
struct cg_view_child view_child;
struct wlr_xdg_popup *wlr_popup;
struct wl_listener destroy;
struct wl_listener map;
struct wl_listener unmap;
struct wl_listener new_popup;
};
struct cg_xdg_decoration {
struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration;
struct cg_server *server;
struct wl_listener destroy;
struct wl_listener commit;
struct wl_listener request_mode;
};
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_shell_surface_new(struct wl_listener *listener, void *data);
void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data);

View file

@ -1,16 +1,17 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2020 Jente Hidskes
* Copyright (C) 2018-2019 Jente Hidskes
*
* See the LICENSE file accompanying this file.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/util/log.h>
#include <wayland-server.h>
#include <wlr/types/wlr_box.h>
#include <wlr/xwayland.h>
#include <wlr/util/log.h>
#include "server.h"
#include "view.h"
@ -41,15 +42,8 @@ static void
get_geometry(struct cg_view *view, int *width_out, int *height_out)
{
struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view);
struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface;
if (xsurface->surface == NULL) {
*width_out = 0;
*height_out = 0;
return;
}
*width_out = xsurface->surface->current.width;
*height_out = xsurface->surface->current.height;
*width_out = xwayland_view->xwayland_surface->surface->current.width;
*height_out = xwayland_view->xwayland_surface->surface->current.height;
}
static bool
@ -90,9 +84,8 @@ static void
maximize(struct cg_view *view, int output_width, int output_height)
{
struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view);
wlr_xwayland_surface_configure(xwayland_view->xwayland_surface, view->lx, view->ly, output_width,
output_height);
wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true, true);
wlr_xwayland_surface_configure(xwayland_view->xwayland_surface, 0, 0, output_width, output_height);
wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true);
}
static void
@ -102,6 +95,18 @@ 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)
{
@ -110,12 +115,24 @@ 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);
}
@ -126,11 +143,17 @@ handle_xwayland_surface_map(struct wl_listener *listener, void *data)
struct cg_view *view = &xwayland_view->view;
if (!xwayland_view_should_manage(view)) {
view->lx = xwayland_view->xwayland_surface->x;
view->ly = xwayland_view->xwayland_surface->y;
view->x = xwayland_view->xwayland_surface->x;
view->y = 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
@ -139,6 +162,8 @@ 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;
@ -154,28 +179,10 @@ 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)
{
@ -191,10 +198,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->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->map.notify = handle_xwayland_surface_map;
wl_signal_add(&xwayland_surface->events.map, &xwayland_view->map);
xwayland_view->unmap.notify = handle_xwayland_surface_unmap;
wl_signal_add(&xwayland_surface->events.unmap, &xwayland_view->unmap);
xwayland_view->destroy.notify = handle_xwayland_surface_destroy;
wl_signal_add(&xwayland_surface->events.destroy, &xwayland_view->destroy);
xwayland_view->request_fullscreen.notify = handle_xwayland_surface_request_fullscreen;

View file

@ -1,7 +1,7 @@
#ifndef CG_XWAYLAND_H
#define CG_XWAYLAND_H
#include <wayland-server-core.h>
#include <wayland-server.h>
#include <wlr/xwayland.h>
#include "view.h"
@ -9,11 +9,25 @@
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;
};