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
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
|
#define _POSIX_C_SOURCE 200809L
#include "public_internal.h"
#include "yasl/cpu.h"
#include "yasl/gl.h"
#include "common/utf8.h"
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
static struct yasl_private_display *display;
static enum yasl_backend requested_backend;
static char *application_id;
static pthread_t owner;
static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER;
static bool pumping;
static unsigned refresh_depth;
static char *delivered_text; /* Strings of the last returned event; freed by the next poll/wait. */
static _Thread_local enum yasl_error last_error;
static _Thread_local int native_error;
void yasl_public_set_error(enum yasl_error error, int native)
{ last_error=error; native_error=native; }
bool yasl_public_fail(int error)
{
if (!error) error=EIO;
enum yasl_error code=YASL_ERROR_IO;
if (error==EINVAL || error==EOVERFLOW || error==EILSEQ) code=YASL_ERROR_INVALID;
else if (error==ENOTSUP || error==ENOSYS) code=YASL_ERROR_UNSUPPORTED;
else if (error==ENOMEM) code=YASL_ERROR_NO_MEMORY;
else if (error==EBUSY) code=YASL_ERROR_BUSY;
else if (error==EPERM) code=YASL_ERROR_WRONG_THREAD;
yasl_public_set_error(code,error); errno=error; return false;
}
enum yasl_error yasl_get_error(const char **description)
{
static const char *const messages[]={"No error","Invalid argument","Unsupported operation",
"Out of memory","Library not initialized","Operation is busy","Wrong thread",
"Input context expired","Native operation failed"};
if(description) *description=messages[last_error];
return last_error;
}
int yasl_get_native_error(void) { return native_error; }
struct yasl_private_display *yasl_public_display(void) { return display; }
bool yasl_public_owner(void)
{
if(!display) { yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0); return false; }
if(!pthread_equal(owner,pthread_self())) return yasl_public_fail(EPERM);
return true;
}
bool yasl_public_surface(yasl_surface *s,bool mutation)
{
if(!yasl_public_owner()) return false;
if(!s || s->display!=display) return yasl_public_fail(EINVAL);
if(mutation && refresh_depth) return yasl_public_fail(EBUSY);
return true;
}
static bool initialize(void)
{
if(display) return yasl_public_owner();
struct yasl_private_display_spec spec={.app_id=application_id,.backend=(enum yasl_private_backend)requested_backend};
struct yasl_private_display *d=yasl_private_display_open(NULL,&spec);
if(!d) return yasl_public_fail(errno);
pthread_mutex_lock(&lifecycle_mutex);
owner=pthread_self(); display=d;
pthread_mutex_unlock(&lifecycle_mutex);
return true;
}
bool yasl_set_app_id(const char *id)
{
if(display) return yasl_public_fail(EBUSY);
char *copy=id ? strdup(id) : NULL;
if(id&&!copy) return yasl_public_fail(ENOMEM);
free(application_id); application_id=copy; return true;
}
bool yasl_set_backend(enum yasl_backend backend)
{
if(display) return yasl_public_fail(EBUSY);
if(backend<YASL_BACKEND_AUTO || backend>YASL_BACKEND_COCOA) return yasl_public_fail(EINVAL);
requested_backend=backend; return true;
}
static bool creation_valid(int width,int height,unsigned flags)
{
if(width<=0 || height<=0 || width>32767 || height>32767 ||
(unsigned long long)width*(unsigned)height>INT32_MAX/4 || (flags & ~15u))
return yasl_public_fail(EINVAL);
if(pumping || refresh_depth) return yasl_public_fail(EBUSY);
return true;
}
static void draw_committed(yasl_surface *s,struct yasl_private_frame *frame,void *ud)
{
(void)ud;
if(s->refresh_callback && pumping && !refresh_depth) {
++refresh_depth; s->refresh_callback(s,s->refresh_userdata); --refresh_depth;
}
if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU || !frame->target.pixels) return;
int w=frame->target.size.x,h=frame->target.size.y;
if(w==s->committed_width && h==s->committed_height && s->committed_pixels)
memcpy(frame->target.pixels,s->committed_pixels,(size_t)w*h*4);
else memset(frame->target.pixels,0,(size_t)w*h*4);
struct yasl_private_damage_list damage={.n=-1};
yasl_private_surface_present(s,&frame->target,&damage);
}
void yasl_public_register_surface(yasl_surface *s,unsigned flags,yasl_surface *parent)
{
s->creation_flags=flags; s->parent=parent;
if(flags&YASL_SURFACE_CPU) s->presenter=YASL_PRIVATE_PRESENTER_CPU;
struct yasl_private_window_cbs cb={.draw=draw_committed};
yasl_private_surface_set_callbacks(s,&cb,NULL);
s->reported_state=s->state;
struct yasl_private_geometry geometry=yasl_private_surface_geometry(s);
yasl_private_surface_notify_configure(s,geometry);
}
yasl_surface *yasl_surface_create(const char *title,int width,int height,unsigned flags)
{
if(!creation_valid(width,height,flags)) return NULL;
bool fresh=!display;
if(!initialize()) return NULL;
struct yasl_private_window_spec spec={.title=title,.size_logical={width,height},
.flags=flags,.external_gpu=!(flags&YASL_SURFACE_CPU)};
yasl_surface *s=yasl_private_surface_create_toplevel(display,&spec);
if(!s) {
int error=errno;
if(fresh) { pthread_mutex_lock(&lifecycle_mutex); struct yasl_private_display *d=display; display=NULL;
pthread_mutex_unlock(&lifecycle_mutex); yasl_private_display_close(d); }
yasl_public_fail(error); return NULL;
}
yasl_public_register_surface(s,flags,NULL); return s;
}
void yasl_surface_destroy(yasl_surface *s)
{
if(!s) return;
if(!yasl_public_surface(s,true)) return;
for(;;) {
yasl_surface *child=NULL;
for(yasl_surface *p=display->surfaces;p;p=p->event_next) if(p->parent==s) { child=p; break; }
if(!child) break;
yasl_surface_destroy(child);
}
free(s->committed_pixels); s->committed_pixels=NULL;
free(s->text_surrounding); s->text_surrounding=NULL;
yasl_private_surface_destroy(s);
}
void yasl_shutdown(void)
{
if(display && !yasl_public_owner()) return;
if(pumping || refresh_depth) { yasl_public_fail(EBUSY); return; }
if(display) {
while(display->surfaces) yasl_surface_destroy(display->surfaces);
pthread_mutex_lock(&lifecycle_mutex); struct yasl_private_display *d=display; display=NULL;
pthread_mutex_unlock(&lifecycle_mutex); yasl_private_display_close(d);
}
free(delivered_text); delivered_text=NULL;
free(application_id); application_id=NULL; requested_backend=YASL_BACKEND_AUTO;
#if YASL_HAS_VULKAN
extern void yasl_private_vulkan_reset(void);
yasl_private_vulkan_reset();
#endif
}
void yasl_wake(void)
{
pthread_mutex_lock(&lifecycle_mutex);
if(display && display->loop) yasl_private_loop_kick(display->loop);
pthread_mutex_unlock(&lifecycle_mutex);
}
uint64_t yasl_time_ns(void) { return yasl_private_loop_now_ns(); }
static double scale_of(const yasl_surface *s)
{ return s && s->scale_120 ? (double)s->scale_120/120.0 : 1.0; }
static int logical(int pixels,double scale) { return (int)lround(pixels/scale); }
enum yasl_backend yasl_surface_backend(const yasl_surface *s)
{ return s ? (enum yasl_backend)s->display->ops->type : YASL_BACKEND_AUTO; }
unsigned yasl_surface_features(const yasl_surface *s)
{
if(!s) return 0;
unsigned caps=yasl_private_display_caps(s->display);
if(s->display->ops->surface_create_gl) caps|=YASL_CAP_OPENGL;
#if YASL_HAS_VULKAN
caps|=YASL_CAP_VULKAN;
#endif
return caps;
}
void yasl_surface_get_framebuffer_size(const yasl_surface *s,int *w,int *h)
{ if(w) *w=s?s->size.x:0; if(h) *h=s?s->size.y:0; }
void yasl_surface_get_size(const yasl_surface *s,int *w,int *h)
{ double scale=scale_of(s); if(w) *w=s?logical(s->size.x,scale):0; if(h) *h=s?logical(s->size.y,scale):0; }
double yasl_surface_get_scale(const yasl_surface *s) { return scale_of(s); }
unsigned yasl_surface_state(const yasl_surface *s) { return s?s->state:0; }
void *yasl_surface_userdata(const yasl_surface *s) { return s?s->userdata:NULL; }
void yasl_surface_set_userdata(yasl_surface *s,void *ud) { if(yasl_public_surface(s,false)) s->userdata=ud; }
bool yasl_surface_set_title(yasl_surface *s,const char *title)
{
if(!yasl_public_surface(s,false)) return false;
if(!title) return yasl_public_fail(EINVAL);
if(!s->display->ops->surface_set_title) return yasl_public_fail(ENOTSUP);
s->display->ops->surface_set_title(s,title); return true;
}
#define REQUEST(name,priv,args,call) bool name args { if(!yasl_public_surface(s,false)) return false; errno=0; return priv call || yasl_public_fail(errno); }
REQUEST(yasl_surface_request_size,yasl_private_surface_set_size,(yasl_surface *s,int w,int h),(s,((struct yasl_private_vector2i){w,h})))
REQUEST(yasl_surface_request_fullscreen,yasl_private_surface_set_fullscreen,(yasl_surface *s,bool v),(s,v))
REQUEST(yasl_surface_request_maximized,yasl_private_surface_set_maximized,(yasl_surface *s,bool v),(s,v))
REQUEST(yasl_surface_request_minimize,yasl_private_surface_minimize,(yasl_surface *s),(s))
REQUEST(yasl_surface_set_pointer_mode,yasl_private_surface_set_pointer_mode,(yasl_surface *s,enum yasl_pointer_mode m),(s,(enum yasl_private_pointer_mode)m))
bool yasl_surface_set_cursor(yasl_surface *s,enum yasl_cursor cursor)
{
if(!yasl_public_surface(s,false)) return false;
if(cursor<YASL_CURSOR_ARROW || cursor>YASL_CURSOR_HIDDEN) return yasl_public_fail(EINVAL);
if(!s->display->ops->surface_set_cursor) return yasl_public_fail(ENOTSUP);
yasl_private_surface_set_cursor(s,(enum yasl_private_cursor)cursor); return true;
}
void yasl_surface_set_refresh_callback(yasl_surface *s,void(*fn)(yasl_surface *,void *),void *ud)
{ if(yasl_public_surface(s,false)) { s->refresh_callback=fn; s->refresh_userdata=ud; } }
void yasl_private_surface_notify_refresh(yasl_surface *s)
{
if(!s || !s->display) return;
struct yasl_private_event ev={.type=YASL_PRIVATE_EVENT_REFRESH,.surface=s};
yasl_private_events_push(s->display,s,&ev);
if(s->refresh_callback && pumping && !refresh_depth) {
++refresh_depth; s->refresh_callback(s,s->refresh_userdata); --refresh_depth;
}
}
static char *copy_text(const char *data,size_t length)
{
if(length==SIZE_MAX || (!data&&length)) { errno=EINVAL; return NULL; }
char *p=malloc(length+1); if(!p) return NULL;
if(length) memcpy(p,data,length);
p[length]=0; return p;
}
static const char *deliver_text(const char *data,size_t length)
{
free(delivered_text); delivered_text=copy_text(data,length); return delivered_text;
}
/* Folds native size, state and focus changes into one WINDOW snapshot; unchanged values report nothing. */
static bool window_event(yasl_surface *s,struct yasl_private_vector2i size,uint32_t scale_120,uint32_t state,yasl_event *e)
{
bool resized=size.x!=s->reported_size.x || size.y!=s->reported_size.y || scale_120!=s->reported_scale_120;
if(!resized && state==s->reported_state) return false;
s->reported_size=size; s->reported_scale_120=scale_120; s->reported_state=state;
if(resized) s->refresh_owed=true;
double scale=scale_120 ? scale_120/120.0 : 1.0;
e->type=YASL_EVENT_WINDOW; e->window.pixel_width=size.x; e->window.pixel_height=size.y; e->window.scale=scale;
e->window.width=logical(size.x,scale); e->window.height=logical(size.y,scale);
e->window.state=state;
return true;
}
static bool refresh_ready(yasl_surface *s) { return s->reported_size.x>0 && s->reported_size.y>0; }
static bool convert_event(const struct yasl_private_event *p,yasl_event *e)
{
*e=(yasl_event){.surface=p->surface,.time_ns=p->time_ns};
double scale=scale_of(p->surface);
switch(p->type) {
case YASL_PRIVATE_EVENT_WINDOW_CLOSE:e->type=YASL_EVENT_CLOSE;break;
case YASL_PRIVATE_EVENT_DISPLAY_QUIT:e->type=YASL_EVENT_QUIT;break;
case YASL_PRIVATE_EVENT_DISPLAY_REOPEN:e->type=YASL_EVENT_REOPEN;break;
case YASL_PRIVATE_EVENT_DISPLAY_LOST:e->type=YASL_EVENT_DISPLAY_LOST;break;
case YASL_PRIVATE_EVENT_REFRESH:
if(!p->surface) return false;
if(!refresh_ready(p->surface)) { p->surface->refresh_owed=true; return false; }
p->surface->refresh_owed=false; e->type=YASL_EVENT_REFRESH; break;
case YASL_PRIVATE_EVENT_INPUT_RESET:e->type=YASL_EVENT_INPUT_RESET;break;
case YASL_PRIVATE_EVENT_FOCUS: {
yasl_surface *s=p->surface; if(!s) return false;
uint32_t state=p->focus.focused ? s->reported_state|YASL_STATE_ACTIVATED : s->reported_state&~(uint32_t)YASL_STATE_ACTIVATED;
return window_event(s,s->reported_size,s->reported_scale_120,state,e);
}
case YASL_PRIVATE_EVENT_WINDOW_STATE:
if(!p->surface) return false;
return window_event(p->surface,p->surface->reported_size,p->surface->reported_scale_120,p->state.flags,e);
case YASL_PRIVATE_EVENT_WINDOW_RESIZE:
if(!p->surface) return false;
return window_event(p->surface,p->resize.geometry.size,p->resize.geometry.scale_120,p->surface->reported_state,e);
case YASL_PRIVATE_EVENT_KEY:
e->type=YASL_EVENT_KEY;e->key.key=p->key.key;e->key.action=(enum yasl_key_action)p->key.action;
e->key.mods=p->key.mods;e->key.codepoint=p->key.codepoint;e->key.shifted=p->key.shifted;e->key.native_code=p->key.native_code;break;
case YASL_PRIVATE_EVENT_TEXT:
e->type=YASL_EVENT_TEXT;e->text.length=p->text.len;e->text.data=deliver_text(p->text.data,p->text.len);return e->text.data!=NULL;
case YASL_PRIVATE_EVENT_DROP:
e->type=YASL_EVENT_DROP;e->drop.action=YASL_DROP_DATA;e->drop.length=p->text.len;
e->drop.data=deliver_text(p->text.data,p->text.len);return e->drop.data!=NULL;
case YASL_PRIVATE_EVENT_PASTE:
e->type=YASL_EVENT_CLIPBOARD;e->clipboard.length=p->paste.len;e->clipboard.request_id=p->paste.request_id;
e->clipboard.status=(enum yasl_transfer_status)p->paste.status;
e->clipboard.data=deliver_text(p->paste.data,p->paste.len);return e->clipboard.data!=NULL;
case YASL_PRIVATE_EVENT_COMPOSITION:
e->type=YASL_EVENT_COMPOSITION;e->composition.length=p->composition.len;e->composition.begin=p->composition.begin;
e->composition.end=p->composition.end;e->composition.active=p->composition.active;
e->composition.revision=p->composition.revision; e->composition.generation=p->composition.generation;
e->composition.data=deliver_text(p->composition.data,p->composition.len);return e->composition.data!=NULL;
case YASL_PRIVATE_EVENT_TEXT_EDIT:
e->type=YASL_EVENT_TEXT_EDIT;e->text_edit.length=p->text_edit.len;e->text_edit.begin=p->text_edit.begin;e->text_edit.end=p->text_edit.end;
e->text_edit.revision=p->text_edit.revision;e->text_edit.generation=p->text_edit.generation;
e->text_edit.data=deliver_text(p->text_edit.data,p->text_edit.len);return e->text_edit.data!=NULL;
case YASL_PRIVATE_EVENT_PEN:
e->type=YASL_EVENT_PEN;
#define PEN(field) e->pen.field=p->pen.field
PEN(device_id);PEN(tool_id);PEN(x);PEN(y);PEN(pressure);PEN(tilt_x);PEN(tilt_y);PEN(rotation);PEN(distance);PEN(tangential);PEN(wheel_degrees);
PEN(axes_valid);PEN(axes_changed);PEN(buttons);PEN(changed_button);PEN(tool);PEN(action);PEN(in_contact);
#undef PEN
break;
case YASL_PRIVATE_EVENT_POINTER_SCROLL:
e->type=YASL_EVENT_SCROLL;e->scroll.x=p->pointer.pos.x/scale;e->scroll.y=p->pointer.pos.y/scale;
e->scroll.lines_x=p->pointer.scroll.x;e->scroll.lines_y=p->pointer.scroll.y;
e->scroll.pixels_x=p->pointer.pixel_scroll.x/scale;e->scroll.pixels_y=p->pointer.pixel_scroll.y/scale;
e->scroll.valid=p->pointer.scroll_valid?p->pointer.scroll_valid:YASL_SCROLL_LINES;
e->scroll.source=p->pointer.scroll_source;e->scroll.phase=p->pointer.scroll_phase;e->scroll.momentum=p->pointer.scroll_momentum;e->scroll.mods=p->pointer.mods;break;
case YASL_PRIVATE_EVENT_POINTER_DOWN:case YASL_PRIVATE_EVENT_POINTER_UP:case YASL_PRIVATE_EVENT_POINTER_MOTION:case YASL_PRIVATE_EVENT_POINTER_LEAVE:
e->type=YASL_EVENT_POINTER;e->pointer.x=(p->pointer.precise_position?p->pointer.precise_x:p->pointer.pos.x)/scale;
e->pointer.y=(p->pointer.precise_position?p->pointer.precise_y:p->pointer.pos.y)/scale;
e->pointer.dx=p->pointer.delta.x/scale;e->pointer.dy=p->pointer.delta.y/scale;
e->pointer.action=p->type==YASL_PRIVATE_EVENT_POINTER_DOWN?YASL_POINTER_DOWN:p->type==YASL_PRIVATE_EVENT_POINTER_UP?YASL_POINTER_UP:
p->type==YASL_PRIVATE_EVENT_POINTER_LEAVE?YASL_POINTER_LEAVE:YASL_POINTER_MOVE;
e->pointer.button=p->pointer.button;e->pointer.buttons=p->pointer.buttons;e->pointer.mods=p->pointer.mods;break;
case YASL_PRIVATE_EVENT_DRAG:
e->type=YASL_EVENT_DRAG;e->drag.action=(enum yasl_drag_action)p->drag.action;
e->drag.x=p->drag.pos.x/scale;e->drag.y=p->drag.pos.y/scale;break;
case YASL_PRIVATE_EVENT_DROP_HOVER:
e->type=YASL_EVENT_DROP;e->drop.action=p->drop.action==YASL_PRIVATE_DROP_LEAVE?YASL_DROP_LEAVE:YASL_DROP_OVER;
e->drop.x=p->drop.pos.x/scale;e->drop.y=p->drop.pos.y/scale;break;
default:return false;
}
return true;
}
static int take_event(yasl_event *out)
{
for(yasl_surface *s=display->surfaces;s;s=s->event_next) if(s->refresh_owed && refresh_ready(s)) {
s->refresh_owed=false;
*out=(yasl_event){.type=YASL_EVENT_REFRESH,.surface=s,.time_ns=yasl_time_ns()};
return 1;
}
struct yasl_private_event p;
while(yasl_private_display_poll_event(display,&p)) {
if(p.surface && ((p.type==YASL_PRIVATE_EVENT_COMPOSITION && p.composition.generation!=p.surface->text_generation) ||
(p.type==YASL_PRIVATE_EVENT_TEXT_EDIT && p.text_edit.generation!=p.surface->text_generation))) continue;
yasl_event event;
errno=0;
if(convert_event(&p,&event)) { *out=event;return 1; }
if(errno) { yasl_public_fail(errno);return -1; }
}
return 0;
}
int yasl_wait_event(yasl_event *out,int timeout)
{
if(!out) { yasl_public_fail(EINVAL);return -1; }
*out=(yasl_event){.type=YASL_EVENT_NONE};
if(!yasl_public_owner()) return -1;
if(pumping || refresh_depth) { yasl_public_fail(EBUSY);return -1; }
free(delivered_text); delivered_text=NULL;
int n=take_event(out); if(n) return n;
pumping=true;
yasl_private_display_flush(display);
n=take_event(out);
if(!n) {
int work=yasl_private_display_dispatch(display,timeout);
if(work<0) { yasl_public_fail(errno);n=-1; }
yasl_private_display_flush(display);
if(n>=0) n=take_event(out);
}
pumping=false;return n;
}
uint32_t *yasl_cpu_pixels(yasl_surface *s,int *w,int *h)
{
if(w) *w=0;
if(h) *h=0;
if(!yasl_public_surface(s,false)) return NULL;
if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) { yasl_public_fail(ENOTSUP); return NULL; }
int width=s->size.x,height=s->size.y;
if(width<=0 || height<=0) { yasl_public_fail(EBUSY); return NULL; }
if(width!=s->committed_width || height!=s->committed_height || !s->committed_pixels) {
uint32_t *next=calloc((size_t)width*height,4);
if(!next) { yasl_public_fail(ENOMEM); return NULL; }
int keep_w=width<s->committed_width?width:s->committed_width,keep_h=height<s->committed_height?height:s->committed_height;
if(s->committed_pixels) for(int y=0;y<keep_h;y++)
memcpy(next+(size_t)y*width,s->committed_pixels+(size_t)y*s->committed_width,(size_t)keep_w*4);
free(s->committed_pixels); s->committed_pixels=next; s->committed_width=width; s->committed_height=height;
}
if(w) *w=width;
if(h) *h=height;
return s->committed_pixels;
}
bool yasl_cpu_present(yasl_surface *s)
{
if(!yasl_public_surface(s,false)) return false;
if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) return yasl_public_fail(ENOTSUP);
if(!s->committed_pixels || s->committed_width!=s->size.x || s->committed_height!=s->size.y) return yasl_public_fail(EINVAL);
yasl_private_surface_request_frame(s); return true;
}
int yasl_double_click_ms(void) { return display?yasl_private_display_double_click_ms(display):400; }
bool yasl_clipboard_set(enum yasl_selection sel,const char *text)
{ if(!yasl_public_owner())return false;errno=0;return yasl_private_clipboard_set(display,(enum yasl_private_selection)sel,text)||yasl_public_fail(errno); }
uint64_t yasl_clipboard_request(yasl_surface *s,enum yasl_selection sel)
{ if(!yasl_public_surface(s,false))return 0;errno=0;uint64_t id=yasl_private_clipboard_request(s,(enum yasl_private_selection)sel);if(!id)yasl_public_fail(errno);return id; }
static bool text_update(yasl_surface *s)
{ if(!s->display->ops->surface_update_text_input)return true;errno=0;return s->display->ops->surface_update_text_input(s)||yasl_public_fail(errno); }
bool yasl_surface_set_text_input(yasl_surface *s,bool active,enum yasl_text_purpose purpose)
{
if(!yasl_public_surface(s,false))return false;
if(purpose<YASL_TEXT_NORMAL||purpose>YASL_TEXT_TERMINAL)return yasl_public_fail(EINVAL);
bool old=s->text_input_active;unsigned old_purpose=s->text_purpose;uint64_t generation=s->text_generation;
if(old!=active || old_purpose!=(unsigned)purpose)++s->text_generation;
s->text_input_active=active;s->text_purpose=purpose;
if(!text_update(s)) { s->text_input_active=old;s->text_purpose=old_purpose;s->text_generation=generation;return false; }return true;
}
bool yasl_surface_set_surrounding_text(yasl_surface *s,const char *text,size_t len,size_t begin,size_t end,uint64_t revision)
{
if(!yasl_public_surface(s,false))return false;
if((!text&&len)||begin>end||end>len||len>INT32_MAX || (len&&memchr(text,0,len)))return yasl_public_fail(EINVAL);
/* Validate UTF-8 and require selection boundaries between code points. */
if((begin<len&&((unsigned char)text[begin]&0xc0)==0x80)||(end<len&&((unsigned char)text[end]&0xc0)==0x80))return yasl_public_fail(EINVAL);
for(size_t i=0;i<len;) { unsigned char c=(unsigned char)text[i];size_t n=c<0x80?1:c>=0xc2&&c<=0xdf?2:c>=0xe0&&c<=0xef?3:c>=0xf0&&c<=0xf4?4:0;
if(!n||i+n>len)return yasl_public_fail(EILSEQ);
for(size_t j=1;j<n;j++)if(((unsigned char)text[i+j]&0xc0)!=0x80)return yasl_public_fail(EILSEQ);
if(n>=3 && ((c==0xe0&&(unsigned char)text[i+1]<0xa0)||(c==0xed&&(unsigned char)text[i+1]>=0xa0)||
(c==0xf0&&(unsigned char)text[i+1]<0x90)||(c==0xf4&&(unsigned char)text[i+1]>=0x90)))return yasl_public_fail(EILSEQ);
i+=n;
}
char *copy=copy_text(text,len);if(!copy)return yasl_public_fail(errno);
char *old=s->text_surrounding;size_t old_len=s->text_length,old_begin=s->text_begin,old_end=s->text_end;uint64_t old_rev=s->text_revision;
s->text_surrounding=copy;s->text_length=len;s->text_begin=begin;s->text_end=end;s->text_revision=revision;
if(!text_update(s)){free(copy);s->text_surrounding=old;s->text_length=old_len;s->text_begin=old_begin;s->text_end=old_end;s->text_revision=old_rev;return false;}
free(old);return true;
}
static bool logical_rect(yasl_surface *s,double x,double y,double w,double h,struct yasl_private_rect2i *r)
{
if(!isfinite(x)||!isfinite(y)||!isfinite(w)||!isfinite(h)||w<0||h<0)return yasl_public_fail(EINVAL);
r->x=yasl_private_scale_to_device(x,s->scale_120);r->y=yasl_private_scale_to_device(y,s->scale_120);
r->width=yasl_private_scale_to_device(w,s->scale_120);r->height=yasl_private_scale_to_device(h,s->scale_120);return true;
}
bool yasl_surface_set_ime_caret(yasl_surface *s,double x,double y,double w,double h)
{
if(!yasl_public_surface(s,false))return false;
struct yasl_private_rect2i r;
if(!logical_rect(s,x,y,w,h,&r))return false;
if(!s->display->ops->surface_set_ime_caret)return yasl_public_fail(ENOTSUP);
yasl_private_surface_set_ime_caret(s,r);return true;
}
static bool trigger_valid(yasl_surface *s,const yasl_event *e)
{
if(!e||e->surface!=s||!e->time_ns||yasl_time_ns()-e->time_ns>5000000000ull ||
!((e->type==YASL_EVENT_POINTER&&e->pointer.action==YASL_POINTER_DOWN)||(e->type==YASL_EVENT_PEN&&e->pen.action==YASL_PEN_DOWN))) {
yasl_public_set_error(YASL_ERROR_CONTEXT_EXPIRED,0);return false;
}return true;
}
static bool popup_spec(yasl_surface *s,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints,struct yasl_private_popup_spec *out)
{
if(w<=0||h<=0 || (unsigned)anchor>8 || (unsigned)gravity>8 || (constraints&~63u))return yasl_public_fail(EINVAL);
*out=(struct yasl_private_popup_spec){.size={yasl_private_scale_to_device(w,s->scale_120),yasl_private_scale_to_device(h,s->scale_120)},.anchor_edges=anchor,.gravity=gravity,.constraints=constraints,.positioner_explicit=true};
return logical_rect(s,x,y,aw,ah,&out->anchor);
}
yasl_surface *yasl_popup_create(yasl_surface *parent,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints,const yasl_event *trigger)
{
if(!yasl_public_surface(parent,true)||!trigger_valid(parent,trigger))return NULL;
struct yasl_private_popup_spec spec;if(!popup_spec(parent,w,h,x,y,aw,ah,anchor,gravity,constraints,&spec))return NULL;
yasl_surface *s=yasl_private_surface_create_popup(parent,&spec);if(!s){yasl_public_fail(errno);return NULL;}
yasl_public_register_surface(s,parent->creation_flags,parent);return s;
}
bool yasl_popup_reposition(yasl_surface *s,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints)
{
if(!yasl_public_surface(s,false))return false;
struct yasl_private_popup_spec spec;
if(!s->parent)return yasl_public_fail(EINVAL);
if(!popup_spec(s->parent,w,h,x,y,aw,ah,anchor,gravity,constraints,&spec))return false;
errno=0;return yasl_private_surface_popup_reposition(s,&spec)||yasl_public_fail(errno);
}
bool yasl_surface_start_interactive_move(yasl_surface *s,const yasl_event *e)
{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;errno=0;return yasl_private_surface_start_interactive_move(s)||yasl_public_fail(errno); }
bool yasl_surface_start_interactive_resize(yasl_surface *s,enum yasl_resize_edge edge,const yasl_event *e)
{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;errno=0;return yasl_private_surface_start_interactive_resize(s,(enum yasl_private_resize_edge)edge)||yasl_public_fail(errno); }
bool yasl_surface_show_window_menu(yasl_surface *s,double x,double y,const yasl_event *e)
{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;
struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_show_window_menu(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); }
bool yasl_drag_start(yasl_surface *s,const yasl_event *e,const uint32_t *pixels,int w,int h,int hx,int hy)
{
if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;
struct yasl_private_drag_image image={.pixels=pixels,.size={w,h},.hotspot={hx,hy}};
errno=0;return yasl_private_display_drag_start(display,s,pixels?&image:NULL,NULL,NULL)||yasl_public_fail(errno);
}
void yasl_drag_cancel(void) { if(yasl_public_owner())yasl_private_display_drag_cancel(display); }
bool yasl_surface_drag_carry(yasl_surface *s,double x,double y)
{ if(!yasl_public_surface(s,false))return false;
struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_drag_carry(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); }
bool yasl_surface_place_under_pointer(yasl_surface *s,double x,double y)
{ if(!yasl_public_surface(s,false))return false;
struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_place_under_pointer(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); }
size_t yasl_drop_next_path(const char **cursor,char *out,size_t cap)
{ return yasl_private_drop_next_path(cursor,out,cap); }
yasl_surface *yasl_gl_surface_create(const char *title,int w,int h,unsigned flags,const int *attrs,yasl_surface *share)
{
if(!creation_valid(w,h,flags)||(flags&YASL_SURFACE_CPU)) { yasl_public_fail(EINVAL);return NULL; }
struct yasl_private_gl_options parsed;if(!yasl_private_gl_parse_options(attrs,&parsed)){yasl_public_fail(errno);return NULL;}
bool fresh=!display;if(!initialize())return NULL;
if(share && (share->display!=display || share->presenter!=YASL_PRIVATE_PRESENTER_GL)) {yasl_public_fail(EINVAL);return NULL;}
if(!display->ops->surface_create_gl) {if(fresh)yasl_shutdown();yasl_public_fail(ENOTSUP);return NULL;}
struct yasl_private_window_spec spec={.title=title,.size_logical={w,h},.flags=flags,.external_gpu=true};
yasl_surface *s=display->ops->surface_create_gl(display,&spec,attrs,share);
if(!s){int error=errno;if(fresh)yasl_shutdown();yasl_public_fail(error);return NULL;}
if(!yasl_private_events_register(s)){int error=errno;display->ops->surface_destroy(s);if(fresh)yasl_shutdown();yasl_public_fail(error);return NULL;}
s->presenter=YASL_PRIVATE_PRESENTER_GL;yasl_public_register_surface(s,flags,NULL);return s;
}
bool yasl_gl_make_current(yasl_surface *s)
{
if(!display){yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0);return false;}
if(s && (s->display!=display||s->presenter!=YASL_PRIVATE_PRESENTER_GL))return yasl_public_fail(EINVAL);
if(!display->ops->gl_make_current)return yasl_public_fail(ENOTSUP);
errno=0;return display->ops->gl_make_current(display,s)||yasl_public_fail(errno);
}
yasl_gl_proc yasl_gl_get_proc_address(const char *name)
{
if(!display||!name||!display->ops->gl_get_proc_address){yasl_public_fail(ENOTSUP);return NULL;}
yasl_gl_proc p=display->ops->gl_get_proc_address(display,name);if(!p)yasl_public_fail(ENOENT);return p;
}
bool yasl_gl_swap_interval(yasl_surface *s,int interval)
{
if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_interval)return yasl_public_fail(EINVAL);
errno=0;return s->display->ops->gl_swap_interval(s->display,s,interval)||yasl_public_fail(errno);
}
bool yasl_gl_swap_buffers(yasl_surface *s)
{
if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_buffers)return yasl_public_fail(EINVAL);
errno=0;return s->display->ops->gl_swap_buffers(s->display,s)||yasl_public_fail(errno);
}
|