1 /// Translated from C to D
2 module glfw3.wl_window;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 //========================================================================
6 // GLFW 3.3 Wayland - www.glfw.org
7 //------------------------------------------------------------------------
8 // Copyright (c) 2014 Jonas Ådahl <jadahl@gmail.com>
9 //
10 // This software is provided 'as-is', without any express or implied
11 // warranty. In no event will the authors be held liable for any damages
12 // arising from the use of this software.
13 //
14 // Permission is granted to anyone to use this software for any purpose,
15 // including commercial applications, and to alter it and redistribute it
16 // freely, subject to the following restrictions:
17 //
18 // 1. The origin of this software must not be misrepresented; you must not
19 //    claim that you wrote the original software. If you use this software
20 //    in a product, an acknowledgment in the product documentation would
21 //    be appreciated but is not required.
22 //
23 // 2. Altered source versions must be plainly marked as such, and must not
24 //    be misrepresented as being the original software.
25 //
26 // 3. This notice may not be removed or altered from any source
27 //    distribution.
28 //
29 //========================================================================
30 // It is fine to use C99 in this file because it will not be built with VS
31 //========================================================================
32 
33 public import glfw3.internal;
34 
35 public import core.stdc.stdio;
36 public import core.stdc.stdlib;
37 public import core.stdc.errno;
38 public import core.sys.posix.unistd;
39 public import core.stdc.string;
40 public import core.sys.posix.fcntl;
41 public import core.sys.posix.sys.mman;
42 public import core.sys.linux.timerfd;
43 public import core.sys.posix.poll;
44 
45 static void shellSurfaceHandlePing(void* data, wl_shell_surface* shellSurface, uint serial) {
46     wl_shell_surface_pong(shellSurface, serial);
47 }
48 
49 static void shellSurfaceHandleConfigure(void* data, wl_shell_surface* shellSurface, uint edges, int width, int height) {
50     auto window = cast(_GLFWwindow*) data;
51     float aspectRatio;
52     float targetRatio;
53 
54     if (!window.monitor)
55     {
56         if (_glfw.wl.viewporter && window.decorated)
57         {
58             width -= _GLFW_DECORATION_HORIZONTAL;
59             height -= _GLFW_DECORATION_VERTICAL;
60         }
61         if (width < 1)
62             width = 1;
63         if (height < 1)
64             height = 1;
65 
66         if (window.numer != GLFW_DONT_CARE && window.denom != GLFW_DONT_CARE)
67         {
68             aspectRatio = cast(float)width / cast(float)height;
69             targetRatio = cast(float)window.numer / cast(float)window.denom;
70             if (aspectRatio < targetRatio)
71                 height = cast(int) (width / targetRatio);
72             else if (aspectRatio > targetRatio)
73                 width = cast(int) (height * targetRatio);
74         }
75 
76         if (window.minwidth != GLFW_DONT_CARE && width < window.minwidth)
77             width = window.minwidth;
78         else if (window.maxwidth != GLFW_DONT_CARE && width > window.maxwidth)
79             width = window.maxwidth;
80 
81         if (window.minheight != GLFW_DONT_CARE && height < window.minheight)
82             height = window.minheight;
83         else if (window.maxheight != GLFW_DONT_CARE && height > window.maxheight)
84             height = window.maxheight;
85     }
86 
87     _glfwInputWindowSize(window, width, height);
88     _glfwPlatformSetWindowSize(window, width, height);
89     _glfwInputWindowDamage(window);
90 }
91 
92 static void shellSurfaceHandlePopupDone(void* data, wl_shell_surface* shellSurface) {
93 }
94 
95 static const(wl_shell_surface_listener) shellSurfaceListener = wl_shell_surface_listener(
96     &shellSurfaceHandlePing,
97     &shellSurfaceHandleConfigure,
98     &shellSurfaceHandlePopupDone
99 );
100 
101 static int createTmpfileCloexec(char* tmpname) {
102     int fd;
103 
104     fd = mkostemp(tmpname, O_CLOEXEC);
105     if (fd >= 0)
106         unlink(tmpname);
107 
108     return fd;
109 }
110 
111 /*
112  * Create a new, unique, anonymous file of the given size, and
113  * return the file descriptor for it. The file descriptor is set
114  * CLOEXEC. The file is immediately suitable for mmap()'ing
115  * the given size at offset zero.
116  *
117  * The file should not have a permanent backing store like a disk,
118  * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
119  *
120  * The file name is deleted from the file system.
121  *
122  * The file is suitable for buffer sharing between processes by
123  * transmitting the file descriptor over Unix sockets using the
124  * SCM_RIGHTS methods.
125  *
126  * posix_fallocate() is used to guarantee that disk space is available
127  * for the file at the given size. If disk space is insufficient, errno
128  * is set to ENOSPC. If posix_fallocate() is not supported, program may
129  * receive SIGBUS on accessing mmap()'ed file contents instead.
130  */
131 static int createAnonymousFile(off_t size) {
132     static const(char)* template_ = "/glfw-shared-XXXXXX";
133     const(char)* path;
134     char* name;
135     int fd;
136     int ret;
137 
138     auto inner() {
139         path = getenv("XDG_RUNTIME_DIR");
140         if (!path)
141         {
142             errno = ENOENT;
143             return -1;
144         }
145 
146         name = cast(char*) calloc(strlen(path) + template_.sizeof, 1);
147         strcpy(name, path);
148         strcat(name, template_);
149 
150         fd = createTmpfileCloexec(name);
151         free(name);
152         if (fd < 0)
153             return -1;
154 
155         return 0;
156     }
157 
158 version (HAVE_MEMFD_CREATE) {
159     fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
160     if (fd >= 0)
161     {
162         // We can add this seal before calling posix_fallocate(), as the file
163         // is currently zero-sized anyway.
164         //
165         // There is also no need to check for the return value, we couldn’t do
166         // anything with it anyway.
167         fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
168     }
169     else
170     {
171         if (inner() < 0) {
172             return -1;
173         }
174     }
175 } else version (SHM_ANON) {
176     import std.conv: octal;
177     fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, octal!600);
178     if (fd < 0) {
179         if (inner() < 0) {
180             return -1;
181         }
182     }
183 }
184 
185 version (SHM_ANON) {
186     // posix_fallocate does not work on SHM descriptors
187     ret = ftruncate(fd, size);
188 } else {
189     ret = posix_fallocate(fd, 0, size);
190 }
191     if (ret != 0)
192     {
193         close(fd);
194         errno = ret;
195         return -1;
196     }
197     return fd;
198 }
199 
200 static wl_buffer* createShmBuffer(const(GLFWimage)* image) {
201     wl_shm_pool* pool;
202     wl_buffer* buffer;
203     int stride = image.width * 4;
204     int length = image.width * image.height * 4;
205     void* data;
206     int fd;int i;
207 
208     fd = createAnonymousFile(length);
209     if (fd < 0)
210     {
211         _glfwInputError(GLFW_PLATFORM_ERROR,
212                         "Wayland: Creating a buffer file for %d B failed: %m",
213                         length);
214         return null;
215     }
216 
217     data = mmap(null, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
218     if (data == MAP_FAILED)
219     {
220         _glfwInputError(GLFW_PLATFORM_ERROR,
221                         "Wayland: mmap failed: %m");
222         close(fd);
223         return null;
224     }
225 
226     pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
227 
228     close(fd);
229     ubyte* source = cast(ubyte*) image.pixels;
230     ubyte* target = data;
231     for (i = 0;  i < image.width * image.height;  i++, source += 4)
232     {
233         uint alpha = source[3];
234 
235         *target++ = cast(ubyte) ((source[2] * alpha) / 255);
236         *target++ = cast(ubyte) ((source[1] * alpha) / 255);
237         *target++ = cast(ubyte) ((source[0] * alpha) / 255);
238         *target++ = cast(ubyte) alpha;
239     }
240 
241     buffer =
242         wl_shm_pool_create_buffer(pool, 0,
243                                   image.width,
244                                   image.height,
245                                   stride, WL_SHM_FORMAT_ARGB8888);
246     munmap(data, length);
247     wl_shm_pool_destroy(pool);
248 
249     return buffer;
250 }
251 
252 static void createDecoration(_GLFWdecorationWayland* decoration, wl_surface* parent, wl_buffer* buffer, GLFWbool opaque, int x, int y, int width, int height) {
253     wl_region* region;
254 
255     decoration.surface = wl_compositor_create_surface(_glfw.wl.compositor);
256     decoration.subsurface =
257         wl_subcompositor_get_subsurface(_glfw.wl.subcompositor,
258                                         decoration.surface, parent);
259     wl_subsurface_set_position(decoration.subsurface, x, y);
260     decoration.viewport = wp_viewporter_get_viewport(_glfw.wl.viewporter,
261                                                       decoration.surface);
262     wp_viewport_set_destination(decoration.viewport, width, height);
263     wl_surface_attach(decoration.surface, buffer, 0, 0);
264 
265     if (opaque)
266     {
267         region = wl_compositor_create_region(_glfw.wl.compositor);
268         wl_region_add(region, 0, 0, width, height);
269         wl_surface_set_opaque_region(decoration.surface, region);
270         wl_surface_commit(decoration.surface);
271         wl_region_destroy(region);
272     }
273     else
274         wl_surface_commit(decoration.surface);
275 }
276 
277 static void createDecorations(_GLFWwindow* window) {
278     ubyte[4] data = [ 224, 224, 224, 255 ];
279     const(GLFWimage) image = GLFWimage( 1, 1, data.ptr );
280     GLFWbool opaque = (data[3] == 255);
281 
282     if (!_glfw.wl.viewporter || !window.decorated || window.wl.decorations.serverSide)
283         return;
284 
285     if (!window.wl.decorations.buffer)
286         window.wl.decorations.buffer = createShmBuffer(&image);
287     if (!window.wl.decorations.buffer)
288         return;
289 
290     createDecoration(&window.wl.decorations.top, window.wl.surface,
291                      window.wl.decorations.buffer, opaque,
292                      0, -_GLFW_DECORATION_TOP,
293                      window.wl.width, _GLFW_DECORATION_TOP);
294     createDecoration(&window.wl.decorations.left, window.wl.surface,
295                      window.wl.decorations.buffer, opaque,
296                      -_GLFW_DECORATION_WIDTH, -_GLFW_DECORATION_TOP,
297                      _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
298     createDecoration(&window.wl.decorations.right, window.wl.surface,
299                      window.wl.decorations.buffer, opaque,
300                      window.wl.width, -_GLFW_DECORATION_TOP,
301                      _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
302     createDecoration(&window.wl.decorations.bottom, window.wl.surface,
303                      window.wl.decorations.buffer, opaque,
304                      -_GLFW_DECORATION_WIDTH, window.wl.height,
305                      window.wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
306 }
307 
308 static void destroyDecoration(_GLFWdecorationWayland* decoration) {
309     if (decoration.surface)
310         wl_surface_destroy(decoration.surface);
311     if (decoration.subsurface)
312         wl_subsurface_destroy(decoration.subsurface);
313     if (decoration.viewport)
314         wp_viewport_destroy(decoration.viewport);
315     decoration.surface = null;
316     decoration.subsurface = null;
317     decoration.viewport = null;
318 }
319 
320 static void destroyDecorations(_GLFWwindow* window) {
321     destroyDecoration(&window.wl.decorations.top);
322     destroyDecoration(&window.wl.decorations.left);
323     destroyDecoration(&window.wl.decorations.right);
324     destroyDecoration(&window.wl.decorations.bottom);
325 }
326 
327 static void xdgDecorationHandleConfigure(void* data, zxdg_toplevel_decoration_v1* decoration, uint mode) {
328     _GLFWwindow* window = data;
329 
330     window.wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
331 
332     if (!window.wl.decorations.serverSide)
333         createDecorations(window);
334 }
335 
336 static const(zxdg_toplevel_decoration_v1_listener) xdgDecorationListener = zxdg_toplevel_decoration_v1_listener(
337     &xdgDecorationHandleConfigure,
338 );
339 
340 // Makes the surface considered as XRGB instead of ARGB.
341 static void setOpaqueRegion(_GLFWwindow* window) {
342     wl_region* region;
343 
344     region = wl_compositor_create_region(_glfw.wl.compositor);
345     if (!region)
346         return;
347 
348     wl_region_add(region, 0, 0, window.wl.width, window.wl.height);
349     wl_surface_set_opaque_region(window.wl.surface, region);
350     wl_surface_commit(window.wl.surface);
351     wl_region_destroy(region);
352 }
353 
354 
355 static void resizeWindow(_GLFWwindow* window) {
356     int scale = window.wl.scale;
357     int scaledWidth = window.wl.width * scale;
358     int scaledHeight = window.wl.height * scale;
359     wl_egl_window_resize(window.wl.native, scaledWidth, scaledHeight, 0, 0);
360     if (!window.wl.transparent)
361         setOpaqueRegion(window);
362     _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
363     _glfwInputWindowContentScale(window, scale, scale);
364 
365     if (!window.wl.decorations.top.surface)
366         return;
367 
368     // Top decoration.
369     wp_viewport_set_destination(window.wl.decorations.top.viewport,
370                                 window.wl.width, _GLFW_DECORATION_TOP);
371     wl_surface_commit(window.wl.decorations.top.surface);
372 
373     // Left decoration.
374     wp_viewport_set_destination(window.wl.decorations.left.viewport,
375                                 _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
376     wl_surface_commit(window.wl.decorations.left.surface);
377 
378     // Right decoration.
379     wl_subsurface_set_position(window.wl.decorations.right.subsurface,
380                                window.wl.width, -_GLFW_DECORATION_TOP);
381     wp_viewport_set_destination(window.wl.decorations.right.viewport,
382                                 _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
383     wl_surface_commit(window.wl.decorations.right.surface);
384 
385     // Bottom decoration.
386     wl_subsurface_set_position(window.wl.decorations.bottom.subsurface,
387                                -_GLFW_DECORATION_WIDTH, window.wl.height);
388     wp_viewport_set_destination(window.wl.decorations.bottom.viewport,
389                                 window.wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
390     wl_surface_commit(window.wl.decorations.bottom.surface);
391 }
392 
393 static void checkScaleChange(_GLFWwindow* window) {
394     int scale = 1;
395     int i;
396     int monitorScale;
397 
398     // Check if we will be able to set the buffer scale or not.
399     if (_glfw.wl.compositorVersion < 3)
400         return;
401 
402     // Get the scale factor from the highest scale monitor.
403     for (i = 0; i < window.wl.monitorsCount; ++i)
404     {
405         monitorScale = window.wl.monitors[i].wl.scale;
406         if (scale < monitorScale)
407             scale = monitorScale;
408     }
409 
410     // Only change the framebuffer size if the scale changed.
411     if (scale != window.wl.scale)
412     {
413         window.wl.scale = scale;
414         wl_surface_set_buffer_scale(window.wl.surface, scale);
415         resizeWindow(window);
416     }
417 }
418 
419 static void surfaceHandleEnter(void* data, wl_surface* surface, wl_output* output) {
420     _GLFWwindow* window = data;
421     _GLFWmonitor* monitor = wl_output_get_user_data(output);
422 
423     if (window.wl.monitorsCount + 1 > window.wl.monitorsSize)
424     {
425         ++window.wl.monitorsSize;
426         window.wl.monitors =
427             realloc(window.wl.monitors,
428                     window.wl.monitorsSize * (_GLFWmonitor*).sizeof);
429     }
430 
431     window.wl.monitors[window.wl.monitorsCount++] = monitor;
432 
433     checkScaleChange(window);
434 }
435 
436 static void surfaceHandleLeave(void* data, wl_surface* surface, wl_output* output) {
437     _GLFWwindow* window = data;
438     _GLFWmonitor* monitor = wl_output_get_user_data(output);
439     GLFWbool found;
440     int i;
441 
442     for (i = 0, found = GLFW_FALSE; i < window.wl.monitorsCount - 1; ++i)
443     {
444         if (monitor == window.wl.monitors[i])
445             found = GLFW_TRUE;
446         if (found)
447             window.wl.monitors[i] = window.wl.monitors[i + 1];
448     }
449     window.wl.monitors[--window.wl.monitorsCount] = null;
450 
451     checkScaleChange(window);
452 }
453 
454 static const(wl_surface_listener) surfaceListener = wl_surface_listener(
455     &surfaceHandleEnter,
456     &surfaceHandleLeave
457 );
458 
459 static void setIdleInhibitor(_GLFWwindow* window, GLFWbool enable) {
460     if (enable && !window.wl.idleInhibitor && _glfw.wl.idleInhibitManager)
461     {
462         window.wl.idleInhibitor =
463             zwp_idle_inhibit_manager_v1_create_inhibitor(
464                 _glfw.wl.idleInhibitManager, window.wl.surface);
465         if (!window.wl.idleInhibitor)
466             _glfwInputError(GLFW_PLATFORM_ERROR,
467                             "Wayland: Idle inhibitor creation failed");
468     }
469     else if (!enable && window.wl.idleInhibitor)
470     {
471         zwp_idle_inhibitor_v1_destroy(window.wl.idleInhibitor);
472         window.wl.idleInhibitor = null;
473     }
474 }
475 
476 static GLFWbool createSurface(_GLFWwindow* window, const(_GLFWwndconfig)* wndconfig) {
477     window.wl.surface = wl_compositor_create_surface(_glfw.wl.compositor);
478     if (!window.wl.surface)
479         return GLFW_FALSE;
480 
481     wl_surface_add_listener(window.wl.surface,
482                             &surfaceListener,
483                             window);
484 
485     wl_surface_set_user_data(window.wl.surface, window);
486 
487     window.wl.native = wl_egl_window_create(window.wl.surface,
488                                              wndconfig.width,
489                                              wndconfig.height);
490     if (!window.wl.native)
491         return GLFW_FALSE;
492 
493     window.wl.width = wndconfig.width;
494     window.wl.height = wndconfig.height;
495     window.wl.scale = 1;
496 
497     if (!window.wl.transparent)
498         setOpaqueRegion(window);
499 
500     return GLFW_TRUE;
501 }
502 
503 static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refreshRate) {
504     if (window.wl.xdg.toplevel)
505     {
506         xdg_toplevel_set_fullscreen(
507             window.wl.xdg.toplevel,
508             monitor.wl.output);
509     }
510     else if (window.wl.shellSurface)
511     {
512         wl_shell_surface_set_fullscreen(
513             window.wl.shellSurface,
514             WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
515             refreshRate * 1000, // Convert Hz to mHz.
516             monitor.wl.output);
517     }
518     setIdleInhibitor(window, GLFW_TRUE);
519     if (!window.wl.decorations.serverSide)
520         destroyDecorations(window);
521 }
522 
523 static GLFWbool createShellSurface(_GLFWwindow* window) {
524     if (!_glfw.wl.shell)
525     {
526         _glfwInputError(GLFW_PLATFORM_ERROR,
527                         "Wayland: wl_shell protocol not available");
528         return GLFW_FALSE;
529     }
530 
531     window.wl.shellSurface = wl_shell_get_shell_surface(_glfw.wl.shell,
532                                                          window.wl.surface);
533     if (!window.wl.shellSurface)
534     {
535         _glfwInputError(GLFW_PLATFORM_ERROR,
536                         "Wayland: Shell surface creation failed");
537         return GLFW_FALSE;
538     }
539 
540     wl_shell_surface_add_listener(window.wl.shellSurface,
541                                   &shellSurfaceListener,
542                                   window);
543 
544     if (window.wl.title)
545         wl_shell_surface_set_title(window.wl.shellSurface, window.wl.title);
546 
547     if (window.monitor)
548     {
549         setFullscreen(window, window.monitor, 0);
550     }
551     else if (window.wl.maximized)
552     {
553         wl_shell_surface_set_maximized(window.wl.shellSurface, null);
554         setIdleInhibitor(window, GLFW_FALSE);
555         createDecorations(window);
556     }
557     else
558     {
559         wl_shell_surface_set_toplevel(window.wl.shellSurface);
560         setIdleInhibitor(window, GLFW_FALSE);
561         createDecorations(window);
562     }
563 
564     wl_surface_commit(window.wl.surface);
565 
566     return GLFW_TRUE;
567 }
568 
569 static void xdgToplevelHandleConfigure(void* data, xdg_toplevel* toplevel, int width, int height, wl_array* states) {
570     _GLFWwindow* window = cast(_GLFWwindow*) data;
571     float aspectRatio;
572     float targetRatio;
573     uint* state;
574     GLFWbool maximized = GLFW_FALSE;
575     GLFWbool fullscreen = GLFW_FALSE;
576     GLFWbool activated = GLFW_FALSE;
577 
578     foreach(state; wl_array_for_each(states))
579     {
580         switch (*state)
581         {
582             case XDG_TOPLEVEL_STATE_MAXIMIZED:
583                 maximized = GLFW_TRUE;
584                 break;
585             case XDG_TOPLEVEL_STATE_FULLSCREEN:
586                 fullscreen = GLFW_TRUE;
587                 break;
588             case XDG_TOPLEVEL_STATE_RESIZING:
589                 break;
590             case XDG_TOPLEVEL_STATE_ACTIVATED:
591                 activated = GLFW_TRUE;
592                 break;
593         }
594     }
595 
596     if (width != 0 && height != 0)
597     {
598         if (!maximized && !fullscreen)
599         {
600             if (window.numer != GLFW_DONT_CARE && window.denom != GLFW_DONT_CARE)
601             {
602                 aspectRatio = cast(float)width / cast(float)height;
603                 targetRatio = cast(float)window.numer / cast(float)window.denom;
604                 if (aspectRatio < targetRatio)
605                     height = width / targetRatio;
606                 else if (aspectRatio > targetRatio)
607                     width = height * targetRatio;
608             }
609         }
610 
611         _glfwInputWindowSize(window, width, height);
612         _glfwPlatformSetWindowSize(window, width, height);
613         _glfwInputWindowDamage(window);
614     }
615 
616     if (window.wl.wasFullscreen && window.autoIconify)
617     {
618         if (!activated || !fullscreen)
619         {
620             _glfwPlatformIconifyWindow(window);
621             window.wl.wasFullscreen = GLFW_FALSE;
622         }
623     }
624     if (fullscreen && activated)
625         window.wl.wasFullscreen = GLFW_TRUE;
626     _glfwInputWindowFocus(window, activated);
627 }
628 
629 static void xdgToplevelHandleClose(void* data, xdg_toplevel* toplevel) {
630     _GLFWwindow* window = data;
631     _glfwInputWindowCloseRequest(window);
632 }
633 
634 static const(xdg_toplevel_listener) xdgToplevelListener = xdg_toplevel_listener(
635     &xdgToplevelHandleConfigure,
636     &xdgToplevelHandleClose
637 );
638 
639 static void xdgSurfaceHandleConfigure(void* data, xdg_surface* surface, uint serial) {
640     xdg_surface_ack_configure(surface, serial);
641 }
642 
643 static const(xdg_surface_listener) xdgSurfaceListener = xdg_surface_listener(
644     &xdgSurfaceHandleConfigure
645 );
646 
647 static void setXdgDecorations(_GLFWwindow* window) {
648     if (_glfw.wl.decorationManager)
649     {
650         window.wl.xdg.decoration =
651             zxdg_decoration_manager_v1_get_toplevel_decoration(
652                 _glfw.wl.decorationManager, window.wl.xdg.toplevel);
653         zxdg_toplevel_decoration_v1_add_listener(window.wl.xdg.decoration,
654                                                  &xdgDecorationListener,
655                                                  window);
656         zxdg_toplevel_decoration_v1_set_mode(
657             window.wl.xdg.decoration,
658             ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
659     }
660     else
661     {
662         window.wl.decorations.serverSide = GLFW_FALSE;
663         createDecorations(window);
664     }
665 }
666 
667 static GLFWbool createXdgSurface(_GLFWwindow* window) {
668     window.wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
669                                                          window.wl.surface);
670     if (!window.wl.xdg.surface)
671     {
672         _glfwInputError(GLFW_PLATFORM_ERROR,
673                         "Wayland: xdg-surface creation failed");
674         return GLFW_FALSE;
675     }
676 
677     xdg_surface_add_listener(window.wl.xdg.surface,
678                              &xdgSurfaceListener,
679                              window);
680 
681     window.wl.xdg.toplevel = xdg_surface_get_toplevel(window.wl.xdg.surface);
682     if (!window.wl.xdg.toplevel)
683     {
684         _glfwInputError(GLFW_PLATFORM_ERROR,
685                         "Wayland: xdg-toplevel creation failed");
686         return GLFW_FALSE;
687     }
688 
689     xdg_toplevel_add_listener(window.wl.xdg.toplevel,
690                               &xdgToplevelListener,
691                               window);
692 
693     if (window.wl.title)
694         xdg_toplevel_set_title(window.wl.xdg.toplevel, window.wl.title);
695 
696     if (window.minwidth != GLFW_DONT_CARE && window.minheight != GLFW_DONT_CARE)
697         xdg_toplevel_set_min_size(window.wl.xdg.toplevel,
698                                   window.minwidth, window.minheight);
699     if (window.maxwidth != GLFW_DONT_CARE && window.maxheight != GLFW_DONT_CARE)
700         xdg_toplevel_set_max_size(window.wl.xdg.toplevel,
701                                   window.maxwidth, window.maxheight);
702 
703     if (window.monitor)
704     {
705         xdg_toplevel_set_fullscreen(window.wl.xdg.toplevel,
706                                     window.monitor.wl.output);
707         setIdleInhibitor(window, GLFW_TRUE);
708     }
709     else if (window.wl.maximized)
710     {
711         xdg_toplevel_set_maximized(window.wl.xdg.toplevel);
712         setIdleInhibitor(window, GLFW_FALSE);
713         setXdgDecorations(window);
714     }
715     else
716     {
717         setIdleInhibitor(window, GLFW_FALSE);
718         setXdgDecorations(window);
719     }
720 
721     wl_surface_commit(window.wl.surface);
722     wl_display_roundtrip(_glfw.wl.display);
723 
724     return GLFW_TRUE;
725 }
726 
727 static void setCursorImage(_GLFWwindow* window, _GLFWcursorWayland* cursorWayland) {
728     itimerspec timer = {};
729     wl_cursor* wlCursor = cursorWayland.cursor;
730     wl_cursor_image* image;
731     wl_buffer* buffer;
732     wl_surface* surface = _glfw.wl.cursorSurface;
733     int scale = 1;
734 
735     if (!wlCursor)
736         buffer = cursorWayland.buffer;
737     else
738     {
739         if (window.wl.scale > 1 && cursorWayland.cursorHiDPI)
740         {
741             wlCursor = cursorWayland.cursorHiDPI;
742             scale = 2;
743         }
744 
745         image = wlCursor.images[cursorWayland.currentImage];
746         buffer = _glfw.wl.cursor.image_get_buffer(image);
747         if (!buffer)
748             return;
749 
750         timer.it_value.tv_sec = image.delay / 1000;
751         timer.it_value.tv_nsec = (image.delay % 1000) * 1000000;
752         timerfd_settime(_glfw.wl.cursorTimerfd, 0, &timer, null);
753 
754         cursorWayland.width = image.width;
755         cursorWayland.height = image.height;
756         cursorWayland.xhot = image.hotspot_x;
757         cursorWayland.yhot = image.hotspot_y;
758     }
759 
760     wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
761                           surface,
762                           cursorWayland.xhot / scale,
763                           cursorWayland.yhot / scale);
764     wl_surface_set_buffer_scale(surface, scale);
765     wl_surface_attach(surface, buffer, 0, 0);
766     wl_surface_damage(surface, 0, 0,
767                       cursorWayland.width, cursorWayland.height);
768     wl_surface_commit(surface);
769 }
770 
771 static void incrementCursorImage(_GLFWwindow* window) {
772     _GLFWcursor* cursor;
773 
774     if (!window || window.wl.decorations.focus != mainWindow)
775         return;
776 
777     cursor = window.wl.currentCursor;
778     if (cursor && cursor.wl.cursor)
779     {
780         cursor.wl.currentImage += 1;
781         cursor.wl.currentImage %= cursor.wl.cursor.image_count;
782         setCursorImage(window, &cursor.wl);
783     }
784 }
785 
786 static void handleEvents(int timeout) {
787     wl_display* display = _glfw.wl.display;
788     pollfd* fds = [
789         [ wl_display_get_fd(display), POLLIN ],
790         [ _glfw.wl.timerfd, POLLIN ],
791         [ _glfw.wl.cursorTimerfd, POLLIN ],
792     ];
793     ssize_t read_ret;
794     ulong repeats;ulong i;
795 
796     while (wl_display_prepare_read(display) != 0)
797         wl_display_dispatch_pending(display);
798 
799     // If an error different from EAGAIN happens, we have likely been
800     // disconnected from the Wayland session, try to handle that the best we
801     // can.
802     if (wl_display_flush(display) < 0 && errno != EAGAIN)
803     {
804         _GLFWwindow* window = _glfw.windowListHead;
805         while (window)
806         {
807             _glfwInputWindowCloseRequest(window);
808             window = window.next;
809         }
810         wl_display_cancel_read(display);
811         return;
812     }
813 
814     if (poll(fds, 3, timeout) > 0)
815     {
816         if (fds[0].revents & POLLIN)
817         {
818             wl_display_read_events(display);
819             wl_display_dispatch_pending(display);
820         }
821         else
822         {
823             wl_display_cancel_read(display);
824         }
825 
826         if (fds[1].revents & POLLIN)
827         {
828             read_ret = read(_glfw.wl.timerfd, &repeats, repeats.sizeof);
829             if (read_ret != 8)
830                 return;
831 
832             for (i = 0; i < repeats; ++i)
833                 _glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey,
834                               _glfw.wl.keyboardLastScancode, GLFW_REPEAT,
835                               _glfw.wl.xkb.modifiers);
836         }
837 
838         if (fds[2].revents & POLLIN)
839         {
840             read_ret = read(_glfw.wl.cursorTimerfd, &repeats, repeats.sizeof);
841             if (read_ret != 8)
842                 return;
843 
844             incrementCursorImage(_glfw.wl.pointerFocus);
845         }
846     }
847     else
848     {
849         wl_display_cancel_read(display);
850     }
851 }
852 
853 // Translates a GLFW standard cursor to a theme cursor name
854 //
855 private const(char)* translateCursorShape(int shape) {
856     switch (shape)
857     {
858         case GLFW_ARROW_CURSOR:
859             return "left_ptr".ptr;
860         case GLFW_IBEAM_CURSOR:
861             return "xterm".ptr;
862         case GLFW_CROSSHAIR_CURSOR:
863             return "crosshair".ptr;
864         case GLFW_HAND_CURSOR:
865             return "hand2".ptr;
866         case GLFW_HRESIZE_CURSOR:
867             return "sb_h_double_arrow".ptr;
868         case GLFW_VRESIZE_CURSOR:
869             return "sb_v_double_arrow".ptr;
870     }
871     return null;
872 }
873 
874 //////////////////////////////////////////////////////////////////////////
875 //////                       GLFW platform API                      //////
876 //////////////////////////////////////////////////////////////////////////
877 
878 int _glfwPlatformCreateWindow(_GLFWwindow* window, const(_GLFWwndconfig)* wndconfig, const(_GLFWctxconfig)* ctxconfig, const(_GLFWfbconfig)* fbconfig) {
879     window.wl.transparent = fbconfig.transparent;
880 
881     if (!createSurface(window, wndconfig))
882         return GLFW_FALSE;
883 
884     if (ctxconfig.client != GLFW_NO_API)
885     {
886         if (ctxconfig.source == GLFW_EGL_CONTEXT_API ||
887             ctxconfig.source == GLFW_NATIVE_CONTEXT_API)
888         {
889             if (!_glfwInitEGL())
890                 return GLFW_FALSE;
891             if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
892                 return GLFW_FALSE;
893         }
894         else if (ctxconfig.source == GLFW_OSMESA_CONTEXT_API)
895         {
896             if (!_glfwInitOSMesa())
897                 return GLFW_FALSE;
898             if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
899                 return GLFW_FALSE;
900         }
901     }
902 
903     if (wndconfig.title)
904         window.wl.title = _glfw_strdup(wndconfig.title);
905 
906     if (wndconfig.visible)
907     {
908         if (_glfw.wl.wmBase)
909         {
910             if (!createXdgSurface(window))
911                 return GLFW_FALSE;
912         }
913         else
914         {
915             if (!createShellSurface(window))
916                 return GLFW_FALSE;
917         }
918 
919         window.wl.visible = GLFW_TRUE;
920     }
921     else
922     {
923         window.wl.xdg.surface = null;
924         window.wl.xdg.toplevel = null;
925         window.wl.shellSurface = null;
926         window.wl.visible = GLFW_FALSE;
927     }
928 
929     window.wl.currentCursor = null;
930 
931     window.wl.monitors = cast(_GLFWmonitor**) calloc(1, (_GLFWmonitor*).sizeof);
932     window.wl.monitorsCount = 0;
933     window.wl.monitorsSize = 1;
934 
935     return GLFW_TRUE;
936 }
937 
938 void _glfwPlatformDestroyWindow(_GLFWwindow* window) {
939     if (window == _glfw.wl.pointerFocus)
940     {
941         _glfw.wl.pointerFocus = null;
942         _glfwInputCursorEnter(window, GLFW_FALSE);
943     }
944     if (window == _glfw.wl.keyboardFocus)
945     {
946         _glfw.wl.keyboardFocus = null;
947         _glfwInputWindowFocus(window, GLFW_FALSE);
948     }
949 
950     if (window.wl.idleInhibitor)
951         zwp_idle_inhibitor_v1_destroy(window.wl.idleInhibitor);
952 
953     if (window.context.destroy)
954         window.context.destroy(window);
955 
956     destroyDecorations(window);
957     if (window.wl.xdg.decoration)
958         zxdg_toplevel_decoration_v1_destroy(window.wl.xdg.decoration);
959 
960     if (window.wl.decorations.buffer)
961         wl_buffer_destroy(window.wl.decorations.buffer);
962 
963     if (window.wl.native)
964         wl_egl_window_destroy(window.wl.native);
965 
966     if (window.wl.shellSurface)
967         wl_shell_surface_destroy(window.wl.shellSurface);
968 
969     if (window.wl.xdg.toplevel)
970         xdg_toplevel_destroy(window.wl.xdg.toplevel);
971 
972     if (window.wl.xdg.surface)
973         xdg_surface_destroy(window.wl.xdg.surface);
974 
975     if (window.wl.surface)
976         wl_surface_destroy(window.wl.surface);
977 
978     free(window.wl.title);
979     free(window.wl.monitors);
980 }
981 
982 void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const(char)* title) {
983     if (window.wl.title)
984         free(window.wl.title);
985     window.wl.title = _glfw_strdup(title);
986     if (window.wl.xdg.toplevel)
987         xdg_toplevel_set_title(window.wl.xdg.toplevel, title);
988     else if (window.wl.shellSurface)
989         wl_shell_surface_set_title(window.wl.shellSurface, title);
990 }
991 
992 void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const(GLFWimage)* images) {
993     _glfwInputError(GLFW_PLATFORM_ERROR,
994                     "Wayland: Setting window icon not supported");
995 }
996 
997 void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) {
998     // A Wayland client is not aware of its position, so just warn and leave it
999     // as (0, 0)
1000 
1001     _glfwInputError(GLFW_PLATFORM_ERROR,
1002                     "Wayland: Window position retrieval not supported");
1003 }
1004 
1005 void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) {
1006     // A Wayland client can not set its position, so just warn
1007 
1008     _glfwInputError(GLFW_PLATFORM_ERROR,
1009                     "Wayland: Window position setting not supported");
1010 }
1011 
1012 void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) {
1013     if (width)
1014         *width = window.wl.width;
1015     if (height)
1016         *height = window.wl.height;
1017 }
1018 
1019 void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) {
1020     window.wl.width = width;
1021     window.wl.height = height;
1022     resizeWindow(window);
1023 }
1024 
1025 void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) {
1026     if (_glfw.wl.wmBase)
1027     {
1028         if (window.wl.xdg.toplevel)
1029         {
1030             if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
1031                 minwidth = minheight = 0;
1032             if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
1033                 maxwidth = maxheight = 0;
1034             xdg_toplevel_set_min_size(window.wl.xdg.toplevel, minwidth, minheight);
1035             xdg_toplevel_set_max_size(window.wl.xdg.toplevel, maxwidth, maxheight);
1036             wl_surface_commit(window.wl.surface);
1037         }
1038     }
1039     else
1040     {
1041         // TODO: find out how to trigger a resize.
1042         // The actual limits are checked in the wl_shell_surface::configure handler.
1043     }
1044 }
1045 
1046 void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) {
1047     // TODO: find out how to trigger a resize.
1048     // The actual limits are checked in the wl_shell_surface::configure handler.
1049 }
1050 
1051 void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) {
1052     _glfwPlatformGetWindowSize(window, width, height);
1053     *width *= window.wl.scale;
1054     *height *= window.wl.scale;
1055 }
1056 
1057 void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) {
1058     if (window.decorated && !window.monitor && !window.wl.decorations.serverSide)
1059     {
1060         if (top)
1061             *top = _GLFW_DECORATION_TOP;
1062         if (left)
1063             *left = _GLFW_DECORATION_WIDTH;
1064         if (right)
1065             *right = _GLFW_DECORATION_WIDTH;
1066         if (bottom)
1067             *bottom = _GLFW_DECORATION_WIDTH;
1068     }
1069 }
1070 
1071 void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale) {
1072     if (xscale)
1073         *xscale = cast(float) window.wl.scale;
1074     if (yscale)
1075         *yscale = cast(float) window.wl.scale;
1076 }
1077 
1078 void _glfwPlatformIconifyWindow(_GLFWwindow* window) {
1079     if (_glfw.wl.wmBase)
1080     {
1081         if (window.wl.xdg.toplevel)
1082             xdg_toplevel_set_minimized(window.wl.xdg.toplevel);
1083     }
1084     else
1085     {
1086         _glfwInputError(GLFW_PLATFORM_ERROR,
1087                         "Wayland: Iconify window not supported on wl_shell");
1088     }
1089 }
1090 
1091 void _glfwPlatformRestoreWindow(_GLFWwindow* window) {
1092     if (window.wl.xdg.toplevel)
1093     {
1094         if (window.monitor)
1095             xdg_toplevel_unset_fullscreen(window.wl.xdg.toplevel);
1096         if (window.wl.maximized)
1097             xdg_toplevel_unset_maximized(window.wl.xdg.toplevel);
1098         // There is no way to unset minimized, or even to know if we are
1099         // minimized, so there is nothing to do here.
1100     }
1101     else if (window.wl.shellSurface)
1102     {
1103         if (window.monitor || window.wl.maximized)
1104             wl_shell_surface_set_toplevel(window.wl.shellSurface);
1105     }
1106     _glfwInputWindowMonitor(window, null);
1107     window.wl.maximized = GLFW_FALSE;
1108 }
1109 
1110 void _glfwPlatformMaximizeWindow(_GLFWwindow* window) {
1111     if (window.wl.xdg.toplevel)
1112     {
1113         xdg_toplevel_set_maximized(window.wl.xdg.toplevel);
1114     }
1115     else if (window.wl.shellSurface)
1116     {
1117         // Let the compositor select the best output.
1118         wl_shell_surface_set_maximized(window.wl.shellSurface, null);
1119     }
1120     window.wl.maximized = GLFW_TRUE;
1121 }
1122 
1123 void _glfwPlatformShowWindow(_GLFWwindow* window) {
1124     if (!window.wl.visible)
1125     {
1126         if (_glfw.wl.wmBase)
1127             createXdgSurface(window);
1128         else if (!window.wl.shellSurface)
1129             createShellSurface(window);
1130         window.wl.visible = GLFW_TRUE;
1131     }
1132 }
1133 
1134 void _glfwPlatformHideWindow(_GLFWwindow* window) {
1135     if (window.wl.xdg.toplevel)
1136     {
1137         xdg_toplevel_destroy(window.wl.xdg.toplevel);
1138         xdg_surface_destroy(window.wl.xdg.surface);
1139         window.wl.xdg.toplevel = null;
1140         window.wl.xdg.surface = null;
1141     }
1142     else if (window.wl.shellSurface)
1143     {
1144         wl_shell_surface_destroy(window.wl.shellSurface);
1145         window.wl.shellSurface = null;
1146     }
1147     window.wl.visible = GLFW_FALSE;
1148 }
1149 
1150 void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) {
1151     // TODO
1152     _glfwInputError(GLFW_PLATFORM_ERROR,
1153                     "Wayland: Window attention request not implemented yet");
1154 }
1155 
1156 void _glfwPlatformFocusWindow(_GLFWwindow* window) {
1157     _glfwInputError(GLFW_PLATFORM_ERROR,
1158                     "Wayland: Focusing a window requires user interaction");
1159 }
1160 
1161 void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate) {
1162     if (monitor)
1163     {
1164         setFullscreen(window, monitor, refreshRate);
1165     }
1166     else
1167     {
1168         if (window.wl.xdg.toplevel)
1169             xdg_toplevel_unset_fullscreen(window.wl.xdg.toplevel);
1170         else if (window.wl.shellSurface)
1171             wl_shell_surface_set_toplevel(window.wl.shellSurface);
1172         setIdleInhibitor(window, GLFW_FALSE);
1173         if (!_glfw.wl.decorationManager)
1174             createDecorations(window);
1175     }
1176     _glfwInputWindowMonitor(window, monitor);
1177 }
1178 
1179 int _glfwPlatformWindowFocused(_GLFWwindow* window) {
1180     return _glfw.wl.keyboardFocus == window;
1181 }
1182 
1183 int _glfwPlatformWindowIconified(_GLFWwindow* window) {
1184     // wl_shell doesn't have any iconified concept, and xdg-shell doesn’t give
1185     // any way to request whether a surface is iconified.
1186     return GLFW_FALSE;
1187 }
1188 
1189 int _glfwPlatformWindowVisible(_GLFWwindow* window) {
1190     return window.wl.visible;
1191 }
1192 
1193 int _glfwPlatformWindowMaximized(_GLFWwindow* window) {
1194     return window.wl.maximized;
1195 }
1196 
1197 int _glfwPlatformWindowHovered(_GLFWwindow* window) {
1198     return window.wl.hovered;
1199 }
1200 
1201 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) {
1202     return window.wl.transparent;
1203 }
1204 
1205 void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) {
1206     // TODO
1207     _glfwInputError(GLFW_PLATFORM_ERROR,
1208                     "Wayland: Window attribute setting not implemented yet");
1209 }
1210 
1211 void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) {
1212     if (!window.monitor)
1213     {
1214         if (enabled)
1215             createDecorations(window);
1216         else
1217             destroyDecorations(window);
1218     }
1219 }
1220 
1221 void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) {
1222     // TODO
1223     _glfwInputError(GLFW_PLATFORM_ERROR,
1224                     "Wayland: Window attribute setting not implemented yet");
1225 }
1226 
1227 float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) {
1228     return 1.0f;
1229 }
1230 
1231 void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) {
1232 }
1233 
1234 void _glfwPlatformSetRawMouseMotion(_GLFWwindow* window, GLFWbool enabled) {
1235     // This is handled in relativePointerHandleRelativeMotion
1236 }
1237 
1238 GLFWbool _glfwPlatformRawMouseMotionSupported() {
1239     return GLFW_TRUE;
1240 }
1241 
1242 void _glfwPlatformPollEvents() {
1243     handleEvents(0);
1244 }
1245 
1246 void _glfwPlatformWaitEvents() {
1247     handleEvents(-1);
1248 }
1249 
1250 void _glfwPlatformWaitEventsTimeout(double timeout) {
1251     handleEvents(cast(int) (timeout * 1e3));
1252 }
1253 
1254 void _glfwPlatformPostEmptyEvent() {
1255     wl_display_sync(_glfw.wl.display);
1256 }
1257 
1258 void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) {
1259     if (xpos)
1260         *xpos = window.wl.cursorPosX;
1261     if (ypos)
1262         *ypos = window.wl.cursorPosY;
1263 }
1264 
1265 static GLFWbool isPointerLocked(_GLFWwindow* window);
1266 
1267 void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) {
1268     if (isPointerLocked(window))
1269     {
1270         zwp_locked_pointer_v1_set_cursor_position_hint(
1271             window.wl.pointerLock.lockedPointer,
1272             wl_fixed_from_double(x), wl_fixed_from_double(y));
1273         wl_surface_commit(window.wl.surface);
1274     }
1275 }
1276 
1277 void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) {
1278     _glfwPlatformSetCursor(window, window.wl.currentCursor);
1279 }
1280 
1281 const(char)* _glfwPlatformGetScancodeName(int scancode) {
1282     // TODO
1283     return null;
1284 }
1285 
1286 int _glfwPlatformGetKeyScancode(int key) {
1287     return _glfw.wl.scancodes[key];
1288 }
1289 
1290 int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const(GLFWimage)* image, int xhot, int yhot) {
1291     cursor.wl.buffer = createShmBuffer(image);
1292     if (!cursor.wl.buffer)
1293         return GLFW_FALSE;
1294 
1295     cursor.wl.width = image.width;
1296     cursor.wl.height = image.height;
1297     cursor.wl.xhot = xhot;
1298     cursor.wl.yhot = yhot;
1299     return GLFW_TRUE;
1300 }
1301 
1302 int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) {
1303     wl_cursor* standardCursor;
1304 
1305     standardCursor = _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorTheme,
1306                                                 translateCursorShape(shape));
1307     if (!standardCursor)
1308     {
1309         _glfwInputError(GLFW_PLATFORM_ERROR,
1310                         "Wayland: Standard cursor \"%s\" not found",
1311                         translateCursorShape(shape));
1312         return GLFW_FALSE;
1313     }
1314 
1315     cursor.wl.cursor = standardCursor;
1316     cursor.wl.currentImage = 0;
1317 
1318     if (_glfw.wl.cursorThemeHiDPI)
1319     {
1320         standardCursor = _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorThemeHiDPI,
1321                                                     translateCursorShape(shape));
1322         cursor.wl.cursorHiDPI = standardCursor;
1323     }
1324 
1325     return GLFW_TRUE;
1326 }
1327 
1328 void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) {
1329     // If it's a standard cursor we don't need to do anything here
1330     if (cursor.wl.cursor)
1331         return;
1332 
1333     if (cursor.wl.buffer)
1334         wl_buffer_destroy(cursor.wl.buffer);
1335 }
1336 
1337 static void relativePointerHandleRelativeMotion(void* data, zwp_relative_pointer_v1* pointer, uint timeHi, uint timeLo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dxUnaccel, wl_fixed_t dyUnaccel) {
1338     _GLFWwindow* window = data;
1339     double xpos = window.virtualCursorPosX;
1340     double ypos = window.virtualCursorPosY;
1341 
1342     if (window.cursorMode != GLFW_CURSOR_DISABLED)
1343         return;
1344 
1345     if (window.rawMouseMotion)
1346     {
1347         xpos += wl_fixed_to_double(dxUnaccel);
1348         ypos += wl_fixed_to_double(dyUnaccel);
1349     }
1350     else
1351     {
1352         xpos += wl_fixed_to_double(dx);
1353         ypos += wl_fixed_to_double(dy);
1354     }
1355 
1356     _glfwInputCursorPos(window, xpos, ypos);
1357 }
1358 
1359 static const(zwp_relative_pointer_v1_listener) relativePointerListener = zwp_relative_pointer_v1_listener(
1360     &relativePointerHandleRelativeMotion
1361 );
1362 
1363 static void lockedPointerHandleLocked(void* data, zwp_locked_pointer_v1* lockedPointer) {
1364 }
1365 
1366 static void unlockPointer(_GLFWwindow* window) {
1367     zwp_relative_pointer_v1* relativePointer = window.wl.pointerLock.relativePointer;
1368     zwp_locked_pointer_v1* lockedPointer = window.wl.pointerLock.lockedPointer;
1369 
1370     zwp_relative_pointer_v1_destroy(relativePointer);
1371     zwp_locked_pointer_v1_destroy(lockedPointer);
1372 
1373     window.wl.pointerLock.relativePointer = null;
1374     window.wl.pointerLock.lockedPointer = null;
1375 }
1376 
1377 static void lockPointer(_GLFWwindow* window);
1378 
1379 static void lockedPointerHandleUnlocked(void* data, zwp_locked_pointer_v1* lockedPointer) {
1380 }
1381 
1382 static const(zwp_locked_pointer_v1_listener) lockedPointerListener = zwp_locked_pointer_v1_listener(
1383     &lockedPointerHandleLocked,
1384     &lockedPointerHandleUnlocked
1385 );
1386 
1387 static void lockPointer(_GLFWwindow* window) {
1388     zwp_relative_pointer_v1* relativePointer;
1389     zwp_locked_pointer_v1* lockedPointer;
1390 
1391     if (!_glfw.wl.relativePointerManager)
1392     {
1393         _glfwInputError(GLFW_PLATFORM_ERROR,
1394                         "Wayland: no relative pointer manager");
1395         return;
1396     }
1397 
1398     relativePointer =
1399         zwp_relative_pointer_manager_v1_get_relative_pointer(
1400             _glfw.wl.relativePointerManager,
1401             _glfw.wl.pointer);
1402     zwp_relative_pointer_v1_add_listener(relativePointer,
1403                                          &relativePointerListener,
1404                                          window);
1405 
1406     lockedPointer =
1407         zwp_pointer_constraints_v1_lock_pointer(
1408             _glfw.wl.pointerConstraints,
1409             window.wl.surface,
1410             _glfw.wl.pointer,
1411             null,
1412             ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
1413     zwp_locked_pointer_v1_add_listener(lockedPointer,
1414                                        &lockedPointerListener,
1415                                        window);
1416 
1417     window.wl.pointerLock.relativePointer = relativePointer;
1418     window.wl.pointerLock.lockedPointer = lockedPointer;
1419 
1420     wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
1421                           null, 0, 0);
1422 }
1423 
1424 static GLFWbool isPointerLocked(_GLFWwindow* window) {
1425     return window.wl.pointerLock.lockedPointer != null;
1426 }
1427 
1428 void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) {
1429     wl_cursor* defaultCursor;
1430     wl_cursor* defaultCursorHiDPI = null;
1431 
1432     if (!_glfw.wl.pointer)
1433         return;
1434 
1435     window.wl.currentCursor = cursor;
1436 
1437     // If we're not in the correct window just save the cursor
1438     // the next time the pointer enters the window the cursor will change
1439     if (window != _glfw.wl.pointerFocus || window.wl.decorations.focus != mainWindow)
1440         return;
1441 
1442     // Unlock possible pointer lock if no longer disabled.
1443     if (window.cursorMode != GLFW_CURSOR_DISABLED && isPointerLocked(window))
1444         unlockPointer(window);
1445 
1446     if (window.cursorMode == GLFW_CURSOR_NORMAL)
1447     {
1448         if (cursor)
1449             setCursorImage(window, &cursor.wl);
1450         else
1451         {
1452             defaultCursor = _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorTheme,
1453                                                        "left_ptr");
1454             if (!defaultCursor)
1455             {
1456                 _glfwInputError(GLFW_PLATFORM_ERROR,
1457                                 "Wayland: Standard cursor not found");
1458                 return;
1459             }
1460             if (_glfw.wl.cursorThemeHiDPI)
1461                 defaultCursorHiDPI =
1462                     _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorThemeHiDPI,
1463                                                "left_ptr");
1464             _GLFWcursorWayland cursorWayland = [
1465                 defaultCursor,
1466                 defaultCursorHiDPI,
1467                 null,
1468                 0, 0,
1469                 0, 0,
1470                 0
1471             ];
1472             setCursorImage(window, &cursorWayland);
1473         }
1474     }
1475     else if (window.cursorMode == GLFW_CURSOR_DISABLED)
1476     {
1477         if (!isPointerLocked(window))
1478             lockPointer(window);
1479     }
1480     else if (window.cursorMode == GLFW_CURSOR_HIDDEN)
1481     {
1482         wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial, null, 0, 0);
1483     }
1484 }
1485 
1486 static void dataSourceHandleTarget(void* data, wl_data_source* dataSource, const(char)* mimeType) {
1487     if (_glfw.wl.dataSource != dataSource)
1488     {
1489         _glfwInputError(GLFW_PLATFORM_ERROR,
1490                         "Wayland: Unknown clipboard data source");
1491         return;
1492     }
1493 }
1494 
1495 static void dataSourceHandleSend(void* data, wl_data_source* dataSource, const(char)* mimeType, int fd) {
1496     const(char)* string = _glfw.wl.clipboardSendString;
1497     size_t len = _glfw.wl.clipboardSendSize;
1498     int ret;
1499 
1500     if (_glfw.wl.dataSource != dataSource)
1501     {
1502         _glfwInputError(GLFW_PLATFORM_ERROR,
1503                         "Wayland: Unknown clipboard data source");
1504         return;
1505     }
1506 
1507     if (!string)
1508     {
1509         _glfwInputError(GLFW_PLATFORM_ERROR,
1510                         "Wayland: Copy requested from an invalid string");
1511         return;
1512     }
1513 
1514     if (strcmp(mimeType, "text/plain;charset=utf-8") != 0)
1515     {
1516         _glfwInputError(GLFW_PLATFORM_ERROR,
1517                         "Wayland: Wrong MIME type asked from clipboard");
1518         close(fd);
1519         return;
1520     }
1521 
1522     while (len > 0)
1523     {
1524         ret = write(fd, string, len);
1525         if (ret == -1 && errno == EINTR)
1526             continue;
1527         if (ret == -1)
1528         {
1529             // TODO: also report errno maybe.
1530             _glfwInputError(GLFW_PLATFORM_ERROR,
1531                             "Wayland: Error while writing the clipboard");
1532             close(fd);
1533             return;
1534         }
1535         len -= ret;
1536     }
1537     close(fd);
1538 }
1539 
1540 static void dataSourceHandleCancelled(void* data, wl_data_source* dataSource) {
1541     wl_data_source_destroy(dataSource);
1542 
1543     if (_glfw.wl.dataSource != dataSource)
1544     {
1545         _glfwInputError(GLFW_PLATFORM_ERROR,
1546                         "Wayland: Unknown clipboard data source");
1547         return;
1548     }
1549 
1550     _glfw.wl.dataSource = null;
1551 }
1552 
1553 static const(wl_data_source_listener) dataSourceListener = wl_data_source_listener(
1554     &dataSourceHandleTarget,
1555     &dataSourceHandleSend,
1556     &dataSourceHandleCancelled,
1557 );
1558 
1559 void _glfwPlatformSetClipboardString(const(char)* string) {
1560     if (_glfw.wl.dataSource)
1561     {
1562         wl_data_source_destroy(_glfw.wl.dataSource);
1563         _glfw.wl.dataSource = null;
1564     }
1565 
1566     if (_glfw.wl.clipboardSendString)
1567     {
1568         free(_glfw.wl.clipboardSendString);
1569         _glfw.wl.clipboardSendString = null;
1570     }
1571 
1572     _glfw.wl.clipboardSendString = strdup(string);
1573     if (!_glfw.wl.clipboardSendString)
1574     {
1575         _glfwInputError(GLFW_PLATFORM_ERROR,
1576                         "Wayland: Impossible to allocate clipboard string");
1577         return;
1578     }
1579     _glfw.wl.clipboardSendSize = strlen(string);
1580     _glfw.wl.dataSource =
1581         wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
1582     if (!_glfw.wl.dataSource)
1583     {
1584         _glfwInputError(GLFW_PLATFORM_ERROR,
1585                         "Wayland: Impossible to create clipboard source");
1586         free(_glfw.wl.clipboardSendString);
1587         return;
1588     }
1589     wl_data_source_add_listener(_glfw.wl.dataSource,
1590                                 &dataSourceListener,
1591                                 null);
1592     wl_data_source_offer(_glfw.wl.dataSource, "text/plain;charset=utf-8");
1593     wl_data_device_set_selection(_glfw.wl.dataDevice,
1594                                  _glfw.wl.dataSource,
1595                                  _glfw.wl.serial);
1596 }
1597 
1598 static GLFWbool growClipboardString() {
1599     char* clipboard = _glfw.wl.clipboardString;
1600 
1601     clipboard = realloc(clipboard, _glfw.wl.clipboardSize * 2);
1602     if (!clipboard)
1603     {
1604         _glfwInputError(GLFW_PLATFORM_ERROR,
1605                         "Wayland: Impossible to grow clipboard string");
1606         return GLFW_FALSE;
1607     }
1608     _glfw.wl.clipboardString = clipboard;
1609     _glfw.wl.clipboardSize = _glfw.wl.clipboardSize * 2;
1610     return GLFW_TRUE;
1611 }
1612 
1613 const(char)* _glfwPlatformGetClipboardString() {
1614     int[2] fds;
1615     int ret;
1616     size_t len = 0;
1617 
1618     if (!_glfw.wl.dataOffer)
1619     {
1620         _glfwInputError(GLFW_FORMAT_UNAVAILABLE,
1621                         "No clipboard data has been sent yet");
1622         return null;
1623     }
1624 
1625     ret = pipe2(fds.ptr, O_CLOEXEC);
1626     if (ret < 0)
1627     {
1628         // TODO: also report errno maybe?
1629         _glfwInputError(GLFW_PLATFORM_ERROR,
1630                         "Wayland: Impossible to create clipboard pipe fds");
1631         return null;
1632     }
1633 
1634     wl_data_offer_receive(_glfw.wl.dataOffer, "text/plain;charset=utf-8", fds[1]);
1635     close(fds[1]);
1636 
1637     // XXX: this is a huge hack, this function shouldn’t be synchronous!
1638     handleEvents(-1);
1639 
1640     while (1)
1641     {
1642         // Grow the clipboard if we need to paste something bigger, there is no
1643         // shrink operation yet.
1644         if (len + 4096 > _glfw.wl.clipboardSize)
1645         {
1646             if (!growClipboardString())
1647             {
1648                 close(fds[0]);
1649                 return null;
1650             }
1651         }
1652 
1653         // Then read from the fd to the clipboard, handling all known errors.
1654         ret = read(fds[0], _glfw.wl.clipboardString + len, 4096);
1655         if (ret == 0)
1656             break;
1657         if (ret == -1 && errno == EINTR)
1658             continue;
1659         if (ret == -1)
1660         {
1661             // TODO: also report errno maybe.
1662             _glfwInputError(GLFW_PLATFORM_ERROR,
1663                             "Wayland: Impossible to read from clipboard fd");
1664             close(fds[0]);
1665             return null;
1666         }
1667         len += ret;
1668     }
1669     close(fds[0]);
1670     if (len + 1 > _glfw.wl.clipboardSize)
1671     {
1672         if (!growClipboardString())
1673             return null;
1674     }
1675     _glfw.wl.clipboardString[len] = '\0';
1676     return _glfw.wl.clipboardString;
1677 }
1678 
1679 void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) {
1680     if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)
1681         return;
1682 
1683     extensions[0] = "VK_KHR_surface";
1684     extensions[1] = "VK_KHR_wayland_surface";
1685 }
1686 
1687 int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint queuefamily) {
1688     PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = cast(PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)
1689         vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceWaylandPresentationSupportKHR");
1690     if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR)
1691     {
1692         _glfwInputError(GLFW_API_UNAVAILABLE,
1693                         "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension");
1694         return VK_NULL_HANDLE;
1695     }
1696 
1697     return vkGetPhysicalDeviceWaylandPresentationSupportKHR(device,
1698                                                             queuefamily,
1699                                                             _glfw.wl.display);
1700 }
1701 
1702 VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const(VkAllocationCallbacks)* allocator, VkSurfaceKHR* surface) {
1703     VkResult err;
1704     VkWaylandSurfaceCreateInfoKHR sci;
1705     PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
1706 
1707     vkCreateWaylandSurfaceKHR = cast(PFN_vkCreateWaylandSurfaceKHR)
1708         vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR");
1709     if (!vkCreateWaylandSurfaceKHR)
1710     {
1711         _glfwInputError(GLFW_API_UNAVAILABLE,
1712                         "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension");
1713         return VkResult.VK_ERROR_EXTENSION_NOT_PRESENT;
1714     }
1715 
1716     memset(&sci, 0, sci.sizeof);
1717     sci.sType = VkStructureType.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
1718     sci.display = _glfw.wl.display;
1719     sci.surface = window.wl.surface;
1720 
1721     err = vkCreateWaylandSurfaceKHR(instance, &sci, allocator, surface);
1722     if (err)
1723     {
1724         _glfwInputError(GLFW_PLATFORM_ERROR,
1725                         "Wayland: Failed to create Vulkan surface: %s",
1726                         _glfwGetVulkanResultString(err));
1727     }
1728 
1729     return err;
1730 }
1731 
1732 
1733 //////////////////////////////////////////////////////////////////////////
1734 //////                        GLFW native API                       //////
1735 //////////////////////////////////////////////////////////////////////////
1736 
1737 wl_display* glfwGetWaylandDisplay() {
1738     mixin(_GLFW_REQUIRE_INIT_OR_RETURN!"null");
1739     return _glfw.wl.display;
1740 }
1741 
1742 wl_surface* glfwGetWaylandWindow(GLFWwindow* handle) {
1743     _GLFWwindow* window = cast(_GLFWwindow*) handle;
1744     mixin(_GLFW_REQUIRE_INIT_OR_RETURN!"null");
1745     return window.wl.surface;
1746 }