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 free(view->clipboardType.data);
187 puglFreeViewInternals(view);
188 free(view);
189 }
190
191 PuglWorld*
puglGetWorld(PuglView * view)192 puglGetWorld(PuglView* view)
193 {
194 return view->world;
195 }
196
197 PuglStatus
puglSetViewHint(PuglView * view,PuglViewHint hint,int value)198 puglSetViewHint(PuglView* view, PuglViewHint hint, int value)
199 {
200 if (value == PUGL_DONT_CARE) {
201 switch (hint) {
202 case PUGL_USE_COMPAT_PROFILE:
203 case PUGL_USE_DEBUG_CONTEXT:
204 case PUGL_CONTEXT_VERSION_MAJOR:
205 case PUGL_CONTEXT_VERSION_MINOR:
206 case PUGL_SWAP_INTERVAL:
207 return PUGL_BAD_PARAMETER;
208 default:
209 break;
210 }
211 }
212
213 if (hint < PUGL_NUM_VIEW_HINTS) {
214 view->hints[hint] = value;
215 return PUGL_SUCCESS;
216 }
217
218 return PUGL_BAD_PARAMETER;
219 }
220
221 int
puglGetViewHint(const PuglView * view,PuglViewHint hint)222 puglGetViewHint(const PuglView* view, PuglViewHint hint)
223 {
224 if (hint < PUGL_NUM_VIEW_HINTS) {
225 return view->hints[hint];
226 }
227
228 return PUGL_DONT_CARE;
229 }
230
231 PuglStatus
puglSetParentWindow(PuglView * view,PuglNativeView parent)232 puglSetParentWindow(PuglView* view, PuglNativeView parent)
233 {
234 view->parent = parent;
235 return PUGL_SUCCESS;
236 }
237
238 PuglStatus
puglSetBackend(PuglView * view,const PuglBackend * backend)239 puglSetBackend(PuglView* view, const PuglBackend* backend)
240 {
241 view->backend = backend;
242 return PUGL_SUCCESS;
243 }
244
245 void
puglSetHandle(PuglView * view,PuglHandle handle)246 puglSetHandle(PuglView* view, PuglHandle handle)
247 {
248 view->handle = handle;
249 }
250
251 PuglHandle
puglGetHandle(PuglView * view)252 puglGetHandle(PuglView* view)
253 {
254 return view->handle;
255 }
256
257 bool
puglGetVisible(const PuglView * view)258 puglGetVisible(const PuglView* view)
259 {
260 return view->visible;
261 }
262
263 PuglRect
puglGetFrame(const PuglView * view)264 puglGetFrame(const PuglView* view)
265 {
266 return view->frame;
267 }
268
269 void*
puglGetContext(PuglView * view)270 puglGetContext(PuglView* view)
271 {
272 return view->backend->getContext(view);
273 }
274
275 #ifndef PUGL_DISABLE_DEPRECATED
276
277 PuglStatus
puglPollEvents(PuglWorld * world,double timeout)278 puglPollEvents(PuglWorld* world, double timeout)
279 {
280 return puglUpdate(world, timeout);
281 }
282
283 PuglStatus
puglDispatchEvents(PuglWorld * world)284 puglDispatchEvents(PuglWorld* world)
285 {
286 return puglUpdate(world, 0.0);
287 }
288
289 PuglStatus
puglShowWindow(PuglView * view)290 puglShowWindow(PuglView* view)
291 {
292 return puglShow(view);
293 }
294
295 PuglStatus
puglHideWindow(PuglView * view)296 puglHideWindow(PuglView* view)
297 {
298 return puglHide(view);
299 }
300
301 #endif
302
303 PuglStatus
puglSetEventFunc(PuglView * view,PuglEventFunc eventFunc)304 puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
305 {
306 view->eventFunc = eventFunc;
307 return PUGL_SUCCESS;
308 }
309
310 /// Return the code point for buf, or the replacement character on error
311 uint32_t
puglDecodeUTF8(const uint8_t * buf)312 puglDecodeUTF8(const uint8_t* buf)
313 {
314 #define FAIL_IF(cond) \
315 do { \
316 if (cond) \
317 return 0xFFFD; \
318 } while (0)
319
320 // http://en.wikipedia.org/wiki/UTF-8
321
322 if (buf[0] < 0x80) {
323 return buf[0];
324 }
325
326 if (buf[0] < 0xC2) {
327 return 0xFFFD;
328 }
329
330 if (buf[0] < 0xE0) {
331 FAIL_IF((buf[1] & 0xC0u) != 0x80);
332 return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u;
333 }
334
335 if (buf[0] < 0xF0) {
336 FAIL_IF((buf[1] & 0xC0u) != 0x80);
337 FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
338 FAIL_IF((buf[2] & 0xC0u) != 0x80);
339 return ((uint32_t)buf[0] << 12u) + //
340 ((uint32_t)buf[1] << 6u) + //
341 ((uint32_t)buf[2] - 0xE2080u);
342 }
343
344 if (buf[0] < 0xF5) {
345 FAIL_IF((buf[1] & 0xC0u) != 0x80);
346 FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
347 FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90);
348 FAIL_IF((buf[2] & 0xC0u) != 0x80u);
349 FAIL_IF((buf[3] & 0xC0u) != 0x80u);
350 return (((uint32_t)buf[0] << 18u) + //
351 ((uint32_t)buf[1] << 12u) + //
352 ((uint32_t)buf[2] << 6u) + //
353 ((uint32_t)buf[3] - 0x3C82080u));
354 }
355
356 return 0xFFFD;
357 }
358
359 static inline bool
puglMustConfigure(PuglView * view,const PuglEventConfigure * configure)360 puglMustConfigure(PuglView* view, const PuglEventConfigure* configure)
361 {
362 return memcmp(configure, &view->lastConfigure, sizeof(PuglEventConfigure));
363 }
364
365 void
puglDispatchSimpleEvent(PuglView * view,const PuglEventType type)366 puglDispatchSimpleEvent(PuglView* view, const PuglEventType type)
367 {
368 assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP ||
369 type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE ||
370 type == PUGL_LOOP_ENTER || type == PUGL_LOOP_LEAVE);
371
372 const PuglEvent event = {{type, 0}};
373 puglDispatchEvent(view, &event);
374 }
375
376 void
puglDispatchEventInContext(PuglView * view,const PuglEvent * event)377 puglDispatchEventInContext(PuglView* view, const PuglEvent* event)
378 {
379 if (event->type == PUGL_CONFIGURE) {
380 view->frame.x = event->configure.x;
381 view->frame.y = event->configure.y;
382 view->frame.width = event->configure.width;
383 view->frame.height = event->configure.height;
384
385 if (puglMustConfigure(view, &event->configure)) {
386 view->eventFunc(view, event);
387 view->lastConfigure = event->configure;
388 }
389 } else if (event->type == PUGL_EXPOSE) {
390 if (event->expose.width > 0 && event->expose.height > 0) {
391 view->eventFunc(view, event);
392 }
393 } else {
394 view->eventFunc(view, event);
395 }
396 }
397
398 void
puglDispatchEvent(PuglView * view,const PuglEvent * event)399 puglDispatchEvent(PuglView* view, const PuglEvent* event)
400 {
401 switch (event->type) {
402 case PUGL_NOTHING:
403 break;
404 case PUGL_CREATE:
405 case PUGL_DESTROY:
406 view->backend->enter(view, NULL);
407 view->eventFunc(view, event);
408 view->backend->leave(view, NULL);
409 break;
410 case PUGL_CONFIGURE:
411 if (puglMustConfigure(view, &event->configure)) {
412 view->backend->enter(view, NULL);
413 puglDispatchEventInContext(view, event);
414 view->backend->leave(view, NULL);
415 }
416 break;
417 case PUGL_EXPOSE:
418 view->backend->enter(view, &event->expose);
419 puglDispatchEventInContext(view, event);
420 view->backend->leave(view, &event->expose);
421 break;
422 default:
423 view->eventFunc(view, event);
424 }
425 }
426
427 const void*
puglGetInternalClipboard(const PuglView * const view,const char ** const type,size_t * const len)428 puglGetInternalClipboard(const PuglView* const view,
429 const char** const type,
430 size_t* const len)
431 {
432 if (len) {
433 *len = view->clipboard.len;
434 }
435
436 if (type) {
437 *type = view->clipboardType.data;
438 }
439
440 return view->clipboard.data;
441 }
442
443 PuglStatus
puglSetInternalClipboard(PuglView * const view,const char * const type,const void * const data,const size_t len)444 puglSetInternalClipboard(PuglView* const view,
445 const char* const type,
446 const void* const data,
447 const size_t len)
448 {
449 if (!type) {
450 return PUGL_UNSUPPORTED_TYPE;
451 }
452
453 puglSetBlob(&view->clipboardType, type, strlen(type) + 1);
454 puglSetBlob(&view->clipboard, data, len);
455 return PUGL_SUCCESS;
456 }
457