wl_window.c 30.6 KB
Newer Older
1
//========================================================================
Camilla Berglund's avatar
Camilla Berglund committed
2
// GLFW 3.3 Wayland - www.glfw.org
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//------------------------------------------------------------------------
// Copyright (c) 2014 Jonas Ådahl <jadahl@gmail.com>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
//========================================================================

27
28
#define _GNU_SOURCE

29
30
31
#include "internal.h"

#include <stdio.h>
32
33
34
35
36
37
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
38
#include <poll.h>
39

40
#include <wayland-egl.h>
41
#include <wayland-cursor.h>
42
43


Camilla Berglund's avatar
Camilla Berglund committed
44
45
46
static void handlePing(void* data,
                       struct wl_shell_surface* shellSurface,
                       uint32_t serial)
47
48
49
50
{
    wl_shell_surface_pong(shellSurface, serial);
}

Camilla Berglund's avatar
Camilla Berglund committed
51
52
53
54
55
static void handleConfigure(void* data,
                            struct wl_shell_surface* shellSurface,
                            uint32_t edges,
                            int32_t width,
                            int32_t height)
56
{
57
    _GLFWwindow* window = data;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    float aspectRatio;
    float targetRatio;

    if (!window->monitor)
    {
        if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
        {
            aspectRatio = (float)width / (float)height;
            targetRatio = (float)window->numer / (float)window->denom;
            if (aspectRatio < targetRatio)
                height = width / targetRatio;
            else if (aspectRatio > targetRatio)
                width = height * targetRatio;
        }

        if (window->minwidth != GLFW_DONT_CARE && width < window->minwidth)
            width = window->minwidth;
        else if (window->maxwidth != GLFW_DONT_CARE && width > window->maxwidth)
            width = window->maxwidth;

        if (window->minheight != GLFW_DONT_CARE && height < window->minheight)
            height = window->minheight;
        else if (window->maxheight != GLFW_DONT_CARE && height > window->maxheight)
            height = window->maxheight;
    }

84
85
86
    _glfwInputWindowSize(window, width, height);
    _glfwPlatformSetWindowSize(window, width, height);
    _glfwInputWindowDamage(window);
87
88
}

Camilla Berglund's avatar
Camilla Berglund committed
89
90
static void handlePopupDone(void* data,
                            struct wl_shell_surface* shellSurface)
91
92
93
94
95
96
97
98
99
{
}

static const struct wl_shell_surface_listener shellSurfaceListener = {
    handlePing,
    handleConfigure,
    handlePopupDone
};

100
101
102
103
104
105
106
static void checkScaleChange(_GLFWwindow* window)
{
    int scaledWidth, scaledHeight;
    int scale = 1;
    int i;
    int monitorScale;

107
    // Check if we will be able to set the buffer scale or not.
108
    if (_glfw.wl.wl_compositor_version < 3)
109
110
        return;

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    // Get the scale factor from the highest scale monitor.
    for (i = 0; i < window->wl.monitorsCount; ++i)
    {
        monitorScale = window->wl.monitors[i]->wl.scale;
        if (scale < monitorScale)
            scale = monitorScale;
    }

    // Only change the framebuffer size if the scale changed.
    if (scale != window->wl.scale)
    {
        window->wl.scale = scale;
        scaledWidth = window->wl.width * scale;
        scaledHeight = window->wl.height * scale;
        wl_surface_set_buffer_scale(window->wl.surface, scale);
        wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
        _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
    }
}

static void handleEnter(void *data,
                        struct wl_surface *surface,
                        struct wl_output *output)
{
    _GLFWwindow* window = data;
    _GLFWmonitor* monitor = wl_output_get_user_data(output);

    if (window->wl.monitorsCount + 1 > window->wl.monitorsSize)
    {
        ++window->wl.monitorsSize;
        window->wl.monitors =
            realloc(window->wl.monitors,
                    window->wl.monitorsSize * sizeof(_GLFWmonitor*));
    }

    window->wl.monitors[window->wl.monitorsCount++] = monitor;

    checkScaleChange(window);
}

static void handleLeave(void *data,
                        struct wl_surface *surface,
                        struct wl_output *output)
{
    _GLFWwindow* window = data;
    _GLFWmonitor* monitor = wl_output_get_user_data(output);
    GLFWbool found;
    int i;

