aboutsummaryrefslogtreecommitdiff
path: root/src/cursor.c
blob: 3dca0c6f49708fff8ff308722bc9dcb0ebfb7f38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
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
#include "../include/cursor.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <X11/extensions/Xfixes.h>

// TODO: Test cursor visibility with XFixesHideCursor

static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image, bool *visible) {
    uint8_t *cursor_data = NULL;
    uint8_t *out = NULL;
    *visible = false;

    if(!x11_cursor_image)
        goto err;
        
    if(!x11_cursor_image->pixels)
        goto err;

    self->hotspot.x = x11_cursor_image->xhot;
    self->hotspot.y = x11_cursor_image->yhot;
    self->egl->glBindTexture(GL_TEXTURE_2D, self->texture_id);

    self->size.x = x11_cursor_image->width;
    self->size.y = x11_cursor_image->height;
    const unsigned long *pixels = x11_cursor_image->pixels;
    cursor_data = malloc(self->size.x * self->size.y * 4);
    if(!cursor_data)
        goto err;
    out = cursor_data;
    /* Un-premultiply alpha */
    for(int y = 0; y < self->size.y; ++y) {
        for(int x = 0; x < self->size.x; ++x) {
            uint32_t pixel = *pixels++;
            uint8_t *in = (uint8_t*)&pixel;
            uint8_t alpha = in[3];
            if(alpha == 0) {
                alpha = 1;
            } else {
                *visible = true;
            }

            *out++ = (unsigned)*in++ * 255/alpha;
            *out++ = (unsigned)*in++ * 255/alpha;
            *out++ = (unsigned)*in++ * 255/alpha;
            *out++ = *in++;
        }
    }

    // TODO: glTextureSubImage2D if same size
    self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, cursor_data);
    free(cursor_data);

    self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    self->egl->glBindTexture(GL_TEXTURE_2D, 0);
    XFree(x11_cursor_image);
    return true;

    err:
    self->egl->glBindTexture(GL_TEXTURE_2D, 0);
    if(x11_cursor_image)
        XFree(x11_cursor_image);
    return false;
}

int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) {
    int x_fixes_error_base = 0;

    assert(egl);
    assert(display);
    memset(self, 0, sizeof(*self));
    self->egl = egl;
    self->display = display;

    self->x_fixes_event_base = 0;
    if(!XFixesQueryExtension(self->display, &self->x_fixes_event_base, &x_fixes_error_base)) {
        fprintf(stderr, "gsr error: gsr_cursor_init: your X11 server is missing the XFixes extension\n");
        gsr_cursor_deinit(self);
        return -1;
    }

    self->egl->glGenTextures(1, &self->texture_id);

    XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), XFixesDisplayCursorNotifyMask);
    gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible);
    self->cursor_image_set = true;

    return 0;
}

void gsr_cursor_deinit(gsr_cursor *self) {
    if(!self->egl)
        return;

    if(self->texture_id) {
        self->egl->glDeleteTextures(1, &self->texture_id);
        self->texture_id = 0;
    }

    if(self->display)
        XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), 0);

    self->display = NULL;
    self->egl = NULL;
}

bool gsr_cursor_on_event(gsr_cursor *self, XEvent *xev) {
    bool updated = false;

    if(xev->type == self->x_fixes_event_base + XFixesCursorNotify) {
        XFixesCursorNotifyEvent *cursor_notify_event = (XFixesCursorNotifyEvent*)xev;
        if(cursor_notify_event->subtype == XFixesDisplayCursorNotify && cursor_notify_event->window == DefaultRootWindow(self->display)) {
            self->cursor_image_set = false;
        }
    }

    if(!self->cursor_image_set) {
        self->cursor_image_set = true;
        gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible);
        updated = true;
    }

    return updated;
}

void gsr_cursor_tick(gsr_cursor *self, Window relative_to) {
    Window dummy_window;
    int dummy_i;
    unsigned int dummy_u;
    XQueryPointer(self->display, relative_to, &dummy_window, &dummy_window, &dummy_i, &dummy_i, &self->position.x, &self->position.y, &dummy_u);
}