wl_window.c 29.6 KB
Newer Older
1
//========================================================================
Camilla Berglund's avatar
Camilla Berglund committed
2
// GLFW 3.2 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
static GLFWbool createSurface(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig)
179
{
180
181
    window->wl.surface = wl_compositor_create_surface(_glfw.wl.compositor);
    if (!window->wl.surface)
182
        return GLFW_FALSE;
183

184
185
186
187
    wl_surface_add_listener(window->wl.surface,
                            &surfaceListener,
                            window);

188
189
    wl_surface_set_user_data(window->wl.surface, window);

190
191
192
193
    window->wl.native = wl_egl_window_create(window->wl.surface,
                                             wndconfig->width,
                                             wndconfig->height);
    if (!window->wl.native)
194
        return GLFW_FALSE;
195

196
197
198
199
200
201
202
203
204
    window->wl.width = wndconfig->width;
    window->wl.height = wndconfig->height;
    window->wl.scale = 1;

    return GLFW_TRUE;
}

static GLFWbool createShellSurface(_GLFWwindow* window)
{
205
206
207
    window->wl.shell_surface = wl_shell_get_shell_surface(_glfw.wl.shell,
                                                          window->wl.surface);
    if (!window->wl.shell_surface)
208
        return GLFW_FALSE;
209

210
    wl_shell_surface_add_listener(window->wl.shell_surface,
211
212
213
                                  &shellSurfaceListener,
                                  window);

214
215
216
217
218
219
220
221
222
223
224
    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);
    }
225
226
227
228
    else if (window->wl.maximized)
    {
        wl_shell_surface_set_maximized(window->wl.shell_surface, NULL);
    }
229
230
231
232
    else
    {
        wl_shell_surface_set_toplevel(window->wl.shell_surface);
    }
233

234
    return GLFW_TRUE;
235
236
}

237
static int
238
createTmpfileCloexec(char* tmpname)
239
240
241
242
243
244
245
246
247
248
{
    int fd;

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

    return fd;
}

249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
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);
    }
}

286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/*
 * 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.
 *
301
302
303
304
 * 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.
305
306
 */
int
307
createAnonymousFile(off_t size)
308
309
{
    static const char template[] = "/glfw-shared-XXXXXX";
310
311
    const char* path;
    char* name;
312
313
314
315
    int fd;
    int ret;

    path = getenv("XDG_RUNTIME_DIR");
316
317
    if (!path)
    {
318
319
320
321
        errno = ENOENT;
        return -1;
    }

322
    name = calloc(strlen(path) + sizeof(template), 1);
323
324
325
    strcpy(name, path);
    strcat(name, template);

326
    fd = createTmpfileCloexec(name);
327
328
329
330
331
332

    free(name);

    if (fd < 0)
        return -1;
    ret = posix_fallocate(fd, 0, size);
333
334
    if (ret != 0)
    {
335
336
337
338
339
340
        close(fd);
        errno = ret;
        return -1;
    }
    return fd;
}
341

342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
// 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;
}

364
365
366
367
368
369
370
371
372
//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
                              const _GLFWctxconfig* ctxconfig,
                              const _GLFWfbconfig* fbconfig)
{
373
374
375
    if (!createSurface(window, wndconfig))
        return GLFW_FALSE;

376
377
    if (ctxconfig->api != GLFW_NO_API)
    {
378
        if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
379
380
            return GLFW_FALSE;
    }
381

382
383
384
385
    if (wndconfig->title)
        window->wl.title = strdup(wndconfig->title);

    if (wndconfig->visible)
386
    {
387
388
389
390
        if (!createShellSurface(window))
            return GLFW_FALSE;

        window->wl.visible = GLFW_TRUE;
391
392
393
    }
    else
    {
394
395
        window->wl.shell_surface = NULL;
        window->wl.visible = GLFW_FALSE;
396
397
    }

398
399
    window->wl.currentCursor = NULL;

400
401
402
403
    window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
    window->wl.monitorsCount = 0;
    window->wl.monitorsSize = 1;

404
    return GLFW_TRUE;
405
406
407
408
}

void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
409
410
411
    if (window == _glfw.wl.pointerFocus)
    {
        _glfw.wl.pointerFocus = NULL;
412
        _glfwInputCursorEnter(window, GLFW_FALSE);
413
414
415
416
    }
    if (window == _glfw.wl.keyboardFocus)
    {
        _glfw.wl.keyboardFocus = NULL;
417
        _glfwInputWindowFocus(window, GLFW_FALSE);
418
419
    }

420
    _glfwDestroyContextEGL(window);
421

422
423
    if (window->wl.native)
        wl_egl_window_destroy(window->wl.native);
424

425
426
    if (window->wl.shell_surface)
        wl_shell_surface_destroy(window->wl.shell_surface);
427

428
429
    if (window->wl.surface)
        wl_surface_destroy(window->wl.surface);
430

431
    free(window->wl.title);
432
    free(window->wl.monitors);
433
434
435
436
}

void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
437
438
439
440
441
    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);
442
443
}