    for (i = 0, found = GLFW_FALSE; i < window->wl.monitorsCount - 1; ++i)
    {
        if (monitor == window->wl.monitors[i])
            found = GLFW_TRUE;
        if (found)
            window->wl.monitors[i] = window->wl.monitors[i + 1];
    }
    window->wl.monitors[--window->wl.monitorsCount] = NULL;

    checkScaleChange(window);
}

static const struct wl_surface_listener surfaceListener = {
    handleEnter,
    handleLeave
};

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
{
    struct wl_region* region;

    region = wl_compositor_create_region(_glfw.wl.compositor);
    if (!region)
        return;

    wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
    wl_surface_set_opaque_region(window->wl.surface, region);
    wl_surface_commit(window->wl.surface);
    wl_region_destroy(region);
}

192
193
static GLFWbool createSurface(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig)
194
{
195
196
    window->wl.surface = wl_compositor_create_surface(_glfw.wl.compositor);
    if (!window->wl.surface)
197
        return GLFW_FALSE;
198

199
200
201
202
    wl_surface_add_listener(window->wl.surface,
                            &surfaceListener,
                            window);

203
204
    wl_surface_set_user_data(window->wl.surface, window);

205
206
207
208
    window->wl.native = wl_egl_window_create(window->wl.surface,
                                             wndconfig->width,
                                             wndconfig->height);
    if (!window->wl.native)
209
        return GLFW_FALSE;
210

211
212
213
214
    window->wl.width = wndconfig->width;
    window->wl.height = wndconfig->height;
    window->wl.scale = 1;

215
216
217
    // TODO: make this optional once issue #197 is fixed.
    setOpaqueRegion(window);

218
219
220
221
222
    return GLFW_TRUE;
}

static GLFWbool createShellSurface(_GLFWwindow* window)
{
223
224
225
    window->wl.shell_surface = wl_shell_get_shell_surface(_glfw.wl.shell,
                                                          window->wl.surface);
    if (!window->wl.shell_surface)
226
        return GLFW_FALSE;
227

228
    wl_shell_surface_add_listener(window->wl.shell_surface,
229
230
231
                                  &shellSurfaceListener,
                                  window);

232
233
234
235
236
237
238
239
240
241
242
    if (window->wl.title)
        wl_shell_surface_set_title(window->wl.shell_surface, window->wl.title);

    if (window->monitor)
    {
        wl_shell_surface_set_fullscreen(
            window->wl.shell_surface,
            WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
            0,
            window->monitor->wl.output);
    }
243
244
245
246
    else if (window->wl.maximized)
    {
        wl_shell_surface_set_maximized(window->wl.shell_surface, NULL);
    }
247
248
249
250
    else
    {
        wl_shell_surface_set_toplevel(window->wl.shell_surface);
    }
251

252
    return GLFW_TRUE;
253
254
}

255
static int
256
createTmpfileCloexec(char* tmpname)
257
258
259
260
261
262
263
264
265
266
{
    int fd;

    fd = mkostemp(tmpname, O_CLOEXEC);
    if (fd >= 0)
        unlink(tmpname);

    return fd;
}

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
static void
handleEvents(int timeout)
{
    struct wl_display* display = _glfw.wl.display;
    struct pollfd fds[] = {
        { wl_display_get_fd(display), POLLIN },
    };

    while (wl_display_prepare_read(display) != 0)
        wl_display_dispatch_pending(display);

    // If an error different from EAGAIN happens, we have likely been
    // disconnected from the Wayland session, try to handle that the best we
    // can.
    if (wl_display_flush(display) < 0 && errno != EAGAIN)
    {
        _GLFWwindow* window = _glfw.windowListHead;
        while (window)
        {
            _glfwInputWindowCloseRequest(window);
            window = window->next;
        }
        wl_display_cancel_read(display);
        return;
    }

    if (poll(fds, 1, timeout) > 0)
    {
        wl_display_read_events(display);
        wl_display_dispatch_pending(display);
    }
    else
    {
        wl_display_cancel_read(display);
    }
}

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
 * Create a new, unique, anonymous file of the given size, and
 * return the file descriptor for it. The file descriptor is set
 * CLOEXEC. The file is immediately suitable for mmap()'ing
 * the given size at offset zero.
 *
 * The file should not have a permanent backing store like a disk,
 * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
 *
 * The file name is deleted from the file system.
 *
 * The file is suitable for buffer sharing between processes by
 * transmitting the file descriptor over Unix sockets using the
 * SCM_RIGHTS methods.
 *
