mirror of
https://github.com/cage-kiosk/cage.git
synced 2026-04-09 08:21:23 -04:00
With Cage now supporting hotplugging of outputs, we shouldn't warp the cursor to the center of every new output. Rather, we should warp it only on the initial startup.
423 lines
11 KiB
C
423 lines
11 KiB
C
/*
|
|
* Cage: A Wayland kiosk.
|
|
*
|
|
* Copyright (C) 2018-2019 Jente Hidskes
|
|
*
|
|
* See the LICENSE file accompanying this file.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200112L
|
|
|
|
#include "config.h"
|
|
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <wayland-server-core.h>
|
|
#include <wlr/backend.h>
|
|
#include <wlr/render/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_output_layout.h>
|
|
#include <wlr/types/wlr_screencopy_v1.h>
|
|
#include <wlr/types/wlr_server_decoration.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_shell.h>
|
|
#include <wlr/types/wlr_xdg_output_v1.h>
|
|
#include <wlr/util/log.h>
|
|
#if CAGE_HAS_XWAYLAND
|
|
#include <wlr/xwayland.h>
|
|
#endif
|
|
|
|
#include "idle_inhibit_v1.h"
|
|
#include "output.h"
|
|
#include "seat.h"
|
|
#include "server.h"
|
|
#include "view.h"
|
|
#include "xdg_shell.h"
|
|
#if CAGE_HAS_XWAYLAND
|
|
#include "xwayland.h"
|
|
#endif
|
|
|
|
static bool
|
|
spawn_primary_client(char *argv[], pid_t *pid_out)
|
|
{
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigprocmask(SIG_SETMASK, &set, NULL);
|
|
execvp(argv[0], argv);
|
|
_exit(1);
|
|
} else if (pid == -1) {
|
|
wlr_log_errno(WLR_ERROR, "Unable to fork");
|
|
return false;
|
|
}
|
|
|
|
*pid_out = pid;
|
|
wlr_log(WLR_DEBUG, "Child process created with pid %d", pid);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
drop_permissions(void)
|
|
{
|
|
if (getuid() != geteuid() || getgid() != getegid()) {
|
|
if (setuid(getuid()) != 0 || setgid(getgid()) != 0) {
|
|
wlr_log(WLR_ERROR, "Unable to drop root, refusing to start");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (setuid(0) != -1) {
|
|
wlr_log(WLR_ERROR, "Unable to drop root (we shouldn't be able to "
|
|
"restore it after setuid), refusing to start");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
handle_signal(int signal, void *data)
|
|
{
|
|
struct wl_display *display = data;
|
|
|
|
switch (signal) {
|
|
case SIGINT:
|
|
/* Fallthrough */
|
|
case SIGTERM:
|
|
wl_display_terminate(display);
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
usage(FILE *file, const char *cage)
|
|
{
|
|
fprintf(file, "Usage: %s [OPTIONS] [--] APPLICATION\n"
|
|
"\n"
|
|
" -d\t Don't draw client side decorations, when possible\n"
|
|
" -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"
|
|
"\n"
|
|
" Use -- when you want to pass arguments to APPLICATION\n",
|
|
cage);
|
|
}
|
|
|
|
static bool
|
|
parse_args(struct cg_server *server, int argc, char *argv[])
|
|
{
|
|
int c;
|
|
#ifdef DEBUG
|
|
while ((c = getopt(argc, argv, "drDh")) != -1) {
|
|
#else
|
|
while ((c = getopt(argc, argv, "drh")) != -1) {
|
|
#endif
|
|
switch (c) {
|
|
case 'd':
|
|
server->xdg_decoration = true;
|
|
break;
|
|
case 'r':
|
|
server->output_transform++;
|
|
if (server->output_transform > WL_OUTPUT_TRANSFORM_270) {
|
|
server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
|
}
|
|
break;
|
|
#ifdef DEBUG
|
|
case 'D':
|
|
server->debug_damage_tracking = true;
|
|
break;
|
|
#endif
|
|
case 'h':
|
|
usage(stdout, argv[0]);
|
|
return false;
|
|
default:
|
|
usage(stderr, argv[0]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (optind >= argc) {
|
|
usage(stderr, argv[0]);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct cg_server server = {0};
|
|
struct wl_event_loop *event_loop = NULL;
|
|
struct wl_event_source *sigint_source = NULL;
|
|
struct wl_event_source *sigterm_source = NULL;
|
|
struct wlr_renderer *renderer = NULL;
|
|
struct wlr_compositor *compositor = NULL;
|
|
struct wlr_data_device_manager *data_device_manager = NULL;
|
|
struct wlr_server_decoration_manager *server_decoration_manager = NULL;
|
|
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL;
|
|
struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager = NULL;
|
|
struct wlr_screencopy_manager_v1 *screencopy_manager = NULL;
|
|
struct wlr_xdg_output_manager_v1 *output_manager = NULL;
|
|
struct wlr_gamma_control_manager_v1 *gamma_control_manager = NULL;
|
|
struct wlr_xdg_shell *xdg_shell = NULL;
|
|
#if CAGE_HAS_XWAYLAND
|
|
struct wlr_xwayland *xwayland = NULL;
|
|
struct wlr_xcursor_manager *xcursor_manager = NULL;
|
|
#endif
|
|
int ret = 0;
|
|
|
|
if (!parse_args(&server, argc, argv)) {
|
|
return 1;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
wlr_log_init(WLR_DEBUG, NULL);
|
|
#else
|
|
wlr_log_init(WLR_ERROR, NULL);
|
|
#endif
|
|
|
|
server.wl_display = wl_display_create();
|
|
if (!server.wl_display) {
|
|
wlr_log(WLR_ERROR, "Cannot allocate a Wayland display");
|
|
return 1;
|
|
}
|
|
|
|
event_loop = wl_display_get_event_loop(server.wl_display);
|
|
sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server.wl_display);
|
|
sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server.wl_display);
|
|
|
|
server.backend = wlr_backend_autocreate(server.wl_display, 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;
|
|
}
|
|
|
|
renderer = wlr_backend_get_renderer(server.backend);
|
|
wlr_renderer_init_wl_display(renderer, server.wl_display);
|
|
|
|
wl_list_init(&server.views);
|
|
wl_list_init(&server.outputs);
|
|
|
|
server.output_layout = wlr_output_layout_create();
|
|
if (!server.output_layout) {
|
|
wlr_log(WLR_ERROR, "Unable to create output layout");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
compositor = wlr_compositor_create(server.wl_display, renderer);
|
|
if (!compositor) {
|
|
wlr_log(WLR_ERROR, "Unable to create the wlroots compositor");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
data_device_manager = wlr_data_device_manager_create(server.wl_display);
|
|
if (!data_device_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the data device manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
/* Configure a listener to be notified when new outputs are
|
|
* available on the backend. We use this only to detect the
|
|
* first output and ignore subsequent outputs. */
|
|
server.new_output.notify = handle_new_output;
|
|
wl_signal_add(&server.backend->events.new_output, &server.new_output);
|
|
|
|
server.seat = seat_create(&server);
|
|
if (!server.seat) {
|
|
wlr_log(WLR_ERROR, "Unable to create the seat");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
server.idle = wlr_idle_create(server.wl_display);
|
|
if (!server.idle) {
|
|
wlr_log(WLR_ERROR, "Unable to create the idle tracker");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
server.idle_inhibit_v1 = wlr_idle_inhibit_v1_create(server.wl_display);
|
|
if (!server.idle_inhibit_v1) {
|
|
wlr_log(WLR_ERROR, "Cannot create the idle inhibitor");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
server.new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1_new;
|
|
wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, &server.new_idle_inhibitor_v1);
|
|
wl_list_init(&server.inhibitors);
|
|
|
|
xdg_shell = wlr_xdg_shell_create(server.wl_display);
|
|
if (!xdg_shell) {
|
|
wlr_log(WLR_ERROR, "Unable to create the XDG shell interface");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new;
|
|
wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface);
|
|
|
|
xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server.wl_display);
|
|
if (!xdg_decoration_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the XDG decoration manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &server.xdg_toplevel_decoration);
|
|
server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration;
|
|
|
|
server_decoration_manager = wlr_server_decoration_manager_create(server.wl_display);
|
|
if (!server_decoration_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the server decoration manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
wlr_server_decoration_manager_set_default_mode(server_decoration_manager,
|
|
server.xdg_decoration ?
|
|
WLR_SERVER_DECORATION_MANAGER_MODE_SERVER :
|
|
WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
|
|
|
|
export_dmabuf_manager = wlr_export_dmabuf_manager_v1_create(server.wl_display);
|
|
if (!export_dmabuf_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
screencopy_manager = wlr_screencopy_manager_v1_create(server.wl_display);
|
|
if (!screencopy_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the screencopy manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
output_manager = wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout);
|
|
if (!output_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the output manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
gamma_control_manager = wlr_gamma_control_manager_v1_create(server.wl_display);
|
|
if (!gamma_control_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the gamma control manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
#if CAGE_HAS_XWAYLAND
|
|
xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
|
|
if (!xwayland) {
|
|
wlr_log(WLR_ERROR, "Cannot create XWayland server");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
|
|
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
|
|
|
|
xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE);
|
|
if (!xcursor_manager) {
|
|
wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if (setenv("DISPLAY", xwayland->display_name, true) < 0) {
|
|
wlr_log_errno(WLR_ERROR, "Unable to set DISPLAY for XWayland.",
|
|
"Clients may not be able to connect");
|
|
} else {
|
|
wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name);
|
|
}
|
|
|
|
if (wlr_xcursor_manager_load(xcursor_manager, 1)) {
|
|
wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme");
|
|
}
|
|
struct wlr_xcursor *xcursor =
|
|
wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1);
|
|
if (xcursor) {
|
|
struct wlr_xcursor_image *image = xcursor->images[0];
|
|
wlr_xwayland_set_cursor(xwayland, image->buffer,
|
|
image->width * 4, image->width, image->height,
|
|
image->hotspot_x, image->hotspot_y);
|
|
}
|
|
#endif
|
|
|
|
const char *socket = wl_display_add_socket_auto(server.wl_display);
|
|
if (!socket) {
|
|
wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if (!wlr_backend_start(server.backend)) {
|
|
wlr_log(WLR_ERROR, "Unable to start the wlroots backend");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if (setenv("WAYLAND_DISPLAY", socket, true) < 0) {
|
|
wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY.",
|
|
"Clients may not be able to connect");
|
|
} else {
|
|
wlr_log(WLR_DEBUG, "Cage is running on Wayland display %s", socket);
|
|
}
|
|
|
|
#if CAGE_HAS_XWAYLAND
|
|
wlr_xwayland_set_seat(xwayland, server.seat->seat);
|
|
#endif
|
|
|
|
pid_t pid;
|
|
if (!spawn_primary_client(argv + optind, &pid)) {
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
/* Place the cursor in the center of the output layout. */
|
|
struct wlr_box *layout_box = wlr_output_layout_get_box(server.output_layout, NULL);
|
|
wlr_cursor_warp(server.seat->cursor, NULL, layout_box->width / 2, layout_box->height / 2);
|
|
|
|
wl_display_run(server.wl_display);
|
|
|
|
#if CAGE_HAS_XWAYLAND
|
|
wlr_xwayland_destroy(xwayland);
|
|
wlr_xcursor_manager_destroy(xcursor_manager);
|
|
#endif
|
|
wl_display_destroy_clients(server.wl_display);
|
|
|
|
waitpid(pid, NULL, 0);
|
|
|
|
end:
|
|
wl_event_source_remove(sigint_source);
|
|
wl_event_source_remove(sigterm_source);
|
|
seat_destroy(server.seat);
|
|
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);
|
|
return ret;
|
|
}
|