Camilla Berglund's avatar
Camilla Berglund committed
444
445
446
447
448
449
450
void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
                                int count, const GLFWimage* images)
{
    // TODO
    fprintf(stderr, "_glfwPlatformSetWindowIcon not implemented yet\n");
}

451
452
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
{
453
454
    // A Wayland client is not aware of its position, so just warn and leave it
    // as (0, 0)
455
456

    _glfwInputError(GLFW_PLATFORM_ERROR,
457
                    "Wayland: Window position retrieval not supported");
458
459
460
461
}

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

    _glfwInputError(GLFW_PLATFORM_ERROR,
465
                    "Wayland: Window position setting not supported");
466
467
468
469
470
}

void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
{
    if (width)
471
        *width = window->wl.width;
472
    if (height)
473
        *height = window->wl.height;
474
475
476
477
}

void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
478
479
    int scaledWidth = width * window->wl.scale;
    int scaledHeight = height * window->wl.scale;
480
481
    window->wl.width = width;
    window->wl.height = height;
482
483
    wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
    _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
484
485
}

486
487
488
489
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
                                      int minwidth, int minheight,
                                      int maxwidth, int maxheight)
{
490
491
    // TODO: find out how to trigger a resize.
    // The actual limits are checked in the wl_shell_surface::configure handler.
492
493
494
495
}

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

500
501
502
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
{
    _glfwPlatformGetWindowSize(window, width, height);
503
504
    *width *= window->wl.scale;
    *height *= window->wl.scale;
505
506
}

507
508
509
510
511
512
513
514
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
                                     int* left, int* top,
                                     int* right, int* bottom)
{
    // TODO
    fprintf(stderr, "_glfwPlatformGetWindowFrameSize not implemented yet\n");
}

515
516
517
518
519
520
521
522
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformIconifyWindow not implemented yet\n");
}

void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{
523
524
525
526
527
528
529
530
    // 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;
    }
531
532
}

533
534
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
{
535
536
537
538
539
540
541
542
543
    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;
    }
544
545
}

546
547
void _glfwPlatformShowWindow(_GLFWwindow* window)
{
548
549
550
551
552
553
    if (!window->monitor)
    {
        if (!window->wl.shell_surface)
            createShellSurface(window);
        window->wl.visible = GLFW_TRUE;
    }
554
555
556
557
}

void _glfwPlatformHideWindow(_GLFWwindow* window)
{
558
559
560
561
562
563
    if (!window->monitor)
    {
        if (window->wl.shell_surface)
            wl_shell_surface_destroy(window->wl.shell_surface);
        window->wl.visible = GLFW_FALSE;
    }
564
565
}

Camilla Berglund's avatar
Camilla Berglund committed
566
567
568
569
570
571
void _glfwPlatformFocusWindow(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformFocusWindow not implemented yet\n");
}

Camilla Berglund's avatar
Camilla Berglund committed
572
573
574
575
576
577
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
                                   _GLFWmonitor* monitor,
                                   int xpos, int ypos,
                                   int width, int height,
                                   int refreshRate)
{
578
579
580
581
582
583
584
585
586
587
588
589
590
    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
591
592
}

593
594
595
int _glfwPlatformWindowFocused(_GLFWwindow* window)
{
    // TODO
596
    return GLFW_FALSE;
597
598
599
600
601
}

int _glfwPlatformWindowIconified(_GLFWwindow* window)
{
    // TODO
602
    return GLFW_FALSE;
603
604
605
606
}

int _glfwPlatformWindowVisible(_GLFWwindow* window)
{
607
    return window->wl.visible;
608
609
}

610
611
int _glfwPlatformWindowMaximized(_GLFWwindow* window)
{
612
    return window->wl.maximized;
613
614
}

615
616
void _glfwPlatformPollEvents(void)
{
617
    handleEvents(0);
618
619
620
621
}

void _glfwPlatformWaitEvents(void)
{
622
    handleEvents(-1);
623
624
}

Camilla Berglund's avatar
Camilla Berglund committed
625
626
627
628
629
void _glfwPlatformWaitEventsTimeout(double timeout)
{
    handleEvents((int) (timeout * 1e3));
}

630
631
void _glfwPlatformPostEmptyEvent(void)
{
632
    wl_display_sync(_glfw.wl.display);
633
634
}

635
636
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
{
637
    if (xpos)
638
        *xpos = window->wl.cursorPosX;
639
    if (ypos)
640
        *ypos = window->wl.cursorPosY;
641
642
}

643
644
static GLFWbool isPointerLocked(_GLFWwindow* window);

645
646
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
{
647
648
649
650
651
652
653
    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);
    }
654
655
}

656
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
657
{
658
    _glfwPlatformSetCursor(window, window->wl.currentCursor);
659
}
Camilla Berglund's avatar
Camilla Berglund committed
660

Camilla Berglund's avatar
Camilla Berglund committed
661
662
663
664
665
666
const char* _glfwPlatformGetKeyName(int key, int scancode)
{
    // TODO
    return NULL;
}

667
668
669
670
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
                              const GLFWimage* image,
                              int xhot, int yhot)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
671
    struct wl_shm_pool* pool;
672
673
    int stride = image->width * 4;
    int length = image->width * image->height * 4;
Jonas Ådahl's avatar
Jonas Ådahl committed
674
    void* data;
675
676
    int fd, i;

677
    fd = createAnonymousFile(length);
Jonas Ådahl's avatar
Jonas Ådahl committed
678
679
    if (fd < 0)
    {
680
        _glfwInputError(GLFW_PLATFORM_ERROR,
Jonas Ådahl's avatar
Jonas Ådahl committed
681
682
                        "Wayland: Creating a buffer file for %d B failed: %m\n",
                        length);
683
        return GLFW_FALSE;
684
685
686
    }

    data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Jonas Ådahl's avatar
Jonas Ådahl committed
687
688
    if (data == MAP_FAILED)
    {
689
        _glfwInputError(GLFW_PLATFORM_ERROR,
Jonas Ådahl's avatar
Jonas Ådahl committed
690
                        "Wayland: Cursor mmap failed: %m\n");
691
        close(fd);
692
        return GLFW_FALSE;
693
694
695
696
697
698
699
700
701
    }

    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
702
        unsigned int alpha = source[3];
703

Camilla Berglund's avatar
Camilla Berglund committed
704
705
706
707
        *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;
708
709
    }

Jonas Ådahl's avatar
Jonas Ådahl committed
710
711
712
713
714
    cursor->wl.buffer =
        wl_shm_pool_create_buffer(pool, 0,
                                  image->width,
                                  image->height,
                                  stride, WL_SHM_FORMAT_ARGB8888);
715
716
717
718
719
720
721
    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;
722
    return GLFW_TRUE;
723
724
}

725
726
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
727
    struct wl_cursor* standardCursor;
728

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
729
730
731
732
    standardCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
                                                translateCursorShape(shape));
    if (!standardCursor)
    {
733
734
735
736
737
738
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Standard cursor \"%s\" not found",
                        translateCursorShape(shape));
        return GLFW_FALSE;
    }

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
739
    cursor->wl.image = standardCursor->images[0];
740
    return GLFW_TRUE;
741
742
}

743
744
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
745
746
747
748
    // If it's a standard cursor we don't need to do anything here
    if (cursor->wl.image)
        return;

749
750
    if (cursor->wl.buffer)
        wl_buffer_destroy(cursor->wl.buffer);
751
752
}

753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
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
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;

    _glfwInputCursorMotion(window,
                           wl_fixed_to_double(dxUnaccel),
                           wl_fixed_to_double(dyUnaccel));
}

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;
}

850
851
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
852
    struct wl_buffer* buffer;
853
    struct wl_cursor* defaultCursor;
Jonas Ådahl's avatar
Jonas Ådahl committed
854
855
    struct wl_cursor_image* image;
    struct wl_surface* surface = _glfw.wl.cursorSurface;
856
857
858
859
860
861
862
863
864
865
866

    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;

867
868
869
870
    // Unlock possible pointer lock if no longer disabled.
    if (window->cursorMode != GLFW_CURSOR_DISABLED && isPointerLocked(window))
        unlockPointer(window);

871
872
    if (window->cursorMode == GLFW_CURSOR_NORMAL)
    {
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
873
874
875
        if (cursor)
            image = cursor->wl.image;
        else
876
        {
877
878
879
880
881
882
883
884
885
886
887
888
889
            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)
        {
890
891
892
893
            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
894
895
896
                                  surface,
                                  image->hotspot_x,
                                  image->hotspot_y);
897
898
            wl_surface_attach(surface, buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
899
                              image->width, image->height);
900
901
902
903
904
            wl_surface_commit(surface);
        }
        else
        {
            wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
Jonas Ådahl's avatar
Jonas Ådahl committed
905
906
907
                                  surface,
                                  cursor->wl.xhot,
                                  cursor->wl.yhot);
908
909
            wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
910
                              cursor->wl.width, cursor->wl.height);
911
912
913
            wl_surface_commit(surface);
        }
    }
914
915
916
917
918
919
    else if (window->cursorMode == GLFW_CURSOR_DISABLED)
    {
        if (!isPointerLocked(window))
            lockPointer(window);
    }
    else if (window->cursorMode == GLFW_CURSOR_HIDDEN)
920
    {
921
922
        wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
                              NULL, 0, 0);
923
    }
924
925
}

926
927
928
929
930
931
932
933
934
935
936
937
938
void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string)
{
    // TODO
    fprintf(stderr, "_glfwPlatformSetClipboardString not implemented yet\n");
}

const char* _glfwPlatformGetClipboardString(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformGetClipboardString not implemented yet\n");
    return NULL;
}

939
char** _glfwPlatformGetRequiredInstanceExtensions(uint32_t* count)
Camilla Berglund's avatar
Camilla Berglund committed
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
{
    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,
958
                                                      uint32_t queuefamily)
Camilla Berglund's avatar
Camilla Berglund committed
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
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
{
    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;
}

1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026

//////////////////////////////////////////////////////////////////////////
//////                        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;
}