319
320
321
322
 * posix_fallocate() is used to guarantee that disk space is available
 * for the file at the given size. If disk space is insufficent, errno
 * is set to ENOSPC. If posix_fallocate() is not supported, program may
 * receive SIGBUS on accessing mmap()'ed file contents instead.
323
324
 */
int
325
createAnonymousFile(off_t size)
326
327
{
    static const char template[] = "/glfw-shared-XXXXXX";
328
329
    const char* path;
    char* name;
330
331
332
333
    int fd;
    int ret;

    path = getenv("XDG_RUNTIME_DIR");
334
335
    if (!path)
    {
336
337
338
339
        errno = ENOENT;
        return -1;
    }

340
    name = calloc(strlen(path) + sizeof(template), 1);
341
342
343
    strcpy(name, path);
    strcat(name, template);

344
    fd = createTmpfileCloexec(name);
345
346
347
348
349
350

    free(name);

    if (fd < 0)
        return -1;
    ret = posix_fallocate(fd, 0, size);
351
352
    if (ret != 0)
    {
353
354
355
356
357
358
        close(fd);
        errno = ret;
        return -1;
    }
    return fd;
}
359

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
// Translates a GLFW standard cursor to a theme cursor name
//
static char *translateCursorShape(int shape)
{
    switch (shape)
    {
        case GLFW_ARROW_CURSOR:
            return "left_ptr";
        case GLFW_IBEAM_CURSOR:
            return "xterm";
        case GLFW_CROSSHAIR_CURSOR:
            return "crosshair";
        case GLFW_HAND_CURSOR:
            return "grabbing";
        case GLFW_HRESIZE_CURSOR:
            return "sb_h_double_arrow";
        case GLFW_VRESIZE_CURSOR:
            return "sb_v_double_arrow";
    }
    return NULL;
}

382
383
384
385
386
387
388
389
390
//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
                              const _GLFWctxconfig* ctxconfig,
                              const _GLFWfbconfig* fbconfig)
{
391
392
393
    if (!createSurface(window, wndconfig))
        return GLFW_FALSE;

394
    if (ctxconfig->client != GLFW_NO_API)
395
    {
396
397
        if (!_glfwInitEGL())
            return GLFW_FALSE;
398
        if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
399
400
            return GLFW_FALSE;
    }
401

402
403
404
405
    if (wndconfig->title)
        window->wl.title = strdup(wndconfig->title);

    if (wndconfig->visible)
406
    {
407
408
409
410
        if (!createShellSurface(window))
            return GLFW_FALSE;

        window->wl.visible = GLFW_TRUE;
411
412
413
    }
    else
    {
414
415
        window->wl.shell_surface = NULL;
        window->wl.visible = GLFW_FALSE;
416
417
    }

418
419
    window->wl.currentCursor = NULL;

420
421
422
423
    window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
    window->wl.monitorsCount = 0;
    window->wl.monitorsSize = 1;

424
    return GLFW_TRUE;
425
426
427
428
}

void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
429
430
431
    if (window == _glfw.wl.pointerFocus)
    {
        _glfw.wl.pointerFocus = NULL;
432
        _glfwInputCursorEnter(window, GLFW_FALSE);
433
434
435
436
    }
    if (window == _glfw.wl.keyboardFocus)
    {
        _glfw.wl.keyboardFocus = NULL;
437
        _glfwInputWindowFocus(window, GLFW_FALSE);
438
439
    }

440
    if (window->context.destroy)
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
441
        window->context.destroy(window);
442

443
444
    if (window->wl.native)
        wl_egl_window_destroy(window->wl.native);
445

446
447
    if (window->wl.shell_surface)
        wl_shell_surface_destroy(window->wl.shell_surface);
448

449
450
    if (window->wl.surface)
        wl_surface_destroy(window->wl.surface);
451

452
    free(window->wl.title);
453
    free(window->wl.monitors);
454
455
456
457
}

void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
458
459
460
461
462
    if (window->wl.title)
        free(window->wl.title);
    window->wl.title = strdup(title);
    if (window->wl.shell_surface)
        wl_shell_surface_set_title(window->wl.shell_surface, title);
463
464
}

Camilla Berglund's avatar
Camilla Berglund committed
465
466
467
void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
                                int count, const GLFWimage* images)
{
468
469
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Setting window icon not supported");
Camilla Berglund's avatar
Camilla Berglund committed
470
471
}

