mirror of
				https://gitlab.freedesktop.org/wlroots/wlroots.git
				synced 2025-10-29 05:40:12 -04:00 
			
		
		
		
	xwm: send selection data
This commit is contained in:
		
							parent
							
								
									ea6f77b484
								
							
						
					
					
						commit
						b0683874e9
					
				
					 8 changed files with 298 additions and 31 deletions
				
			
		|  | @ -28,4 +28,6 @@ struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input, | |||
| 
 | ||||
| bool input_view_has_focus(struct roots_input *input, struct roots_view *view); | ||||
| 
 | ||||
| struct roots_seat *input_get_seat(struct roots_input *input, char *name); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #include <time.h> | ||||
| #include <stdbool.h> | ||||
| #include <wlr/types/wlr_compositor.h> | ||||
| #include <wlr/types/wlr_seat.h> | ||||
| #include <xcb/xcb.h> | ||||
| 
 | ||||
| #ifdef HAS_XCB_ICCCM | ||||
|  | @ -16,7 +17,6 @@ struct wlr_xwayland_cursor; | |||
| struct wlr_xwayland { | ||||
| 	pid_t pid; | ||||
| 	int display; | ||||
| 	struct wlr_seat *seat; | ||||
| 	int x_fd[2], wl_fd[2], wm_fd[2]; | ||||
| 	struct wl_client *client; | ||||
| 	struct wl_display *wl_display; | ||||
|  | @ -170,4 +170,7 @@ void wlr_xwayland_surface_set_maximized(struct wlr_xwayland *wlr_xwayland, | |||
| void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland *wlr_xwayland, | ||||
| 	struct wlr_xwayland_surface *surface, bool fullscreen); | ||||
| 
 | ||||
| void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, | ||||
| 		struct wlr_seat *seat); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ static const char *device_type(enum wlr_input_device_type type) { | |||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| static struct roots_seat *input_get_seat(struct roots_input *input, char *name) { | ||||
| struct roots_seat *input_get_seat(struct roots_input *input, char *name) { | ||||
| 	struct roots_seat *seat = NULL; | ||||
| 	wl_list_for_each(seat, &input->seats, link) { | ||||
| 		if (strcmp(seat->seat->name, name) == 0) { | ||||
|  | @ -86,6 +86,7 @@ struct roots_input *input_create(struct roots_server *server, | |||
| 	input->server = server; | ||||
| 
 | ||||
| 	wl_list_init(&input->seats); | ||||
| 	roots_seat_create(input, ROOTS_CONFIG_DEFAULT_SEAT_NAME); | ||||
| 
 | ||||
| 	input->input_add.notify = input_add_notify; | ||||
| 	wl_signal_add(&server->backend->events.input_add, &input->input_add); | ||||
|  |  | |||
|  | @ -13,6 +13,19 @@ | |||
| struct roots_server server = { 0 }; | ||||
| 
 | ||||
| static void ready(struct wl_listener *listener, void *data) { | ||||
| 	struct roots_desktop *desktop = | ||||
| 		wl_container_of(listener, desktop, xwayland_ready); | ||||
| 
 | ||||
| #ifdef HAS_XWAYLAND | ||||
| 	struct wlr_xwayland *xwayland = data; | ||||
| 	if (xwayland) { | ||||
| 		struct roots_seat *seat = | ||||
| 			input_get_seat(desktop->server->input, | ||||
| 				ROOTS_CONFIG_DEFAULT_SEAT_NAME); | ||||
| 		wlr_xwayland_set_seat(xwayland, seat->seat); | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| 	if (server.config->startup_cmd != NULL) { | ||||
| 		const char *cmd = server.config->startup_cmd; | ||||
| 		pid_t pid = fork(); | ||||
|  | @ -38,12 +51,6 @@ int main(int argc, char **argv) { | |||
| 	server.desktop = desktop_create(&server, server.config); | ||||
| 	server.input = input_create(&server, server.config); | ||||
| 
 | ||||
| 	struct roots_seat *default_seat = | ||||
| 		roots_seat_create(server.input, ROOTS_CONFIG_DEFAULT_SEAT_NAME); | ||||
| 	if (server.desktop->xwayland) { | ||||
| 		server.desktop->xwayland->seat = default_seat->seat; | ||||
| 	} | ||||
| 
 | ||||
| 	const char *socket = wl_display_add_socket_auto(server.wl_display); | ||||
| 	if (!socket) { | ||||
| 		wlr_log_errno(L_ERROR, "Unable to open wayland socket"); | ||||
|  |  | |||
|  | @ -2,40 +2,238 @@ | |||
| #include <stdlib.h> | ||||
| #include <unistd.h> | ||||
| #include <string.h> | ||||
| #include <assert.h> | ||||
| #include <xcb/xfixes.h> | ||||
| #include <fcntl.h> | ||||
| #include "wlr/util/log.h" | ||||
| #include "wlr/types/wlr_data_device.h" | ||||
| #include "xwm.h" | ||||
| 
 | ||||
| static int xwm_handle_selection_property_notify(struct wlr_xwm *xwm, | ||||
| 		xcb_generic_event_t *event) { | ||||
| 	xcb_property_notify_event_t *property_notify = | ||||
| 		(xcb_property_notify_event_t *) event; | ||||
| static const size_t incr_chunk_size = 64 * 1024; | ||||
| 
 | ||||
| static void xwm_send_selection_notify(struct wlr_xwm *xwm, | ||||
| 		xcb_atom_t property) { | ||||
| 	xcb_selection_notify_event_t selection_notify; | ||||
| 
 | ||||
| 	memset(&selection_notify, 0, sizeof selection_notify); | ||||
| 	selection_notify.response_type = XCB_SELECTION_NOTIFY; | ||||
| 	selection_notify.sequence = 0; | ||||
| 	selection_notify.time = xwm->selection_request.time; | ||||
| 	selection_notify.requestor = xwm->selection_request.requestor; | ||||
| 	selection_notify.selection = xwm->selection_request.selection; | ||||
| 	selection_notify.target = xwm->selection_request.target; | ||||
| 	selection_notify.property = property; | ||||
| 
 | ||||
| 	xcb_send_event(xwm->xcb_conn, 0, // propagate
 | ||||
| 		xwm->selection_request.requestor, | ||||
| 		XCB_EVENT_MASK_NO_EVENT, (char *)&selection_notify); | ||||
| } | ||||
| 
 | ||||
| static int xwm_flush_source_data(struct wlr_xwm *xwm) | ||||
| { | ||||
| 	xcb_change_property(xwm->xcb_conn, | ||||
| 		XCB_PROP_MODE_REPLACE, | ||||
| 		xwm->selection_request.requestor, | ||||
| 		xwm->selection_request.property, | ||||
| 		xwm->selection_target, | ||||
| 		8, // format
 | ||||
| 		xwm->source_data.size, | ||||
| 		xwm->source_data.data); | ||||
| 	xwm->selection_property_set = 1; | ||||
| 	int length = xwm->source_data.size; | ||||
| 	xwm->source_data.size = 0; | ||||
| 
 | ||||
| 	return length; | ||||
| } | ||||
| 
 | ||||
| static int xwm_read_data_source(int fd, uint32_t mask, void *data) { | ||||
| 	struct wlr_xwm *xwm = data; | ||||
| 	void *p; | ||||
| 
 | ||||
| 	int current = xwm->source_data.size; | ||||
| 	if (xwm->source_data.size < incr_chunk_size) { | ||||
| 		p = wl_array_add(&xwm->source_data, incr_chunk_size); | ||||
| 	} else { | ||||
| 		p = (char *) xwm->source_data.data + xwm->source_data.size; | ||||
| 	} | ||||
| 
 | ||||
| 	int available = xwm->source_data.alloc - current; | ||||
| 
 | ||||
| 	int len = read(fd, p, available); | ||||
| 	if (len == -1) { | ||||
| 		wlr_log(L_ERROR, "read error from data source: %m\n"); | ||||
| 		xwm_send_selection_notify(xwm, XCB_ATOM_NONE); | ||||
| 		wl_event_source_remove(xwm->property_source); | ||||
| 		xwm->property_source = NULL; | ||||
| 		close(fd); | ||||
| 		wl_array_release(&xwm->source_data); | ||||
| 	} | ||||
| 
 | ||||
| 	wlr_log(L_DEBUG, "read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", | ||||
| 			len, available, mask, len, (char *) p); | ||||
| 
 | ||||
| 	xwm->source_data.size = current + len; | ||||
| 	if (xwm->source_data.size >= incr_chunk_size) { | ||||
| 		if (!xwm->incr) { | ||||
| 			wlr_log(L_DEBUG, "got %zu bytes, starting incr\n", | ||||
| 					xwm->source_data.size); | ||||
| 			xwm->incr = 1; | ||||
| 			xcb_change_property(xwm->xcb_conn, | ||||
| 					XCB_PROP_MODE_REPLACE, | ||||
| 					xwm->selection_request.requestor, | ||||
| 					xwm->selection_request.property, | ||||
| 					xwm->atoms[INCR], | ||||
| 					32, /* format */ | ||||
| 					1, &incr_chunk_size); | ||||
| 			xwm->selection_property_set = 1; | ||||
| 			xwm->flush_property_on_delete = 1; | ||||
| 			wl_event_source_remove(xwm->property_source); | ||||
| 			xwm->property_source = NULL; | ||||
| 			xwm_send_selection_notify(xwm, xwm->selection_request.property); | ||||
| 		} else if (xwm->selection_property_set) { | ||||
| 			wlr_log(L_DEBUG, "got %zu bytes, waiting for " | ||||
| 				"property delete\n", xwm->source_data.size); | ||||
| 
 | ||||
| 			xwm->flush_property_on_delete = 1; | ||||
| 			wl_event_source_remove(xwm->property_source); | ||||
| 			xwm->property_source = NULL; | ||||
| 		} else { | ||||
| 			wlr_log(L_DEBUG, "got %zu bytes, " | ||||
| 				"property deleted, setting new property\n", | ||||
| 				xwm->source_data.size); | ||||
| 			xwm_flush_source_data(xwm); | ||||
| 		} | ||||
| 	} else if (len == 0 && !xwm->incr) { | ||||
| 		wlr_log(L_DEBUG, "non-incr transfer complete\n"); | ||||
| 		/* Non-incr transfer all done. */ | ||||
| 		xwm_flush_source_data(xwm); | ||||
| 		xwm_send_selection_notify(xwm, xwm->selection_request.property); | ||||
| 		xcb_flush(xwm->xcb_conn); | ||||
| 		wl_event_source_remove(xwm->property_source); | ||||
| 		xwm->property_source = NULL; | ||||
| 		close(fd); | ||||
| 		wl_array_release(&xwm->source_data); | ||||
| 		xwm->selection_request.requestor = XCB_NONE; | ||||
| 	} else if (len == 0 && xwm->incr) { | ||||
| 		wlr_log(L_DEBUG, "incr transfer complete\n"); | ||||
| 
 | ||||
| 		xwm->flush_property_on_delete = 1; | ||||
| 		if (xwm->selection_property_set) { | ||||
| 			wlr_log(L_DEBUG, "got %zu bytes, waiting for " | ||||
| 					"property delete\n", xwm->source_data.size); | ||||
| 		} else { | ||||
| 			wlr_log(L_DEBUG, "got %zu bytes, " | ||||
| 					"property deleted, setting new property\n", | ||||
| 					xwm->source_data.size); | ||||
| 			xwm_flush_source_data(xwm); | ||||
| 		} | ||||
| 		xcb_flush(xwm->xcb_conn); | ||||
| 		wl_event_source_remove(xwm->property_source); | ||||
| 		xwm->property_source = NULL; | ||||
| 		close(xwm->data_source_fd); | ||||
| 		xwm->data_source_fd = -1; | ||||
| 		close(fd); | ||||
| 	} else { | ||||
| 		wlr_log(L_DEBUG, "nothing happened, buffered the bytes\n"); | ||||
| 	} | ||||
| 
 | ||||
| 	if (property_notify->window == xwm->selection_window) { | ||||
| 		if (property_notify->state == XCB_PROPERTY_NEW_VALUE && | ||||
| 				property_notify->atom == xwm->atoms[WL_SELECTION] && | ||||
| 				xwm->incr) | ||||
| 			wlr_log(L_DEBUG, "TODO: get selection"); | ||||
| 			return 1; | ||||
| 	} else if (property_notify->window == xwm->selection_request.requestor) { | ||||
| 		if (property_notify->state == XCB_PROPERTY_DELETE && | ||||
| 				property_notify->atom == xwm->selection_request.property && | ||||
| 				xwm->incr) | ||||
| 			wlr_log(L_DEBUG, "TODO: send selection"); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| 	return 0; | ||||
| static void xwm_send_data(struct wlr_xwm *xwm, xcb_atom_t target, | ||||
| 		const char *mime_type) { | ||||
| 	struct wlr_data_source *source; | ||||
| 	int p[2]; | ||||
| 
 | ||||
| 	if (pipe(p) == -1) { | ||||
| 		wlr_log(L_ERROR, "pipe failed: %m\n"); | ||||
| 		xwm_send_selection_notify(xwm, XCB_ATOM_NONE); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	fcntl(p[0], F_SETFD, FD_CLOEXEC); | ||||
| 	fcntl(p[0], F_SETFL, O_NONBLOCK); | ||||
| 	fcntl(p[1], F_SETFD, FD_CLOEXEC); | ||||
| 	fcntl(p[1], F_SETFL, O_NONBLOCK); | ||||
| 
 | ||||
| 	wl_array_init(&xwm->source_data); | ||||
| 	xwm->selection_target = target; | ||||
| 	xwm->data_source_fd = p[0]; | ||||
| 	struct wl_event_loop *loop = | ||||
| 		wl_display_get_event_loop(xwm->xwayland->wl_display); | ||||
| 	xwm->property_source = wl_event_loop_add_fd(loop, | ||||
| 		xwm->data_source_fd, | ||||
| 		WL_EVENT_READABLE, | ||||
| 		xwm_read_data_source, | ||||
| 		xwm); | ||||
| 
 | ||||
| 	source = xwm->seat->selection_source; | ||||
| 	source->send(source, mime_type, p[1]); | ||||
| 	close(p[1]); | ||||
| } | ||||
| 
 | ||||
| static void xwm_send_timestamp(struct wlr_xwm *xwm) { | ||||
| 	xcb_change_property(xwm->xcb_conn, | ||||
| 		XCB_PROP_MODE_REPLACE, | ||||
| 		xwm->selection_request.requestor, | ||||
| 		xwm->selection_request.property, | ||||
| 		XCB_ATOM_INTEGER, | ||||
| 		32, // format
 | ||||
| 		1, &xwm->selection_timestamp); | ||||
| 
 | ||||
| 	xwm_send_selection_notify(xwm, xwm->selection_request.property); | ||||
| } | ||||
| 
 | ||||
| static void xwm_send_targets(struct wlr_xwm *xwm) { | ||||
| 	xcb_atom_t targets[] = { | ||||
| 		xwm->atoms[TIMESTAMP], | ||||
| 		xwm->atoms[TARGETS], | ||||
| 		xwm->atoms[UTF8_STRING], | ||||
| 		xwm->atoms[TEXT], | ||||
| 	}; | ||||
| 
 | ||||
| 	xcb_change_property(xwm->xcb_conn, | ||||
| 		XCB_PROP_MODE_REPLACE, | ||||
| 		xwm->selection_request.requestor, | ||||
| 		xwm->selection_request.property, | ||||
| 		XCB_ATOM_ATOM, | ||||
| 		32, // format
 | ||||
| 		sizeof(targets) / sizeof(targets[0]), targets); | ||||
| 
 | ||||
| 	xwm_send_selection_notify(xwm, xwm->selection_request.property); | ||||
| } | ||||
| 
 | ||||
| static void xwm_handle_selection_request(struct wlr_xwm *xwm, | ||||
| 		xcb_generic_event_t *event) { | ||||
| 	wlr_log(L_DEBUG, "TODO: SELECTION REQUEST"); | ||||
| 	xcb_selection_request_event_t *selection_request = | ||||
| 		(xcb_selection_request_event_t *) event; | ||||
| 
 | ||||
| 	xwm->selection_request = *selection_request; | ||||
| 	xwm->incr = 0; | ||||
| 	xwm->flush_property_on_delete = 0; | ||||
| 
 | ||||
| 	if (selection_request->selection == xwm->atoms[CLIPBOARD_MANAGER]) { | ||||
| 		// The wlroots clipboard should already have grabbed
 | ||||
| 		// the first target, so just send selection notify
 | ||||
| 		// now.  This isn't synchronized with the clipboard
 | ||||
| 		// finishing getting the data, so there's a race here.
 | ||||
| 		xwm_send_selection_notify(xwm, xwm->selection_request.property); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (selection_request->target == xwm->atoms[TARGETS]) { | ||||
| 		xwm_send_targets(xwm); | ||||
| 	} else if (selection_request->target == xwm->atoms[TIMESTAMP]) { | ||||
| 		xwm_send_timestamp(xwm); | ||||
| 	} else if (selection_request->target == xwm->atoms[UTF8_STRING] || | ||||
| 			selection_request->target == xwm->atoms[TEXT]) { | ||||
| 		xwm_send_data(xwm, xwm->atoms[UTF8_STRING], "text/plain;charset=utf-8"); | ||||
| 	} else { | ||||
| 		wlr_log(L_DEBUG, "can only handle UTF8_STRING targets\n"); | ||||
| 		xwm_send_selection_notify(xwm, XCB_ATOM_NONE); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int writable_callback(int fd, uint32_t mask, void *data) { | ||||
| 	struct wlr_xwm *xwm = data; | ||||
| 
 | ||||
|  | @ -205,7 +403,7 @@ static void xwm_get_selection_targets(struct wlr_xwm *xwm) { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	wlr_seat_set_selection(xwm->xwayland->seat, &source->base, | ||||
| 	wlr_seat_set_selection(xwm->seat, &source->base, | ||||
| 		wl_display_next_serial(xwm->xwayland->wl_display)); | ||||
| 
 | ||||
| 	free(reply); | ||||
|  | @ -271,7 +469,7 @@ static int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, | |||
| 
 | ||||
| int xwm_handle_selection_event(struct wlr_xwm *xwm, | ||||
| 		xcb_generic_event_t *event) { | ||||
| 	if (!xwm->xwayland->seat) { | ||||
| 	if (!xwm->seat) { | ||||
| 		wlr_log(L_DEBUG, "not handling selection events:" | ||||
| 			"no seat assigned to xwayland"); | ||||
| 		return 0; | ||||
|  | @ -281,8 +479,6 @@ int xwm_handle_selection_event(struct wlr_xwm *xwm, | |||
| 	case XCB_SELECTION_NOTIFY: | ||||
| 		xwm_handle_selection_notify(xwm, event); | ||||
| 		return 1; | ||||
| 	case XCB_PROPERTY_NOTIFY: | ||||
| 		return xwm_handle_selection_property_notify(xwm, event); | ||||
| 	case XCB_SELECTION_REQUEST: | ||||
| 		xwm_handle_selection_request(xwm, event); | ||||
| 		return 1; | ||||
|  | @ -327,3 +523,44 @@ void xwm_selection_init(struct wlr_xwm *xwm) { | |||
| 	xcb_xfixes_select_selection_input(xwm->xcb_conn, xwm->selection_window, | ||||
| 		xwm->atoms[CLIPBOARD], mask); | ||||
| } | ||||
| 
 | ||||
| static void handle_seat_set_selection(struct wl_listener *listener, | ||||
| 		void *data) { | ||||
| 	struct wlr_seat *seat = data; | ||||
| 	struct wlr_xwm *xwm = | ||||
| 		wl_container_of(listener, xwm, seat_selection_change); | ||||
| 	struct wlr_data_source *source = seat->selection_source; | ||||
| 
 | ||||
| 	if (source == NULL) { | ||||
| 		if (xwm->selection_owner == xwm->selection_window) { | ||||
| 			xcb_set_selection_owner(xwm->xcb_conn, | ||||
| 				XCB_ATOM_NONE, | ||||
| 				xwm->atoms[CLIPBOARD], | ||||
| 				xwm->selection_timestamp); | ||||
| 		} | ||||
| 
 | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (source->send == data_source_send) | ||||
| 		return; | ||||
| 
 | ||||
| 	xcb_set_selection_owner(xwm->xcb_conn, | ||||
| 		xwm->selection_window, | ||||
| 		xwm->atoms[CLIPBOARD], | ||||
| 		XCB_TIME_CURRENT_TIME); | ||||
| } | ||||
| 
 | ||||
| void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat) { | ||||
| 	assert(xwm); | ||||
| 	assert(seat); | ||||
| 	if (xwm->seat) { | ||||
| 		wl_list_remove(&xwm->seat_selection_change.link); | ||||
| 		xwm->seat = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	wl_signal_add(&seat->events.selection, &xwm->seat_selection_change); | ||||
| 	xwm->seat_selection_change.notify = handle_seat_set_selection; | ||||
| 	xwm->seat = seat; | ||||
| 	handle_seat_set_selection(&xwm->seat_selection_change, seat); | ||||
| } | ||||
|  |  | |||
|  | @ -341,3 +341,8 @@ void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland, | |||
| 	wlr_xwayland->cursor->hotspot_x = hotspot_x; | ||||
| 	wlr_xwayland->cursor->hotspot_y = hotspot_y; | ||||
| } | ||||
| 
 | ||||
| void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, | ||||
| 		struct wlr_seat *seat) { | ||||
| 	xwm_set_seat(xwayland->xwm, seat); | ||||
| } | ||||
|  |  | |||
|  | @ -48,6 +48,8 @@ const char *atom_map[ATOM_LAST] = { | |||
| 	"TARGETS", | ||||
| 	"CLIPBOARD_MANAGER", | ||||
| 	"INCR", | ||||
| 	"TEXT", | ||||
| 	"TIMESTAMP", | ||||
| }; | ||||
| 
 | ||||
| /* General helpers */ | ||||
|  |  | |||
|  | @ -36,6 +36,8 @@ enum atom_name { | |||
| 	TARGETS, | ||||
| 	CLIPBOARD_MANAGER, | ||||
| 	INCR, | ||||
| 	TEXT, | ||||
| 	TIMESTAMP, | ||||
| 	ATOM_LAST, | ||||
| }; | ||||
| 
 | ||||
|  | @ -50,6 +52,7 @@ enum net_wm_state_action { | |||
| struct wlr_xwm { | ||||
| 	struct wlr_xwayland *xwayland; | ||||
| 	struct wl_event_source *event_source; | ||||
| 	struct wlr_seat *seat; | ||||
| 
 | ||||
| 	xcb_atom_t atoms[ATOM_LAST]; | ||||
| 	xcb_connection_t *xcb_conn; | ||||
|  | @ -70,6 +73,10 @@ struct wlr_xwm { | |||
| 	int property_start; | ||||
| 	xcb_get_property_reply_t *property_reply; | ||||
| 	struct wl_event_source *property_source; | ||||
| 	int flush_property_on_delete; | ||||
| 	struct wl_array source_data; | ||||
| 	xcb_atom_t selection_target; | ||||
| 	bool selection_property_set; | ||||
| 
 | ||||
| 	struct wlr_xwayland_surface *focus_surface; | ||||
| 
 | ||||
|  | @ -79,6 +86,7 @@ struct wlr_xwm { | |||
| 	const xcb_query_extension_reply_t *xfixes; | ||||
| 
 | ||||
| 	struct wl_listener compositor_surface_create; | ||||
| 	struct wl_listener seat_selection_change; | ||||
| }; | ||||
| 
 | ||||
| struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland); | ||||
|  | @ -92,4 +100,6 @@ int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event); | |||
| 
 | ||||
| void xwm_selection_init(struct wlr_xwm *xwm); | ||||
| 
 | ||||
| void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tony Crisci
						Tony Crisci