summaryrefslogtreecommitdiff
path: root/src/wayland/popup.c
blob: f0ef305dfc19db207256bd3d4082e83932de89a2 (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
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include "internal.h"

static void popup_configure(void *data, struct xdg_popup *popup,
        int32_t x, int32_t y, int32_t width, int32_t height)
{
    (void)data; (void)popup; (void)x; (void)y; (void)width; (void)height;
}

static void popup_done(void *data, struct xdg_popup *popup)
{
    (void)popup;
    surface_fail(data);
}

static void popup_repositioned(void *data, struct xdg_popup *popup, uint32_t token)
{
    (void)data; (void)popup; (void)token;
}

static const struct xdg_popup_listener popup_listener = {
    .configure = popup_configure,
    .popup_done = popup_done,
    .repositioned = popup_repositioned,
};

static bool popup_spec_valid(const struct yasl_popup_spec *spec)
{
    return spec && spec->size.x > 0 && spec->size.y > 0 &&
           spec->content_offset.x >= 0 && spec->content_offset.y >= 0 &&
           spec->content_size.x >= 0 && spec->content_size.y >= 0 &&
           spec->content_offset.x + spec->content_size.x <= spec->size.x &&
           spec->content_offset.y + spec->content_size.y <= spec->size.y;
}

static bool popup_apply_spec(struct wayland_surface *w, const struct yasl_popup_spec *spec)
{
    uint32_t scale = w->parent->scale_120;
    struct yasl_vector2i logical = {
        .x = logical_px_ceil(spec->size.x, scale),
        .y = logical_px_ceil(spec->size.y, scale),
    };
    int width = device_px(logical.x, scale);
    int height = device_px(logical.y, scale);
    size_t bytes;
    if (logical.x <= 0 || logical.y <= 0 || !buffer_size(width, height, &bytes))
        return false;
    struct yasl_vector2i content = spec->content_size;
    if (!content.x) content.x = spec->size.x - spec->content_offset.x;
    if (!content.y) content.y = spec->size.y - spec->content_offset.y;
    bool rescaled = scale != w->scale_120;
    w->scale_120 = w->pending_scale_120 = scale;
    w->scale_factor = (double)scale / YASL_SCALE_UNIT;
    w->width = width;
    w->height = height;
    w->logical_width = logical.x;
    w->logical_height = logical.y;
    w->content = (struct yasl_rect2i){
        .x = spec->content_offset.x, .y = spec->content_offset.y,
        .width = content.x, .height = content.y
    };
    for (int i = 0; i < NBUF; i++)
        w->buffers[i].presented = false;
    if (w->viewport)
        wp_viewport_set_destination(w->viewport, logical.x, logical.y);
    else if ((rescaled || !w->configured) && scale % YASL_SCALE_UNIT == 0 &&
             wl_proxy_get_version((struct wl_proxy *)w->surface) >=
             WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION)
        wl_surface_set_buffer_scale(w->surface, (int32_t)(scale / YASL_SCALE_UNIT));
    return true;
}

static void popup_set_geometry(struct wayland_surface *w)
{
    uint32_t scale = w->scale_120;
    int gx = yasl_scale_to_logical(w->content.x, scale);
    int gy = yasl_scale_to_logical(w->content.y, scale);
    int gw = yasl_scale_to_logical(w->content.width, scale);
    int gh = yasl_scale_to_logical(w->content.height, scale);
    if (gw < 1) gw = 1;
    if (gh < 1) gh = 1;
    if (gx + gw > w->logical_width)
        gx = w->logical_width - gw > 0 ? w->logical_width - gw : 0;
    if (gy + gh > w->logical_height)
        gy = w->logical_height - gh > 0 ? w->logical_height - gh : 0;
    xdg_surface_set_window_geometry(w->xdg_surface, gx, gy, gw, gh);
}

static struct xdg_positioner *popup_positioner(struct wayland_surface *w,
        const struct yasl_popup_spec *spec)
{
    struct wayland_surface *parent = w->parent;
    struct xdg_positioner *pos = xdg_wm_base_create_positioner(w->conn->wm_base);
    if (!pos)
        return NULL;
    uint32_t scale = parent->scale_120;
    int gw = yasl_scale_to_logical(w->content.width, scale);
    int gh = yasl_scale_to_logical(w->content.height, scale);
    xdg_positioner_set_size(pos, gw > 0 ? gw : 1, gh > 0 ? gh : 1);
    struct yasl_vector2i at = {
        .x = spec->anchor.x - parent->content.x,
        .y = spec->anchor.y - parent->content.y,
    };
    int aw = yasl_scale_to_logical(spec->anchor.width, scale);
    int ah = yasl_scale_to_logical(spec->anchor.height, scale);
    xdg_positioner_set_anchor_rect(pos,
        yasl_scale_to_logical(at.x, scale),
        yasl_scale_to_logical(at.y, scale),
        aw > 0 ? aw : 1, ah > 0 ? ah : 1);
    xdg_positioner_set_anchor(pos, spec->beside ? XDG_POSITIONER_ANCHOR_TOP_RIGHT
                                                : XDG_POSITIONER_ANCHOR_TOP_LEFT);
    xdg_positioner_set_gravity(pos, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
    xdg_positioner_set_constraint_adjustment(pos,
        XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X |
        XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y |
        XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X |
        XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
    if (xdg_positioner_get_version(pos) >= XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION)
        xdg_positioner_set_reactive(pos);
    return pos;
}

struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *parent,
        const struct yasl_popup_spec *spec)
{
    struct wayland_display *c = parent ? parent->conn : NULL;
    struct seat *seat = c ? conn_active_seat(c) : NULL;
    if (!c || c->lost || parent->closed || !parent->xdg_surface || !seat ||
        !seat->input_serial || !popup_spec_valid(spec))
        return NULL;

    struct wayland_surface *w = calloc(1, sizeof(*w));
    if (!w)
        return NULL;
    w->base.display = &c->base;
    w->conn = c;
    w->conn_next = c->surfaces;
    c->surfaces = w;
    w->parent = parent;
    wayland_surface_set_callbacks(w, NULL, NULL);
    w->clock.refresh_ns = parent->clock.refresh_ns;

    w->surface = wl_compositor_create_surface(c->compositor);
    if (!w->surface || surface_init_scaling(w) < 0)
        goto err;
    w->xdg_surface = xdg_wm_base_get_xdg_surface(c->wm_base, w->surface);
    if (!w->xdg_surface)
        goto err;
    xdg_surface_add_listener(w->xdg_surface, &shell_surface_listener, w);
    if (!popup_apply_spec(w, spec))
        goto err;
    struct xdg_positioner *pos = popup_positioner(w, spec);
    if (!pos)
        goto err;
    w->popup = xdg_surface_get_popup(w->xdg_surface, parent->xdg_surface, pos);
    xdg_positioner_destroy(pos);
    if (!w->popup)
        goto err;
    xdg_popup_add_listener(w->popup, &popup_listener, w);
    popup_set_geometry(w);
    xdg_popup_grab(w->popup, seat->proxy, seat->input_serial);
    w->want_draw = true;
    wl_surface_commit(w->surface);
    return w;

err:
    wayland_surface_destroy(w);
    return NULL;
}

bool wayland_surface_popup_reposition(struct wayland_surface *w, const struct yasl_popup_spec *spec)
{
    if (!w || !w->popup || w->closed || !w->conn || w->conn->lost ||
        !popup_spec_valid(spec) ||
        xdg_popup_get_version(w->popup) < XDG_POPUP_REPOSITION_SINCE_VERSION)
        return false;
    struct yasl_popup_spec old = {
        .size = { .x = w->width, .y = w->height },
        .content_offset = { .x = w->content.x, .y = w->content.y },
        .content_size = { .x = w->content.width, .y = w->content.height },
    };
    if (!popup_apply_spec(w, spec)) {
        popup_apply_spec(w, &old);
        return false;
    }
    struct xdg_positioner *pos = popup_positioner(w, spec);
    if (!pos)
        return false;
    popup_set_geometry(w);
    static uint32_t token;
    xdg_popup_reposition(w->popup, pos, ++token);
    xdg_positioner_destroy(pos);
    wayland_surface_schedule_redraw(w);
    return true;
}