mirror of
https://github.com/cage-kiosk/cage.git
synced 2025-10-29 05:40:19 -04:00
Only terminate if the last output was nested under the Wayland or X11 backend. If not, using DRM backend for example, terminating Cage when unplugging the last monitor or simply turning it off does not seem to be the right behavior.
386 lines
11 KiB
C
386 lines
11 KiB
C
/*
|
|
* Cage: A Wayland kiosk.
|
|
*
|
|
* Copyright (C) 2018-2021 Jente Hidskes
|
|
* Copyright (C) 2019 The Sway authors
|
|
*
|
|
* See the LICENSE file accompanying this file.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200112L
|
|
|
|
#include "config.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <wayland-server-core.h>
|
|
#include <wlr/backend.h>
|
|
#include <wlr/backend/wayland.h>
|
|
#include <wlr/config.h>
|
|
#if WLR_HAS_X11_BACKEND
|
|
#include <wlr/backend/x11.h>
|
|
#endif
|
|
#include <wlr/render/wlr_renderer.h>
|
|
#include <wlr/types/wlr_compositor.h>
|
|
#include <wlr/types/wlr_data_device.h>
|
|
#include <wlr/types/wlr_matrix.h>
|
|
#include <wlr/types/wlr_output.h>
|
|
#include <wlr/types/wlr_output_damage.h>
|
|
#include <wlr/types/wlr_output_layout.h>
|
|
#include <wlr/types/wlr_output_management_v1.h>
|
|
#include <wlr/types/wlr_scene.h>
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
|
#include <wlr/util/log.h>
|
|
#include <wlr/util/region.h>
|
|
|
|
#include "output.h"
|
|
#include "seat.h"
|
|
#include "server.h"
|
|
#include "view.h"
|
|
#if CAGE_HAS_XWAYLAND
|
|
#include "xwayland.h"
|
|
#endif
|
|
|
|
#define OUTPUT_CONFIG_UPDATED \
|
|
(WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM | \
|
|
WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED)
|
|
|
|
static void
|
|
update_output_manager_config(struct cg_server *server)
|
|
{
|
|
struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create();
|
|
|
|
struct cg_output *output;
|
|
wl_list_for_each (output, &server->outputs, link) {
|
|
struct wlr_output *wlr_output = output->wlr_output;
|
|
struct wlr_output_configuration_head_v1 *config_head =
|
|
wlr_output_configuration_head_v1_create(config, wlr_output);
|
|
struct wlr_box output_box;
|
|
|
|
wlr_output_layout_get_box(server->output_layout, wlr_output, &output_box);
|
|
if (!wlr_box_empty(&output_box)) {
|
|
config_head->state.x = output_box.x;
|
|
config_head->state.y = output_box.y;
|
|
}
|
|
}
|
|
|
|
wlr_output_manager_v1_set_configuration(server->output_manager_v1, config);
|
|
}
|
|
|
|
static void
|
|
output_enable(struct cg_output *output)
|
|
{
|
|
struct wlr_output *wlr_output = output->wlr_output;
|
|
|
|
/* Outputs get enabled by the backend before firing the new_output event,
|
|
* so we can't do a check for already enabled outputs here unless we
|
|
* duplicate the enabled property in cg_output. */
|
|
wlr_log(WLR_DEBUG, "Enabling output %s", wlr_output->name);
|
|
|
|
wlr_output_layout_add_auto(output->server->output_layout, wlr_output);
|
|
wlr_output_enable(wlr_output, true);
|
|
wlr_output_commit(wlr_output);
|
|
|
|
output->scene_output = wlr_scene_get_scene_output(output->server->scene, wlr_output);
|
|
assert(output->scene_output != NULL);
|
|
|
|
update_output_manager_config(output->server);
|
|
}
|
|
|
|
static void
|
|
output_disable(struct cg_output *output)
|
|
{
|
|
struct wlr_output *wlr_output = output->wlr_output;
|
|
|
|
if (!wlr_output->enabled) {
|
|
wlr_log(WLR_DEBUG, "Not disabling already disabled output %s", wlr_output->name);
|
|
return;
|
|
}
|
|
|
|
output->scene_output = NULL;
|
|
|
|
wlr_log(WLR_DEBUG, "Disabling output %s", wlr_output->name);
|
|
wlr_output_enable(wlr_output, false);
|
|
wlr_output_layout_remove(output->server->output_layout, wlr_output);
|
|
wlr_output_commit(wlr_output);
|
|
}
|
|
|
|
static bool
|
|
output_apply_config(struct cg_output *output, struct wlr_output_configuration_head_v1 *head, bool test_only)
|
|
{
|
|
wlr_output_enable(output->wlr_output, head->state.enabled);
|
|
|
|
if (head->state.enabled) {
|
|
/* Do not mess with these parameters for output to be disabled */
|
|
wlr_output_set_scale(output->wlr_output, head->state.scale);
|
|
wlr_output_set_transform(output->wlr_output, head->state.transform);
|
|
|
|
if (head->state.mode) {
|
|
wlr_output_set_mode(output->wlr_output, head->state.mode);
|
|
} else {
|
|
wlr_output_set_custom_mode(output->wlr_output, head->state.custom_mode.width,
|
|
head->state.custom_mode.height, head->state.custom_mode.refresh);
|
|
}
|
|
}
|
|
|
|
if (test_only) {
|
|
bool ret = wlr_output_test(output->wlr_output);
|
|
wlr_output_rollback(output->wlr_output);
|
|
return ret;
|
|
}
|
|
|
|
/* Apply output configuration */
|
|
if (!wlr_output_commit(output->wlr_output)) {
|
|
return false;
|
|
}
|
|
|
|
if (head->state.enabled) {
|
|
wlr_output_layout_add(output->server->output_layout, head->state.output, head->state.x, head->state.y);
|
|
} else {
|
|
wlr_output_layout_remove(output->server->output_layout, output->wlr_output);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
handle_output_frame(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_output *output = wl_container_of(listener, output, frame);
|
|
|
|
if (!output->wlr_output->enabled) {
|
|
return;
|
|
}
|
|
|
|
wlr_scene_output_commit(output->scene_output);
|
|
|
|
struct timespec now = {0};
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
wlr_scene_output_send_frame_done(output->scene_output, &now);
|
|
}
|
|
|
|
static void
|
|
handle_output_commit(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_output *output = wl_container_of(listener, output, commit);
|
|
struct wlr_output_event_commit *event = data;
|
|
|
|
/* Notes:
|
|
* - output layout change will also be called if needed to position the views
|
|
* - always update output manager configuration even if the output is now disabled */
|
|
|
|
if (event->committed & WLR_OUTPUT_STATE_ENABLED) {
|
|
if (output->wlr_output->enabled) {
|
|
output->scene_output = wlr_scene_get_scene_output(output->server->scene, output->wlr_output);
|
|
assert(output->scene_output != NULL);
|
|
} else {
|
|
output->scene_output = NULL;
|
|
}
|
|
}
|
|
|
|
if (event->committed & OUTPUT_CONFIG_UPDATED) {
|
|
update_output_manager_config(output->server);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_output_mode(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_output *output = wl_container_of(listener, output, mode);
|
|
|
|
if (!output->wlr_output->enabled) {
|
|
return;
|
|
}
|
|
|
|
view_position_all(output->server);
|
|
update_output_manager_config(output->server);
|
|
}
|
|
|
|
void
|
|
handle_output_layout_change(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_server *server = wl_container_of(listener, server, output_layout_change);
|
|
|
|
view_position_all(server);
|
|
update_output_manager_config(server);
|
|
}
|
|
|
|
static bool
|
|
is_nested_output(struct cg_output *output)
|
|
{
|
|
if (wlr_output_is_wl(output->wlr_output)) {
|
|
return true;
|
|
}
|
|
#if WLR_HAS_X11_BACKEND
|
|
if (wlr_output_is_x11(output->wlr_output)) {
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
output_destroy(struct cg_output *output)
|
|
{
|
|
struct cg_server *server = output->server;
|
|
bool was_nested_output = is_nested_output(output);
|
|
|
|
output->wlr_output->data = NULL;
|
|
|
|
wl_list_remove(&output->destroy.link);
|
|
wl_list_remove(&output->commit.link);
|
|
wl_list_remove(&output->mode.link);
|
|
wl_list_remove(&output->frame.link);
|
|
wl_list_remove(&output->link);
|
|
|
|
wlr_output_layout_remove(server->output_layout, output->wlr_output);
|
|
|
|
free(output);
|
|
|
|
if (wl_list_empty(&server->outputs) && was_nested_output) {
|
|
wl_display_terminate(server->wl_display);
|
|
} else if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST && !wl_list_empty(&server->outputs)) {
|
|
struct cg_output *prev = wl_container_of(server->outputs.next, prev, link);
|
|
output_enable(prev);
|
|
view_position_all(server);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_output_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_output *output = wl_container_of(listener, output, destroy);
|
|
output_destroy(output);
|
|
}
|
|
|
|
void
|
|
handle_new_output(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_server *server = wl_container_of(listener, server, new_output);
|
|
struct wlr_output *wlr_output = data;
|
|
|
|
if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) {
|
|
wlr_log(WLR_ERROR, "Failed to initialize output rendering");
|
|
return;
|
|
}
|
|
|
|
struct cg_output *output = calloc(1, sizeof(struct cg_output));
|
|
if (!output) {
|
|
wlr_log(WLR_ERROR, "Failed to allocate output");
|
|
return;
|
|
}
|
|
|
|
output->wlr_output = wlr_output;
|
|
wlr_output->data = output;
|
|
output->server = server;
|
|
|
|
wl_list_insert(&server->outputs, &output->link);
|
|
|
|
output->commit.notify = handle_output_commit;
|
|
wl_signal_add(&wlr_output->events.commit, &output->commit);
|
|
output->mode.notify = handle_output_mode;
|
|
wl_signal_add(&wlr_output->events.mode, &output->mode);
|
|
output->destroy.notify = handle_output_destroy;
|
|
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
|
|
output->frame.notify = handle_output_frame;
|
|
wl_signal_add(&wlr_output->events.frame, &output->frame);
|
|
|
|
if (!wl_list_empty(&wlr_output->modes)) {
|
|
struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output);
|
|
if (preferred_mode) {
|
|
wlr_output_set_mode(wlr_output, preferred_mode);
|
|
}
|
|
if (!wlr_output_test(wlr_output)) {
|
|
struct wlr_output_mode *mode;
|
|
wl_list_for_each (mode, &wlr_output->modes, link) {
|
|
if (mode == preferred_mode) {
|
|
continue;
|
|
}
|
|
|
|
wlr_output_set_mode(wlr_output, mode);
|
|
if (wlr_output_test(wlr_output)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST && wl_list_length(&server->outputs) > 1) {
|
|
struct cg_output *next = wl_container_of(output->link.next, next, link);
|
|
output_disable(next);
|
|
}
|
|
|
|
if (!wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) {
|
|
wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f", wlr_output->name,
|
|
wlr_output->scale);
|
|
}
|
|
|
|
output_enable(output);
|
|
view_position_all(output->server);
|
|
}
|
|
|
|
void
|
|
output_set_window_title(struct cg_output *output, const char *title)
|
|
{
|
|
struct wlr_output *wlr_output = output->wlr_output;
|
|
|
|
if (!wlr_output->enabled) {
|
|
wlr_log(WLR_DEBUG, "Not setting window title for disabled output %s", wlr_output->name);
|
|
return;
|
|
}
|
|
|
|
if (wlr_output_is_wl(wlr_output)) {
|
|
wlr_wl_output_set_title(wlr_output, title);
|
|
#if WLR_HAS_X11_BACKEND
|
|
} else if (wlr_output_is_x11(wlr_output)) {
|
|
wlr_x11_output_set_title(wlr_output, title);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static bool
|
|
output_config_apply(struct cg_server *server, struct wlr_output_configuration_v1 *config, bool test_only)
|
|
{
|
|
struct wlr_output_configuration_head_v1 *head;
|
|
|
|
wl_list_for_each (head, &config->heads, link) {
|
|
struct cg_output *output = head->state.output->data;
|
|
|
|
if (!output_apply_config(output, head, test_only)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
handle_output_manager_apply(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_server *server = wl_container_of(listener, server, output_manager_apply);
|
|
struct wlr_output_configuration_v1 *config = data;
|
|
|
|
if (output_config_apply(server, config, false)) {
|
|
wlr_output_configuration_v1_send_succeeded(config);
|
|
} else {
|
|
wlr_output_configuration_v1_send_failed(config);
|
|
}
|
|
|
|
wlr_output_configuration_v1_destroy(config);
|
|
}
|
|
|
|
void
|
|
handle_output_manager_test(struct wl_listener *listener, void *data)
|
|
{
|
|
struct cg_server *server = wl_container_of(listener, server, output_manager_test);
|
|
struct wlr_output_configuration_v1 *config = data;
|
|
|
|
if (output_config_apply(server, config, true)) {
|
|
wlr_output_configuration_v1_send_succeeded(config);
|
|
} else {
|
|
wlr_output_configuration_v1_send_failed(config);
|
|
}
|
|
|
|
wlr_output_configuration_v1_destroy(config);
|
|
}
|