wl_window.c 17.2 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
static GLFWbool createSurface(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig)
77
{
78
79
    window->wl.surface = wl_compositor_create_surface(_glfw.wl.compositor);
    if (!window->wl.surface)
80
        return GLFW_FALSE;
81

82
83
    wl_surface_set_user_data(window->wl.surface, window);

84
85
86
87
    window->wl.native = wl_egl_window_create(window->wl.surface,
                                             wndconfig->width,
                                             wndconfig->height);
    if (!window->wl.native)
88
        return GLFW_FALSE;
89

90
91
92
    window->wl.shell_surface = wl_shell_get_shell_surface(_glfw.wl.shell,
                                                          window->wl.surface);
    if (!window->wl.shell_surface)
93
        return GLFW_FALSE;
94

95
    wl_shell_surface_add_listener(window->wl.shell_surface,
96
97
98
                                  &shellSurfaceListener,
                                  window);

99
100
    window->wl.width = wndconfig->width;
    window->wl.height = wndconfig->height;
101

102
    return GLFW_TRUE;
103
104
}

105
static int
106
createTmpfileCloexec(char* tmpname)
107
108
109
110
111
112
113
114
115
116
{
    int fd;

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

    return fd;
}

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

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
 * 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.
 *
169
170
171
172
 * 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.
173
174
 */
int
175
createAnonymousFile(off_t size)
176
177
{
    static const char template[] = "/glfw-shared-XXXXXX";
178
179
    const char* path;
    char* name;
180
181
182
183
    int fd;
    int ret;

    path = getenv("XDG_RUNTIME_DIR");
184
185
    if (!path)
    {
186
187
188
189
        errno = ENOENT;
        return -1;
    }

190
    name = calloc(strlen(path) + sizeof(template), 1);
191
192
193
    strcpy(name, path);
    strcat(name, template);

194
    fd = createTmpfileCloexec(name);
195
196
197
198
199
200

    free(name);

    if (fd < 0)
        return -1;
    ret = posix_fallocate(fd, 0, size);
201
202
    if (ret != 0)
    {
203
204
205
206
207
208
        close(fd);
        errno = ret;
        return -1;
    }
    return fd;
}
209

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// 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;
}

232
233
234
235
236
237
238
239
240
//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
                              const _GLFWctxconfig* ctxconfig,
                              const _GLFWfbconfig* fbconfig)
{
241
242
243
    if (!createSurface(window, wndconfig))
        return GLFW_FALSE;

244
245
246
247
248
    if (ctxconfig->api != GLFW_NO_API)
    {
        if (!_glfwCreateContext(window, ctxconfig, fbconfig))
            return GLFW_FALSE;
    }
249
250
251

    if (wndconfig->monitor)
    {
Camilla Berglund's avatar
Camilla Berglund committed
252
253
254
255
256
        wl_shell_surface_set_fullscreen(
            window->wl.shell_surface,
            WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
            0,
            wndconfig->monitor->wl.output);
257
258
259
    }
    else
    {
260
        wl_shell_surface_set_toplevel(window->wl.shell_surface);
261
262
    }

263
264
    window->wl.currentCursor = NULL;

265
    return GLFW_TRUE;
266
267
268
269
}

void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
270
271
272
    if (window == _glfw.wl.pointerFocus)
    {
        _glfw.wl.pointerFocus = NULL;
273
        _glfwInputCursorEnter(window, GLFW_FALSE);
274
275
276
277
    }
    if (window == _glfw.wl.keyboardFocus)
    {
        _glfw.wl.keyboardFocus = NULL;
278
        _glfwInputWindowFocus(window, GLFW_FALSE);
279
280
    }

281
282
    _glfwDestroyContext(window);

283
284
    if (window->wl.native)
        wl_egl_window_destroy(window->wl.native);
285

286
287
    if (window->wl.shell_surface)
        wl_shell_surface_destroy(window->wl.shell_surface);
288

289
290
    if (window->wl.surface)
        wl_surface_destroy(window->wl.surface);
291
292
293
294
}

void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
295
    wl_shell_surface_set_title(window->wl.shell_surface, title);
296
297
298
299
}

void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
{
300
301
    // A Wayland client is not aware of its position, so just warn and leave it
    // as (0, 0)
302
303

    _glfwInputError(GLFW_PLATFORM_ERROR,
304
                    "Wayland: Window position retrieval not supported");
305
306
307
308
}

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

    _glfwInputError(GLFW_PLATFORM_ERROR,
312
                    "Wayland: Window position setting not supported");
313
314
315
316
317
}

void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
{
    if (width)
318
        *width = window->wl.width;
319
    if (height)
320
        *height = window->wl.height;
321
322
323
324
}

void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
325
326
327
    wl_egl_window_resize(window->wl.native, width, height, 0, 0);
    window->wl.width = width;
    window->wl.height = height;
328
329
}

330
331
332
333
334
335
336
337
338
339
340
341
342
343
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");
}

344
345
346
347
348
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
{
    _glfwPlatformGetWindowSize(window, width, height);
}

349
350
351
352
353
354
355
356
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
                                     int* left, int* top,
                                     int* right, int* bottom)
{
    // TODO
    fprintf(stderr, "_glfwPlatformGetWindowFrameSize not implemented yet\n");
}

357
358
359
360
361
362
363
364
365
366
367
368
369
370
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)
{
371
    wl_shell_surface_set_toplevel(window->wl.shell_surface);
372
373
}

374
375
376
377
378
379
void _glfwPlatformUnhideWindow(_GLFWwindow* window)
{
    // TODO
    fprintf(stderr, "_glfwPlatformUnhideWindow not implemented yet\n");
}