472
473
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
{
474
475
    // A Wayland client is not aware of its position, so just warn and leave it
    // as (0, 0)
476
477

    _glfwInputError(GLFW_PLATFORM_ERROR,
478
                    "Wayland: Window position retrieval not supported");
479
480
481
482
}

void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
{
Camilla Berglund's avatar
Camilla Berglund committed
483
    // A Wayland client can not set its position, so just warn
484
485

    _glfwInputError(GLFW_PLATFORM_ERROR,
486
                    "Wayland: Window position setting not supported");
487
488
489
490
491
}

void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
{
    if (width)
492
        *width = window->wl.width;
493
    if (height)
494
        *height = window->wl.height;
495
496
497
498
}

void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
499
500
    int scaledWidth = width * window->wl.scale;
    int scaledHeight = height * window->wl.scale;
501
502
    window->wl.width = width;
    window->wl.height = height;
503
    wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
504
    setOpaqueRegion(window);
505
    _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
506
507
}

508
509
510
511
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
                                      int minwidth, int minheight,
                                      int maxwidth, int maxheight)
{
512
513
    // TODO: find out how to trigger a resize.
    // The actual limits are checked in the wl_shell_surface::configure handler.
514
515
516
517
}

void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
{
518
519
    // TODO: find out how to trigger a resize.
    // The actual limits are checked in the wl_shell_surface::configure handler.
520
521
}

522
523
524
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
{
    _glfwPlatformGetWindowSize(window, width, height);
525
526
    *width *= window->wl.scale;
    *height *= window->wl.scale;
527
528
}

529
530
531
532
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
                                     int* left, int* top,
                                     int* right, int* bottom)
{
533
534
    // TODO: will need a proper implementation once decorations are
    // implemented, but for now just leave everything as 0.
535
536
}

537
538
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
539
540
541
    // TODO: move to xdg_shell instead of wl_shell.
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Iconify window not supported");
542
543
544
545
}

void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{
546
547
548
549
550
551
552
553
    // TODO: also do the same for iconified.
    if (window->monitor || window->wl.maximized)
    {
        if (window->wl.shell_surface)
            wl_shell_surface_set_toplevel(window->wl.shell_surface);

        window->wl.maximized = GLFW_FALSE;
    }
554
555
}

556
557
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
{
558
559
560
561
562
563
564
565
566
    if (!window->monitor && !window->wl.maximized)
    {
        if (window->wl.shell_surface)
        {
            // Let the compositor select the best output.
            wl_shell_surface_set_maximized(window->wl.shell_surface, NULL);
        }
        window->wl.maximized = GLFW_TRUE;
    }
567
568
}

569
570
void _glfwPlatformShowWindow(_GLFWwindow* window)
{
571
572
573
574
575
576
    if (!window->monitor)
    {
        if (!window->wl.shell_surface)
            createShellSurface(window);
        window->wl.visible = GLFW_TRUE;
    }
577
578
579
580
}

void _glfwPlatformHideWindow(_GLFWwindow* window)
{
581
582
583
584
585
586
    if (!window->monitor)
    {
        if (window->wl.shell_surface)
            wl_shell_surface_destroy(window->wl.shell_surface);
        window->wl.visible = GLFW_FALSE;
    }
587
588
}

Camilla Berglund's avatar
Camilla Berglund committed
589
590
void _glfwPlatformFocusWindow(_GLFWwindow* window)
{
591
592
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Focusing a window requires user interaction");
Camilla Berglund's avatar
Camilla Berglund committed
593
594
}

Camilla Berglund's avatar
Camilla Berglund committed
595
596
597
598
599
600
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
                                   _GLFWmonitor* monitor,
                                   int xpos, int ypos,
                                   int width, int height,
                                   int refreshRate)
{
601
602
603
604
605
606
607
608
609
610
611
612
613
    if (monitor)
    {
        wl_shell_surface_set_fullscreen(
            window->wl.shell_surface,
            WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
            refreshRate * 1000, // Convert Hz to mHz.
            monitor->wl.output);
    }
    else
    {
        wl_shell_surface_set_toplevel(window->wl.shell_surface);
    }
    _glfwInputWindowMonitorChange(window, monitor);
Camilla Berglund's avatar
Camilla Berglund committed
614
615
}

616
617
int _glfwPlatformWindowFocused(_GLFWwindow* window)
{
618
    return _glfw.wl.keyboardFocus == window;
619
620
621
622
}

