1 #include <pangolin/platform.h>
2 #include <pangolin/display/display.h>
3 #include <pangolin/display/display_internal.h>
4 #include <pangolin/factory/factory_registry.h>
5 #include <pangolin/gl/colour.h>
6 #include <pangolin/gl/gldraw.h>
7
8 #include <wayland-client.h>
9 #include <wayland-egl.h>
10 #include <EGL/egl.h>
11 #include <xkbcommon/xkbcommon.h>
12 #include <wayland-cursor.h>
13 #include <linux/input.h>
14 #include <sys/mman.h>
15
16 #include <mutex>
17 #include <string.h>
18 #include <unistd.h>
19 #include <cstdlib>
20
21 #if USE_WL_XDG
22 #include <xdg-shell-client-protocol.h>
23 #endif
24
25 #define WAYLAND_VERSION_GE(MAJ, MIN) WAYLAND_VERSION_MAJOR >= MAJ && WAYLAND_VERSION_MINOR >= MIN
26
27 namespace pangolin {
28
29 extern __thread PangolinGl* context;
30
31 namespace wayland {
32
33 static const std::map<enum wl_shell_surface_resize, std::string> resize_cursor = {
34 {WL_SHELL_SURFACE_RESIZE_NONE, "grabbing"},
35 {WL_SHELL_SURFACE_RESIZE_TOP, "top_side"},
36 {WL_SHELL_SURFACE_RESIZE_BOTTOM, "bottom_side"},
37 {WL_SHELL_SURFACE_RESIZE_LEFT, "left_side"},
38 {WL_SHELL_SURFACE_RESIZE_TOP_LEFT, "top_left_corner"},
39 {WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT, "bottom_left_corner"},
40 {WL_SHELL_SURFACE_RESIZE_RIGHT, "right_side"},
41 {WL_SHELL_SURFACE_RESIZE_TOP_RIGHT, "top_right_corner"},
42 {WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT, "bottom_right_corner"}
43 };
44
45 struct ButtonSurface {
46 struct wl_surface *surface;
47 struct wl_subsurface *subsurface;
48 struct wl_egl_window *egl_window;
49 EGLSurface egl_surface;
50 EGLContext egl_context;
51 EGLDisplay egl_display;
52 const int32_t x, y;
53 const uint width, height;
54 pangolin::Colour colour;
55
56 enum type {
57 CLOSE = 100,
58 MAXIMISE
59 } function;
60
ButtonSurfacepangolin::wayland::ButtonSurface61 ButtonSurface(wl_compositor* compositor, wl_subcompositor* subcompositor,
62 wl_surface* source, EGLDisplay egl_display, EGLConfig config,
63 int32_t x, int32_t y, uint width, uint height,
64 type fnct, pangolin::Colour colour
65 ) :
66 egl_display(egl_display),
67 x(x), y(y),
68 width(width), height(height),
69 colour(colour),
70 function(fnct)
71 {
72 surface = wl_compositor_create_surface(compositor);
73 subsurface = wl_subcompositor_get_subsurface(subcompositor, surface, source);
74 wl_subsurface_set_desync(subsurface);
75 egl_context = eglCreateContext (egl_display, config, EGL_NO_CONTEXT, nullptr);
76 egl_window = wl_egl_window_create(surface, width, height);
77 egl_surface = eglCreateWindowSurface(egl_display, config, (EGLNativeWindowType)egl_window, nullptr);
78 }
79
~ButtonSurfacepangolin::wayland::ButtonSurface80 ~ButtonSurface() {
81 if(egl_surface) eglDestroySurface(egl_display, egl_surface);
82 if(egl_window) wl_egl_window_destroy(egl_window);
83 if(egl_context) eglDestroyContext(egl_display, egl_context);
84
85 if(subsurface) wl_subsurface_destroy(subsurface);
86 if(surface) wl_surface_destroy(surface);
87 }
88
repositionpangolin::wayland::ButtonSurface89 void reposition(const int main_w) const {
90 wl_subsurface_set_position(subsurface, main_w-x-width, y);
91 }
92
drawpangolin::wayland::ButtonSurface93 void draw() const {
94 eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
95 glClearColor(colour.r, colour.g, colour.b, colour.a);
96 glClear(GL_COLOR_BUFFER_BIT);
97 switch (function) {
98 case CLOSE:
99 glLineWidth(3);
100 glColor3f(1, 1, 1);
101 glBegin(GL_LINES);
102 glVertex2f(-1, -1);
103 glVertex2f(1, 1);
104 glVertex2f(1, -1);
105 glVertex2f(-1, 1);
106 glEnd();
107 break;
108 case MAXIMISE:
109 glLineWidth(2);
110 glColor3f(0, 0, 0);
111 glBegin(GL_LINE_LOOP);
112 glVertex2f(-0.7f, -0.7f);
113 glVertex2f(0.7f, -0.7f);
114 glVertex2f(0.7f, 0.7f);
115 glVertex2f(-0.7f, 0.7f);
116 glEnd();
117 glLineWidth(3);
118 glBegin(GL_LINES);
119 glVertex2f(+0.7f, +0.7f);
120 glVertex2f(-0.7f, +0.7f);
121 glEnd();
122 break;
123 }
124 eglSwapBuffers(egl_display, egl_surface);
125 }
126 };
127
128 struct DecorationSurface {
129 struct wl_surface *surface;
130 struct wl_subsurface *subsurface;
131 struct wl_egl_window *egl_window;
132 EGLSurface egl_surface;
133 EGLContext egl_context;
134 EGLDisplay egl_display;
135 pangolin::Colour colour;
136 uint border_size;
137 uint title_bar_size;
138
139 enum wl_shell_surface_resize function;
140
DecorationSurfacepangolin::wayland::DecorationSurface141 DecorationSurface(wl_compositor* compositor, wl_subcompositor* subcompositor,
142 wl_surface* source, EGLDisplay egl_display, EGLConfig config,
143 const uint _border_size, const uint _title_bar_size,
144 enum wl_shell_surface_resize type, pangolin::Colour _colour
145 ) :
146 egl_display(egl_display),
147 colour(_colour),
148 border_size(_border_size),
149 title_bar_size(_title_bar_size),
150 function(type)
151 {
152 surface = wl_compositor_create_surface(compositor);
153 subsurface = wl_subcompositor_get_subsurface(subcompositor, surface, source);
154 wl_subsurface_set_desync(subsurface);
155 egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, nullptr);
156 egl_window = wl_egl_window_create(surface, 50, 50);
157 egl_surface = eglCreateWindowSurface(egl_display, config, (EGLNativeWindowType)egl_window, nullptr);
158 }
159
~DecorationSurfacepangolin::wayland::DecorationSurface160 ~DecorationSurface() {
161 if(egl_surface) eglDestroySurface(egl_display, egl_surface);
162 if(egl_window) wl_egl_window_destroy(egl_window);
163 if(egl_context) eglDestroyContext(egl_display, egl_context);
164
165 if(subsurface) wl_subsurface_destroy(subsurface);
166 if(surface) wl_surface_destroy(surface);
167 }
168
calc_dimpangolin::wayland::DecorationSurface169 void calc_dim(const int main_w, const int main_h, int &x, int &y, int &w, int &h) const {
170 // get position and dimension from type and main surface
171 switch (function) {
172 case WL_SHELL_SURFACE_RESIZE_NONE:
173 x=0; y=-title_bar_size;
174 w=main_w; h=title_bar_size;
175 break;
176 case WL_SHELL_SURFACE_RESIZE_TOP:
177 x=0; y=-title_bar_size-border_size;
178 w=main_w; h=border_size;
179 break;
180 case WL_SHELL_SURFACE_RESIZE_BOTTOM:
181 x=0; y=main_h;
182 w=main_w; h=border_size;
183 break;
184 case WL_SHELL_SURFACE_RESIZE_LEFT:
185 x=-border_size; y=-title_bar_size;
186 w=border_size; h=main_h+title_bar_size;
187 break;
188 case WL_SHELL_SURFACE_RESIZE_TOP_LEFT:
189 x=-border_size; y=-border_size-title_bar_size;
190 w=border_size; h=border_size;
191 break;
192 case WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT:
193 x=-border_size; y=main_h;
194 w=border_size; h=border_size;
195 break;
196 case WL_SHELL_SURFACE_RESIZE_RIGHT:
197 x=main_w; y=-title_bar_size;
198 w=border_size; h=main_h+title_bar_size;
199 break;
200 case WL_SHELL_SURFACE_RESIZE_TOP_RIGHT:
201 x=main_w; y=-border_size-title_bar_size;
202 w=border_size; h=border_size;
203 break;
204 case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT:
205 x=main_w; y=main_h;
206 w=border_size; h=border_size;
207 break;
208 }
209 }
210
resizepangolin::wayland::DecorationSurface211 void resize(const int main_w, const int main_h) const {
212 int x=0, y=0, w=0, h=0;
213 calc_dim(main_w, main_h, x, y, w, h);
214 wl_subsurface_set_position(subsurface, x, y);
215 wl_egl_window_resize(egl_window, w, h, 0, 0);
216 }
217
drawpangolin::wayland::DecorationSurface218 void draw() const {
219 eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
220 glClearColor(colour.r, colour.g, colour.b, colour.a);
221 glClear(GL_COLOR_BUFFER_BIT);
222 eglSwapBuffers(egl_display, egl_surface);
223 }
224 };
225
226 struct Decoration {
Decorationpangolin::wayland::Decoration227 Decoration(const uint border_size,
228 const uint title_size,
229 const pangolin::Colour colour,
230 wl_compositor* compositor,
231 wl_subcompositor* subcompositor,
232 wl_surface* surface,
233 EGLDisplay egl_display,
234 EGLConfig config
235 ) :
236 border_size(border_size),
237 title_size(title_size),
238 colour(colour),
239 egl_display(egl_display),
240 compositor(compositor),
241 subcompositor(subcompositor),
242 surface(surface),
243 config(config)
244 { }
245
createpangolin::wayland::Decoration246 void create() {
247 // reserve memory to prevent that DecorationSurface's destructor gets
248 // called by 'emplace_back'
249 decorations.reserve(9);
250
251 // title bar, 2D movement
252 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_NONE, colour);
253
254 // sides, 1D resizing
255 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_LEFT, colour);
256 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_RIGHT, colour);
257 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_TOP, colour);
258 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_BOTTOM, colour);
259
260 // corners, 2D resizing
261 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_TOP_LEFT, colour);
262 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_TOP_RIGHT, colour);
263 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT, colour);
264 decorations.emplace_back(compositor, subcompositor, surface, egl_display, config, border_size, title_size, WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT, colour);
265
266 // buttons
267 buttons.reserve(2);
268 buttons.emplace_back(compositor, subcompositor, decorations[0].surface, egl_display, config, 5, 1, button_width, button_height, ButtonSurface::type::CLOSE, pangolin::Colour(0, 110/255.0, 182/255.0));
269 buttons.emplace_back(compositor, subcompositor, decorations[0].surface, egl_display, config, 10+button_width, 1, button_width, button_height, ButtonSurface::type::MAXIMISE, pangolin::Colour(1.0, 204/255.0f, 0));
270 }
271
destroypangolin::wayland::Decoration272 void destroy() {
273 decorations.clear();
274 buttons.clear();
275 }
276
resizepangolin::wayland::Decoration277 void resize(const int32_t width, const int32_t height) {
278 for(const DecorationSurface &d : decorations) { d.resize(width, height); }
279 for(const ButtonSurface &b : buttons) { b.reposition(width); }
280 }
281
drawpangolin::wayland::Decoration282 void draw() {
283 for(const DecorationSurface &d : decorations) { d.draw(); }
284 for(const ButtonSurface &b : buttons) { b.draw(); }
285 }
286
setTypeFromSurfacepangolin::wayland::Decoration287 void setTypeFromSurface(const wl_surface *surface) {
288 for(const DecorationSurface &d : decorations) {
289 if(d.surface==surface) {
290 last_type = d.function;
291 return;
292 }
293 }
294 for(const ButtonSurface &b : buttons) {
295 if(b.surface==surface) {
296 last_type = b.function;
297 return;
298 }
299 }
300 // surface is not part of the window decoration
301 last_type = -1;
302 }
303
getCursorForCurrentSurfacepangolin::wayland::Decoration304 const std::string getCursorForCurrentSurface() const {
305 return resize_cursor.count((enum wl_shell_surface_resize)last_type) ? resize_cursor.at((enum wl_shell_surface_resize)last_type) : "left_ptr";
306 }
307
308 std::vector<DecorationSurface> decorations;
309 int last_type;
310
311 std::vector<ButtonSurface> buttons;
312
313 const uint border_size;
314 const uint title_size;
315 const pangolin::Colour colour;
316 EGLDisplay egl_display;
317 wl_compositor* compositor;
318 wl_subcompositor* subcompositor;
319 wl_surface* surface;
320 EGLConfig config;
321
322 static const uint button_width;
323 static const uint button_height;
324 };
325
326 const uint Decoration::button_width = 25;
327 const uint Decoration::button_height = 15;
328
329 struct WaylandDisplay {
330 WaylandDisplay(const int width, const int height, const std::string title = "");
331
332 ~WaylandDisplay();
333
334 struct wl_display *wdisplay = nullptr;
335 struct wl_registry *wregistry = nullptr;
336 struct wl_compositor *wcompositor = nullptr;
337 struct wl_subcompositor *wsubcompositor = nullptr;
338 struct wl_surface *wsurface = nullptr;
339 struct wl_egl_window *egl_window = nullptr;
340 #if USE_WL_XDG
341 struct xdg_wm_base *xshell = nullptr;
342 struct xdg_surface *xshell_surface = nullptr;
343 struct xdg_toplevel *xshell_toplevel = nullptr;
344 #else
345 struct wl_shell *wshell = nullptr;
346 struct wl_shell_surface *wshell_surface = nullptr;
347 #endif
348
349 struct wl_seat *wseat = nullptr;
350 struct wl_keyboard *wkeyboard = nullptr;
351 struct wl_pointer *pointer = nullptr;
352
353 // for cursor
354 struct wl_shm *shm = nullptr;
355 struct wl_cursor_theme *cursor_theme = nullptr;
356 struct wl_surface *cursor_surface = nullptr;
357
358 // xkbcommon
359 struct xkb_context *xkb_context = nullptr;
360 struct xkb_keymap *keymap = nullptr;
361 struct xkb_state *xkb_state = nullptr;
362
363 std::unique_ptr<Decoration> decoration;
364
365 bool pressed = false;
366 int lastx=0;
367 int lasty=0;
368
369 std::vector<EGLConfig> egl_configs;
370 EGLSurface egl_surface = nullptr;
371 EGLContext egl_context = nullptr;
372 EGLDisplay egl_display = nullptr;
373
374 EGLint width, height;
375 bool is_fullscreen;
376 bool is_maximised;
377 static constexpr EGLint attribs[] = {
378 EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT,
379 EGL_RED_SIZE , 8,
380 EGL_GREEN_SIZE , 8,
381 EGL_BLUE_SIZE , 8,
382 EGL_ALPHA_SIZE , 8,
383 EGL_DEPTH_SIZE , 24,
384 EGL_STENCIL_SIZE , 8,
385 EGL_NONE
386 };
387 };
388
389 constexpr EGLint WaylandDisplay::attribs[];
390
391 struct WaylandWindow : public PangolinGl
392 {
393 WaylandWindow(const int width, const int height, std::unique_ptr<WaylandDisplay> display);
394
395 ~WaylandWindow() override;
396
397 void ToggleFullscreen() override;
398
399 void Move(const int x, const int y) override;
400
401 void Resize(const unsigned int w, const unsigned int h) override;
402
403 void MakeCurrent() override;
404
405 void RemoveCurrent() override;
406
407 void SwapBuffers() override;
408
409 void ProcessEvents() override;
410
411 std::unique_ptr<WaylandDisplay> display;
412 };
413
414 // map wayland ids to pangolin ids
415 static const std::map<uint,int> wl_button_ids = {
416 {BTN_LEFT, 0},
417 {BTN_MIDDLE, 1},
418 {BTN_RIGHT, 2},
419 };
420
421 static const std::map<uint,KeyModifier> wl_key_mod_ids = {
422 {KEY_LEFTSHIFT, KeyModifierShift},
423 {KEY_RIGHTSHIFT, KeyModifierShift},
424 {KEY_LEFTCTRL, KeyModifierCtrl},
425 {KEY_RIGHTCTRL, KeyModifierCtrl},
426 {KEY_LEFTALT, KeyModifierAlt},
427 {KEY_RIGHTALT, KeyModifierAlt},
428 };
429
430 static const std::map<uint,int> wl_key_special_ids = {
431 {KEY_F1, PANGO_KEY_F1},
432 {KEY_F2, PANGO_KEY_F2},
433 {KEY_F3, PANGO_KEY_F3},
434 {KEY_F4, PANGO_KEY_F4},
435 {KEY_F5, PANGO_KEY_F5},
436 {KEY_F6, PANGO_KEY_F6},
437 {KEY_F7, PANGO_KEY_F7},
438 {KEY_F8, PANGO_KEY_F8},
439 {KEY_F9, PANGO_KEY_F9},
440 {KEY_F10, PANGO_KEY_F10},
441 {KEY_F11, PANGO_KEY_F11},
442 {KEY_F12, PANGO_KEY_F12},
443
444 {KEY_LEFT, PANGO_KEY_LEFT},
445 {KEY_UP, PANGO_KEY_UP},
446 {KEY_RIGHT, PANGO_KEY_RIGHT},
447 {KEY_DOWN, PANGO_KEY_DOWN},
448
449 {KEY_PAGEUP, PANGO_KEY_PAGE_UP},
450 {KEY_PAGEDOWN, PANGO_KEY_PAGE_DOWN},
451 {KEY_HOME, PANGO_KEY_HOME},
452 {KEY_END, PANGO_KEY_END},
453 {KEY_INSERT, PANGO_KEY_INSERT},
454 };
455
456 #if USE_WL_XDG
handle_configure_toplevel(void * data,struct xdg_toplevel *,int32_t width,int32_t height,struct wl_array *)457 static void handle_configure_toplevel(void *data, struct xdg_toplevel */*xdg_toplevel*/, int32_t width, int32_t height, struct wl_array */*states*/) {
458 #else
459 static void handle_configure(void *data, struct wl_shell_surface */*shell_surface*/, uint32_t /*edges*/, int32_t width, int32_t height) {
460 #endif
461
462 const static uint min_width = 70;
463 const static uint min_height = 70;
464
465 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
466
467 const bool provided = !(width==0 && height==0);
468
469 int main_w, main_h;
470 if(provided) {
471 // use the provided dimensions
472 if(w->is_fullscreen) {
473 // without decorations
474 main_w = width;
475 main_h = height;
476 }
477 else {
478 // with decorations
479 main_w = std::max(width-int(2*w->decoration->border_size), int(min_width));
480 main_h = std::max(height-2*int(w->decoration->border_size)-int(w->decoration->title_size), int(min_height));
481 }
482 }
483 else {
484 // restore from saved dimensions
485 main_w = std::max((w->width)-int(2*w->decoration->border_size), int(min_width));
486 main_h = std::max((w->height)-2*int(w->decoration->border_size)-int(w->decoration->title_size), int(min_height));
487 }
488
489 // resize main surface
490 wl_egl_window_resize(w->egl_window, main_w, main_h, 0, 0);
491
492 // set opaque region
493 struct wl_region* wregion = wl_compositor_create_region(w->wcompositor);
494 wl_region_add(wregion, 0, 0, main_w, main_h);
495 wl_surface_set_opaque_region(w->wsurface, wregion);
496 wl_region_destroy(wregion);
497
498 // resize all decoration elements
499 w->decoration->resize(main_w, main_h);
500
501 // notify Panglin views about resized area
502 pangolin::process::Resize(main_w, main_h);
503 }
504
505 #if USE_WL_XDG
506 static void handle_configure(void */*data*/, struct xdg_surface *xdg_surface, uint32_t serial) {
507 xdg_surface_ack_configure(xdg_surface, serial);
508 }
509
510 static const struct xdg_surface_listener shell_surface_listener = {
511 handle_configure,
512 };
513
514 static void handle_toplevel_close(void */*data*/, struct xdg_toplevel */*xdg_toplevel*/) {
515 pangolin::QuitAll();
516 }
517
518 static const struct xdg_toplevel_listener toplevel_listener = {
519 .configure = handle_configure_toplevel,
520 .close = handle_toplevel_close,
521 };
522
523 static void xdg_wm_base_ping(void */*data*/, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {
524 xdg_wm_base_pong(xdg_wm_base, serial);
525 }
526
527 static const struct xdg_wm_base_listener shell_listener = {
528 .ping = xdg_wm_base_ping
529 };
530 #else
531 static void handle_ping(void */*data*/, struct wl_shell_surface *shell_surface, uint32_t serial) {
532 wl_shell_surface_pong(shell_surface, serial);
533 }
534
535 static void handle_popup_done(void */*data*/, struct wl_shell_surface */*shell_surface*/) { }
536
537 static const struct wl_shell_surface_listener shell_surface_listener = {
538 handle_ping,
539 handle_configure,
540 handle_popup_done
541 };
542 #endif
543
544 static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t /*sx*/, wl_fixed_t /*sy*/) {
545 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
546 w->decoration->setTypeFromSurface(surface);
547
548 const std::string cursor = w->decoration->getCursorForCurrentSurface();
549
550 const auto image = wl_cursor_theme_get_cursor(w->cursor_theme, cursor.c_str())->images[0];
551 wl_pointer_set_cursor(pointer, serial, w->cursor_surface, image->hotspot_x, image->hotspot_y);
552 wl_surface_attach(w->cursor_surface, wl_cursor_image_get_buffer(image), 0, 0);
553 wl_surface_damage(w->cursor_surface, 0, 0, image->width, image->height);
554 wl_surface_commit(w->cursor_surface);
555 }
556
557 static void pointer_handle_leave(void *data, struct wl_pointer */*pointer*/, uint32_t /*serial*/, struct wl_surface */*surface*/) {
558 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
559 w->pressed = false;
560 }
561
562 static void pointer_handle_motion(void *data, struct wl_pointer */*pointer*/, uint32_t /*time*/, wl_fixed_t sx, wl_fixed_t sy) {
563 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
564
565 w->lastx=wl_fixed_to_int(sx);
566 w->lasty=wl_fixed_to_int(sy);
567 if(w->pressed) {
568 pangolin::process::MouseMotion(w->lastx, w->lasty);
569 }
570 else {
571 pangolin::process::PassiveMouseMotion(w->lastx, w->lasty);
572 }
573 }
574
575 static void pointer_handle_button(void *data, struct wl_pointer */*wl_pointer*/, uint32_t serial, uint32_t /*time*/, uint32_t button, uint32_t state) {
576 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
577
578 if(w->decoration->last_type<0) {
579 // input goes to pangoling view
580 if(!wl_button_ids.count(button))
581 return;
582
583 w->pressed = (state==WL_POINTER_BUTTON_STATE_PRESSED);
584 pangolin::process::Mouse(wl_button_ids.at(button), (state==WL_POINTER_BUTTON_STATE_RELEASED), w->lastx, w->lasty);
585 }
586 else {
587 // input goes to window decoration
588 // resizing using window decoration
589 if((button==BTN_LEFT) && (state==WL_POINTER_BUTTON_STATE_PRESSED)) {
590 switch (w->decoration->last_type) {
591 case WL_SHELL_SURFACE_RESIZE_NONE:
592 #if USE_WL_XDG
593 xdg_toplevel_move(w->xshell_toplevel, w->wseat, serial);
594 #else
595 wl_shell_surface_move(w->wshell_surface, w->wseat, serial);
596 #endif
597 break;
598 case WL_SHELL_SURFACE_RESIZE_TOP:
599 case WL_SHELL_SURFACE_RESIZE_BOTTOM:
600 case WL_SHELL_SURFACE_RESIZE_LEFT:
601 case WL_SHELL_SURFACE_RESIZE_TOP_LEFT:
602 case WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT:
603 case WL_SHELL_SURFACE_RESIZE_RIGHT:
604 case WL_SHELL_SURFACE_RESIZE_TOP_RIGHT:
605 case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT:
606 #if USE_WL_XDG
607 xdg_toplevel_resize(w->xshell_toplevel, w->wseat, serial, w->decoration->last_type);
608 #else
609 wl_shell_surface_resize(w->wshell_surface, w->wseat, serial, w->decoration->last_type);
610 #endif
611 break;
612 case ButtonSurface::type::CLOSE:
613 pangolin::QuitAll();
614 break;
615 case ButtonSurface::type::MAXIMISE:
616 w->is_maximised = !w->is_maximised;
617 if(w->is_maximised) {
618 #if USE_WL_XDG
619 xdg_toplevel_set_maximized(w->xshell_toplevel);
620 #else
621 // store original window size
622 wl_egl_window_get_attached_size(w->egl_window, &w->width, &w->height);
623 wl_shell_surface_set_maximized(w->wshell_surface, nullptr);
624 #endif
625 }
626 else {
627 #if USE_WL_XDG
628 xdg_toplevel_unset_maximized(w->xshell_toplevel);
629 #else
630 wl_shell_surface_set_toplevel(w->wshell_surface);
631 handle_configure(data, nullptr, 0,
632 w->width+2*w->decoration->border_size,
633 w->height+2*w->decoration->border_size+w->decoration->title_size);
634 #endif
635 }
636
637 break;
638 }
639 }
640 }
641 }
642
643 static void pointer_handle_axis(void *data, struct wl_pointer */*wl_pointer*/, uint32_t /*time*/, uint32_t axis, wl_fixed_t value) {
644 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
645
646 int button_id = -1;
647 switch (axis) {
648 case REL_X: button_id = (value<0) ? 3 : 4; break; // up, down
649 case REL_Y: button_id = (value<0) ? 5 : 6; break; // left, right
650 }
651
652 if(button_id>0) {
653 pangolin::process::Mouse(button_id, 0, w->lastx, w->lasty);
654 }
655 }
656
657 #if WAYLAND_VERSION_GE(1,12)
658
659 static void pointer_handle_frame(void */*data*/, struct wl_pointer */*wl_pointer*/) { }
660
661 static void pointer_handle_axis_source(void */*data*/, struct wl_pointer */*wl_pointer*/, uint32_t /*axis_source*/) { }
662
663 static void pointer_handle_axis_stop(void */*data*/, struct wl_pointer */*wl_pointer*/, uint32_t /*time*/, uint32_t /*axis*/) { }
664
665 static void pointer_handle_axis_discrete(void */*data*/, struct wl_pointer */*wl_pointer*/, uint32_t /*axis*/, int32_t /*discrete*/) { }
666
667 #endif
668
669 static const struct wl_pointer_listener pointer_listener = {
670 pointer_handle_enter,
671 pointer_handle_leave,
672 pointer_handle_motion,
673 pointer_handle_button,
674 pointer_handle_axis,
675 #if WAYLAND_VERSION_GE(1,12)
676 pointer_handle_frame,
677 pointer_handle_axis_source,
678 pointer_handle_axis_stop,
679 pointer_handle_axis_discrete,
680 #endif
681 };
682
683 static void keyboard_handle_keymap(void *data, struct wl_keyboard */*keyboard*/, uint32_t /*format*/, int fd, uint32_t size) {
684 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
685
686 char *keymap_string = static_cast<char*>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0));
687 xkb_keymap_unref(w->keymap);
688 w->keymap = xkb_keymap_new_from_string(w->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
689 munmap(keymap_string, size);
690 close(fd);
691 xkb_state_unref(w->xkb_state);
692 w->xkb_state = xkb_state_new(w->keymap);
693 }
694
695 static void keyboard_handle_enter(void */*data*/, struct wl_keyboard */*keyboard*/, uint32_t /*serial*/, struct wl_surface */*surface*/, struct wl_array */*keys*/) { }
696
697 static void keyboard_handle_leave(void */*data*/, struct wl_keyboard */*keyboard*/, uint32_t /*serial*/, struct wl_surface */*surface*/) { }
698
699 static void keyboard_handle_key(void *data, struct wl_keyboard */*keyboard*/, uint32_t /*serial*/, uint32_t /*time*/, uint32_t key, uint32_t state) {
700 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
701
702 // modifier keys
703 if(wl_key_mod_ids.count(key)) {
704 if(state==WL_KEYBOARD_KEY_STATE_PRESSED) {
705 pangolin::context->mouse_state |= wl_key_mod_ids.at(key);
706 }
707 else if (state==WL_KEYBOARD_KEY_STATE_RELEASED) {
708 pangolin::context->mouse_state &= ~wl_key_mod_ids.at(key);
709 }
710 return;
711 }
712
713 // character and special keys
714 int pango_key = -1;
715 if(wl_key_special_ids.count(key)) {
716 // special keys
717 pango_key = PANGO_SPECIAL + wl_key_special_ids.at(key);
718 }
719 else {
720 // character keys
721 const uint32_t utf32 = xkb_state_key_get_utf32(w->xkb_state, key+8);
722 // filter non-ASCII
723 if(utf32>0 && utf32<=127) {
724 pango_key = int(utf32);
725 }
726 }
727
728 if(pango_key>0) {
729 if(state==WL_KEYBOARD_KEY_STATE_PRESSED) {
730 pangolin::process::Keyboard(uint8_t(pango_key), w->lastx, w->lasty);
731 }else if(state==WL_KEYBOARD_KEY_STATE_RELEASED){
732 pangolin::process::KeyboardUp(uint8_t(pango_key), w->lastx, w->lasty);
733 }
734 }
735 }
736
737 static void keyboard_handle_modifiers(void *data, struct wl_keyboard */*keyboard*/, uint32_t /*serial*/, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
738 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
739
740 xkb_state_update_mask(w->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
741 }
742
743 #if WAYLAND_VERSION_GE(1,12)
744
745 static void keyboard_handle_repeat_info(void */*data*/, struct wl_keyboard */*wl_keyboard*/, int32_t /*rate*/, int32_t /*delay*/) { }
746
747 #endif
748
749 static const struct wl_keyboard_listener keyboard_listener = {
750 keyboard_handle_keymap,
751 keyboard_handle_enter,
752 keyboard_handle_leave,
753 keyboard_handle_key,
754 keyboard_handle_modifiers,
755 #if WAYLAND_VERSION_GE(1,12)
756 keyboard_handle_repeat_info,
757 #endif
758 };
759
760 static void seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps1) {
761 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
762
763 enum wl_seat_capability caps;
764 caps = (enum wl_seat_capability)caps1;
765 if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
766 w->wkeyboard = wl_seat_get_keyboard(seat);
767 wl_keyboard_add_listener(w->wkeyboard, &keyboard_listener, data);
768 } else {
769 wl_keyboard_destroy(w->wkeyboard);
770 w->wkeyboard = nullptr;
771 }
772 if (caps & WL_SEAT_CAPABILITY_POINTER) {
773 w->pointer = wl_seat_get_pointer(seat);
774 w->cursor_surface = wl_compositor_create_surface(w->wcompositor);
775 wl_pointer_add_listener(w->pointer, &pointer_listener, data);
776 } else {
777 wl_pointer_destroy(w->pointer);
778 w->pointer = nullptr;
779 }
780 }
781
782 static void seat_handle_name(void */*data*/, struct wl_seat */*wl_seat*/, const char */*name*/) { }
783
784 static const struct wl_seat_listener seat_listener = {
785 seat_handle_capabilities,
786 seat_handle_name,
787 };
788
789 static void global_registry_handler(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) {
790 WaylandDisplay* const w = static_cast<WaylandDisplay*>(data);
791
792 if (strcmp(interface, wl_compositor_interface.name) == 0) {
793 w->wcompositor = reinterpret_cast<wl_compositor*> (wl_registry_bind(registry, id, &wl_compositor_interface, version));
794 }
795 else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
796 w->wsubcompositor = static_cast<wl_subcompositor*>(wl_registry_bind(registry, id, &wl_subcompositor_interface, version));
797 }
798 #if USE_WL_XDG
799 else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
800 w->xshell = reinterpret_cast<xdg_wm_base*> (wl_registry_bind(registry, id, &xdg_wm_base_interface, version));
801 }
802 #else
803 else if (strcmp(interface, wl_shell_interface.name) == 0) {
804 w->wshell = reinterpret_cast<wl_shell*> (wl_registry_bind(registry, id, &wl_shell_interface, version));
805 }
806 #endif
807 else if (strcmp(interface, wl_seat_interface.name) == 0) {
808 w->wseat = reinterpret_cast<wl_seat*>(wl_registry_bind(registry, id, &wl_seat_interface, version));
809 wl_seat_add_listener(w->wseat, &seat_listener, data);
810 }
811 else if (strcmp(interface, wl_shm_interface.name) == 0) {
812 w->shm = static_cast<wl_shm*>(wl_registry_bind(registry, id, &wl_shm_interface, version));
813 w->cursor_theme = wl_cursor_theme_load(nullptr, 16, w->shm);
814 }
815 }
816
817 static void global_registry_remover(void */*data*/, struct wl_registry */*registry*/, uint32_t /*id*/) { }
818
819 static const struct wl_registry_listener wregistry_listener = {
820 global_registry_handler,
821 global_registry_remover
822 };
823
824 WaylandDisplay::WaylandDisplay(const int width, const int height, const std::string title) : width(width), height(height) {
825 xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
826
827 wdisplay = wl_display_connect(nullptr);
828 if (wdisplay == nullptr) {
829 throw std::runtime_error("Cannot connect to Wayland compositor!");
830 }
831
832 wregistry = wl_display_get_registry(wdisplay);
833 wl_registry_add_listener(wregistry, &wregistry_listener, this);
834
835 wl_display_roundtrip(wdisplay);
836
837 egl_display = eglGetDisplay((EGLNativeDisplayType)wdisplay);
838 if(!egl_display) {
839 std::cerr << "Failed to open EGL display" << std::endl;
840 }
841
842 EGLint major, minor;
843 if(eglInitialize(egl_display, &major, &minor)==EGL_FALSE) {
844 std::cerr << "EGL init failed" << std::endl;
845 }
846
847 if(eglBindAPI(EGL_OPENGL_API)==EGL_FALSE) {
848 std::cerr << "EGL bind failed" << std::endl;
849 }
850
851 EGLint count;
852 eglGetConfigs(egl_display, nullptr, 0, &count);
853
854 egl_configs.resize(count);
855
856 EGLint numConfigs;
857 eglChooseConfig(egl_display, attribs, egl_configs.data(), count, &numConfigs);
858
859 egl_context = eglCreateContext(egl_display, egl_configs[0], EGL_NO_CONTEXT, nullptr);
860
861 wsurface = wl_compositor_create_surface(wcompositor);
862
863 egl_window = wl_egl_window_create(wsurface, width, height);
864 if(!egl_window) {
865 std::cerr << "Cannot create EGL window" << std::endl;
866 }
867
868 egl_surface = eglCreateWindowSurface(egl_display, egl_configs[0], (EGLNativeWindowType)egl_window, nullptr);
869 if (egl_surface == EGL_NO_SURFACE) {
870 std::cerr << "Cannot create EGL surface" << std::endl;
871 }
872
873 if(
874 #if USE_WL_XDG
875 xshell
876 #else
877 wshell
878 #endif
879 ==nullptr) {
880 throw std::runtime_error("No Wayland shell available!");
881 }
882
883 #if USE_WL_XDG
884 xdg_wm_base_add_listener(xshell, &shell_listener, this);
885 xshell_surface = xdg_wm_base_get_xdg_surface(xshell, wsurface);
886 xdg_surface_add_listener(xshell_surface, &shell_surface_listener, this);
887 xshell_toplevel = xdg_surface_get_toplevel(xshell_surface);
888 xdg_toplevel_add_listener(xshell_toplevel, &toplevel_listener, this);
889 xdg_toplevel_set_title(xshell_toplevel, title.c_str());
890 xdg_toplevel_set_app_id(xshell_toplevel, title.c_str());
891 #else
892 wshell_surface = wl_shell_get_shell_surface(wshell, wsurface);
893 wl_shell_surface_add_listener(wshell_surface, &shell_surface_listener, this);
894 wl_shell_surface_set_toplevel(wshell_surface);
895 wl_shell_surface_set_title(wshell_surface, title.c_str());
896 wl_shell_surface_set_class(wshell_surface, title.c_str());
897 #endif
898
899 wl_display_sync(wdisplay);
900
901 // construct window decoration
902 const pangolin::Colour grey(0.5f, 0.5f, 0.5f, 0.5f);
903 decoration = std::unique_ptr<Decoration>(new Decoration(5, 20, grey, wcompositor, wsubcompositor, wsurface, egl_display, egl_configs[0]));
904 decoration->create();
905 decoration->resize(width, height);
906 }
907
908 WaylandDisplay::~WaylandDisplay() {
909 if(decoration) decoration->destroy();
910
911 // cleanup EGL
912 if(egl_context) eglDestroyContext(egl_display, egl_context);
913 if(egl_surface) eglDestroySurface(egl_display, egl_surface);
914 if(egl_window) wl_egl_window_destroy(egl_window);
915 if(egl_display) eglTerminate(egl_display);
916
917 // cleanup Wayland
918 #if USE_WL_XDG
919 if(xshell_surface) xdg_surface_destroy(xshell_surface);
920 if(xshell_toplevel) xdg_toplevel_destroy(xshell_toplevel);
921 if(xshell) xdg_wm_base_destroy(xshell);
922 #else
923 if(wshell_surface) wl_shell_surface_destroy(wshell_surface);
924 if(wshell) wl_shell_destroy(wshell);
925 #endif
926
927 if(wsurface) wl_surface_destroy(wsurface);
928 if(wregistry) wl_registry_destroy(wregistry);
929 if(wdisplay) wl_display_disconnect(wdisplay);
930
931 if(xkb_context) xkb_context_unref(xkb_context);
932 }
933
934 WaylandWindow::WaylandWindow(const int w, const int h, std::unique_ptr<WaylandDisplay> display) : display(std::move(display)){
935 windowed_size[0] = w;
936 windowed_size[1] = h;
937 }
938
939 WaylandWindow::~WaylandWindow() { }
940
941 void WaylandWindow::MakeCurrent() {
942 eglMakeCurrent(display->egl_display, display->egl_surface, display->egl_surface, display->egl_context);
943 context = this;
944 }
945
946 void WaylandWindow::RemoveCurrent() {
947 eglMakeCurrent(display->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
948 }
949
950 void WaylandWindow::ToggleFullscreen() {
951 is_fullscreen = !is_fullscreen; // state for Pangolin
952 display->is_fullscreen = is_fullscreen; // state for Wayland
953 if(is_fullscreen) {
954 display->decoration->destroy();
955 #if USE_WL_XDG
956 xdg_toplevel_set_fullscreen(display->xshell_toplevel, nullptr);
957 #else
958 wl_shell_surface_set_fullscreen(display->wshell_surface, WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, nullptr);
959 #endif
960 }
961 else {
962 display->decoration->create();
963 #if USE_WL_XDG
964 xdg_toplevel_unset_fullscreen(display->xshell_toplevel);
965 #else
966 wl_shell_surface_set_toplevel(display->wshell_surface);
967 #endif
968
969 #if !USE_WL_XDG
970 if(display->is_maximised) {
971 wl_shell_surface_set_maximized(display->wshell_surface, nullptr);
972 }
973 else {
974 handle_configure(static_cast<void*>(display.get()), nullptr, 0,
975 windowed_size[0]+2*display->decoration->border_size,
976 windowed_size[1]+2*display->decoration->border_size+display->decoration->title_size);
977 }
978 #endif
979 }
980
981 wl_display_sync(display->wdisplay);
982 }
983
984 void WaylandWindow::Move(const int /*x*/, const int /*y*/) { }
985
986 void WaylandWindow::Resize(const unsigned int /*w*/, const unsigned int /*h*/) { }
987
988 void WaylandWindow::ProcessEvents() { }
989
990 void WaylandWindow::SwapBuffers() {
991 eglSwapBuffers(display->egl_display, display->egl_surface);
992
993 // draw all decoration elements
994 display->decoration->draw();
995
996 MakeCurrent();
997
998 wl_display_roundtrip(display->wdisplay);
999 }
1000
1001
1002 std::unique_ptr<WindowInterface> CreateWaylandWindowAndBind(const std::string window_title, const int w, const int h, const std::string /*display_name*/, const bool /*double_buffered*/, const int /*sample_buffers*/, const int /*samples*/) {
1003
1004 try{
1005 std::unique_ptr<WaylandDisplay> newdisplay = std::unique_ptr<WaylandDisplay>(new WaylandDisplay(w, h, window_title));
1006
1007 // glewInit() fails with SIGSEGV for glew < 2.0 since it links to GLX
1008 if(atoi((char*)glewGetString(GLEW_VERSION_MAJOR))<2)
1009 return nullptr;
1010
1011 WaylandWindow* win = new WaylandWindow(w, h, std::move(newdisplay));
1012
1013 return std::unique_ptr<WindowInterface>(win);
1014 }
1015 catch(const std::runtime_error&) {
1016 // return null pointer for fallback to X11
1017 return nullptr;
1018 }
1019 }
1020
1021 } // namespace wayland
1022
PANGOLIN_REGISTER_FACTORY(WaylandWindow)1023 PANGOLIN_REGISTER_FACTORY(WaylandWindow)
1024 {
1025 struct WaylandWindowFactory : public FactoryInterface<WindowInterface> {
1026 std::unique_ptr<WindowInterface> Open(const Uri& uri) override {
1027
1028 const std::string window_title = uri.Get<std::string>("window_title", "window");
1029 const int w = uri.Get<int>("w", 640);
1030 const int h = uri.Get<int>("h", 480);
1031 const std::string display_name = uri.Get<std::string>("display_name", "");
1032 const bool double_buffered = uri.Get<bool>("double_buffered", true);
1033 const int sample_buffers = uri.Get<int>("sample_buffers", 1);
1034 const int samples = uri.Get<int>("samples", 1);
1035 return std::unique_ptr<WindowInterface>(wayland::CreateWaylandWindowAndBind(window_title, w, h, display_name, double_buffered, sample_buffers, samples));
1036 }
1037
1038 virtual ~WaylandWindowFactory() { }
1039 };
1040
1041 auto factory = std::make_shared<WaylandWindowFactory>();
1042 FactoryRegistry<WindowInterface>::I().RegisterFactory(factory, 10, "wayland");
1043 FactoryRegistry<WindowInterface>::I().RegisterFactory(factory, 9, "linux");
1044 FactoryRegistry<WindowInterface>::I().RegisterFactory(factory, 90, "default");
1045 }
1046
1047 } // namespace pangolin
1048
1049