1 /*
2 Copyright 2012-2020 David Robillard <d@drobilla.net>
3
4 Permission to use, copy, modify, and/or distribute this software for any
5 purpose with or without fee is hereby granted, provided that the above
6 copyright notice and this permission notice appear in all copies.
7
8 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include "implementation.h"
18
19 #include "pugl/pugl.h"
20
21 #include <assert.h>
22 #include <stdbool.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 const char*
puglStrerror(const PuglStatus status)27 puglStrerror(const PuglStatus status)
28 {
29 // clang-format off
30 switch (status) {
31 case PUGL_SUCCESS: return "Success";
32 case PUGL_FAILURE: return "Non-fatal failure";
33 case PUGL_UNKNOWN_ERROR: return "Unknown system error";
34 case PUGL_BAD_BACKEND: return "Invalid or missing backend";
35 case PUGL_BAD_CONFIGURATION: return "Invalid view configuration";
36 case PUGL_BAD_PARAMETER: return "Invalid parameter";
37 case PUGL_BACKEND_FAILED: return "Backend initialisation failed";
38 case PUGL_REGISTRATION_FAILED: return "Class registration failed";
39 case PUGL_REALIZE_FAILED: return "View creation failed";
40 case PUGL_SET_FORMAT_FAILED: return "Failed to set pixel format";
41 case PUGL_CREATE_CONTEXT_FAILED: return "Failed to create drawing context";
42 case PUGL_UNSUPPORTED_TYPE: return "Unsupported data type";
43 }
44 // clang-format on
45
46 return "Unknown error";
47 }
48
49 void
puglSetString(char ** dest,const char * string)50 puglSetString(char** dest, const char* string)
51 {
52 if (*dest != string) {
53 const size_t len = strlen(string);
54
55 *dest = (char*)realloc(*dest, len + 1);
56 strncpy(*dest, string, len + 1);
57 }
58 }
59
60 void
puglSetBlob(PuglBlob * const dest,const void * const data,const size_t len)61 puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len)
62 {
63 if (data) {
64 dest->len = len;
65 dest->data = realloc(dest->data, len + 1);
66 memcpy(dest->data, data, len);
67 ((char*)dest->data)[len] = 0;
68 } else {
69 dest->len = 0;
70 dest->data = NULL;
71 }
72 }
73
74 static void
puglSetDefaultHints(PuglHints hints)75 puglSetDefaultHints(PuglHints hints)
76 {
77 hints[PUGL_USE_COMPAT_PROFILE] = PUGL_TRUE;
78 hints[PUGL_CONTEXT_VERSION_MAJOR] = 2;
79 hints[PUGL_CONTEXT_VERSION_MINOR] = 0;
80 hints[PUGL_RED_BITS] = 8;
81 hints[PUGL_GREEN_BITS] = 8;
82 hints[PUGL_BLUE_BITS] = 8;
83 hints[PUGL_ALPHA_BITS] = 8;
84 hints[PUGL_DEPTH_BITS] = 0;
85 hints[PUGL_STENCIL_BITS] = 0;
86 hints[PUGL_SAMPLES] = 0;
87 hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE;
88 hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE;
89 hints[PUGL_RESIZABLE] = PUGL_FALSE;
90 hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE;
91 hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE;
92 }
93
94 PuglWorld*
puglNewWorld(PuglWorldType type,PuglWorldFlags flags)95 puglNewWorld(PuglWorldType type, PuglWorldFlags flags)
96 {
97 PuglWorld* world = (PuglWorld*)calloc(1, sizeof(PuglWorld));
98 if (!world || !(world->impl = puglInitWorldInternals(type, flags))) {
99 free(world);
100 return NULL;
101 }
102
103 world->startTime = puglGetTime(world);
104
105 puglSetString(&world->className, "Pugl");
106
107 return world;
108 }
109
110 void
puglFreeWorld(PuglWorld * const world)111 puglFreeWorld(PuglWorld* const world)
112 {
113 puglFreeWorldInternals(world);
114 free(world->className);
115 free(world->views);
116 free(world);
117 }
118
119 void
puglSetWorldHandle(PuglWorld * world,PuglWorldHandle handle)120 puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle)
121 {
122 world->handle = handle;
123 }
124
125 PuglWorldHandle
puglGetWorldHandle(PuglWorld * world)126 puglGetWorldHandle(PuglWorld* world)
127 {
128 return world->handle;
129 }
130
131 PuglStatus
puglSetClassName(PuglWorld * const world,const char * const name)132 puglSetClassName(PuglWorld* const world, const char* const name)
133 {
134 puglSetString(&world->className, name);
135 return PUGL_SUCCESS;
136 }
137
138 PuglView*
puglNewView(PuglWorld * const world)139 puglNewView(PuglWorld* const world)
140 {
141 PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
142 if (!view || !(view->impl = puglInitViewInternals())) {
143 free(view);
144 return NULL;
145 }
146
147 view->world = world;
148 view->minWidth = 1;
149 view->minHeight = 1;
150
151 puglSetDefaultHints(view->hints);
152
153 // Add to world view list
154 ++world->numViews;
155 world->views =
156 (PuglView**)realloc(world->views, world->numViews * sizeof(PuglView*));
157
158 world->views[world->numViews - 1] = view;
159
160 return view;
161 }
162
163 void
puglFreeView(PuglView * view)164 puglFreeView(PuglView* view)
165 {
166 puglDispatchSimpleEvent(view, PUGL_DESTROY);
167
168 // Remove from world view list
169 PuglWorld* world = view->world;
170 for (size_t i = 0; i < world->numViews; ++i) {
171 if (world->views[i] == view) {
172 if (i == world->numViews - 1) {
173 world->views[i] = NULL;
174 } else {
175 memmove(world->views + i,
176 world->views + i + 1,
177 sizeof(PuglView*) * (world->numViews - i - 1));
178 world->views[world->numViews - 1] = NULL;
179 }
180 --world->numViews;
181 }
182 }
183
184 free(view->title);
185 free(view->clipboard.data);
186 puglFreeViewInternals(view);
187 free(view);
188 }
189
190 PuglWorld*
puglGetWorld(PuglView * view)191 puglGetWorld(PuglView* view)
192 {
193 return view->world;
194 }
195
196 PuglStatus
puglSetViewHint(PuglView * view,PuglViewHint hint,int value)197 puglSetViewHint(PuglView* view, PuglViewHint hint, int value)
198 {
199 if (value == PUGL_DONT_CARE) {
200 switch (hint) {
201 case PUGL_USE_COMPAT_PROFILE:
202 case PUGL_USE_DEBUG_CONTEXT:
203 case PUGL_CONTEXT_VERSION_MAJOR:
204 case PUGL_CONTEXT_VERSION_MINOR:
205 case PUGL_SWAP_INTERVAL:
206 return PUGL_BAD_PARAMETER;
207 default:
208 break;
209 }
210 }
211
212 if (hint < PUGL_NUM_VIEW_HINTS) {
213 view->hints[hint] = value;
214 return PUGL_SUCCESS;
215 }
216
217 return PUGL_BAD_PARAMETER;
218 }
219
220 int
puglGetViewHint(const PuglView * view,PuglViewHint hint)221 puglGetViewHint(const PuglView* view, PuglViewHint hint)
222 {
223 if (hint < PUGL_NUM_VIEW_HINTS) {
224 return view->hints[hint];
225 }
226
227 return PUGL_DONT_CARE;
228 }
229
230 PuglStatus
puglSetParentWindow(PuglView * view,PuglNativeView parent)231 puglSetParentWindow(PuglView* view, PuglNativeView parent)
232 {
233 view->parent = parent;
234 return PUGL_SUCCESS;
235 }
236
237 PuglStatus
puglSetBackend(PuglView * view,const PuglBackend * backend)238 puglSetBackend(PuglView* view, const PuglBackend* backend)
239 {
240 view->backend = backend;
241 return PUGL_SUCCESS;
242 }
243
244 void
puglSetHandle(PuglView * view,PuglHandle handle)245 puglSetHandle(PuglView* view, PuglHandle handle)
246 {
247 view->handle = handle;
248 }
249
250 PuglHandle
puglGetHandle(PuglView * view)251 puglGetHandle(PuglView* view)
252 {
253 return view->handle;
254 }
255
256 bool
puglGetVisible(const PuglView * view)257 puglGetVisible(const PuglView* view)
258 {
259 return view->visible;
260 }
261
262 PuglRect
puglGetFrame(const PuglView * view)263 puglGetFrame(const PuglView* view)
264 {
265 return view->frame;
266 }
267
268 void*
puglGetContext(PuglView * view)269 puglGetContext(PuglView* view)
270 {
271 return view->backend->getContext(view);
272 }
273
274 #ifndef PUGL_DISABLE_DEPRECATED
275
276 PuglStatus
puglPollEvents(PuglWorld * world,double timeout)277 puglPollEvents(PuglWorld* world, double timeout)
278 {
279 return puglUpdate(world, timeout);
280 }
281
282 PuglStatus
puglDispatchEvents(PuglWorld * world)283 puglDispatchEvents(PuglWorld* world)
284 {
285 return puglUpdate(world, 0.0);
286 }
287
288 PuglStatus
puglShowWindow(PuglView * view)289 puglShowWindow(PuglView* view)
290 {
291 return puglShow(view);
292 }
293
294 PuglStatus
puglHideWindow(PuglView * view)295 puglHideWindow(PuglView* view)
296 {
297 return puglHide(view);
298 }
299
300 #endif
301
302 PuglStatus
puglSetEventFunc(PuglView * view,PuglEventFunc eventFunc)303 puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
304 {
305 view->eventFunc = eventFunc;
306 return PUGL_SUCCESS;
307 }
308
309 /// Return the code point for buf, or the replacement character on error
310 uint32_t
puglDecodeUTF8(const uint8_t * buf)311 puglDecodeUTF8(const uint8_t* buf)
312 {
313 #define FAIL_IF(cond) \
314 do { \
315 if (cond) \
316 return 0xFFFD; \
317 } while (0)
318
319 // http://en.wikipedia.org/wiki/UTF-8
320
321 if (buf[0] < 0x80) {
322 return buf[0];
323 }
324
325 if (buf[0] < 0xC2) {
326 return 0xFFFD;
327 }
328
329 if (buf[0] < 0xE0) {
330 FAIL_IF((buf[1] & 0xC0u) != 0x80);
331 return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u;
332 }
333
334 if (buf[0] < 0xF0) {
335 FAIL_IF((buf[1] & 0xC0u) != 0x80);
336 FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
337 FAIL_IF((buf[2] & 0xC0u) != 0x80);
338 return ((uint32_t)buf[0] << 12u) + //
339 ((uint32_t)buf[1] << 6u) + //
340 ((uint32_t)buf[2] - 0xE2080u);
341 }
342
343 if (buf[0] < 0xF5) {
344 FAIL_IF((buf[1] & 0xC0u) != 0x80);
345 FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
346 FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90);
347 FAIL_IF((buf[2] & 0xC0u) != 0x80u);
348 FAIL_IF((buf[3] & 0xC0u) != 0x80u);
349 return (((uint32_t)buf[0] << 18u) + //
350 ((uint32_t)buf[1] << 12u) + //
351 ((uint32_t)buf[2] << 6u) + //
352 ((uint32_t)buf[3] - 0x3C82080u));
353 }
354
355 return 0xFFFD;
356 }
357
358 static inline bool
puglMustConfigure(PuglView * view,const PuglEventConfigure * configure)359 puglMustConfigure(PuglView* view, const PuglEventConfigure* configure)
360 {
361 return memcmp(configure, &view->lastConfigure, sizeof(PuglEventConfigure));
362 }
363
364 void
puglDispatchSimpleEvent(PuglView * view,const PuglEventType type)365 puglDispatchSimpleEvent(PuglView* view, const PuglEventType type)
366 {
367 assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP ||
368 type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE ||
369 type == PUGL_LOOP_ENTER || type == PUGL_LOOP_LEAVE);
370
371 const PuglEvent event = {{type, 0}};
372 puglDispatchEvent(view, &event);
373 }
374
375 void
puglDispatchEventInContext(PuglView * view,const PuglEvent * event)376 puglDispatchEventInContext(PuglView* view, const PuglEvent* event)
377 {
378 if (event->type == PUGL_CONFIGURE) {
379 view->frame.x = event->configure.x;
380 view->frame.y = event->configure.y;
381 view->frame.width = event->configure.width;
382 view->frame.height = event->configure.height;
383
384 if (puglMustConfigure(view, &event->configure)) {
385 view->eventFunc(view, event);
386 view->lastConfigure = event->configure;
387 }
388 } else if (event->type == PUGL_EXPOSE) {
389 if (event->expose.width > 0 && event->expose.height > 0) {
390 view->eventFunc(view, event);
391 }
392 } else {
393 view->eventFunc(view, event);
394 }
395 }
396
397 void
puglDispatchEvent(PuglView * view,const PuglEvent * event)398 puglDispatchEvent(PuglView* view, const PuglEvent* event)
399 {
400 switch (event->type) {
401 case PUGL_NOTHING:
402 break;
403 case PUGL_CREATE:
404 case PUGL_DESTROY:
405 view->backend->enter(view, NULL);
406 view->eventFunc(view, event);
407 view->backend->leave(view, NULL);
408 break;
409 case PUGL_CONFIGURE:
410 if (puglMustConfigure(view, &event->configure)) {
411 view->backend->enter(view, NULL);
412 puglDispatchEventInContext(view, event);
413 view->backend->leave(view, NULL);
414 }
415 break;
416 case PUGL_EXPOSE:
417 view->backend->enter(view, &event->expose);
418 puglDispatchEventInContext(view, event);
419 view->backend->leave(view, &event->expose);
420 break;
421 default:
422 view->eventFunc(view, event);
423 }
424 }
425
426 const void*
puglGetInternalClipboard(const PuglView * const view,const char ** const type,size_t * const len)427 puglGetInternalClipboard(const PuglView* const view,
428 const char** const type,
429 size_t* const len)
430 {
431 if (len) {
432 *len = view->clipboard.len;
433 }
434
435 if (type) {
436 *type = "text/plain";
437 }
438
439 return view->clipboard.data;
440 }
441
442 PuglStatus
puglSetInternalClipboard(PuglView * const view,const char * const type,const void * const data,const size_t len)443 puglSetInternalClipboard(PuglView* const view,
444 const char* const type,
445 const void* const data,
446 const size_t len)
447 {
448 if (type && strcmp(type, "text/plain")) {
449 return PUGL_UNSUPPORTED_TYPE;
450 }
451
452 puglSetBlob(&view->clipboard, data, len);
453 return PUGL_SUCCESS;
454 }
455