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 }