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 "types.h"
20 
21 #include "pugl/pugl.h"
22 
23 #include <assert.h>
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 const char*
puglStrerror(const PuglStatus status)29 puglStrerror(const PuglStatus status)
30 {
31   // clang-format off
32   switch (status) {
33   case PUGL_SUCCESS:               return "Success";
34   case PUGL_FAILURE:               return "Non-fatal failure";
35   case PUGL_UNKNOWN_ERROR:         return "Unknown system error";
36   case PUGL_BAD_BACKEND:           return "Invalid or missing backend";
37   case PUGL_BAD_CONFIGURATION:     return "Invalid view configuration";
38   case PUGL_BAD_PARAMETER:         return "Invalid parameter";
39   case PUGL_BACKEND_FAILED:        return "Backend initialisation failed";
40   case PUGL_REGISTRATION_FAILED:   return "Class registration failed";
41   case PUGL_REALIZE_FAILED:        return "View creation failed";
42   case PUGL_SET_FORMAT_FAILED:     return "Failed to set pixel format";
43   case PUGL_CREATE_CONTEXT_FAILED: return "Failed to create drawing context";
44   case PUGL_UNSUPPORTED_TYPE:      return "Unsupported data type";
45   }
46   // clang-format on
47 
48   return "Unknown error";
49 }
50 
51 void
puglSetString(char ** dest,const char * string)52 puglSetString(char** dest, const char* string)
53 {
54   if (*dest != string) {
55     const size_t len = strlen(string);
56 
57     *dest = (char*)realloc(*dest, len + 1);
58     strncpy(*dest, string, len + 1);
59   }
60 }
61 
62 void
puglSetBlob(PuglBlob * const dest,const void * const data,const size_t len)63 puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len)
64 {
65   if (data) {
66     dest->len  = len;
67     dest->data = realloc(dest->data, len + 1);
68     memcpy(dest->data, data, len);
69     ((char*)dest->data)[len] = 0;
70   } else {
71     dest->len  = 0;
72     dest->data = NULL;
73   }
74 }
75 
76 static void
puglSetDefaultHints(PuglHints hints)77 puglSetDefaultHints(PuglHints hints)
78 {
79   hints[PUGL_USE_COMPAT_PROFILE]    = PUGL_TRUE;
80   hints[PUGL_CONTEXT_VERSION_MAJOR] = 2;
81   hints[PUGL_CONTEXT_VERSION_MINOR] = 0;
82   hints[PUGL_RED_BITS]              = 8;
83   hints[PUGL_GREEN_BITS]            = 8;
84   hints[PUGL_BLUE_BITS]             = 8;
85   hints[PUGL_ALPHA_BITS]            = 8;
86   hints[PUGL_DEPTH_BITS]            = 0;
87   hints[PUGL_STENCIL_BITS]          = 0;
88   hints[PUGL_SAMPLES]               = 0;
89   hints[PUGL_DOUBLE_BUFFER]         = PUGL_TRUE;
90   hints[PUGL_SWAP_INTERVAL]         = PUGL_DONT_CARE;
91   hints[PUGL_RESIZABLE]             = PUGL_FALSE;
92   hints[PUGL_IGNORE_KEY_REPEAT]     = PUGL_FALSE;
93   hints[PUGL_REFRESH_RATE]          = PUGL_DONT_CARE;
94 }
95 
96 PuglWorld*
puglNewWorld(PuglWorldType type,PuglWorldFlags flags)97 puglNewWorld(PuglWorldType type, PuglWorldFlags flags)
98 {
99   PuglWorld* world = (PuglWorld*)calloc(1, sizeof(PuglWorld));
100   if (!world || !(world->impl = puglInitWorldInternals(type, flags))) {
101     free(world);
102     return NULL;
103   }
104 
105   world->startTime = puglGetTime(world);
106 
107   puglSetString(&world->className, "Pugl");
108 
109   return world;
110 }
111 
112 void
puglFreeWorld(PuglWorld * const world)113 puglFreeWorld(PuglWorld* const world)
114 {
115   puglFreeWorldInternals(world);
116   free(world->className);
117   free(world->views);
118   free(world);
119 }
120 
121 void
puglSetWorldHandle(PuglWorld * world,PuglWorldHandle handle)122 puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle)
123 {
124   world->handle = handle;
125 }
126 
127 PuglWorldHandle
puglGetWorldHandle(PuglWorld * world)128 puglGetWorldHandle(PuglWorld* world)
129 {
130   return world->handle;
131 }
132 
133 PuglStatus
puglSetClassName(PuglWorld * const world,const char * const name)134 puglSetClassName(PuglWorld* const world, const char* const name)
135 {
136   puglSetString(&world->className, name);
137   return PUGL_SUCCESS;
138 }
139 
140 PuglView*
puglNewView(PuglWorld * const world)141 puglNewView(PuglWorld* const world)
142 {
143   PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
144   if (!view || !(view->impl = puglInitViewInternals())) {
145     free(view);
146     return NULL;
147   }
148 
149   view->world     = world;
150   view->minWidth  = 1;
151   view->minHeight = 1;
152 
153   puglSetDefaultHints(view->hints);
154 
155   // Add to world view list
156   ++world->numViews;
157   world->views =
158     (PuglView**)realloc(world->views, world->numViews * sizeof(PuglView*));
159 
160   world->views[world->numViews - 1] = view;
161 
162   return view;
163 }
164 
165 void
puglFreeView(PuglView * view)166 puglFreeView(PuglView* view)
167 {
168   if (view->eventFunc && view->backend) {
169     puglDispatchSimpleEvent(view, PUGL_DESTROY);
170   }
171 
172   // Remove from world view list
173   PuglWorld* world = view->world;
174   for (size_t i = 0; i < world->numViews; ++i) {
175     if (world->views[i] == view) {
176       if (i == world->numViews - 1) {
177         world->views[i] = NULL;
178       } else {
179         memmove(world->views + i,
180                 world->views + i + 1,
181                 sizeof(PuglView*) * (world->numViews - i - 1));
182         world->views[world->numViews - 1] = NULL;
183       }
184       --world->numViews;
185     }
186   }
187 
188   free(view->title);
189   free(view->clipboard.data);
190   puglFreeViewInternals(view);
191   free(view);
192 }
193 
194 PuglWorld*
puglGetWorld(PuglView * view)195 puglGetWorld(PuglView* view)
196 {
197   return view->world;
198 }
199 
200 PuglStatus
puglSetViewHint(PuglView * view,PuglViewHint hint,int value)201 puglSetViewHint(PuglView* view, PuglViewHint hint, int value)
202 {
203   if (value == PUGL_DONT_CARE) {
204     switch (hint) {
205     case PUGL_USE_COMPAT_PROFILE:
206     case PUGL_USE_DEBUG_CONTEXT:
207     case PUGL_CONTEXT_VERSION_MAJOR:
208     case PUGL_CONTEXT_VERSION_MINOR:
209     case PUGL_SWAP_INTERVAL:
210       return PUGL_BAD_PARAMETER;
211     default:
212       break;
213     }
214   }
215 
216   if (hint < PUGL_NUM_VIEW_HINTS) {
217     view->hints[hint] = value;
218     return PUGL_SUCCESS;
219   }
220 
221   return PUGL_BAD_PARAMETER;
222 }
223 
224 int
puglGetViewHint(const PuglView * view,PuglViewHint hint)225 puglGetViewHint(const PuglView* view, PuglViewHint hint)
226 {
227   return (hint < PUGL_NUM_VIEW_HINTS) ? view->hints[hint] : 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
puglConfigure(PuglView * view,const PuglEvent * event)376 puglConfigure(PuglView* view, const PuglEvent* event)
377 {
378   assert(event->type == PUGL_CONFIGURE);
379 
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 }
390 
391 void
puglExpose(PuglView * view,const PuglEvent * event)392 puglExpose(PuglView* view, const PuglEvent* event)
393 {
394   if (event->expose.width > 0.0 && event->expose.height > 0.0) {
395     view->eventFunc(view, event);
396   }
397 }
398 
399 void
puglDispatchEvent(PuglView * view,const PuglEvent * event)400 puglDispatchEvent(PuglView* view, const PuglEvent* event)
401 {
402   switch (event->type) {
403   case PUGL_NOTHING:
404     break;
405   case PUGL_CREATE:
406   case PUGL_DESTROY:
407     view->backend->enter(view, NULL);
408     view->eventFunc(view, event);
409     view->backend->leave(view, NULL);
410     break;
411   case PUGL_CONFIGURE:
412     if (puglMustConfigure(view, &event->configure)) {
413       view->backend->enter(view, NULL);
414       puglConfigure(view, event);
415       view->backend->leave(view, NULL);
416     }
417     break;
418   case PUGL_MAP:
419     if (!view->visible) {
420       view->visible = true;
421       view->eventFunc(view, event);
422     }
423     break;
424   case PUGL_UNMAP:
425     if (view->visible) {
426       view->visible = false;
427       view->eventFunc(view, event);
428     }
429     break;
430   case PUGL_EXPOSE:
431     view->backend->enter(view, &event->expose);
432     puglExpose(view, event);
433     view->backend->leave(view, &event->expose);
434     break;
435   default:
436     view->eventFunc(view, event);
437   }
438 }
439 
440 const void*
puglGetInternalClipboard(const PuglView * const view,const char ** const type,size_t * const len)441 puglGetInternalClipboard(const PuglView* const view,
442                          const char** const    type,
443                          size_t* const         len)
444 {
445   if (len) {
446     *len = view->clipboard.len;
447   }
448 
449   if (type) {
450     *type = "text/plain";
451   }
452 
453   return view->clipboard.data;
454 }
455 
456 PuglStatus
puglSetInternalClipboard(PuglView * const view,const char * const type,const void * const data,const size_t len)457 puglSetInternalClipboard(PuglView* const   view,
458                          const char* const type,
459                          const void* const data,
460                          const size_t      len)
461 {
462   if (type && !!strcmp(type, "text/plain")) {
463     return PUGL_UNSUPPORTED_TYPE;
464   }
465 
466   puglSetBlob(&view->clipboard, data, len);
467   return PUGL_SUCCESS;
468 }
469