int _glfwPlatformWindowIconified(_GLFWwindow* window)
{
623
    // TODO: move to xdg_shell, wl_shell doesn't have any iconified concept.
624
    return GLFW_FALSE;
625
626
627
628
}

int _glfwPlatformWindowVisible(_GLFWwindow* window)
{
629
    return window->wl.visible;
630
631
}

632
633
int _glfwPlatformWindowMaximized(_GLFWwindow* window)
{
634
    return window->wl.maximized;
635
636
}

637
638
void _glfwPlatformPollEvents(void)
{
639
    handleEvents(0);
640
641
642
643
}

void _glfwPlatformWaitEvents(void)
{
644
    handleEvents(-1);
645
646
}

Camilla Berglund's avatar
Camilla Berglund committed
647
648
649
650
651
void _glfwPlatformWaitEventsTimeout(double timeout)
{
    handleEvents((int) (timeout * 1e3));
}

652
653
void _glfwPlatformPostEmptyEvent(void)
{
654
    wl_display_sync(_glfw.wl.display);
655
656
}

657
658
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
{
659
    if (xpos)
660
        *xpos = window->wl.cursorPosX;
661
    if (ypos)
662
        *ypos = window->wl.cursorPosY;
663
664
}

665
666
static GLFWbool isPointerLocked(_GLFWwindow* window);

667
668
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
{
669
670
671
672
673
674
675
    if (isPointerLocked(window))
    {
        zwp_locked_pointer_v1_set_cursor_position_hint(
            window->wl.pointerLock.lockedPointer,
            wl_fixed_from_double(x), wl_fixed_from_double(y));
        wl_surface_commit(window->wl.surface);
    }
676
677
}

678
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
679
{
680
    _glfwPlatformSetCursor(window, window->wl.currentCursor);
681
}
Camilla Berglund's avatar
Camilla Berglund committed
682

Camilla Berglund's avatar
Camilla Berglund committed
683
684
685
686
687
688
const char* _glfwPlatformGetKeyName(int key, int scancode)
{
    // TODO
    return NULL;
}

689
690
691
692
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
                              const GLFWimage* image,
                              int xhot, int yhot)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
693
    struct wl_shm_pool* pool;
694
695
    int stride = image->width * 4;
    int length = image->width * image->height * 4;
Jonas Ådahl's avatar
Jonas Ådahl committed
696
    void* data;
697
698
    int fd, i;

699
    fd = createAnonymousFile(length);
Jonas Ådahl's avatar
Jonas Ådahl committed
700
701
    if (fd < 0)
    {
702
        _glfwInputError(GLFW_PLATFORM_ERROR,
703
                        "Wayland: Creating a buffer file for %d B failed: %m",
Jonas Ådahl's avatar
Jonas Ådahl committed
704
                        length);
705
        return GLFW_FALSE;
706
707
708
    }

    data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Jonas Ådahl's avatar
Jonas Ådahl committed
709
710
    if (data == MAP_FAILED)
    {
711
        _glfwInputError(GLFW_PLATFORM_ERROR,
712
                        "Wayland: Cursor mmap failed: %m");
713
        close(fd);
714
        return GLFW_FALSE;
715
716
717
718
719
720
721
722
723
    }

    pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);

    close(fd);
    unsigned char* source = (unsigned char*) image->pixels;
    unsigned char* target = data;
    for (i = 0;  i < image->width * image->height;  i++, source += 4)
    {
Camilla Berglund's avatar
Camilla Berglund committed
724
        unsigned int alpha = source[3];
725

Camilla Berglund's avatar
Camilla Berglund committed
726
727
728
729
        *target++ = (unsigned char) ((source[2] * alpha) / 255);
        *target++ = (unsigned char) ((source[1] * alpha) / 255);
        *target++ = (unsigned char) ((source[0] * alpha) / 255);
        *target++ = (unsigned char) alpha;
730
731
    }

Jonas Ådahl's avatar
Jonas Ådahl committed
732
733
734
735
736
    cursor->wl.buffer =
        wl_shm_pool_create_buffer(pool, 0,
                                  image->width,
                                  image->height,
                                  stride, WL_SHM_FORMAT_ARGB8888);
737
738
739
740
741
742
743
    munmap(data, length);
    wl_shm_pool_destroy(pool);

    cursor->wl.width = image->width;
    cursor->wl.height = image->height;
    cursor->wl.xhot = xhot;
    cursor->wl.yhot = yhot;
