diff --git a/cage.c b/cage.c index 5ce1a6d..f07c856 100644 --- a/cage.c +++ b/cage.c @@ -9,636 +9,23 @@ #define _POSIX_C_SOURCE 200112L #include +#include #include #include #include #include #include #include -#include #include #include -#include -#include +#include #include #include -struct cg_server { - struct wl_display *wl_display; - struct wlr_backend *backend; - - struct wl_listener new_xdg_surface; - struct wl_list views; - - struct wlr_cursor *cursor; - struct wlr_xcursor_manager *cursor_mgr; - struct wl_listener cursor_motion; - struct wl_listener cursor_motion_absolute; - struct wl_listener cursor_button; - struct wl_listener cursor_axis; - - struct wlr_seat *seat; - struct wl_listener new_input; - struct wl_listener request_cursor; - struct wl_list keyboards; - - struct wlr_output_layout *output_layout; - struct cg_output *output; - struct wl_listener new_output; -}; - -struct cg_output { - struct cg_server *server; - struct wlr_output *wlr_output; - struct wl_listener frame; - struct wl_listener destroy; -}; - -struct cg_view { - struct wl_list link; - struct cg_server *server; - struct wlr_xdg_surface *xdg_surface; - struct wl_listener map; - struct wl_listener destroy; - int x, y; -}; - -struct cg_keyboard { - struct wl_list link; - struct cg_server *server; - struct wlr_input_device *device; - - struct wl_listener modifiers; - struct wl_listener key; - struct wl_listener destroy; -}; - -static inline bool -is_fullscreen_view(struct cg_view *view) -{ - struct wlr_xdg_surface *parent = view->xdg_surface->toplevel->parent; - /* FIXME: role is 0? */ - return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ -} - -static inline bool -have_dialogs_open(struct cg_server *server) -{ - /* We only need to test if there is more than a single - element. We don't need to know the entire length of the - list. */ - return server->views.next != server->views.prev; -} - -static void -maximize_view(struct cg_view *view) -{ - int output_width, output_height; - struct cg_output *output = view->server->output; - - wlr_output_effective_resolution(output->wlr_output, &output_width, &output_height); - wlr_xdg_toplevel_set_size(view->xdg_surface, output_width, output_height); - wlr_xdg_toplevel_set_maximized(view->xdg_surface, true); -} - -static void -center_view(struct cg_view *view) -{ - struct cg_server *server = view->server; - struct wlr_output *output = server->output->wlr_output; - int output_width, output_height; - - wlr_output_effective_resolution(output, &output_width, &output_height); - - struct wlr_box geom; - wlr_xdg_surface_get_geometry(view->xdg_surface, &geom); - view->x = (output_width - geom.width) / 2; - view->y = (output_height - geom.height) / 2; -} - -static void -focus_view(struct cg_view *view) -{ - struct cg_server *server = view->server; - struct wlr_seat *seat = server->seat; - struct wlr_surface *surface = view->xdg_surface->surface; - struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; - - if (prev_surface == surface) { - return; - } - - if (prev_surface) { - struct wlr_xdg_surface *previous = wlr_xdg_surface_from_wlr_surface( - seat->keyboard_state.focused_surface); - wlr_xdg_toplevel_set_activated(previous, false); - } - - /* Move the view to the front, but only if it isn't the - fullscreen view. */ - if (!is_fullscreen_view(view)) { - wl_list_remove(&view->link); - wl_list_insert(&server->views, &view->link); - } - - wlr_xdg_toplevel_set_activated(view->xdg_surface, true); - - struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); - wlr_seat_keyboard_notify_enter(seat, view->xdg_surface->surface, - keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); -} - -/* This event is raised when a modifier key, such as Shift or Alt, is - * pressed. We simply communicate this to the client. */ -static void -handle_keyboard_modifiers(struct wl_listener *listener, void *data) -{ - struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, modifiers); - - wlr_seat_set_keyboard(keyboard->server->seat, keyboard->device); - wlr_seat_keyboard_notify_modifiers(keyboard->server->seat, - &keyboard->device->keyboard->modifiers); -} - -static bool -handle_keybinding(struct cg_server *server, xkb_keysym_t sym) -{ - switch (sym) { -#ifdef DEBUG - case XKB_KEY_Escape: - wl_display_terminate(server->wl_display); - break; -#endif - default: - return false; - } - return true; -} - -/* This event is raised when a key is pressed or released. */ -static void -handle_keyboard_key(struct wl_listener *listener, void *data) -{ - struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, key); - struct cg_server *server = keyboard->server; - struct wlr_event_keyboard_key *event = data; - struct wlr_seat *seat = server->seat; - - /* Translate from libinput keycode to an xkbcommon keycode. */ - uint32_t keycode = event->keycode + 8; - - const xkb_keysym_t *syms; - int nsyms = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, keycode, &syms); - - bool handled = false; - uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard); - if ((modifiers & WLR_MODIFIER_ALT) && event->state == WLR_KEY_PRESSED) { - /* If Alt is held down and this button was pressed, we - * attempt to process it as a compositor - * keybinding. */ - for (int i = 0; i < nsyms; i++) { - handled = handle_keybinding(server, syms[i]); - } - } - - if (!handled) { - /* Otherwise, we pass it along to the client. */ - wlr_seat_set_keyboard(seat, keyboard->device); - wlr_seat_keyboard_notify_key(seat, event->time_msec, - event->keycode, event->state); - } -} - -static void -handle_keyboard_destroy(struct wl_listener *listener, void *data) -{ - struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); - - wl_list_remove(&keyboard->destroy.link); - wl_list_remove(&keyboard->modifiers.link); - wl_list_remove(&keyboard->key.link); - free(keyboard); -} - -static void -server_new_keyboard(struct cg_server *server, struct wlr_input_device *device) -{ - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!context) { - wlr_log(WLR_ERROR, "Unable to create XBK context"); - return; - } - - struct xkb_rule_names rules = { 0 }; - struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, - XKB_KEYMAP_COMPILE_NO_FLAGS); - if (!keymap) { - wlr_log(WLR_ERROR, "Unable to configure keyboard: keymap does not exist"); - xkb_context_unref(context); - return; - } - - struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard)); - keyboard->server = server; - keyboard->device = device; - wlr_keyboard_set_keymap(device->keyboard, keymap); - - xkb_keymap_unref(keymap); - xkb_context_unref(context); - wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); - - keyboard->modifiers.notify = handle_keyboard_modifiers; - wl_signal_add(&device->keyboard->events.modifiers, &keyboard->modifiers); - keyboard->key.notify = handle_keyboard_key; - wl_signal_add(&device->keyboard->events.key, &keyboard->key); - keyboard->destroy.notify = handle_keyboard_destroy; - wl_signal_add(&device->events.destroy, &keyboard->destroy); - - wlr_seat_set_keyboard(server->seat, device); - - wl_list_insert(&server->keyboards, &keyboard->link); -} - -/* This event is raised by the backend when a new input device becomes - * available. */ -static void -server_new_input(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, new_input); - struct wlr_input_device *device = data; - - switch (device->type) { - case WLR_INPUT_DEVICE_KEYBOARD: - server_new_keyboard(server, device); - break; - case WLR_INPUT_DEVICE_POINTER: - wlr_cursor_attach_input_device(server->cursor, device); - - /* Place the cursor in the center of the screen and make it visible. */ - wlr_cursor_warp_absolute(server->cursor, NULL, .5, .5); - wlr_xcursor_manager_set_cursor_image(server->cursor_mgr, "left_ptr", server->cursor); - break; - case WLR_INPUT_DEVICE_TOUCH: - wlr_log(WLR_DEBUG, "Touch input is not yet implemented"); - return; - case WLR_INPUT_DEVICE_TABLET_TOOL: - case WLR_INPUT_DEVICE_TABLET_PAD: - wlr_log(WLR_DEBUG, "Tablet input is not implemented"); - return; - } - - /* Let the wlr_seat know what our capabilities are. In Cage we - * always have a cursor, even if there are no pointer devices, - * so we always include that capability. */ - uint32_t caps = WL_SEAT_CAPABILITY_POINTER; - if (!wl_list_empty(&server->keyboards)) { - caps |= WL_SEAT_CAPABILITY_KEYBOARD; - } - wlr_seat_set_capabilities(server->seat, caps); -} - -/* This event is raised by the seat when a client provides a cursor image. */ -static void -seat_request_cursor(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, request_cursor); - struct wlr_seat_pointer_request_set_cursor_event *event = data; - struct wlr_seat_client *focused_client = server->seat->pointer_state.focused_client; - - /* This can be sent by any client, so we check to make sure - * this one actually has pointer focus first. */ - if (focused_client == event->seat_client) { - wlr_cursor_set_surface(server->cursor, event->surface, - event->hotspot_x, event->hotspot_y); - } -} - -/* XDG toplevels may have nested surfaces, such as popup windows for context - * menus or tooltips. This function tests if any of those are underneath the - * coordinates lx and ly (in output Layout Coordinates). If so, it sets the - * surface pointer to that wlr_surface and the sx and sy coordinates to the - * coordinates relative to that surface's top-left corner. */ -static bool -view_at(struct cg_view *view, double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy) -{ - double view_sx = lx - view->x; - double view_sy = ly - view->y; - - double _sx, _sy; - struct wlr_surface *_surface = NULL; - _surface = wlr_xdg_surface_surface_at(view->xdg_surface, - view_sx, view_sy, - &_sx, &_sy); - - if (_surface != NULL) { - *sx = _sx; - *sy = _sy; - *surface = _surface; - return true; - } - - return false; -} - -/* This iterates over all of our surfaces and attempts to find one under the - * cursor. This relies on server->views being ordered from top-to-bottom. */ -static struct cg_view * -desktop_view_at(struct cg_server *server, double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy) -{ - struct cg_view *view; - - wl_list_for_each(view, &server->views, link) { - if (view_at(view, lx, ly, surface, sx, sy)) { - return view; - } - } - - return NULL; -} - -/* Find the view under the pointer and send the event along. */ -static void -process_cursor_motion(struct cg_server *server, uint32_t time) -{ - double sx, sy; - struct wlr_seat *seat = server->seat; - struct wlr_surface *surface = NULL; - - struct cg_view *view = desktop_view_at(server, - server->cursor->x, server->cursor->y, - &surface, &sx, &sy); - - /* If desktop_view_at returns a view, there is also a - surface. There cannot be a surface without a view, - either. It's both or nothing. */ - if (!view) { - wlr_xcursor_manager_set_cursor_image(server->cursor_mgr, "left_ptr", server->cursor); - wlr_seat_pointer_clear_focus(seat); - } else { - wlr_seat_pointer_notify_enter(seat, surface, sx, sy); - - bool focus_changed = seat->pointer_state.focused_surface != surface; - if (!focus_changed) { - wlr_seat_pointer_notify_motion(seat, time, sx, sy); - } - } -} - -/* This event is forwarded by the cursor when a pointer emits a - * _relative_ pointer motion event (i.e. a delta). */ -static void -server_cursor_motion(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, cursor_motion); - struct wlr_event_pointer_motion *event = data; - - wlr_cursor_move(server->cursor, event->device, event->delta_x, event->delta_y); - process_cursor_motion(server, event->time_msec); -} - -/* This event is forwarded by the cursor when a pointer emits an - * _absolute_ motion event, from 0..1 on each axis. */ -static void -server_cursor_motion_absolute(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, cursor_motion_absolute); - struct wlr_event_pointer_motion_absolute *event = data; - - wlr_cursor_warp_absolute(server->cursor, event->device, event->x, event->y); - process_cursor_motion(server, event->time_msec); -} - -/* This event is forwarded by the cursor when a pointer emits a button - * event. */ -static void -server_cursor_button(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, cursor_button); - struct wlr_event_pointer_button *event = data; - - wlr_seat_pointer_notify_button(server->seat, - event->time_msec, event->button, event->state); - if (event->state == WLR_BUTTON_PRESSED && !have_dialogs_open(server)) { - /* Focus that client if the button was pressed and - there are no open dialogs. */ - double sx, sy; - struct wlr_surface *surface; - struct cg_view *view = desktop_view_at(server, - server->cursor->x, - server->cursor->y, - &surface, &sx, &sy); - if (view) { - focus_view(view); - } - } -} - -/* This event is forwarded by the cursor when a pointer emits an axis - * event, for example when you move the scroll wheel. */ -static void -server_cursor_axis(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, cursor_axis); - struct wlr_event_pointer_axis *event = data; - - wlr_seat_pointer_notify_axis(server->seat, - event->time_msec, event->orientation, event->delta, - event->delta_discrete, event->source); -} - -/* 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 *output; - struct cg_view *view; - struct timespec *when; -}; - -/* This function is called for every surface that needs to be - rendered. */ -static void -render_surface(struct wlr_surface *surface, int sx, int sy, void *data) -{ - struct render_data *rdata = data; - struct cg_view *view = rdata->view; - struct wlr_output *output = rdata->output; - - if (!wlr_surface_has_buffer(surface)) { - return; - } - - struct wlr_texture *texture = wlr_surface_get_texture(surface); - if (!texture) { - wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); - return; - } - - double ox = 0, oy = 0; - wlr_output_layout_output_coords(view->server->output_layout, output, &ox, &oy); - ox += view->x + sx, oy += view->y + sy; - - /* We also have to apply the scale factor for HiDPI - * outputs. This is only part of the puzzle, Cage does not - * fully support HiDPI. */ - struct wlr_box box = { - .x = ox * output->scale, - .y = oy * output->scale, - .width = surface->current.width * output->scale, - .height = surface->current.height * output->scale, - }; - - 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); - wlr_render_texture_with_matrix(surface->renderer, texture, matrix, 1); - wlr_surface_send_frame_done(surface, rdata->when); -} - -/* This function is called every time an output is ready to display a - * frame, generally at the output's refresh rate (e.g. 60Hz). */ -static void -output_frame(struct wl_listener *listener, void *data) -{ - struct cg_output *output = wl_container_of(listener, output, frame); - struct wlr_renderer *renderer = wlr_backend_get_renderer(output->server->backend); - - if (!wlr_output_make_current(output->wlr_output, NULL)) { - wlr_log(WLR_DEBUG, "Cannot make damage output current"); - return; - } - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - - int width, height; - wlr_output_effective_resolution(output->wlr_output, &width, &height); - - wlr_renderer_begin(renderer, width, height); - - float color[4] = {0.3, 0.3, 0.3, 1.0}; - wlr_renderer_clear(renderer, color); - - struct cg_view *view; - wl_list_for_each_reverse(view, &output->server->views, link) { - struct render_data rdata = { - .output = output->wlr_output, - .view = view, - .when = &now, - }; - wlr_xdg_surface_for_each_surface(view->xdg_surface, render_surface, &rdata); - } - - wlr_renderer_end(renderer); - wlr_output_swap_buffers(output->wlr_output, NULL, NULL); -} - -static void -output_destroy_notify(struct wl_listener *listener, void *data) -{ - struct cg_output *output = wl_container_of(listener, output, destroy); - struct cg_server *server = output->server; - - wl_list_remove(&output->destroy.link); - wl_list_remove(&output->frame.link); - free(output); - server->output = NULL; - - /* Since there is no use in continuing without our (single) - * output, terminate. */ - wl_display_terminate(server->wl_display); -} - -/* This event is raised by the backend when a new output (aka a - * display or monitor) becomes available. A kiosk requires only a - * single output, hence we do nothing in case subsequent outputs - * become available. */ -static void -server_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; - - /* 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 *mode = wl_container_of(wlr_output->modes.prev, mode, link); - wlr_output_set_mode(wlr_output, mode); - } - - server->output = calloc(1, sizeof(struct cg_output)); - server->output->wlr_output = wlr_output; - server->output->server = server; - server->output->frame.notify = output_frame; - wl_signal_add(&wlr_output->events.frame, &server->output->frame); - server->output->destroy.notify = output_destroy_notify; - wl_signal_add(&wlr_output->events.destroy, &server->output->destroy); - 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); -} - -/* Called when the surface is mapped. */ -static void -xdg_surface_map(struct wl_listener *listener, void *data) -{ - struct cg_view *view = wl_container_of(listener, view, map); - - if (is_fullscreen_view(view)) { - maximize_view(view); - } else { - center_view(view); - } - - focus_view(view); -} - -/* Called when the surface is destroyed and should never be shown - again. */ -static void -xdg_surface_destroy(struct wl_listener *listener, void *data) -{ - struct cg_view *view = wl_container_of(listener, view, destroy); - /* We only listen for events on toplevels, so this is safe. */ - struct cg_server *server = view->server; - bool terminate = is_fullscreen_view(view); - - wl_list_remove(&view->link); - free(view); - - /* If this was our fullscreen view, exit. */ - if (terminate) { - wl_display_terminate(server->wl_display); - } -} - -/* This event is raised when wlr_xdg_shell receives a new xdg surface - * from a client, either a toplevel (application window) or popup. */ -static void -server_new_xdg_surface(struct wl_listener *listener, void *data) -{ - struct cg_server *server = wl_container_of(listener, server, new_xdg_surface); - struct wlr_xdg_surface *xdg_surface = data; - - if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - return; - } - - struct cg_view *view = calloc(1, sizeof(struct cg_view)); - view->server = server; - view->xdg_surface = xdg_surface; - - view->map.notify = xdg_surface_map; - wl_signal_add(&xdg_surface->events.map, &view->map); - view->destroy.notify = xdg_surface_destroy; - wl_signal_add(&xdg_surface->events.destroy, &view->destroy); - - wl_list_insert(&server->views, &view->link); -} +#include "output.h" +#include "seat.h" +#include "server.h" +#include "xdg_shell.h" static bool spawn_primary_client(char *argv[], pid_t *pid_out) @@ -697,7 +84,7 @@ main(int argc, char *argv[]) server.wl_display = wl_display_create(); if (!server.wl_display) { - wlr_log(WLR_ERROR, "Could not allocate a Wayland display"); + wlr_log(WLR_ERROR, "Cannot allocate a Wayland display"); return 1; } @@ -739,9 +126,16 @@ main(int argc, char *argv[]) /* 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 = server_new_output; + server.new_output.notify = handle_new_output; wl_signal_add(&server.backend->events.new_output, &server.new_output); + server.seat = cg_seat_create(&server); + if (!server.seat) { + wlr_log(WLR_ERROR, "Unable to create the seat"); + ret = 1; + goto end; + } + xdg_shell = wlr_xdg_shell_create(server.wl_display); if (!xdg_shell) { wlr_log(WLR_ERROR, "Unable to create the XDG shell interface"); @@ -749,36 +143,8 @@ main(int argc, char *argv[]) goto end; } wl_list_init(&server.views); - server.new_xdg_surface.notify = server_new_xdg_surface; - wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_surface); - - /* Creates a cursor, which is a wlroots utility for tracking - * the cursor image shown on screen. */ - server.cursor = wlr_cursor_create(); - if (!server.cursor) { - wlr_log(WLR_ERROR, "Unable to create cursor"); - ret = 1; - goto end; - } - wlr_cursor_attach_output_layout(server.cursor, server.output_layout); - server.cursor_motion.notify = server_cursor_motion; - wl_signal_add(&server.cursor->events.motion, &server.cursor_motion); - server.cursor_motion_absolute.notify = server_cursor_motion_absolute; - wl_signal_add(&server.cursor->events.motion_absolute, &server.cursor_motion_absolute); - server.cursor_button.notify = server_cursor_button; - wl_signal_add(&server.cursor->events.button, &server.cursor_button); - server.cursor_axis.notify = server_cursor_axis; - wl_signal_add(&server.cursor->events.axis, &server.cursor_axis); - - server.cursor_mgr = wlr_xcursor_manager_create(NULL, 24); - wlr_xcursor_manager_load(server.cursor_mgr, 1); - - wl_list_init(&server.keyboards); - server.new_input.notify = server_new_input; - wl_signal_add(&server.backend->events.new_input, &server.new_input); - server.seat = wlr_seat_create(server.wl_display, "seat0"); - server.request_cursor.notify = seat_request_cursor; - wl_signal_add(&server.seat->events.request_set_cursor, &server.request_cursor); + server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new; + wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface); const char *socket = wl_display_add_socket_auto(server.wl_display); if (!socket) { @@ -811,10 +177,7 @@ main(int argc, char *argv[]) waitpid(pid, NULL, 0); end: - wlr_xcursor_manager_destroy(server.cursor_mgr); - if (server.cursor) { - wlr_cursor_destroy(server.cursor); - } + cg_seat_destroy(server.seat); wlr_xdg_shell_destroy(xdg_shell); wlr_data_device_manager_destroy(data_device_mgr); wlr_compositor_destroy(compositor); diff --git a/meson.build b/meson.build index 4b342a5..f65688a 100644 --- a/meson.build +++ b/meson.build @@ -52,12 +52,24 @@ server_protos = declare_dependency( ) cage_sources = [ - 'cage.c' + 'cage.c', + 'output.c', + 'seat.c', + 'view.c', + 'xdg_shell.c', +] + +cage_headers = [ + 'output.h', + 'seat.h', + 'server.h', + 'view.h', + 'xdg_shell.h', ] executable( meson.project_name(), - cage_sources, + cage_sources + cage_headers, dependencies: [ server_protos, wayland_server, diff --git a/output.c b/output.c new file mode 100644 index 0000000..0f933e2 --- /dev/null +++ b/output.c @@ -0,0 +1,166 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200112L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "server.h" +#include "view.h" + +/* 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 cg_view *view; + struct timespec *when; +}; + +static void +render_surface(struct wlr_surface *surface, int sx, int sy, void *data) +{ + struct render_data *rdata = data; + struct cg_view *view = rdata->view; + struct wlr_output *output = rdata->output; + + if (!wlr_surface_has_buffer(surface)) { + return; + } + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (!texture) { + wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); + return; + } + + double ox = 0, oy = 0; + wlr_output_layout_output_coords(rdata->output_layout, output, &ox, &oy); + ox += view->x + sx, oy += view->y + sy; + + struct wlr_box box = { + .x = ox * output->scale, + .y = oy * output->scale, + .width = surface->current.width * output->scale, + .height = surface->current.height * output->scale, + }; + + 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); + wlr_render_texture_with_matrix(surface->renderer, texture, matrix, 1); + wlr_surface_send_frame_done(surface, rdata->when); +} + +static void +view_for_each_surface(struct cg_view *view, struct render_data *rdata, + wlr_surface_iterator_func_t iterator) +{ + switch(view->type) { + case CAGE_XDG_SHELL_VIEW: + wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator, rdata); + break; + default: + wlr_log(WLR_ERROR, "Unrecognized view type: %d", view->type); + } +} + +static void +handle_output_frame(struct wl_listener *listener, void *data) +{ + struct cg_output *output = wl_container_of(listener, output, frame); + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->server->backend); + + if (!wlr_output_make_current(output->wlr_output, NULL)) { + wlr_log(WLR_DEBUG, "Cannot make damage output current"); + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + int width, height; + wlr_output_effective_resolution(output->wlr_output, &width, &height); + + wlr_renderer_begin(renderer, width, height); + + float color[4] = {0.3, 0.3, 0.3, 1.0}; + wlr_renderer_clear(renderer, color); + + struct render_data rdata = { + .output_layout = output->server->output_layout, + .output = output->wlr_output, + .when = &now, + }; + + struct cg_view *view; + wl_list_for_each_reverse(view, &output->server->views, link) { + rdata.view = view; + view_for_each_surface(view, &rdata, render_surface); + } + + wlr_renderer_end(renderer); + wlr_output_swap_buffers(output->wlr_output, NULL, NULL); +} + +static void +handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct cg_output *output = wl_container_of(listener, output, destroy); + struct cg_server *server = output->server; + + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->frame.link); + free(output); + server->output = NULL; + + /* Since there is no use in continuing without our (single) + * output, terminate. */ + wl_display_terminate(server->wl_display); +} + +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; + + /* 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 *mode = wl_container_of(wlr_output->modes.prev, mode, link); + wlr_output_set_mode(wlr_output, mode); + } + + server->output = calloc(1, sizeof(struct cg_output)); + server->output->wlr_output = wlr_output; + server->output->server = server; + + server->output->frame.notify = handle_output_frame; + wl_signal_add(&wlr_output->events.frame, &server->output->frame); + server->output->destroy.notify = handle_output_destroy; + wl_signal_add(&wlr_output->events.destroy, &server->output->destroy); + + 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); +} diff --git a/output.h b/output.h new file mode 100644 index 0000000..1bef177 --- /dev/null +++ b/output.h @@ -0,0 +1,19 @@ +#ifndef CG_OUTPUT_H +#define CG_OUTPUT_H + +#include +#include + +#include "server.h" + +struct cg_output { + struct cg_server *server; + struct wlr_output *wlr_output; + + struct wl_listener frame; + struct wl_listener destroy; +}; + +void handle_new_output(struct wl_listener *listener, void *data); + +#endif diff --git a/seat.c b/seat.c new file mode 100644 index 0000000..96c98b7 --- /dev/null +++ b/seat.c @@ -0,0 +1,544 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "seat.h" +#include "server.h" +#include "view.h" + +#define DEFAULT_XCURSOR "left_ptr" +#define XCURSOR_SIZE 24 + +static inline bool +have_dialogs_open(struct cg_server *server) +{ + /* We only need to test if there is more than a single + element. We don't need to know the entire length of the + list. */ + return server->views.next != server->views.prev; +} + +/* XDG toplevels may have nested surfaces, such as popup windows for context + * menus or tooltips. This function tests if any of those are underneath the + * coordinates lx and ly (in output Layout Coordinates). If so, it sets the + * surface pointer to that wlr_surface and the sx and sy coordinates to the + * coordinates relative to that surface's top-left corner. */ +static bool +view_at(struct cg_view *view, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) +{ + double view_sx = lx - view->x; + double view_sy = ly - view->y; + + double _sx, _sy; + struct wlr_surface *_surface = NULL; + switch (view->type) { + case CAGE_XDG_SHELL_VIEW: + _surface = wlr_xdg_surface_surface_at(view->xdg_surface, + view_sx, view_sy, + &_sx, &_sy); + break; + default: + wlr_log(WLR_ERROR, "Unrecognized view type: %d", view->type); + } + + + if (_surface != NULL) { + *sx = _sx; + *sy = _sy; + *surface = _surface; + return true; + } + + return false; +} + +/* This iterates over all of our surfaces and attempts to find one + * under the cursor. This relies on server->views being ordered from + * top-to-bottom. */ +static struct cg_view * +desktop_view_at(struct cg_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) +{ + struct cg_view *view; + + wl_list_for_each(view, &server->views, link) { + if (view_at(view, lx, ly, surface, sx, sy)) { + return view; + } + } + + return NULL; +} + +static void +update_capabilities(struct cg_seat *seat) { + uint32_t caps = 0; + + if (!wl_list_empty(&seat->keyboards)) { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + if (!wl_list_empty(&seat->pointers)) { + caps |= WL_SEAT_CAPABILITY_POINTER; + } + wlr_seat_set_capabilities(seat->seat, caps); + + /* Hide cursor if the seat doesn't have pointer capability. */ + if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { + wlr_cursor_set_image(seat->cursor, NULL, 0, 0, 0, 0, 0, 0); + } else { + wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, + DEFAULT_XCURSOR, + seat->cursor); + } +} + +static void +handle_pointer_destroy(struct wl_listener *listener, void *data) +{ + struct cg_pointer *pointer = wl_container_of(listener, pointer, destroy); + struct cg_seat *seat = pointer->seat; + + wl_list_remove(&pointer->link); + wlr_cursor_detach_input_device(seat->cursor, pointer->device); + wl_list_remove(&pointer->destroy.link); + free(pointer); + + update_capabilities(seat); +} + +static void +handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device) +{ + struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); + if (!pointer) { + wlr_log(WLR_ERROR, "Cannot allocate pointer"); + return; + } + + pointer->seat = seat; + pointer->device = device; + wlr_cursor_attach_input_device(seat->cursor, device); + + wl_list_insert(&seat->pointers, &pointer->link); + pointer->destroy.notify = handle_pointer_destroy; + wl_signal_add(&device->events.destroy, &pointer->destroy); +} + +static void +handle_keyboard_modifiers(struct wl_listener *listener, void *data) +{ + struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, modifiers); + + wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device); + wlr_seat_keyboard_notify_modifiers(keyboard->seat->seat, + &keyboard->device->keyboard->modifiers); +} + +static bool +handle_keybinding(struct cg_server *server, xkb_keysym_t sym) +{ + switch (sym) { +#ifdef DEBUG + case XKB_KEY_Escape: + wl_display_terminate(server->wl_display); + break; +#endif + default: + return false; + } + return true; +} + +static void +handle_keyboard_key(struct wl_listener *listener, void *data) +{ + struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct cg_seat *seat = keyboard->seat; + struct wlr_event_keyboard_key *event = data; + + /* Translate from libinput keycode to an xkbcommon keycode. */ + xkb_keycode_t keycode = event->keycode + 8; + + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, keycode, &syms); + + bool handled = false; + uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard); + if ((modifiers & WLR_MODIFIER_ALT) && event->state == WLR_KEY_PRESSED) { + /* If Alt is held down and this button was pressed, we + * attempt to process it as a compositor + * keybinding. */ + for (int i = 0; i < nsyms; i++) { + handled = handle_keybinding(seat->server, syms[i]); + } + } + + if (!handled) { + /* Otherwise, we pass it along to the client. */ + wlr_seat_set_keyboard(seat->seat, keyboard->device); + wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, + event->keycode, event->state); + } +} + +static void +handle_keyboard_destroy(struct wl_listener *listener, void *data) +{ + struct cg_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + struct cg_seat *seat = keyboard->seat; + + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->modifiers.link); + wl_list_remove(&keyboard->key.link); + wl_list_remove(&keyboard->link); + free(keyboard); + + update_capabilities(seat); +} + +static void +handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device) +{ + struct cg_keyboard *keyboard = calloc(1, sizeof(struct cg_keyboard)); + if (!keyboard) { + wlr_log(WLR_ERROR, "Cannot allocate keyboard"); + return; + } + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Unable to create XBK context"); + free(keyboard); + return; + } + + struct xkb_rule_names rules = { 0 }; + struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Unable to configure keyboard: keymap does not exist"); + free(keyboard); + xkb_context_unref(context); + return; + } + + keyboard->seat = seat; + keyboard->device = device; + wlr_keyboard_set_keymap(device->keyboard, keymap); + + xkb_keymap_unref(keymap); + xkb_context_unref(context); + wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); + + wl_list_insert(&seat->keyboards, &keyboard->link); + keyboard->destroy.notify = handle_keyboard_destroy; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->key.notify = handle_keyboard_key; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->modifiers.notify = handle_keyboard_modifiers; + wl_signal_add(&device->keyboard->events.modifiers, &keyboard->modifiers); + + wlr_seat_set_keyboard(seat->seat, device); +} + +static void +handle_new_input(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, new_input); + struct wlr_input_device *device = data; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + handle_new_keyboard(seat, device); + break; + case WLR_INPUT_DEVICE_POINTER: + handle_new_pointer(seat, device); + break; + case WLR_INPUT_DEVICE_TOUCH: + wlr_log(WLR_DEBUG, "Touch input is not implemented"); + return; + case WLR_INPUT_DEVICE_SWITCH: + wlr_log(WLR_DEBUG, "Switch input is not implemented"); + return; + case WLR_INPUT_DEVICE_TABLET_TOOL: + case WLR_INPUT_DEVICE_TABLET_PAD: + wlr_log(WLR_DEBUG, "Tablet input is not implemented"); + return; + } + + update_capabilities(seat); +} + +static void +handle_request_set_cursor(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, request_set_cursor); + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_surface *focused_surface = event->seat_client->seat->pointer_state.focused_surface; + bool has_focused = focused_surface != NULL && focused_surface->resource != NULL; + struct wl_client *focused_client = NULL; + if (has_focused) { + focused_client = wl_resource_get_client(focused_surface->resource); + } + + /* This can be sent by any client, so we check to make sure + * this one actually has pointer focus first. */ + if (focused_client == event->seat_client->client) { + wlr_cursor_set_surface(seat->cursor, event->surface, + event->hotspot_x, event->hotspot_y); + } +} + +static void +handle_cursor_axis(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, cursor_axis); + struct wlr_event_pointer_axis *event = data; + + wlr_seat_pointer_notify_axis(seat->seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source); +} + +static void +handle_cursor_button(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, cursor_button); + struct cg_server *server = seat->server; + struct wlr_event_pointer_button *event = data; + + wlr_seat_pointer_notify_button(seat->seat, + event->time_msec, event->button, event->state); + if (event->state == WLR_BUTTON_PRESSED && !have_dialogs_open(server)) { + /* Focus that client if the button was pressed and + there are no open dialogs. */ + double sx, sy; + struct wlr_surface *surface; + struct cg_view *view = desktop_view_at(server, + seat->cursor->x, + seat->cursor->y, + &surface, &sx, &sy); + if (view) { + seat_set_focus(seat, view); + } + } +} + +static void +process_cursor_motion(struct cg_seat *seat, uint32_t time) +{ + double sx, sy; + struct wlr_seat *wlr_seat = seat->seat; + struct wlr_surface *surface = NULL; + + struct cg_view *view = desktop_view_at(seat->server, + seat->cursor->x, seat->cursor->y, + &surface, &sx, &sy); + + /* If desktop_view_at returns a view, there is also a + surface. There cannot be a surface without a view, + either. It's both or nothing. */ + if (!view) { + // wlr_xcursor_manager_set_cursor_image(seat->cursor_mgr, DEFAULT_XCURSOR, seat->cursor); + wlr_seat_pointer_clear_focus(wlr_seat); + } else { + wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); + + bool focus_changed = wlr_seat->pointer_state.focused_surface != surface; + if (!focus_changed && time > 0) { + wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); + } + } +} + +static void +handle_cursor_motion_absolute(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_absolute); + struct wlr_event_pointer_motion_absolute *event = data; + + wlr_cursor_warp_absolute(seat->cursor, event->device, event->x, event->y); + process_cursor_motion(seat, event->time_msec); +} + +static void +handle_cursor_motion(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion); + struct wlr_event_pointer_motion *event = data; + + wlr_cursor_move(seat->cursor, event->device, event->delta_x, event->delta_y); + process_cursor_motion(seat, event->time_msec); +} + +static void +handle_destroy(struct wl_listener *listener, void *data) +{ + struct cg_seat *seat = wl_container_of(listener, seat, destroy); + wl_list_remove(&seat->destroy.link); + + struct cg_keyboard *keyboard, *keyboard_tmp; + wl_list_for_each_safe(keyboard, keyboard_tmp, &seat->keyboards, link) { + handle_keyboard_destroy(&keyboard->destroy, NULL); + } + struct cg_pointer *pointer, *pointer_tmp; + wl_list_for_each_safe(pointer, pointer_tmp, &seat->pointers, link) { + handle_pointer_destroy(&pointer->destroy, NULL); + } + wl_list_remove(&seat->new_input.link); + + wlr_xcursor_manager_destroy(seat->xcursor_manager); + if (seat->cursor) { + wlr_cursor_destroy(seat->cursor); + } + wl_list_remove(&seat->cursor_motion.link); + wl_list_remove(&seat->cursor_motion_absolute.link); + wl_list_remove(&seat->cursor_button.link); + wl_list_remove(&seat->cursor_axis.link); + wl_list_remove(&seat->request_set_cursor.link); +} + +struct cg_seat * +cg_seat_create(struct cg_server *server) +{ + struct cg_seat *seat = calloc(1, sizeof(struct cg_seat)); + if (!seat) { + wlr_log(WLR_ERROR, "Cannot allocate seat"); + return NULL; + } + + seat->seat = wlr_seat_create(server->wl_display, "seat0"); + if (!seat->seat) { + wlr_log(WLR_ERROR, "Cannot allocate seat0"); + free(seat); + return NULL; + } + seat->server = server; + seat->destroy.notify = handle_destroy; + wl_signal_add(&seat->seat->events.destroy, &seat->destroy); + + seat->cursor = wlr_cursor_create(); + if (!seat->cursor) { + wlr_log(WLR_ERROR, "Unable to create cursor"); + wl_list_remove(&seat->destroy.link); + free(seat); + return NULL; + } + wlr_cursor_attach_output_layout(seat->cursor, server->output_layout); + + if (!seat->xcursor_manager) { + seat->xcursor_manager = wlr_xcursor_manager_create(NULL, XCURSOR_SIZE); + if (!seat->xcursor_manager) { + wlr_log(WLR_ERROR, "Cannot create XCursor manager"); + wlr_cursor_destroy(seat->cursor); + wl_list_remove(&seat->destroy.link); + free(seat); + return NULL; + } + } + + float scale = 1.f; // TODO: segault because server->output->wlr_output->scale does not exist yet; + if (wlr_xcursor_manager_load(seat->xcursor_manager, scale)) { + wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f", + server->output->wlr_output->name, + server->output->wlr_output->scale); + } + + wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, DEFAULT_XCURSOR, seat->cursor); + + // TODO: warp to the center? + /* /\* Place the cursor in the center of the screen and make it visible. *\/ */ + /* wlr_cursor_warp_absolute(seat->cursor, NULL, .5, .5); */ + wlr_cursor_warp(seat->cursor, NULL, seat->cursor->x, seat->cursor->y); + + seat->cursor_motion.notify = handle_cursor_motion; + wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); + seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute; + wl_signal_add(&seat->cursor->events.motion_absolute, &seat->cursor_motion_absolute); + seat->cursor_button.notify = handle_cursor_button; + wl_signal_add(&seat->cursor->events.button, &seat->cursor_button); + seat->cursor_axis.notify = handle_cursor_axis; + wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis); + + seat->request_set_cursor.notify = handle_request_set_cursor; + wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_set_cursor); + + wl_list_init(&seat->keyboards); + wl_list_init(&seat->pointers); + + seat->new_input.notify = handle_new_input; + wl_signal_add(&server->backend->events.new_input, &seat->new_input); + + return seat; +} + +void +cg_seat_destroy(struct cg_seat *seat) +{ + if (!seat) { + return; + } + + handle_destroy(&seat->destroy, NULL); + wlr_seat_destroy(seat->seat); +} + +struct cg_view * +seat_get_focus(struct cg_seat *seat) +{ + struct wlr_surface *prev_surface = seat->seat->keyboard_state.focused_surface; + return cg_view_from_wlr_surface(seat->server, prev_surface); +} + +void +seat_set_focus(struct cg_seat *seat, struct cg_view *view) +{ + struct cg_server *server = seat->server; + struct wlr_seat *wlr_seat = seat->seat; + struct cg_view *prev_view = seat_get_focus(seat); + + if (prev_view == view || !view) { + return; + } + + if (prev_view) { + view_activate(prev_view, false); + } + + /* Move the view to the front, but only if it isn't the + fullscreen view. */ + if (!view_is_primary(view)) { + wl_list_remove(&view->link); + wl_list_insert(&server->views, &view->link); + } + + view_activate(view, true); + + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); + if (keyboard) { + wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, + keyboard->keycodes, + keyboard->num_keycodes, + &keyboard->modifiers); + } else { + wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, + NULL, 0, NULL); + } + + process_cursor_motion(seat, -1); +} diff --git a/seat.h b/seat.h new file mode 100644 index 0000000..e7304c0 --- /dev/null +++ b/seat.h @@ -0,0 +1,54 @@ +#ifndef CG_SEAT_H +#define CG_SEAT_H + +#include +#include +#include +#include +#include + +#include "server.h" +#include "view.h" + +struct cg_seat { + struct wlr_seat *seat; + struct cg_server *server; + struct wl_listener destroy; + + struct wl_list keyboards; + struct wl_list pointers; + struct wl_listener new_input; + + struct wlr_cursor *cursor; + struct wlr_xcursor_manager *xcursor_manager; + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener request_set_cursor; +}; + +struct cg_keyboard { + struct wl_list link; // seat::keyboards + struct cg_seat *seat; + struct wlr_input_device *device; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +}; + +struct cg_pointer { + struct wl_list link; // seat::pointers + struct cg_seat *seat; + struct wlr_input_device *device; + + struct wl_listener destroy; +}; + +struct cg_seat *cg_seat_create(struct cg_server *server); +void cg_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); + +#endif diff --git a/server.h b/server.h new file mode 100644 index 0000000..20dbdcf --- /dev/null +++ b/server.h @@ -0,0 +1,25 @@ +#ifndef CG_SERVER_H +#define CG_SERVER_H + +#include +#include +#include + +#include "output.h" +#include "seat.h" + +struct cg_server { + struct wl_display *wl_display; + struct wlr_backend *backend; + + struct wl_listener new_xdg_shell_surface; + struct wl_list views; + + struct cg_seat *seat; + + struct wlr_output_layout *output_layout; + struct cg_output *output; + struct wl_listener new_output; +}; + +#endif diff --git a/view.c b/view.c new file mode 100644 index 0000000..8999ac3 --- /dev/null +++ b/view.c @@ -0,0 +1,108 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include +#include +#include + +#include "output.h" +#include "seat.h" +#include "server.h" +#include "view.h" + +void +view_activate(struct cg_view *view, bool activate) +{ + view->activate(view, activate); +} + +void +view_maximize(struct cg_view *view) +{ + struct cg_output *output = view->server->output; + int output_width, output_height; + + wlr_output_effective_resolution(output->wlr_output, &output_width, &output_height); + view->maximize(view, output_width, output_height); +} + +void +view_center(struct cg_view *view) +{ + struct cg_server *server = view->server; + struct wlr_output *output = server->output->wlr_output; + + int output_width, output_height; + wlr_output_effective_resolution(output, &output_width, &output_height); + + struct wlr_box geom; + view->get_geometry(view, &geom); + + view->x = (output_width - geom.width) / 2; + view->y = (output_height - geom.height) / 2; +} + +bool +view_is_primary(struct cg_view *view) +{ + return view->is_primary(view); +} + +void +view_map(struct cg_view *view, struct wlr_surface *surface) +{ + view->wlr_surface = surface; + + if (view_is_primary(view)) { + view_maximize(view); + } else { + view_center(view); + } + + seat_set_focus(view->server->seat, view); +} + +void +view_destroy(struct cg_view *view) +{ + struct cg_server *server = view->server; + bool terminate = view_is_primary(view); + + wl_list_remove(&view->link); + free(view); + + /* If this was our primary view, exit. */ + if (terminate) { + wl_display_terminate(server->wl_display); + } +} + +struct cg_view * +cg_view_create(struct cg_server *server) +{ + struct cg_view *view = calloc(1, sizeof(struct cg_view)); + + view->server = server; + wl_list_insert(&server->views, &view->link); + return view; +} + +struct cg_view * +cg_view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface) +{ + struct cg_view *view; + wl_list_for_each(view, &server->views, link) { + if (view->wlr_surface == surface) { + return view; + } + } + return NULL; +} diff --git a/view.h b/view.h new file mode 100644 index 0000000..91efd54 --- /dev/null +++ b/view.h @@ -0,0 +1,47 @@ +#ifndef CG_VIEW_H +#define CG_VIEW_H + +#include +#include +#include +#include +#include + +#include "server.h" + +enum cg_view_type { + CAGE_XDG_SHELL_VIEW, +}; + +struct cg_view { + struct cg_server *server; + struct wl_list link; // server::views + struct wlr_surface *wlr_surface; + int x, y; + + enum cg_view_type type; + union { + struct wlr_xdg_surface *xdg_surface; + }; + + struct wl_listener destroy; + struct wl_listener map; + // TODO: allow applications to go to fullscreen from maximized? + // struct wl_listener request_fullscreen; + + void (*activate)(struct cg_view *view, bool activate); + void (*maximize)(struct cg_view *view, int output_width, int output_height); + void (*get_geometry)(struct cg_view *view, struct wlr_box *geom); + bool (*is_primary)(struct cg_view *view); +}; + +void view_activate(struct cg_view *view, bool activate); +void view_maximize(struct cg_view *view); +void view_center(struct cg_view *view); +bool view_is_primary(struct cg_view *view); +void view_map(struct cg_view *view, struct wlr_surface *surface); +void view_destroy(struct cg_view *view); +struct cg_view *cg_view_create(struct cg_server *server); +struct cg_view *cg_view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface); + +#endif diff --git a/xdg_shell.c b/xdg_shell.c new file mode 100644 index 0000000..a990946 --- /dev/null +++ b/xdg_shell.c @@ -0,0 +1,81 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include + +#include "server.h" +#include "view.h" + +static void +activate(struct cg_view *view, bool activate) +{ + wlr_xdg_toplevel_set_activated(view->xdg_surface, activate); +} + +static void +maximize(struct cg_view *view, int output_width, int output_height) +{ + wlr_xdg_toplevel_set_size(view->xdg_surface, output_width, output_height); + wlr_xdg_toplevel_set_maximized(view->xdg_surface, true); +} + +static void +get_geometry(struct cg_view *view, struct wlr_box *geom) +{ + wlr_xdg_surface_get_geometry(view->xdg_surface, geom); +} + +static bool +is_primary(struct cg_view *view) +{ + struct wlr_xdg_surface *parent = view->xdg_surface->toplevel->parent; + /* FIXME: role is 0? */ + return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ +} + +static void +handle_xdg_shell_surface_map(struct wl_listener *listener, void *data) +{ + struct cg_view *view = wl_container_of(listener, view, map); + view_map(view, view->xdg_surface->surface); +} + +static void +handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *data) +{ + struct cg_view *view = wl_container_of(listener, view, destroy); + view_destroy(view); +} + +void +handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) +{ + struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface); + struct wlr_xdg_surface *xdg_surface = data; + + if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + struct cg_view *view = cg_view_create(server); + view->type = CAGE_XDG_SHELL_VIEW; + view->xdg_surface = xdg_surface; + + view->map.notify = handle_xdg_shell_surface_map; + wl_signal_add(&xdg_surface->events.map, &view->map); + view->destroy.notify = handle_xdg_shell_surface_destroy; + wl_signal_add(&xdg_surface->events.destroy, &view->destroy); + + view->activate = activate; + view->maximize = maximize; + view->get_geometry = get_geometry; + view->is_primary = is_primary; +} diff --git a/xdg_shell.h b/xdg_shell.h new file mode 100644 index 0000000..c3b94a8 --- /dev/null +++ b/xdg_shell.h @@ -0,0 +1,8 @@ +#ifndef XDG_SHELL_H +#define XDG_SHELL_H + +#include + +void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data); + +#endif