380
381
void _glfwPlatformHideWindow(_GLFWwindow* window)
{
382
383
    wl_surface_attach(window->wl.surface, NULL, 0, 0);
    wl_surface_commit(window->wl.surface);
384
385
}

386
387
388
int _glfwPlatformWindowFocused(_GLFWwindow* window)
{
    // TODO
389
    return GLFW_FALSE;
390
391
392
393
394
}

int _glfwPlatformWindowIconified(_GLFWwindow* window)
{
    // TODO
395
    return GLFW_FALSE;
396
397
398
399
400
}

int _glfwPlatformWindowVisible(_GLFWwindow* window)
{
    // TODO
401
    return GLFW_FALSE;
402
403
}

404
405
void _glfwPlatformPollEvents(void)
{
406
    handleEvents(0);
407
408
409
410
}

void _glfwPlatformWaitEvents(void)
{
411
    handleEvents(-1);
412
413
414
415
}

void _glfwPlatformPostEmptyEvent(void)
{
416
    wl_display_sync(_glfw.wl.display);
417
418
}

419
420
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
{
421
    if (xpos)
422
        *xpos = window->wl.cursorPosX;
423
    if (ypos)
424
        *ypos = window->wl.cursorPosY;
425
426
}

427
428
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
{
Camilla Berglund's avatar
Camilla Berglund committed
429
    // A Wayland client can not set the cursor position
430
    _glfwInputError(GLFW_PLATFORM_ERROR,
431
                    "Wayland: Cursor position setting not supported");
432
433
}

434
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
435
{
436
    _glfwPlatformSetCursor(window, window->wl.currentCursor);
437
}
Camilla Berglund's avatar
Camilla Berglund committed
438

Camilla Berglund's avatar
Camilla Berglund committed
439
440
441
442
443
444
const char* _glfwPlatformGetKeyName(int key, int scancode)
{
    // TODO
    return NULL;
}

445
446
447
448
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
                              const GLFWimage* image,
                              int xhot, int yhot)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
449
    struct wl_shm_pool* pool;
450
451
    int stride = image->width * 4;
    int length = image->width * image->height * 4;
Jonas Ådahl's avatar
Jonas Ådahl committed
452
    void* data;
453
454
    int fd, i;

455
    fd = createAnonymousFile(length);
Jonas Ådahl's avatar
Jonas Ådahl committed
456
457
    if (fd < 0)
    {
458
        _glfwInputError(GLFW_PLATFORM_ERROR,
Jonas Ådahl's avatar
Jonas Ådahl committed
459
460
                        "Wayland: Creating a buffer file for %d B failed: %m\n",
                        length);
461
        return GLFW_FALSE;
462
463
464
    }

    data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Jonas Ådahl's avatar
Jonas Ådahl committed
465
466
    if (data == MAP_FAILED)
    {
467
        _glfwInputError(GLFW_PLATFORM_ERROR,
Jonas Ådahl's avatar
Jonas Ådahl committed
468
                        "Wayland: Cursor mmap failed: %m\n");
469
        close(fd);
470
        return GLFW_FALSE;
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
    }

    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
486
487
488
489
490
    cursor->wl.buffer =
        wl_shm_pool_create_buffer(pool, 0,
                                  image->width,
                                  image->height,
                                  stride, WL_SHM_FORMAT_ARGB8888);
491
492
493
494
495
496
497
    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;
498
    return GLFW_TRUE;
499
500
}

501
502
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
503
    struct wl_cursor* standardCursor;
504

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
505
506
507
508
    standardCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
                                                translateCursorShape(shape));
    if (!standardCursor)
    {
509
510
511
512
513
514
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Standard cursor \"%s\" not found",
                        translateCursorShape(shape));
        return GLFW_FALSE;
    }

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
515
    cursor->wl.image = standardCursor->images[0];
516
    return GLFW_TRUE;
517
518
}

519
520
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
521
522
523
524
    // If it's a standard cursor we don't need to do anything here
    if (cursor->wl.image)
        return;

525
526
    if (cursor->wl.buffer)
        wl_buffer_destroy(cursor->wl.buffer);
527
528
529
530
}

void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
Jonas Ådahl's avatar
Jonas Ådahl committed
531
    struct wl_buffer* buffer;
532
    struct wl_cursor* defaultCursor;
Jonas Ådahl's avatar
Jonas Ådahl committed
533
534
    struct wl_cursor_image* image;
    struct wl_surface* surface = _glfw.wl.cursorSurface;
535
536
537
538
539
540
541
542
543
544
545
546
547

    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
548
549
550
        if (cursor)
            image = cursor->wl.image;
        else
551
        {
552
553
554
555
556
557
558
559
560
561
562
563
564
            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)
        {
565
566
567
568
            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
569
570
571
                                  surface,
                                  image->hotspot_x,
                                  image->hotspot_y);
572
573
            wl_surface_attach(surface, buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
574
                              image->width, image->height);
575
576
577
578
579
            wl_surface_commit(surface);
        }
        else
        {
            wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
Jonas Ådahl's avatar
Jonas Ådahl committed
580
581
582
                                  surface,
                                  cursor->wl.xhot,
                                  cursor->wl.yhot);
583
584
            wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
            wl_surface_damage(surface, 0, 0,
Jonas Ådahl's avatar
Jonas Ådahl committed
585
                              cursor->wl.width, cursor->wl.height);
586
587
588
589
590
591
592
            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);
    }
593
594
}

595
596
597
598
599
600
601
602
603
604
605
606
607
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;
}

608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625

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