744
    return GLFW_TRUE;
745
746
}

747
748
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
749
    struct wl_cursor* standardCursor;
750

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
751
752
753
754
    standardCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
                                                translateCursorShape(shape));
    if (!standardCursor)
    {
755
756
757
758
759
760
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Standard cursor \"%s\" not found",
                        translateCursorShape(shape));
        return GLFW_FALSE;
    }

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
761
    cursor->wl.image = standardCursor->images[0];
762
    return GLFW_TRUE;
763
764
}

765
766
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
767
768
769
770
    // If it's a standard cursor we don't need to do anything here
    if (cursor->wl.image)
        return;

771
772
    if (cursor->wl.buffer)
        wl_buffer_destroy(cursor->wl.buffer);
773
774
}

775
776
777
778
779
780
781
782
783
784
785
786
787
788
static void handleRelativeMotion(void* data,
                                 struct zwp_relative_pointer_v1* pointer,
                                 uint32_t timeHi,
                                 uint32_t timeLo,
                                 wl_fixed_t dx,
                                 wl_fixed_t dy,
                                 wl_fixed_t dxUnaccel,
                                 wl_fixed_t dyUnaccel)
{
    _GLFWwindow* window = data;

    if (window->cursorMode != GLFW_CURSOR_DISABLED)
        return;

789
790
791
    _glfwInputCursorPos(window,
                        window->virtualCursorPosX + wl_fixed_to_double(dxUnaccel),
                        window->virtualCursorPosY + wl_fixed_to_double(dyUnaccel));
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
}

static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
    handleRelativeMotion
};

static void handleLocked(void* data,
                         struct zwp_locked_pointer_v1* lockedPointer)
{
}

static void unlockPointer(_GLFWwindow* window)
{
    struct zwp_relative_pointer_v1* relativePointer =
        window->wl.pointerLock.relativePointer;
    struct zwp_locked_pointer_v1* lockedPointer =
        window->wl.pointerLock.lockedPointer;

    zwp_relative_pointer_v1_destroy(relativePointer);
    zwp_locked_pointer_v1_destroy(lockedPointer);

    window->wl.pointerLock.relativePointer = NULL;
    window->wl.pointerLock.lockedPointer = NULL;
}

static void lockPointer(_GLFWwindow* window);

static void handleUnlocked(void* data,
                           struct zwp_locked_pointer_v1* lockedPointer)
{
}

static const struct zwp_locked_pointer_v1_listener lockedPointerListener = {
    handleLocked,
    handleUnlocked
};

static void lockPointer(_GLFWwindow* window)
{
    struct zwp_relative_pointer_v1* relativePointer;
    struct zwp_locked_pointer_v1* lockedPointer;

    if (!_glfw.wl.relativePointerManager)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: no relative pointer manager");
        return;
    }

    relativePointer =
        zwp_relative_pointer_manager_v1_get_relative_pointer(
            _glfw.wl.relativePointerManager,
            _glfw.wl.pointer);
    zwp_relative_pointer_v1_add_listener(relativePointer,
                                         &relativePointerListener,
                                         window);

    lockedPointer =
        zwp_pointer_constraints_v1_lock_pointer(
            _glfw.wl.pointerConstraints,
            window->wl.surface,
            _glfw.wl.pointer,
            NULL,
            ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
    zwp_locked_pointer_v1_add_listener(lockedPointer,
                                       &lockedPointerListener,
                                       window);

    window->wl.pointerLock.relativePointer = relativePointer;
    window->wl.pointerLock.lockedPointer = lockedPointer;

    wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
                          NULL, 0, 0);
}

static GLFWbool isPointerLocked(_GLFWwindow* window)
{
    return window->wl.pointerLock.lockedPointer != NULL;
}

872
873
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
874
    struct wl_buffer* buffer;
875
    struct wl_cursor* defaultCursor;
Jonas Ådahl's avatar
Jonas Ådahl committed
876
877
    struct wl_cursor_image* image;
    struct wl_surface* surface = _glfw.wl.cursorSurface;
878
879
880
881
882
883
884
885
886
887
888

    if (!_glfw.wl.pointer)
        return;

    window->wl.currentCursor = cursor;

    // If we're not in the correct window just save the cursor
    // the next time the pointer enters the window the cursor will change
    if (window != _glfw.wl.pointerFocus)
        return;

