mirror of
				https://github.com/cage-kiosk/cage.git
				synced 2025-10-29 05:40:19 -04:00 
			
		
		
		
	Refactor cage into separate source files
This makes Cage much easier to maintain. Not only is it easier where to look and to maintain a mental model of the code, there is also more encapsulation, better abstractions and better extendability.
This commit is contained in:
		
							parent
							
								
									e1525a20c8
								
							
						
					
					
						commit
						2cf40f7a9b
					
				
					 11 changed files with 1084 additions and 657 deletions
				
			
		
							
								
								
									
										673
									
								
								cage.c
									
										
									
									
									
								
							
							
						
						
									
										673
									
								
								cage.c
									
										
									
									
									
								
							|  | @ -9,636 +9,23 @@ | |||
| #define _POSIX_C_SOURCE 200112L | ||||
| 
 | ||||
| #include <signal.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <sys/wait.h> | ||||
| #include <unistd.h> | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/backend.h> | ||||
| #include <wlr/render/wlr_renderer.h> | ||||
| #include <wlr/types/wlr_cursor.h> | ||||
| #include <wlr/types/wlr_compositor.h> | ||||
| #include <wlr/types/wlr_data_device.h> | ||||
| #include <wlr/types/wlr_matrix.h> | ||||
| #include <wlr/types/wlr_xcursor_manager.h> | ||||
| #include <wlr/types/wlr_output_layout.h> | ||||
| #include <wlr/types/wlr_xdg_shell.h> | ||||
| #include <wlr/util/log.h> | ||||
| 
 | ||||
| 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); | ||||
|  |  | |||
							
								
								
									
										16
									
								
								meson.build
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								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, | ||||
|  |  | |||
							
								
								
									
										166
									
								
								output.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								output.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <stdlib.h> | ||||
| #include <unistd.h> | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/backend.h> | ||||
| #include <wlr/render/wlr_renderer.h> | ||||
| #include <wlr/types/wlr_matrix.h> | ||||
| #include <wlr/types/wlr_output.h> | ||||
| #include <wlr/types/wlr_output_layout.h> | ||||
| #include <wlr/types/wlr_surface.h> | ||||
| #include <wlr/types/wlr_xdg_shell.h> | ||||
| #include <wlr/util/log.h> | ||||
| 
 | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										19
									
								
								output.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								output.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| #ifndef CG_OUTPUT_H | ||||
| #define CG_OUTPUT_H | ||||
| 
 | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/types/wlr_output.h> | ||||
| 
 | ||||
| #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 | ||||
							
								
								
									
										544
									
								
								seat.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								seat.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,544 @@ | |||
| /*
 | ||||
|  * Cage: A Wayland kiosk. | ||||
|  * | ||||
|  * Copyright (C) 2018 Jente Hidskes | ||||
|  * | ||||
|  * See the LICENSE file accompanying this file. | ||||
|  */ | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/backend.h> | ||||
| #include <wlr/types/wlr_cursor.h> | ||||
| #include <wlr/types/wlr_seat.h> | ||||
| #include <wlr/types/wlr_surface.h> | ||||
| #include <wlr/types/wlr_xcursor_manager.h> | ||||
| #include <wlr/util/log.h> | ||||
| 
 | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										54
									
								
								seat.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								seat.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| #ifndef CG_SEAT_H | ||||
| #define CG_SEAT_H | ||||
| 
 | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/types/wlr_cursor.h> | ||||
| #include <wlr/types/wlr_input_device.h> | ||||
| #include <wlr/types/wlr_seat.h> | ||||
| #include <wlr/types/wlr_xcursor_manager.h> | ||||
| 
 | ||||
| #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 | ||||
							
								
								
									
										25
									
								
								server.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								server.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| #ifndef CG_SERVER_H | ||||
| #define CG_SERVER_H | ||||
| 
 | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/backend.h> | ||||
| #include <wlr/types/wlr_output_layout.h> | ||||
| 
 | ||||
| #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 | ||||
							
								
								
									
										108
									
								
								view.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								view.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| /*
 | ||||
|  * Cage: A Wayland kiosk. | ||||
|  * | ||||
|  * Copyright (C) 2018 Jente Hidskes | ||||
|  * | ||||
|  * See the LICENSE file accompanying this file. | ||||
|  */ | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/types/wlr_box.h> | ||||
| #include <wlr/types/wlr_output.h> | ||||
| #include <wlr/types/wlr_surface.h> | ||||
| 
 | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										47
									
								
								view.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								view.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| #ifndef CG_VIEW_H | ||||
| #define CG_VIEW_H | ||||
| 
 | ||||
| #include <stdbool.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 "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 | ||||
							
								
								
									
										81
									
								
								xdg_shell.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								xdg_shell.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | |||
| /*
 | ||||
|  * Cage: A Wayland kiosk. | ||||
|  * | ||||
|  * Copyright (C) 2018 Jente Hidskes | ||||
|  * | ||||
|  * See the LICENSE file accompanying this file. | ||||
|  */ | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <wayland-server.h> | ||||
| #include <wlr/types/wlr_box.h> | ||||
| #include <wlr/types/wlr_xdg_shell.h> | ||||
| 
 | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										8
									
								
								xdg_shell.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								xdg_shell.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| #ifndef XDG_SHELL_H | ||||
| #define XDG_SHELL_H | ||||
| 
 | ||||
| #include <wayland-server.h> | ||||
| 
 | ||||
| void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data); | ||||
| 
 | ||||
| #endif | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jente Hidskes
						Jente Hidskes