blob: c0d908fb25cb04fe1249f6ff78cf6481d17cc27f [file] [log] [blame]
Jonas Ådahl61831f42016-03-15 18:14:00 +08001/*
2 * Copyright © 2010 Intel Corporation
3 * Copyright © 2012 Collabora, Ltd.
4 * Copyright © 2012 Jonas Ådahl
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 */
25
26#include "config.h"
27
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +080028#include <stdbool.h>
Jonas Ådahl61831f42016-03-15 18:14:00 +080029#include <stdint.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <cairo.h>
34#include <math.h>
35#include <assert.h>
36#include <sys/timerfd.h>
37#include <sys/epoll.h>
38#include <unistd.h>
39
40#include <linux/input.h>
41#include <wayland-client.h>
42
43#include "window.h"
44#include "shared/helpers.h"
45#include "shared/xalloc.h"
46
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +080047#define NUM_COMPLEX_REGION_RECTS 9
48
49static int32_t option_complex_confine_region;
50static int32_t option_help;
51
Jonas Ådahl61831f42016-03-15 18:14:00 +080052struct confine {
53 struct display *display;
54 struct window *window;
55 struct widget *widget;
56
57 cairo_surface_t *buffer;
58
59 struct {
60 int32_t x, y;
61 int32_t old_x, old_y;
62 } line;
63
64 int reset;
65
66 struct input *cursor_timeout_input;
67 int cursor_timeout_fd;
68 struct task cursor_timeout_task;
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +080069
70 bool pointer_confined;
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +080071
72 bool complex_confine_region_enabled;
73 bool complex_confine_region_dirty;
74 struct rectangle complex_confine_region[NUM_COMPLEX_REGION_RECTS];
Jonas Ådahl61831f42016-03-15 18:14:00 +080075};
76
77static void
78draw_line(struct confine *confine, cairo_t *cr,
79 struct rectangle *allocation)
80{
81 cairo_t *bcr;
82 cairo_surface_t *tmp_buffer = NULL;
83
84 if (confine->reset) {
85 tmp_buffer = confine->buffer;
86 confine->buffer = NULL;
87 confine->line.x = -1;
88 confine->line.y = -1;
89 confine->line.old_x = -1;
90 confine->line.old_y = -1;
91 confine->reset = 0;
92 }
93
94 if (confine->buffer == NULL) {
95 confine->buffer =
96 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
97 allocation->width,
98 allocation->height);
99 bcr = cairo_create(confine->buffer);
100 cairo_set_source_rgba(bcr, 0, 0, 0, 0);
101 cairo_rectangle(bcr,
102 0, 0,
103 allocation->width, allocation->height);
104 cairo_fill(bcr);
105 }
106 else
107 bcr = cairo_create(confine->buffer);
108
109 if (tmp_buffer) {
110 cairo_set_source_surface(bcr, tmp_buffer, 0, 0);
111 cairo_rectangle(bcr, 0, 0,
112 allocation->width, allocation->height);
113 cairo_clip(bcr);
114 cairo_paint(bcr);
115
116 cairo_surface_destroy(tmp_buffer);
117 }
118
119 if (confine->line.x != -1 && confine->line.y != -1) {
120 if (confine->line.old_x != -1 &&
121 confine->line.old_y != -1) {
122 cairo_set_line_width(bcr, 2.0);
123 cairo_set_source_rgb(bcr, 1, 1, 1);
124 cairo_translate(bcr,
125 -allocation->x, -allocation->y);
126
127 cairo_move_to(bcr,
128 confine->line.old_x,
129 confine->line.old_y);
130 cairo_line_to(bcr,
131 confine->line.x,
132 confine->line.y);
133
134 cairo_stroke(bcr);
135 }
136
137 confine->line.old_x = confine->line.x;
138 confine->line.old_y = confine->line.y;
139 }
140 cairo_destroy(bcr);
141
142 cairo_set_source_surface(cr, confine->buffer,
143 allocation->x, allocation->y);
144 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
145 cairo_rectangle(cr,
146 allocation->x, allocation->y,
147 allocation->width, allocation->height);
148 cairo_clip(cr);
149 cairo_paint(cr);
150}
151
152static void
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800153calculate_complex_confine_region(struct confine *confine)
154{
155 struct rectangle allocation;
156 int32_t x, y, w, h;
157 struct rectangle *rs = confine->complex_confine_region;
158
159 if (!confine->complex_confine_region_dirty)
160 return;
161
162 widget_get_allocation(confine->widget, &allocation);
163 x = allocation.x;
164 y = allocation.y;
165 w = allocation.width;
166 h = allocation.height;
167
168 /*
169 * The code below constructs a region made up of rectangles that
170 * is then used to set up both an illustrative shaded region in the
171 * widget and a confine region used when confining the pointer.
172 */
173
174 rs[0].x = x + (int)round(w * 0.05);
175 rs[0].y = y + (int)round(h * 0.15);
176 rs[0].width = (int)round(w * 0.35);
177 rs[0].height = (int)round(h * 0.7);
178
179 rs[1].x = rs[0].x + rs[0].width;
180 rs[1].y = y + (int)round(h * 0.45);
181 rs[1].width = (int)round(w * 0.09);
182 rs[1].height = (int)round(h * 0.1);
183
184 rs[2].x = rs[1].x + rs[1].width;
185 rs[2].y = y + (int)round(h * 0.48);
186 rs[2].width = (int)round(w * 0.02);
187 rs[2].height = (int)round(h * 0.04);
188
189 rs[3].x = rs[2].x + rs[2].width;
190 rs[3].y = y + (int)round(h * 0.45);
191 rs[3].width = (int)round(w * 0.09);
192 rs[3].height = (int)round(h * 0.1);
193
194 rs[4].x = rs[3].x + rs[3].width;
195 rs[4].y = y + (int)round(h * 0.15);
196 rs[4].width = (int)round(w * 0.35);
197 rs[4].height = (int)round(h * 0.7);
198
199 rs[5].x = x + (int)round(w * 0.05);
200 rs[5].y = y + (int)round(h * 0.05);
201 rs[5].width = rs[0].width + rs[1].width + rs[2].width +
202 rs[3].width + rs[4].width;
203 rs[5].height = (int)round(h * 0.10);
204
205 rs[6].x = x + (int)round(w * 0.1);
206 rs[6].y = rs[4].y + rs[4].height + (int)round(h * 0.02);
207 rs[6].width = (int)round(w * 0.8);
208 rs[6].height = (int)round(h * 0.03);
209
210 rs[7].x = x + (int)round(w * 0.05);
211 rs[7].y = rs[6].y + rs[6].height;
212 rs[7].width = (int)round(w * 0.9);
213 rs[7].height = (int)round(h * 0.03);
214
215 rs[8].x = x + (int)round(w * 0.1);
216 rs[8].y = rs[7].y + rs[7].height;
217 rs[8].width = (int)round(w * 0.8);
218 rs[8].height = (int)round(h * 0.03);
219
220 confine->complex_confine_region_dirty = false;
221}
222
223static void
224draw_complex_confine_region_mask(struct confine *confine, cairo_t *cr)
225{
226 int i;
227
228 calculate_complex_confine_region(confine);
229
230 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
231
232 for (i = 0; i < NUM_COMPLEX_REGION_RECTS; i++) {
233 cairo_rectangle(cr,
234 confine->complex_confine_region[i].x,
235 confine->complex_confine_region[i].y,
236 confine->complex_confine_region[i].width,
237 confine->complex_confine_region[i].height);
238 cairo_set_source_rgba(cr, 0.14, 0.14, 0.14, 0.9);
239 cairo_fill(cr);
240 }
241}
242
243static void
Jonas Ådahl61831f42016-03-15 18:14:00 +0800244redraw_handler(struct widget *widget, void *data)
245{
246 struct confine *confine = data;
247 cairo_surface_t *surface;
248 cairo_t *cr;
249 struct rectangle allocation;
250
251 widget_get_allocation(confine->widget, &allocation);
252
253 surface = window_get_surface(confine->window);
254
255 cr = cairo_create(surface);
256 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
257 cairo_rectangle(cr,
258 allocation.x,
259 allocation.y,
260 allocation.width,
261 allocation.height);
262 cairo_set_source_rgba(cr, 0, 0, 0, 0.8);
263 cairo_fill(cr);
264
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800265 if (confine->complex_confine_region_enabled) {
266 draw_complex_confine_region_mask(confine, cr);
267 }
268
Jonas Ådahl61831f42016-03-15 18:14:00 +0800269 draw_line(confine, cr, &allocation);
270
271 cairo_destroy(cr);
272
273 cairo_surface_destroy(surface);
274}
275
276static void
277keyboard_focus_handler(struct window *window,
278 struct input *device, void *data)
279{
280 struct confine *confine = data;
281
282 window_schedule_redraw(confine->window);
283}
284
285static void
286key_handler(struct window *window, struct input *input, uint32_t time,
287 uint32_t key, uint32_t sym,
288 enum wl_keyboard_key_state state, void *data)
289{
290 struct confine *confine = data;
291
292 if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
293 return;
294
295 switch (sym) {
296 case XKB_KEY_Escape:
297 display_exit(confine->display);
298 break;
Jonas Ådahlcadea762015-05-08 13:54:44 +0800299 case XKB_KEY_BackSpace:
300 cairo_surface_destroy(confine->buffer);
301 confine->buffer = NULL;
302 window_schedule_redraw(confine->window);
303 break;
Jonas Ådahlcbfde132015-06-25 17:45:01 +0800304 case XKB_KEY_m:
305 window_set_maximized(confine->window,
306 !window_is_maximized(window));
307 break;
Jonas Ådahl61831f42016-03-15 18:14:00 +0800308 }
309}
310
311static void
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +0800312toggle_pointer_confine(struct confine *confine, struct input *input)
313{
314 if (confine->pointer_confined) {
315 window_unconfine_pointer(confine->window);
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800316 } else if (confine->complex_confine_region_enabled) {
317 calculate_complex_confine_region(confine);
318 window_confine_pointer_to_rectangles(
319 confine->window,
320 input,
321 confine->complex_confine_region,
322 NUM_COMPLEX_REGION_RECTS);
323
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +0800324 } else {
325 window_confine_pointer_to_widget(confine->window,
326 confine->widget,
327 input);
328 }
329
330 confine->pointer_confined = !confine->pointer_confined;
331}
332
333static void
334button_handler(struct widget *widget,
335 struct input *input, uint32_t time,
336 uint32_t button,
337 enum wl_pointer_button_state state, void *data)
338{
339 struct confine *confine = data;
340 bool is_pressed = state == WL_POINTER_BUTTON_STATE_PRESSED;
341
342 if (is_pressed && button == BTN_LEFT)
343 toggle_pointer_confine(confine, input);
344 widget_schedule_redraw(widget);
345}
346
347static void
Jonas Ådahl61831f42016-03-15 18:14:00 +0800348cursor_timeout_reset(struct confine *confine)
349{
350 const long cursor_timeout = 500;
351 struct itimerspec its;
352
353 its.it_interval.tv_sec = 0;
354 its.it_interval.tv_nsec = 0;
355 its.it_value.tv_sec = cursor_timeout / 1000;
356 its.it_value.tv_nsec = (cursor_timeout % 1000) * 1000 * 1000;
357 timerfd_settime(confine->cursor_timeout_fd, 0, &its, NULL);
358}
359
360static int
361motion_handler(struct widget *widget,
362 struct input *input, uint32_t time,
363 float x, float y, void *data)
364{
365 struct confine *confine = data;
366 confine->line.x = x;
367 confine->line.y = y;
368
369 window_schedule_redraw(confine->window);
370
371 cursor_timeout_reset(confine);
372 confine->cursor_timeout_input = input;
373
374 return CURSOR_BLANK;
375}
376
377static void
378resize_handler(struct widget *widget,
379 int32_t width, int32_t height,
380 void *data)
381{
382 struct confine *confine = data;
383
384 confine->reset = 1;
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800385
Jonas Ådahlcbfde132015-06-25 17:45:01 +0800386 if (confine->complex_confine_region_enabled) {
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800387 confine->complex_confine_region_dirty = true;
Jonas Ådahlcbfde132015-06-25 17:45:01 +0800388
389 if (confine->pointer_confined) {
390 calculate_complex_confine_region(confine);
391 window_update_confine_rectangles(
392 confine->window,
393 confine->complex_confine_region,
394 NUM_COMPLEX_REGION_RECTS);
395 }
396 }
Jonas Ådahl61831f42016-03-15 18:14:00 +0800397}
398
399static void
400leave_handler(struct widget *widget,
401 struct input *input, void *data)
402{
403 struct confine *confine = data;
404
405 confine->reset = 1;
406}
407
408static void
409cursor_timeout_func(struct task *task, uint32_t events)
410{
411 struct confine *confine =
412 container_of(task, struct confine, cursor_timeout_task);
413 uint64_t exp;
414
415 if (read(confine->cursor_timeout_fd, &exp, sizeof (uint64_t)) !=
416 sizeof(uint64_t))
417 abort();
418
419 input_set_pointer_image(confine->cursor_timeout_input,
420 CURSOR_LEFT_PTR);
421}
422
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +0800423static void
424pointer_unconfined(struct window *window, struct input *input, void *data)
425{
426 struct confine *confine = data;
427
428 confine->pointer_confined = false;
429}
430
Jonas Ådahl61831f42016-03-15 18:14:00 +0800431static struct confine *
432confine_create(struct display *display)
433{
434 struct confine *confine;
435
436 confine = xzalloc(sizeof *confine);
437 confine->window = window_create(display);
438 confine->widget = window_frame_create(confine->window, confine);
439 window_set_title(confine->window, "Wayland Confine");
440 confine->display = display;
441 confine->buffer = NULL;
442
443 window_set_key_handler(confine->window, key_handler);
444 window_set_user_data(confine->window, confine);
445 window_set_keyboard_focus_handler(confine->window,
446 keyboard_focus_handler);
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +0800447 window_set_pointer_confined_handler(confine->window,
448 NULL,
449 pointer_unconfined);
Jonas Ådahl61831f42016-03-15 18:14:00 +0800450
451 widget_set_redraw_handler(confine->widget, redraw_handler);
Jonas Ådahlfdeb2bb2014-11-26 17:47:29 +0800452 widget_set_button_handler(confine->widget, button_handler);
Jonas Ådahl61831f42016-03-15 18:14:00 +0800453 widget_set_motion_handler(confine->widget, motion_handler);
454 widget_set_resize_handler(confine->widget, resize_handler);
455 widget_set_leave_handler(confine->widget, leave_handler);
456
457 widget_schedule_resize(confine->widget, 500, 400);
458 confine->line.x = -1;
459 confine->line.y = -1;
460 confine->line.old_x = -1;
461 confine->line.old_y = -1;
462 confine->reset = 0;
463
464 confine->cursor_timeout_fd =
465 timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
466 confine->cursor_timeout_task.run = cursor_timeout_func;
467 display_watch_fd(window_get_display(confine->window),
468 confine->cursor_timeout_fd,
469 EPOLLIN, &confine->cursor_timeout_task);
470
471 return confine;
472}
473
474static void
475confine_destroy(struct confine *confine)
476{
477 display_unwatch_fd(window_get_display(confine->window),
478 confine->cursor_timeout_fd);
479 close(confine->cursor_timeout_fd);
480 if (confine->buffer)
481 cairo_surface_destroy(confine->buffer);
482 widget_destroy(confine->widget);
483 window_destroy(confine->window);
484 free(confine);
485}
486
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800487static const struct weston_option confine_options[] = {
488 { WESTON_OPTION_BOOLEAN, "complex-confine-region", 0, &option_complex_confine_region },
489 { WESTON_OPTION_BOOLEAN, "help", 0, &option_help },
490};
491
492static void
493print_help(const char *argv0)
494{
495 printf("Usage: %s [--complex-confine-region]\n", argv0);
496}
497
Jonas Ådahl61831f42016-03-15 18:14:00 +0800498int
499main(int argc, char *argv[])
500{
501 struct display *display;
502 struct confine *confine;
503
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800504 if (parse_options(confine_options,
505 ARRAY_LENGTH(confine_options),
506 &argc, argv) > 1 ||
507 option_help) {
508 print_help(argv[0]);
509 return 0;
510 }
511
Jonas Ådahl61831f42016-03-15 18:14:00 +0800512 display = display_create(&argc, argv);
513 if (display == NULL) {
514 fprintf(stderr, "failed to create display: %m\n");
515 return -1;
516 }
517
518 confine = confine_create(display);
519
Jonas Ådahl4a1b9a92015-05-08 14:18:11 +0800520 if (option_complex_confine_region) {
521 confine->complex_confine_region_dirty = true;
522 confine->complex_confine_region_enabled = true;
523 }
524
Jonas Ådahl61831f42016-03-15 18:14:00 +0800525 display_run(display);
526
527 confine_destroy(confine);
528 display_destroy(display);
529
530 return 0;
531}