Merge pull request #786 from emersion/x11-backend-multiple-outputs
	
		
	
				
					
				
			backend/x11: add support for multiple outputsmaster
						commit
						96c6091f80
					
				| @ -0,0 +1,136 @@ | ||||
| #include <wlr/interfaces/wlr_input_device.h> | ||||
| #include <wlr/interfaces/wlr_keyboard.h> | ||||
| #include <wlr/interfaces/wlr_pointer.h> | ||||
| #include <wlr/util/log.h> | ||||
| #include <xcb/xcb.h> | ||||
| #ifdef __linux__ | ||||
| #include <linux/input-event-codes.h> | ||||
| #elif __FreeBSD__ | ||||
| #include <dev/evdev/input-event-codes.h> | ||||
| #endif | ||||
| #include "backend/x11.h" | ||||
| #include "util/signal.h" | ||||
| 
 | ||||
| static uint32_t xcb_button_to_wl(uint32_t button) { | ||||
| 	switch (button) { | ||||
| 	case XCB_BUTTON_INDEX_1: return BTN_LEFT; | ||||
| 	case XCB_BUTTON_INDEX_2: return BTN_MIDDLE; | ||||
| 	case XCB_BUTTON_INDEX_3: return BTN_RIGHT; | ||||
| 	// XXX: I'm not sure the scroll-wheel direction is right
 | ||||
| 	case XCB_BUTTON_INDEX_4: return BTN_GEAR_UP; | ||||
| 	case XCB_BUTTON_INDEX_5: return BTN_GEAR_DOWN; | ||||
| 	default: return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool x11_handle_input_event(struct wlr_x11_backend *x11, | ||||
| 		xcb_generic_event_t *event) { | ||||
| 	switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { | ||||
| 	case XCB_KEY_PRESS: | ||||
| 	case XCB_KEY_RELEASE: { | ||||
| 		xcb_key_press_event_t *ev = (xcb_key_press_event_t *)event; | ||||
| 		struct wlr_event_keyboard_key key = { | ||||
| 			.time_msec = ev->time, | ||||
| 			.keycode = ev->detail - 8, | ||||
| 			.state = event->response_type == XCB_KEY_PRESS ? | ||||
| 				WLR_KEY_PRESSED : WLR_KEY_RELEASED, | ||||
| 			.update_state = true, | ||||
| 		}; | ||||
| 
 | ||||
| 		// TODO use xcb-xkb for more precise modifiers state?
 | ||||
| 		wlr_keyboard_notify_key(&x11->keyboard, &key); | ||||
| 		x11->time = ev->time; | ||||
| 		return true; | ||||
| 	} | ||||
| 	case XCB_BUTTON_PRESS: { | ||||
| 		xcb_button_press_event_t *ev = (xcb_button_press_event_t *)event; | ||||
| 
 | ||||
| 		if (ev->detail == XCB_BUTTON_INDEX_4 || | ||||
| 				ev->detail == XCB_BUTTON_INDEX_5) { | ||||
| 			double delta = (ev->detail == XCB_BUTTON_INDEX_4 ? -15 : 15); | ||||
| 			struct wlr_event_pointer_axis axis = { | ||||
| 				.device = &x11->pointer_dev, | ||||
| 				.time_msec = ev->time, | ||||
| 				.source = WLR_AXIS_SOURCE_WHEEL, | ||||
| 				.orientation = WLR_AXIS_ORIENTATION_VERTICAL, | ||||
| 				.delta = delta, | ||||
| 			}; | ||||
| 			wlr_signal_emit_safe(&x11->pointer.events.axis, &axis); | ||||
| 			x11->time = ev->time; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	/* fallthrough */ | ||||
| 	case XCB_BUTTON_RELEASE: { | ||||
| 		xcb_button_press_event_t *ev = (xcb_button_press_event_t *)event; | ||||
| 
 | ||||
| 		if (ev->detail != XCB_BUTTON_INDEX_4 && | ||||
| 				ev->detail != XCB_BUTTON_INDEX_5) { | ||||
| 			struct wlr_event_pointer_button button = { | ||||
| 				.device = &x11->pointer_dev, | ||||
| 				.time_msec = ev->time, | ||||
| 				.button = xcb_button_to_wl(ev->detail), | ||||
| 				.state = event->response_type == XCB_BUTTON_PRESS ? | ||||
| 					WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED, | ||||
| 			}; | ||||
| 
 | ||||
| 			wlr_signal_emit_safe(&x11->pointer.events.button, &button); | ||||
| 		} | ||||
| 		x11->time = ev->time; | ||||
| 		return true; | ||||
| 	} | ||||
| 	case XCB_MOTION_NOTIFY: { | ||||
| 		xcb_motion_notify_event_t *ev = (xcb_motion_notify_event_t *)event; | ||||
| 
 | ||||
| 		struct wlr_x11_output *output = | ||||
| 			x11_output_from_window_id(x11, ev->event); | ||||
| 		if (output == NULL) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		struct wlr_output *wlr_output = &output->wlr_output; | ||||
| 
 | ||||
| 		struct wlr_box box = { .x = ev->event_x, .y = ev->event_y }; | ||||
| 		wlr_box_transform(&box, wlr_output->transform, wlr_output->width, | ||||
| 			wlr_output->height, &box); | ||||
| 		box.x /= wlr_output->scale; | ||||
| 		box.y /= wlr_output->scale; | ||||
| 
 | ||||
| 		struct wlr_box layout_box; | ||||
| 		x11_output_layout_get_box(x11, &layout_box); | ||||
| 
 | ||||
| 		double ox = wlr_output->lx / (double)layout_box.width; | ||||
| 		double oy = wlr_output->ly / (double)layout_box.height; | ||||
| 
 | ||||
| 		struct wlr_event_pointer_motion_absolute wlr_event = { | ||||
| 			.device = &x11->pointer_dev, | ||||
| 			.time_msec = ev->time, | ||||
| 			.x = box.x / (double)layout_box.width + ox, | ||||
| 			.y = box.y / (double)layout_box.height + oy, | ||||
| 		}; | ||||
| 
 | ||||
| 		wlr_signal_emit_safe(&x11->pointer.events.motion_absolute, &wlr_event); | ||||
| 
 | ||||
| 		x11->time = ev->time; | ||||
| 		return true; | ||||
| 	} | ||||
| 	default: | ||||
| #ifdef WLR_HAS_XCB_XKB | ||||
| 		if (x11->xkb_supported && event->response_type == x11->xkb_base_event) { | ||||
| 			xcb_xkb_state_notify_event_t *ev = | ||||
| 				(xcb_xkb_state_notify_event_t *)event; | ||||
| 			wlr_keyboard_notify_modifiers(&x11->keyboard, ev->baseMods, | ||||
| 				ev->latchedMods, ev->lockedMods, ev->lockedGroup); | ||||
| 			return true; | ||||
| 		} | ||||
| #endif | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| const struct wlr_input_device_impl input_device_impl = { 0 }; | ||||
| 
 | ||||
| bool wlr_input_device_is_x11(struct wlr_input_device *wlr_dev) { | ||||
| 	return wlr_dev->impl == &input_device_impl; | ||||
| } | ||||
| @ -0,0 +1,182 @@ | ||||
| #include <assert.h> | ||||
| #include <stdlib.h> | ||||
| #include <wlr/interfaces/wlr_output.h> | ||||
| #include <wlr/util/log.h> | ||||
| #include "backend/x11.h" | ||||
| #include "util/signal.h" | ||||
| 
 | ||||
| static int signal_frame(void *data) { | ||||
| 	struct wlr_x11_output *output = data; | ||||
| 	wlr_output_send_frame(&output->wlr_output); | ||||
| 	wl_event_source_timer_update(output->frame_timer, output->frame_delay); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void parse_xcb_setup(struct wlr_output *output, xcb_connection_t *xcb_conn) { | ||||
| 	const xcb_setup_t *xcb_setup = xcb_get_setup(xcb_conn); | ||||
| 
 | ||||
| 	snprintf(output->make, sizeof(output->make), "%.*s", | ||||
| 			xcb_setup_vendor_length(xcb_setup), | ||||
| 			xcb_setup_vendor(xcb_setup)); | ||||
| 	snprintf(output->model, sizeof(output->model), "%"PRIu16".%"PRIu16, | ||||
| 			xcb_setup->protocol_major_version, | ||||
| 			xcb_setup->protocol_minor_version); | ||||
| } | ||||
| 
 | ||||
| static bool output_set_custom_mode(struct wlr_output *wlr_output, int32_t width, | ||||
| 		int32_t height, int32_t refresh) { | ||||
| 	struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; | ||||
| 	struct wlr_x11_backend *x11 = output->x11; | ||||
| 
 | ||||
| 	wlr_output_update_custom_mode(&output->wlr_output, wlr_output->width, | ||||
| 		wlr_output->height, refresh); | ||||
| 	output->frame_delay = 1000000 / refresh; | ||||
| 
 | ||||
| 	const uint32_t values[] = { width, height }; | ||||
| 	xcb_configure_window(x11->xcb_conn, output->win, | ||||
| 		XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void output_transform(struct wlr_output *wlr_output, | ||||
| 		enum wl_output_transform transform) { | ||||
| 	struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; | ||||
| 	output->wlr_output.transform = transform; | ||||
| } | ||||
| 
 | ||||
| static void output_destroy(struct wlr_output *wlr_output) { | ||||
| 	struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; | ||||
| 	struct wlr_x11_backend *x11 = output->x11; | ||||
| 
 | ||||
| 	wl_list_remove(&output->link); | ||||
| 	wl_event_source_remove(output->frame_timer); | ||||
| 	eglDestroySurface(x11->egl.display, output->surf); | ||||
| 	xcb_destroy_window(x11->xcb_conn, output->win); | ||||
| 	free(output); | ||||
| } | ||||
| 
 | ||||
| static bool output_make_current(struct wlr_output *wlr_output, int *buffer_age) { | ||||
| 	struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; | ||||
| 	struct wlr_x11_backend *x11 = output->x11; | ||||
| 
 | ||||
| 	return wlr_egl_make_current(&x11->egl, output->surf, buffer_age); | ||||
| } | ||||
| 
 | ||||
| static bool output_swap_buffers(struct wlr_output *wlr_output, | ||||
| 		pixman_region32_t *damage) { | ||||
| 	struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; | ||||
| 	struct wlr_x11_backend *x11 = output->x11; | ||||
| 
 | ||||
| 	return wlr_egl_swap_buffers(&x11->egl, output->surf, damage); | ||||
| } | ||||
| 
 | ||||
| static const struct wlr_output_impl output_impl = { | ||||
| 	.set_custom_mode = output_set_custom_mode, | ||||
| 	.transform = output_transform, | ||||
| 	.destroy = output_destroy, | ||||
| 	.make_current = output_make_current, | ||||
| 	.swap_buffers = output_swap_buffers, | ||||
| }; | ||||
| 
 | ||||
| struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) { | ||||
| 	assert(wlr_backend_is_x11(backend)); | ||||
| 	struct wlr_x11_backend *x11 = (struct wlr_x11_backend *)backend; | ||||
| 
 | ||||
| 	if (!x11->started) { | ||||
| 		++x11->requested_outputs; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	struct wlr_x11_output *output = calloc(1, sizeof(struct wlr_x11_output)); | ||||
| 	if (output == NULL) { | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	output->x11 = x11; | ||||
| 
 | ||||
| 	struct wlr_output *wlr_output = &output->wlr_output; | ||||
| 	wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->wl_display); | ||||
| 
 | ||||
| 	wlr_output->refresh = 60 * 1000000; | ||||
| 	output->frame_delay = 16; // 60 Hz
 | ||||
| 
 | ||||
| 	snprintf(wlr_output->name, sizeof(wlr_output->name), "X11-%d", | ||||
| 		wl_list_length(&x11->outputs) + 1); | ||||
| 	parse_xcb_setup(wlr_output, x11->xcb_conn); | ||||
| 
 | ||||
| 	uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; | ||||
| 	uint32_t values[2] = { | ||||
| 		x11->screen->white_pixel, | ||||
| 		XCB_EVENT_MASK_EXPOSURE | | ||||
| 		XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | | ||||
| 		XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | | ||||
| 		XCB_EVENT_MASK_POINTER_MOTION | | ||||
| 		XCB_EVENT_MASK_STRUCTURE_NOTIFY | ||||
| 	}; | ||||
| 	output->win = xcb_generate_id(x11->xcb_conn); | ||||
| 	xcb_create_window(x11->xcb_conn, XCB_COPY_FROM_PARENT, output->win, | ||||
| 		x11->screen->root, 0, 0, 1024, 768, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, | ||||
| 		x11->screen->root_visual, mask, values); | ||||
| 
 | ||||
| 	output->surf = wlr_egl_create_surface(&x11->egl, &output->win); | ||||
| 	if (!output->surf) { | ||||
| 		wlr_log(L_ERROR, "Failed to create EGL surface"); | ||||
| 		free(output); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	xcb_change_property(x11->xcb_conn, XCB_PROP_MODE_REPLACE, output->win, | ||||
| 		x11->atoms.wm_protocols, XCB_ATOM_ATOM, 32, 1, | ||||
| 		&x11->atoms.wm_delete_window); | ||||
| 
 | ||||
| 	char title[32]; | ||||
| 	if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) { | ||||
| 		xcb_change_property(x11->xcb_conn, XCB_PROP_MODE_REPLACE, output->win, | ||||
| 			x11->atoms.net_wm_name, x11->atoms.utf8_string, 8, | ||||
| 			strlen(title), title); | ||||
| 	} | ||||
| 
 | ||||
| 	xcb_map_window(x11->xcb_conn, output->win); | ||||
| 	xcb_flush(x11->xcb_conn); | ||||
| 
 | ||||
| 	struct wl_event_loop *ev = wl_display_get_event_loop(x11->wl_display); | ||||
| 	output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output); | ||||
| 
 | ||||
| 	wl_event_source_timer_update(output->frame_timer, output->frame_delay); | ||||
| 	wlr_output_update_enabled(wlr_output, true); | ||||
| 
 | ||||
| 	wl_list_insert(&x11->outputs, &output->link); | ||||
| 	wlr_signal_emit_safe(&x11->backend.events.new_output, wlr_output); | ||||
| 
 | ||||
| 	return wlr_output; | ||||
| } | ||||
| 
 | ||||
| void x11_output_handle_configure_notify(struct wlr_x11_output *output, | ||||
| 		xcb_configure_notify_event_t *ev) { | ||||
| 	struct wlr_x11_backend *x11 = output->x11; | ||||
| 
 | ||||
| 	wlr_output_update_custom_mode(&output->wlr_output, ev->width, | ||||
| 		ev->height, output->wlr_output.refresh); | ||||
| 
 | ||||
| 	// Move the pointer to its new location
 | ||||
| 	xcb_query_pointer_cookie_t cookie = | ||||
| 		xcb_query_pointer(x11->xcb_conn, output->win); | ||||
| 	xcb_query_pointer_reply_t *pointer = | ||||
| 		xcb_query_pointer_reply(x11->xcb_conn, cookie, NULL); | ||||
| 	if (!pointer) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	struct wlr_event_pointer_motion_absolute abs = { | ||||
| 		.device = &x11->pointer_dev, | ||||
| 		.time_msec = x11->time, | ||||
| 		.x = (double)pointer->root_x / output->wlr_output.width, | ||||
| 		.y = (double)pointer->root_y / output->wlr_output.height, | ||||
| 	}; | ||||
| 
 | ||||
| 	wlr_signal_emit_safe(&x11->pointer.events.motion_absolute, &abs); | ||||
| 	free(pointer); | ||||
| } | ||||
| 
 | ||||
| bool wlr_output_is_x11(struct wlr_output *wlr_output) { | ||||
| 	return wlr_output->impl == &output_impl; | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue