wl_window.c 19.9 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
58
59
60
61
    _GLFWwindow* window = data;
    _glfwInputFramebufferSize(window, width, height);
    _glfwInputWindowSize(window, width, height);
    _glfwPlatformSetWindowSize(window, width, height);
    _glfwInputWindowDamage(window);
62
63
}

Camilla Berglund's avatar
Camilla Berglund committed
64
65
static void handlePopupDone(void* data,
                            struct wl_shell_surface* shellSurface)
66
67
68
69
70
71
72
73
74
{
}

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

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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
static void checkScaleChange(_GLFWwindow* window)
{
    int scaledWidth, scaledHeight;
    int scale = 1;
    int i;
    int monitorScale;

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

148
149
static GLFWbool createSurface(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig)
150
{
151
152
    window->wl.surface = wl_compositor_create_surface(_glfw.wl.compositor);
    if (!window->wl.surface)
153
        return GLFW_FALSE;
154

155
156
157
158
    wl_surface_add_listener(window->wl.surface,
                            &surfaceListener,
                            window);

159
160
    wl_surface_set_user_data(window->wl.surface, window);

161
162
163
164
    window->wl.native = wl_egl_window_create(window->wl.surface,
                                             wndconfig->width,
                                             wndconfig->height);
    if (!window->wl.native)
165
        return GLFW_FALSE;
166

167
168
169
    window->wl.shell_surface = wl_shell_get_shell_surface(_glfw.wl.shell,
                                                          window->wl.surface);
    if (!window->wl.shell_surface)
170
        return GLFW_FALSE;
171

172
    wl_shell_surface_add_listener(window->wl.shell_surface,
173
174
175
                                  &shellSurfaceListener,
                                  window);

176
177
    window->wl.width = wndconfig->width;
    window->wl.height = wndconfig->height;
178
    window->wl.scale = 1;
179

180
    return GLFW_TRUE;
181
182
}

183
static int
184
createTmpfileCloexec(char* tmpname)
185
186
187
188
189
190
191
192
193
194
{
    int fd;

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

    return fd;
}

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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);
    }
}

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/*
 * 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.
 *
247
248
249
250
 * 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.
251
252
 */
int
253
createAnonymousFile(off_t size)
254
255
{
    static const char template[] = "/glfw-shared-XXXXXX";
256
257
    const char* path;
    char* name;
258
259
260
261
    int fd;
    int ret;

    path = getenv("XDG_RUNTIME_DIR");
262
263
    if (!path)
    {
264
265
266
267
        errno = ENOENT;
        return -1;
    }

268
    name = calloc(strlen(path) + sizeof(template), 1);
269
270
271
    strcpy(name, path);
    strcat(name, template);

272
    fd = createTmpfileCloexec(name);
273
274
275
276
277
278

    free(name);

    if (fd < 0)
        return -1;
    ret = posix_fallocate(fd, 0, size);
279
280
    if (ret != 0)
    {
281
282
283
284
285
286
        close(fd);
        errno = ret;
        return -1;
    }
    return fd;
}
287

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// 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;
}

310
311
312
313
314
315
316
317
318
//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
                              const _GLFWctxconfig* ctxconfig,
                              const _GLFWfbconfig* fbconfig)
{
319
320
321
    if (!createSurface(window, wndconfig))
        return GLFW_FALSE;

322
323
    if (ctxconfig->api != GLFW_NO_API)
    {
324
        if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
325
326
            return GLFW_FALSE;
    }
327
328
329

    if (wndconfig->monitor)
    {
Camilla Berglund's avatar
Camilla Berglund committed
330
331
332
333
334
        wl_shell_surface_set_fullscreen(
            window->wl.shell_surface,
            WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
            0,
            wndconfig->monitor->wl.output);
335
336
337
    }
    else
    {
338
        wl_shell_surface_set_toplevel(window->wl.shell_surface);
339
340
    }

341
342
    window->wl.currentCursor = NULL;

343
344
345
346
    window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
    window->wl.monitorsCount = 0;
    window->wl.monitorsSize = 1;

347
    return GLFW_TRUE;
348
349
350
351
}

void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
352
353
354
    if (window == _glfw.wl.pointerFocus)
    {
        _glfw.wl.pointerFocus = NULL;
355
        _glfwInputCursorEnter(window, GLFW_FALSE);
356
357
358
359
    }
    if (window == _glfw.wl.keyboardFocus)
    {
        _glfw.wl.keyboardFocus = NULL;
360
        _glfwInputWindowFocus(window, GLFW_FALSE);
361
362
    }

363
    _glfwDestroyContextEGL(window);
364

365
366
    if (window->wl.native)
        wl_egl_window_destroy(window->wl.native);
367

368
369
    if (window->wl.shell_surface)
        wl_shell_surface_destroy(window->wl.shell_surface);
370

371
372
    if (window->wl.surface)
        wl_surface_destroy(window->wl.surface);
373
374

    free(window->wl.monitors);
375
376
377
378
}

void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
379
    wl_shell_surface_set_title(window->wl.shell_surface, title);
380
381
382
383
}

void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
{
384
385
    // A Wayland client is not aware of its position, so just warn and leave it
    // as (0, 0)
386
387

    _glfwInputError(GLFW_PLATFORM_ERROR,
388
                    "Wayland: Window position retrieval not supported");
389
390
391
392
}

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

    _glfwInputError(GLFW_PLATFORM_ERROR,
396
                    "Wayland: Window position setting not supported");
397
398
399
400
401
}

void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
{
    if (width)
402
        *width = window->wl.width;
403
    if (height)
404
        *height = window->wl.height;
405
406
407
408
}

void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
409
410
    int scaledWidth = width * window->wl.scale;
    int scaledHeight = height * window->wl.scale;
411
412
    window->wl.width = width;
    window->wl.height = height;
413
414
    wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
    _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
415
416
}

417
418
419
420
421
422
423
424
425
426
427
428
429
430
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
                                      int minwidth, int minheight,
                                      int maxwidth, int maxheight)
{
    // TODO
    fprintf(stderr, "_glfwPlatformSetWindowSizeLimits not implemented yet\n");
}

void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
{
    // TODO
    fprintf(stderr, "_glfwPlatformSetWindowAspectRatio not implemented yet\n");
}

431
432
433
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
{
    _glfwPlatformGetWindowSize(window, width, height);
434
435
    *width *= window->wl.scale;
    *height *= window->wl.scale;
436
437
}

438
439
440
441
442
443
444
445
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
                                     int* left, int* top,
                                     int* right, int* bottom)
{
    // TODO
    fprintf(stderr, "_glfwPlatformGetWindowFrameSize not implemented yet\n");
}

446
447
448
449
450
451
452
453
454
455
456
457
458
459
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformIconifyWindow not implemented yet\n");
}

void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformRestoreWindow not implemented yet\n");
}

void _glfwPlatformShowWindow(_GLFWwindow* window)
{
460
    wl_shell_surface_set_toplevel(window->wl.shell_surface);
461
462
}

463
464
465
466
467
468
void _glfwPlatformUnhideWindow(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformUnhideWindow not implemented yet\n");
}

469
470
void _glfwPlatformHideWindow(_GLFWwindow* window)
{
471
472
    wl_surface_attach(window->wl.surface, NULL, 0, 0);
    wl_surface_commit(window->wl.surface);
473
474
}

475
476
477
int _glfwPlatformWindowFocused(_GLFWwindow* window)
{
    // TODO
478
    return GLFW_FALSE;
479
480
481
482
483
}

int _glfwPlatformWindowIconified(_GLFWwindow* window)
{
    // TODO
484
    return GLFW_FALSE;
485
486
487
488
489
}

int _glfwPlatformWindowVisible(_GLFWwindow* window)
{
    // TODO
490
    return GLFW_FALSE;
491
492
}

493
494
void _glfwPlatformPollEvents(void)
{
495
    handleEvents(0);
496
497
498
499
}

void _glfwPlatformWaitEvents(void)
{
500
    handleEvents(-1);
501
502
503
504
}

void _glfwPlatformPostEmptyEvent(void)
{
505
    wl_display_sync(_glfw.wl.display);
506
507
}

508
509
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
{
510
    if (xpos)
511
        *xpos = window->wl.cursorPosX;
512
    if (ypos)
513
        *ypos = window->wl.cursorPosY;
514
515
}

516
517
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
{
Camilla Berglund's avatar
Camilla Berglund committed
518
    // A Wayland client can not set the cursor position
519
    _glfwInputError(GLFW_PLATFORM_ERROR,
520
                    "Wayland: Cursor position setting not supported");
521
522
}

523
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
524
{
525
    _glfwPlatformSetCursor(window, window->wl.currentCursor);
526
}
Camilla Berglund's avatar
Camilla Berglund committed
527

Camilla Berglund's avatar
Camilla Berglund committed
528
529
530
531
532
533
const char* _glfwPlatformGetKeyName(int key, int scancode)
{
    // TODO
    return NULL;
}

534
535
536
537
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
                              const GLFWimage* image,
                              int xhot, int yhot)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
538
    struct wl_shm_pool* pool;
539
540
    int stride = image->width * 4;
    int length = image->width * image->height * 4;
Jonas Ådahl's avatar
Jonas Ådahl committed
541
    void* data;
542
543
    int fd, i;

544
    fd = createAnonymousFile(length);
Jonas Ådahl's avatar
Jonas Ådahl committed
545
546
    if (fd < 0)
    {
547
        _glfwInputError(GLFW_PLATFORM_ERROR,
Jonas Ådahl's avatar
Jonas Ådahl committed
548
549
                        "Wayland: Creating a buffer file for %d B failed: %m\n",
                        length);
550
        return GLFW_FALSE;
551
552
553
    }

    data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Jonas Ådahl's avatar
Jonas Ådahl committed
554
555
    if (data == MAP_FAILED)
    {
556
        _glfwInputError(GLFW_PLATFORM_ERROR,
Jonas Ådahl's avatar
Jonas Ådahl committed
557
                        "Wayland: Cursor mmap failed: %m\n");
558
        close(fd);
559
        return GLFW_FALSE;
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
    }

    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)
    {
        *target++ = source[2];
        *target++ = source[1];
        *target++ = source[0];
        *target++ = source[3];
    }

Jonas Ådahl's avatar
Jonas Ådahl committed
575
576
577
578
579
    cursor->wl.buffer =
        wl_shm_pool_create_buffer(pool, 0,
                                  image->width,
                                  image->height,
                                  stride, WL_SHM_FORMAT_ARGB8888);
580
581
582
583
584
585
586
    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;
587
    return GLFW_TRUE;
588
589
}

590
591
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
592
    struct wl_cursor* standardCursor;
593

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
594
595
596
597
    standardCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
                                                translateCursorShape(shape));
    if (!standardCursor)
    {
598
599
600
601
602
603
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Standard cursor \"%s\" not found",
                        translateCursorShape(shape));
        return GLFW_FALSE;
    }

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
604
    cursor->wl.image = standardCursor->images[0];
605
    return GLFW_TRUE;
606
607
}

608
609
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
610
611
612
613
    // If it's a standard cursor we don't need to do anything here
    if (cursor->wl.image)
        return;

614
615
    if (cursor->wl.buffer)
        wl_buffer_destroy(cursor->wl.buffer);
616
617
618
619
}

void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
620
    struct wl_buffer* buffer;
621
    struct wl_cursor* defaultCursor;
Jonas Ådahl's avatar
Jonas Ådahl committed
622
623
    struct wl_cursor_image* image;
    struct wl_surface* surface = _glfw.wl.cursorSurface;
624
625
626
627
628
629
630
631
632
633
634
635
636

    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;

    if (window->cursorMode == GLFW_CURSOR_NORMAL)
    {
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
637
638
639
        if (cursor)
            image = cursor->wl.image;
        else
640
        {
641
642
643
644
645
646
647
648
649
650
651
652
653
            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)
        {
654
655
656
657
            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
658
659
660
                                  surface,
                                  image->hotspot_x,
                                  image->hotspot_y);
661
662
            wl_surface_attach(surface, buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
663
                              image->width, image->height);
664
665
666
667
668
            wl_surface_commit(surface);
        }
        else
        {
            wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
Jonas Ådahl's avatar
Jonas Ådahl committed
669
670
671
                                  surface,
                                  cursor->wl.xhot,
                                  cursor->wl.yhot);
672
673
            wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
674
                              cursor->wl.width, cursor->wl.height);
675
676
677
678
679
680
681
            wl_surface_commit(surface);
        }
    }
    else /* Cursor is hidden set cursor surface to NULL */
    {
        wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial, NULL, 0, 0);
    }
682
683
}

684
685
686
687
688
689
690
691
692
693
694
695
696
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;
}

697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714

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