889
890
891
892
    // Unlock possible pointer lock if no longer disabled.
    if (window->cursorMode != GLFW_CURSOR_DISABLED && isPointerLocked(window))
        unlockPointer(window);

893
894
    if (window->cursorMode == GLFW_CURSOR_NORMAL)
    {
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
895
896
897
        if (cursor)
            image = cursor->wl.image;
        else
898
        {
899
900
901
902
903
904
905
906
907
908
909
910
911
            defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
                                                       "left_ptr");
            if (!defaultCursor)
            {
                _glfwInputError(GLFW_PLATFORM_ERROR,
                                "Wayland: Standard cursor not found");
                return;
            }
            image = defaultCursor->images[0];
        }

        if (image)
        {
912
913
914
915
            buffer = wl_cursor_image_get_buffer(image);
            if (!buffer)
                return;
            wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
Jonas Ådahl's avatar
Jonas Ådahl committed
916
917
918
                                  surface,
                                  image->hotspot_x,
                                  image->hotspot_y);
919
920
            wl_surface_attach(surface, buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
921
                              image->width, image->height);
922
923
924
925
926
            wl_surface_commit(surface);
        }
        else
        {
            wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
Jonas Ådahl's avatar
Jonas Ådahl committed
927
928
929
                                  surface,
                                  cursor->wl.xhot,
                                  cursor->wl.yhot);
930
931
            wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
932
                              cursor->wl.width, cursor->wl.height);
933
934
935
            wl_surface_commit(surface);
        }
    }
936
937
938
939
940
941
    else if (window->cursorMode == GLFW_CURSOR_DISABLED)
    {
        if (!isPointerLocked(window))
            lockPointer(window);
    }
    else if (window->cursorMode == GLFW_CURSOR_HIDDEN)
942
    {
943
944
        wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
                              NULL, 0, 0);
945
    }
946
947
}

948
949
950
void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string)
{
    // TODO
951
952
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Clipboard setting not implemented yet");
953
954
955
956
957
}

const char* _glfwPlatformGetClipboardString(_GLFWwindow* window)
{
    // TODO
958
959
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Clipboard getting not implemented yet");
960
961
962
    return NULL;
}

963
char** _glfwPlatformGetRequiredInstanceExtensions(uint32_t* count)
Camilla Berglund's avatar
Camilla Berglund committed
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
{
    char** extensions;

    *count = 0;

    if (!_glfw.vk.KHR_wayland_surface)
        return NULL;

    extensions = calloc(2, sizeof(char*));
    extensions[0] = strdup("VK_KHR_surface");
    extensions[1] = strdup("VK_KHR_wayland_surface");

    *count = 2;
    return extensions;
}

int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
                                                      VkPhysicalDevice device,
982
                                                      uint32_t queuefamily)
Camilla Berglund's avatar
Camilla Berglund committed
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
{
    PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR =
        (PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)
        vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceWaylandPresentationSupportKHR");
    if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR)
    {
        _glfwInputError(GLFW_API_UNAVAILABLE,
                        "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension");
        return VK_NULL_HANDLE;
    }

    return vkGetPhysicalDeviceWaylandPresentationSupportKHR(device,
                                                            queuefamily,
                                                            _glfw.wl.display);
}

VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
                                          _GLFWwindow* window,
                                          const VkAllocationCallbacks* allocator,
                                          VkSurfaceKHR* surface)
{
    VkResult err;
    VkWaylandSurfaceCreateInfoKHR sci;
    PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;

    vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)
        vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR");
    if (!vkCreateWaylandSurfaceKHR)
    {
        _glfwInputError(GLFW_API_UNAVAILABLE,
                        "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension");
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }

    memset(&sci, 0, sizeof(sci));
    sci.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
    sci.display = _glfw.wl.display;
    sci.surface = window->wl.surface;

    err = vkCreateWaylandSurfaceKHR(instance, &sci, allocator, surface);
    if (err)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Failed to create Vulkan surface: %s",
                        _glfwGetVulkanResultString(err));
    }

    return err;
}

1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050

//////////////////////////////////////////////////////////////////////////
//////                        GLFW native API                       //////
//////////////////////////////////////////////////////////////////////////

GLFWAPI struct wl_display* glfwGetWaylandDisplay(void)
{
    _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
    return _glfw.wl.display;
}

GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
{
    _GLFWwindow* window = (_GLFWwindow*) handle;
    _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
    return window->wl.surface;
}