DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

wasm.c (36473B)


      1 // Copyright 2012-2022 David Robillard <d@drobilla.net>
      2 // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com>
      3 // SPDX-License-Identifier: ISC
      4 
      5 #include "wasm.h"
      6 
      7 #include "../pugl-upstream/src/internal.h"
      8 
      9 #include <stdio.h>
     10 
     11 #include <emscripten/html5.h>
     12 
     13 #ifdef __cplusplus
     14 #  define PUGL_INIT_STRUCT \
     15     {}
     16 #else
     17 #  define PUGL_INIT_STRUCT \
     18     {                      \
     19       0                    \
     20     }
     21 #endif
     22 
     23 #ifdef __MOD_DEVICES__
     24 #  define MOD_SCALE_FACTOR_MULT 1
     25 #endif
     26 
     27 // #define PUGL_WASM_AUTO_POINTER_LOCK
     28 // #define PUGL_WASM_NO_KEYBOARD_INPUT
     29 // #define PUGL_WASM_NO_MOUSEWHEEL_INPUT
     30 
     31 PuglWorldInternals*
     32 puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
     33 {
     34   PuglWorldInternals* impl =
     35     (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
     36 
     37   impl->scaleFactor = emscripten_get_device_pixel_ratio();
     38 #ifdef __MOD_DEVICES__
     39   impl->scaleFactor *= MOD_SCALE_FACTOR_MULT;
     40 #endif
     41 
     42   printf("DONE: %s %d | -> %f\n", __func__, __LINE__, impl->scaleFactor);
     43 
     44   return impl;
     45 }
     46 
     47 void*
     48 puglGetNativeWorld(PuglWorld*)
     49 {
     50   printf("DONE: %s %d\n", __func__, __LINE__);
     51   return NULL;
     52 }
     53 
     54 PuglInternals*
     55 puglInitViewInternals(PuglWorld* const world)
     56 {
     57   printf("DONE: %s %d\n", __func__, __LINE__);
     58   PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
     59 
     60   impl->buttonPressTimeout = -1;
     61   impl->supportsTouch = PUGL_DONT_CARE; // not yet known
     62 
     63 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
     64   impl->supportsClipboardRead = (PuglViewHintValue)EM_ASM_INT({
     65     if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.readText) === 'function' && window.isSecureContext) {
     66       return 1; // PUGL_TRUE
     67     }
     68     return 0; // PUGL_FALSE
     69   });
     70 
     71   impl->supportsClipboardWrite = (PuglViewHintValue)EM_ASM_INT({
     72     if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
     73       return 1; // PUGL_TRUE
     74     }
     75     if (typeof(document.queryCommandSupported) !== 'undefined' && document.queryCommandSupported("copy")) {
     76       return 1; // PUGL_TRUE
     77     }
     78     return 0; // PUGL_FALSE
     79   });
     80 #endif
     81 
     82   return impl;
     83 }
     84 
     85 static PuglStatus
     86 puglDispatchEventWithContext(PuglView* const view, const PuglEvent* event)
     87 {
     88   PuglStatus st0 = PUGL_SUCCESS;
     89   PuglStatus st1 = PUGL_SUCCESS;
     90 
     91   if (!(st0 = view->backend->enter(view, NULL))) {
     92     st0 = view->eventFunc(view, event);
     93     st1 = view->backend->leave(view, NULL);
     94   }
     95 
     96   return st0 ? st0 : st1;
     97 }
     98 
     99 static PuglMods
    100 translateModifiers(const EM_BOOL ctrlKey,
    101                    const EM_BOOL shiftKey,
    102                    const EM_BOOL altKey,
    103                    const EM_BOOL metaKey)
    104 {
    105   return (ctrlKey  ? PUGL_MOD_CTRL  : 0u) |
    106          (shiftKey ? PUGL_MOD_SHIFT : 0u) |
    107          (altKey   ? PUGL_MOD_ALT   : 0u) |
    108          (metaKey  ? PUGL_MOD_SUPER : 0u);
    109 }
    110 
    111 #ifndef PUGL_WASM_NO_KEYBOARD_INPUT
    112 static PuglKey
    113 keyCodeToSpecial(const unsigned long code, const unsigned long location)
    114 {
    115   switch (code) {
    116   case 0x08: return PUGL_KEY_BACKSPACE;
    117   case 0x1B: return PUGL_KEY_ESCAPE;
    118   case 0x2E: return PUGL_KEY_DELETE;
    119   case 0x70: return PUGL_KEY_F1;
    120   case 0x71: return PUGL_KEY_F2;
    121   case 0x72: return PUGL_KEY_F3;
    122   case 0x73: return PUGL_KEY_F4;
    123   case 0x74: return PUGL_KEY_F5;
    124   case 0x75: return PUGL_KEY_F6;
    125   case 0x76: return PUGL_KEY_F7;
    126   case 0x77: return PUGL_KEY_F8;
    127   case 0x78: return PUGL_KEY_F9;
    128   case 0x79: return PUGL_KEY_F10;
    129   case 0x7A: return PUGL_KEY_F11;
    130   case 0x7B: return PUGL_KEY_F12;
    131   case 0x25: return PUGL_KEY_LEFT;
    132   case 0x26: return PUGL_KEY_UP;
    133   case 0x27: return PUGL_KEY_RIGHT;
    134   case 0x28: return PUGL_KEY_DOWN;
    135   case 0x21: return PUGL_KEY_PAGE_UP;
    136   case 0x22: return PUGL_KEY_PAGE_DOWN;
    137   case 0x24: return PUGL_KEY_HOME;
    138   case 0x23: return PUGL_KEY_END;
    139   case 0x2D: return PUGL_KEY_INSERT;
    140   case 0x10: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SHIFT_R : PUGL_KEY_SHIFT_L;
    141   case 0x11: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_CTRL_R : PUGL_KEY_CTRL_L;
    142   case 0x12: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_ALT_R : PUGL_KEY_ALT_L;
    143   case 0xE0: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SUPER_R : PUGL_KEY_SUPER_L;
    144   case 0x5D: return PUGL_KEY_MENU;
    145   case 0x14: return PUGL_KEY_CAPS_LOCK;
    146   case 0x91: return PUGL_KEY_SCROLL_LOCK;
    147   case 0x90: return PUGL_KEY_NUM_LOCK;
    148   case 0x2C: return PUGL_KEY_PRINT_SCREEN;
    149   case 0x13: return PUGL_KEY_PAUSE;
    150   case '\r': return (PuglKey)'\r';
    151   default: break;
    152   }
    153 
    154   return (PuglKey)0;
    155 }
    156 
    157 static bool
    158 decodeCharacterString(const unsigned long keyCode,
    159                       const EM_UTF8 key[EM_HTML5_SHORT_STRING_LEN_BYTES],
    160                       char str[8])
    161 {
    162   if (key[1] == 0)
    163   {
    164     str[0] = key[0];
    165     return true;
    166   }
    167 
    168   return false;
    169 }
    170 
    171 static EM_BOOL
    172 puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEvent, void* const userData)
    173 {
    174   PuglView* const view = (PuglView*)userData;
    175 
    176   if (!view->impl->visible) {
    177     return EM_FALSE;
    178   }
    179 
    180   if (keyEvent->repeat && view->hints[PUGL_IGNORE_KEY_REPEAT])
    181     return EM_TRUE;
    182 
    183   PuglStatus st0 = PUGL_SUCCESS;
    184   PuglStatus st1 = PUGL_SUCCESS;
    185 
    186   const uint state = translateModifiers(keyEvent->ctrlKey,
    187                                         keyEvent->shiftKey,
    188                                         keyEvent->altKey,
    189                                         keyEvent->metaKey);
    190 
    191   const PuglKey special = keyCodeToSpecial(keyEvent->keyCode, keyEvent->location);
    192 
    193   uint key = keyEvent->key[0] >= ' ' && keyEvent->key[0] <= '~' && keyEvent->key[1] == '\0'
    194            ? keyEvent->key[0]
    195            : keyEvent->keyCode;
    196 
    197   if (key >= 'A' && key <= 'Z' && !keyEvent->shiftKey)
    198       key += 'a' - 'A';
    199 
    200   PuglEvent event = {{PUGL_NOTHING, 0}};
    201   event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
    202   event.key.time = keyEvent->timestamp / 1e3;
    203   // event.key.x     = xevent.xkey.x;
    204   // event.key.y     = xevent.xkey.y;
    205   // event.key.xRoot = xevent.xkey.x_root;
    206   // event.key.yRoot = xevent.xkey.y_root;
    207   event.key.key     = special ? special : key;
    208   event.key.keycode = keyEvent->keyCode;
    209   event.key.state   = state;
    210   st0 = puglDispatchEventWithContext(view, &event);
    211 
    212   d_debug("key event \n"
    213            "\tdown:     %d\n"
    214            "\trepeat:   %d\n"
    215            "\tlocation: %d\n"
    216            "\tstate:    0x%x\n"
    217            "\tkey[]:    '%s'\n"
    218            "\tcode[]:   '%s'\n"
    219            "\tlocale[]: '%s'\n"
    220            "\tkeyCode:  0x%lx:'%c' [deprecated, use key]\n"
    221            "\twhich:    0x%lx:'%c' [deprecated, use key, same as keycode?]\n"
    222            "\tspecial:  0x%x",
    223            eventType == EMSCRIPTEN_EVENT_KEYDOWN,
    224            keyEvent->repeat,
    225            keyEvent->location,
    226            state,
    227            keyEvent->key,
    228            keyEvent->code,
    229            keyEvent->locale,
    230            keyEvent->keyCode, keyEvent->keyCode >= ' ' && keyEvent->keyCode <= '~' ? keyEvent->keyCode : 0,
    231            keyEvent->which, keyEvent->which >= ' ' && keyEvent->which <= '~' ? keyEvent->which : 0,
    232            special);
    233 
    234   if (event.type == PUGL_KEY_PRESS && !special && !(keyEvent->ctrlKey|keyEvent->altKey|keyEvent->metaKey)) {
    235     char str[8] = PUGL_INIT_STRUCT;
    236 
    237     if (decodeCharacterString(keyEvent->keyCode, keyEvent->key, str)) {
    238       d_debug("resulting string is '%s'", str);
    239 
    240       event.text.type      = PUGL_TEXT;
    241       event.text.character = event.key.key;
    242       memcpy(event.text.string, str, sizeof(event.text.string));
    243       st1 = puglDispatchEventWithContext(view, &event);
    244     }
    245   }
    246 
    247   return (st0 ? st0 : st1) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
    248 }
    249 #endif
    250 
    251 static EM_BOOL
    252 puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEvent, void* const userData)
    253 {
    254   PuglView* const view = (PuglView*)userData;
    255 
    256   if (!view->impl->visible) {
    257     return EM_FALSE;
    258   }
    259 
    260   PuglEvent event = {{PUGL_NOTHING, 0}};
    261 
    262   const double   time  = mouseEvent->timestamp / 1e3;
    263   const PuglMods state = translateModifiers(mouseEvent->ctrlKey,
    264                                             mouseEvent->shiftKey,
    265                                             mouseEvent->altKey,
    266                                             mouseEvent->metaKey);
    267 
    268   double scaleFactor = view->world->impl->scaleFactor;
    269 #ifdef __MOD_DEVICES__
    270   if (!view->impl->isFullscreen) {
    271     scaleFactor /= EM_ASM_DOUBLE({
    272       return parseFloat(
    273         RegExp('^scale\\\((.*)\\\)$')
    274         .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
    275       );
    276     }) * MOD_SCALE_FACTOR_MULT;
    277   }
    278 #endif
    279 
    280   // workaround missing pointer lock callback, see https://github.com/emscripten-core/emscripten/issues/9681
    281   EmscriptenPointerlockChangeEvent e;
    282   if (emscripten_get_pointerlock_status(&e) == EMSCRIPTEN_RESULT_SUCCESS)
    283       view->impl->pointerLocked = e.isActive;
    284 
    285 #ifdef __MOD_DEVICES__
    286   const long canvasX = mouseEvent->canvasX;
    287   const long canvasY = mouseEvent->canvasY;
    288 #else
    289   const char* const className = view->world->strings[PUGL_CLASS_NAME];
    290   const double canvasX = mouseEvent->clientX - EM_ASM_DOUBLE({
    291     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    292     return canvasWrapper.getBoundingClientRect().x;
    293   }, className);
    294   const double canvasY = mouseEvent->clientY - EM_ASM_DOUBLE({
    295     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    296     return canvasWrapper.getBoundingClientRect().y;
    297   }, className);
    298 #endif
    299 
    300   switch (eventType) {
    301   case EMSCRIPTEN_EVENT_MOUSEDOWN:
    302   case EMSCRIPTEN_EVENT_MOUSEUP:
    303     event.button.type  = eventType == EMSCRIPTEN_EVENT_MOUSEDOWN ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
    304     event.button.time  = time;
    305     event.button.x     = canvasX * scaleFactor;
    306     event.button.y     = canvasY * scaleFactor;
    307     event.button.xRoot = mouseEvent->screenX * scaleFactor;
    308     event.button.yRoot = mouseEvent->screenY * scaleFactor;
    309     event.button.state = state;
    310     switch (mouseEvent->button) {
    311     case 1:
    312       event.button.button = 2;
    313       break;
    314     case 2:
    315       event.button.button = 1;
    316       break;
    317     default:
    318       event.button.button = mouseEvent->button;
    319       break;
    320     }
    321     break;
    322   case EMSCRIPTEN_EVENT_MOUSEMOVE:
    323     event.motion.type = PUGL_MOTION;
    324     event.motion.time = time;
    325     if (view->impl->pointerLocked) {
    326       // adjust local values for delta
    327       const double movementX = mouseEvent->movementX * scaleFactor;
    328       const double movementY = mouseEvent->movementY * scaleFactor;
    329       view->impl->lastMotion.x += movementX;
    330       view->impl->lastMotion.y += movementY;
    331       view->impl->lastMotion.xRoot += movementX;
    332       view->impl->lastMotion.yRoot += movementY;
    333       // now set x, y, xRoot and yRoot
    334       event.motion.x = view->impl->lastMotion.x;
    335       event.motion.y = view->impl->lastMotion.y;
    336       event.motion.xRoot = view->impl->lastMotion.xRoot;
    337       event.motion.yRoot = view->impl->lastMotion.yRoot;
    338     } else {
    339       // cache values for possible pointer lock movement later
    340       view->impl->lastMotion.x = event.motion.x = canvasX * scaleFactor;
    341       view->impl->lastMotion.y = event.motion.y = canvasY * scaleFactor;
    342       view->impl->lastMotion.xRoot = event.motion.xRoot = mouseEvent->screenX * scaleFactor;
    343       view->impl->lastMotion.yRoot = event.motion.yRoot = mouseEvent->screenY * scaleFactor;
    344     }
    345     event.motion.state = state;
    346     break;
    347   case EMSCRIPTEN_EVENT_MOUSEENTER:
    348   case EMSCRIPTEN_EVENT_MOUSELEAVE:
    349     event.crossing.type  = eventType == EMSCRIPTEN_EVENT_MOUSEENTER ? PUGL_POINTER_IN : PUGL_POINTER_OUT;
    350     event.crossing.time  = time;
    351     event.crossing.x     = canvasX * scaleFactor;
    352     event.crossing.y     = canvasY * scaleFactor;
    353     event.crossing.xRoot = mouseEvent->screenX * scaleFactor;
    354     event.crossing.yRoot = mouseEvent->screenY * scaleFactor;
    355     event.crossing.state = state;
    356     event.crossing.mode  = PUGL_CROSSING_NORMAL;
    357     break;
    358   }
    359 
    360   if (event.type == PUGL_NOTHING)
    361     return EM_FALSE;
    362 
    363   puglDispatchEventWithContext(view, &event);
    364 
    365 #ifdef PUGL_WASM_AUTO_POINTER_LOCK
    366   switch (eventType) {
    367   case EMSCRIPTEN_EVENT_MOUSEDOWN:
    368     emscripten_request_pointerlock(view->world->strings[PUGL_CLASS_NAME], false);
    369     break;
    370   case EMSCRIPTEN_EVENT_MOUSEUP:
    371     emscripten_exit_pointerlock();
    372     break;
    373   }
    374 #endif
    375 
    376   // note: we must always return false, otherwise canvas never gets keyboard input
    377   return EM_FALSE;
    378 }
    379 
    380 static void
    381 puglTouchStartDelay(void* const userData)
    382 {
    383   PuglView*      const view = (PuglView*)userData;
    384   PuglInternals* const impl  = view->impl;
    385 
    386   impl->buttonPressTimeout = -1;
    387   impl->nextButtonEvent.button.time += 2000;
    388   puglDispatchEventWithContext(view, &impl->nextButtonEvent);
    389 }
    390 
    391 static EM_BOOL
    392 puglTouchCallback(const int eventType, const EmscriptenTouchEvent* const touchEvent, void* const userData)
    393 {
    394   if (touchEvent->numTouches <= 0) {
    395     return EM_FALSE;
    396   }
    397 
    398   PuglView*      const view   = (PuglView*)userData;
    399   PuglInternals* const impl   = view->impl;
    400   const char* const className = view->world->strings[PUGL_CLASS_NAME];
    401 
    402   if (impl->supportsTouch == PUGL_DONT_CARE) {
    403     impl->supportsTouch = PUGL_TRUE;
    404 
    405     // stop using mouse press events which conflict with touch
    406     emscripten_set_mousedown_callback(className, view, false, NULL);
    407     emscripten_set_mouseup_callback(className, view, false, NULL);
    408   }
    409 
    410   if (!view->impl->visible) {
    411     return EM_FALSE;
    412   }
    413 
    414   PuglEvent event = {{PUGL_NOTHING, 0}};
    415 
    416   const double   time  = touchEvent->timestamp / 1e3;
    417   const PuglMods state = translateModifiers(touchEvent->ctrlKey,
    418                                             touchEvent->shiftKey,
    419                                             touchEvent->altKey,
    420                                             touchEvent->metaKey);
    421 
    422   double scaleFactor = view->world->impl->scaleFactor;
    423 #ifdef __MOD_DEVICES__
    424   if (!view->impl->isFullscreen) {
    425     scaleFactor /= EM_ASM_DOUBLE({
    426       return parseFloat(
    427         RegExp('^scale\\\((.*)\\\)$')
    428         .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
    429       );
    430     }) * MOD_SCALE_FACTOR_MULT;
    431   }
    432 #endif
    433 
    434   d_debug("touch %d|%s %d || %ld",
    435           eventType,
    436           eventType == EMSCRIPTEN_EVENT_TOUCHSTART ? "start" :
    437           eventType == EMSCRIPTEN_EVENT_TOUCHEND ? "end" : "cancel",
    438           touchEvent->numTouches,
    439           impl->buttonPressTimeout);
    440 
    441   const EmscriptenTouchPoint* point = &touchEvent->touches[0];
    442 
    443   if (impl->buttonPressTimeout != -1 || eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) {
    444     // if we received an event while touch is active, trigger initial click now
    445     if (impl->buttonPressTimeout != -1) {
    446       emscripten_clear_timeout(impl->buttonPressTimeout);
    447       impl->buttonPressTimeout = -1;
    448       if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) {
    449         impl->nextButtonEvent.button.button = 0;
    450       }
    451     }
    452     impl->nextButtonEvent.button.time = time;
    453     puglDispatchEventWithContext(view, &impl->nextButtonEvent);
    454   }
    455 
    456 #ifdef __MOD_DEVICES__
    457   const long canvasX = point->canvasX;
    458   const long canvasY = point->canvasY;
    459 #else
    460   const double canvasX = point->clientX - EM_ASM_DOUBLE({
    461     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    462     return canvasWrapper.getBoundingClientRect().x;
    463   }, className);
    464   const double canvasY = point->clientY - EM_ASM_DOUBLE({
    465     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    466     return canvasWrapper.getBoundingClientRect().y;
    467   }, className);
    468 #endif
    469 
    470   switch (eventType) {
    471   case EMSCRIPTEN_EVENT_TOUCHEND:
    472   case EMSCRIPTEN_EVENT_TOUCHCANCEL:
    473     event.button.type   = PUGL_BUTTON_RELEASE;
    474     event.button.time   = time;
    475     event.button.button = eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL ? 1 : 0;
    476     event.button.x      = canvasX * scaleFactor;
    477     event.button.y      = canvasY * scaleFactor;
    478     event.button.xRoot  = point->screenX * scaleFactor;
    479     event.button.yRoot  = point->screenY * scaleFactor;
    480     event.button.state  = state;
    481     break;
    482 
    483   case EMSCRIPTEN_EVENT_TOUCHSTART:
    484     // this event can be used for a couple of things, store it until we know more
    485     event.button.type   = PUGL_BUTTON_PRESS;
    486     event.button.time   = time;
    487     event.button.button = 1; // if no other event occurs soon, treat it as right-click
    488     event.button.x      = canvasX * scaleFactor;
    489     event.button.y      = canvasY * scaleFactor;
    490     event.button.xRoot  = point->screenX * scaleFactor;
    491     event.button.yRoot  = point->screenY * scaleFactor;
    492     event.button.state  = state;
    493     memcpy(&impl->nextButtonEvent, &event, sizeof(PuglEvent));
    494     impl->buttonPressTimeout = emscripten_set_timeout(puglTouchStartDelay, 2000, view);
    495     // fall through, moving "mouse" to touch position
    496 
    497   case EMSCRIPTEN_EVENT_TOUCHMOVE:
    498     event.motion.type  = PUGL_MOTION;
    499     event.motion.time  = time;
    500     event.motion.x     = canvasX * scaleFactor;
    501     event.motion.y     = canvasY * scaleFactor;
    502     event.motion.xRoot = point->screenX * scaleFactor;
    503     event.motion.yRoot = point->screenY * scaleFactor;
    504     event.motion.state = state;
    505     break;
    506   }
    507 
    508   if (event.type == PUGL_NOTHING)
    509     return EM_FALSE;
    510 
    511   puglDispatchEventWithContext(view, &event);
    512 
    513   // FIXME we must always return false??
    514   return EM_FALSE;
    515 }
    516 
    517 static EM_BOOL
    518 puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData)
    519 {
    520   PuglView* const view = (PuglView*)userData;
    521 
    522   if (!view->impl->visible) {
    523     return EM_FALSE;
    524   }
    525 
    526   d_debug("focus %d|%s", eventType, eventType == EMSCRIPTEN_EVENT_FOCUSIN ? "focus-in" : "focus-out");
    527 
    528   PuglEvent event = {{eventType == EMSCRIPTEN_EVENT_FOCUSIN ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT, 0}};
    529   event.focus.mode = PUGL_CROSSING_NORMAL;
    530 
    531   puglDispatchEventWithContext(view, &event);
    532 
    533   // note: we must always return false, otherwise canvas never gets proper focus
    534   return EM_FALSE;
    535 }
    536 
    537 static EM_BOOL
    538 puglPointerLockChangeCallback(const int eventType, const EmscriptenPointerlockChangeEvent* event, void* const userData)
    539 {
    540   PuglView* const view = (PuglView*)userData;
    541 
    542   view->impl->pointerLocked = event->isActive;
    543   return EM_TRUE;
    544 }
    545 
    546 #ifndef PUGL_WASM_NO_MOUSEWHEEL_INPUT
    547 static EM_BOOL
    548 puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEvent, void* const userData)
    549 {
    550   PuglView* const view = (PuglView*)userData;
    551 
    552   if (!view->impl->visible) {
    553     return EM_FALSE;
    554   }
    555 
    556   double scaleFactor = view->world->impl->scaleFactor;
    557 #ifdef __MOD_DEVICES__
    558   if (!view->impl->isFullscreen) {
    559     scaleFactor /= EM_ASM_DOUBLE({
    560       return parseFloat(
    561         RegExp('^scale\\\((.*)\\\)$')
    562         .exec(document.getElementById("pedalboard-dashboard").style.transform)[1]
    563       );
    564     }) * MOD_SCALE_FACTOR_MULT;
    565   }
    566 #endif
    567 
    568 #ifdef __MOD_DEVICES__
    569   const long canvasX = wheelEvent->mouse.canvasX;
    570   const long canvasY = wheelEvent->mouse.canvasY;
    571 #else
    572   const char* const className = view->world->strings[PUGL_CLASS_NAME];
    573   const double canvasX = wheelEvent->mouse.canvasX - EM_ASM_INT({
    574     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    575     return canvasWrapper.getBoundingClientRect().x;
    576   }, className);
    577   const double canvasY = wheelEvent->mouse.canvasY - EM_ASM_INT({
    578     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    579     return canvasWrapper.getBoundingClientRect().y;
    580   }, className);
    581 #endif
    582 
    583   PuglEvent event = {{PUGL_SCROLL, 0}};
    584   event.scroll.time  = wheelEvent->mouse.timestamp / 1e3;
    585   event.scroll.x     = canvasX;
    586   event.scroll.y     = canvasY;
    587   event.scroll.xRoot = wheelEvent->mouse.screenX;
    588   event.scroll.yRoot = wheelEvent->mouse.screenY;
    589   event.scroll.state = translateModifiers(wheelEvent->mouse.ctrlKey,
    590                                           wheelEvent->mouse.shiftKey,
    591                                           wheelEvent->mouse.altKey,
    592                                           wheelEvent->mouse.metaKey);
    593   event.scroll.direction = PUGL_SCROLL_SMOOTH;
    594   // FIXME handle wheelEvent->deltaMode
    595   event.scroll.dx = wheelEvent->deltaX * 0.01 * scaleFactor;
    596   event.scroll.dy = -wheelEvent->deltaY * 0.01 * scaleFactor;
    597 
    598   return puglDispatchEventWithContext(view, &event) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
    599 }
    600 #endif
    601 
    602 static EM_BOOL
    603 puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData)
    604 {
    605   PuglView* const view = (PuglView*)userData;
    606   const char* const className = view->world->strings[PUGL_CLASS_NAME];
    607   const bool isFullscreen = view->impl->isFullscreen;
    608 
    609   // FIXME
    610   const int width = EM_ASM_INT({
    611     var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    612     canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
    613     if ($1) {
    614       return window.innerWidth;
    615     } else {
    616       return canvasWrapper.clientWidth;
    617     }
    618   }, className, isFullscreen);
    619 
    620   const int height = EM_ASM_INT({
    621     if ($1) {
    622       return window.innerHeight;
    623     } else {
    624       var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    625       return canvasWrapper.clientHeight;
    626     }
    627   }, className, isFullscreen);
    628 
    629   if (!width || !height)
    630     return EM_FALSE;
    631 
    632   double scaleFactor = emscripten_get_device_pixel_ratio();
    633 #ifdef __MOD_DEVICES__
    634   scaleFactor *= MOD_SCALE_FACTOR_MULT;
    635 #endif
    636   view->world->impl->scaleFactor = scaleFactor;
    637 
    638   PuglEvent event        = {{PUGL_CONFIGURE, 0}};
    639   event.configure.x      = view->lastConfigure.x;
    640   event.configure.y      = view->lastConfigure.y;
    641   event.configure.width  = width * scaleFactor;
    642   event.configure.height = height * scaleFactor;
    643   puglDispatchEvent(view, &event);
    644 
    645   emscripten_set_canvas_element_size(view->world->strings[PUGL_CLASS_NAME],
    646                                      width * scaleFactor,
    647                                      height * scaleFactor);
    648 
    649 #ifdef __MOD_DEVICES__
    650   if (isFullscreen) {
    651     EM_ASM({
    652       document.getElementById("pedalboard-dashboard").style.transform = "scale(1.0)";
    653     });
    654   }
    655 #endif
    656 
    657   return EM_TRUE;
    658 }
    659 
    660 static EM_BOOL
    661 puglFullscreenChangeCallback(const int eventType, const EmscriptenFullscreenChangeEvent* const fscEvent, void* const userData)
    662 {
    663   PuglView* const view = (PuglView*)userData;
    664 
    665   view->impl->isFullscreen = fscEvent->isFullscreen;
    666 
    667   double scaleFactor = emscripten_get_device_pixel_ratio();
    668 #ifdef __MOD_DEVICES__
    669   scaleFactor *= MOD_SCALE_FACTOR_MULT;
    670 #endif
    671 
    672   view->world->impl->scaleFactor = scaleFactor;
    673 
    674   if (!fscEvent->isFullscreen)
    675     return puglUiCallback(0, NULL, userData);
    676 
    677   const int width = EM_ASM_INT({
    678     return window.innerWidth;
    679   });
    680 
    681   const int height = EM_ASM_INT({
    682     return window.innerHeight;
    683   });
    684 
    685   PuglEvent event        = {{PUGL_CONFIGURE, 0}};
    686   event.configure.x      = 0;
    687   event.configure.y      = 0;
    688   event.configure.width  = width;
    689   event.configure.height = height;
    690   puglDispatchEvent(view, &event);
    691 
    692   emscripten_set_canvas_element_size(view->world->strings[PUGL_CLASS_NAME], width, height);
    693 
    694 #ifdef __MOD_DEVICES__
    695   EM_ASM({
    696     document.getElementById("pedalboard-dashboard").style.transform = "scale(1.0)";
    697   });
    698 #endif
    699 
    700   return EM_TRUE;
    701 }
    702 
    703 static EM_BOOL
    704 puglVisibilityChangeCallback(const int eventType, const EmscriptenVisibilityChangeEvent* const visibilityChangeEvent, void* const userData)
    705 {
    706   PuglView* const view = (PuglView*)userData;
    707 
    708   view->impl->visible = visibilityChangeEvent->hidden == EM_FALSE;
    709   return EM_FALSE;
    710 }
    711 
    712 PuglStatus
    713 puglRealize(PuglView* const view)
    714 {
    715   printf("TODO: %s %d\n", __func__, __LINE__);
    716   PuglStatus st = PUGL_SUCCESS;
    717 
    718   // Ensure that we do not have a parent
    719   if (view->parent) {
    720   printf("TODO: %s %d\n", __func__, __LINE__);
    721     return PUGL_FAILURE;
    722   }
    723 
    724   if (!view->backend || !view->backend->configure) {
    725   printf("TODO: %s %d\n", __func__, __LINE__);
    726     return PUGL_BAD_BACKEND;
    727   }
    728 
    729   const char* const className = view->world->strings[PUGL_CLASS_NAME];
    730   d_stdout("className is %s", className);
    731 
    732   PuglViewSize defaultSize = view->sizeHints[PUGL_DEFAULT_SIZE];
    733   if (!defaultSize.width || !defaultSize.height) {
    734     return PUGL_BAD_CONFIGURATION;
    735   }
    736 
    737   // Configure and create the backend
    738   if ((st = view->backend->configure(view)) || (st = view->backend->create(view))) {
    739     view->backend->destroy(view);
    740     return st;
    741   }
    742 
    743   if (view->strings[PUGL_WINDOW_TITLE]) {
    744     emscripten_set_window_title(view->strings[PUGL_WINDOW_TITLE]);
    745   }
    746 
    747   puglDispatchSimpleEvent(view, PUGL_REALIZE);
    748 
    749   PuglEvent event        = {{PUGL_CONFIGURE, 0}};
    750   event.configure.x      = view->defaultX;
    751   event.configure.y      = view->defaultY;
    752   event.configure.width  = defaultSize.width;
    753   event.configure.height = defaultSize.height;
    754   puglDispatchEvent(view, &event);
    755 
    756   view->impl->created = true;
    757 
    758   EM_ASM({
    759    var canvasWrapper = document.getElementById(UTF8ToString($0)).parentElement;
    760    canvasWrapper.style.setProperty("--device-pixel-ratio", window.devicePixelRatio);
    761   }, className);
    762 
    763   emscripten_set_canvas_element_size(className, defaultSize.width, defaultSize.height);
    764 #ifndef PUGL_WASM_NO_KEYBOARD_INPUT
    765 //   emscripten_set_keypress_callback(className, view, false, puglKeyCallback);
    766   emscripten_set_keydown_callback(className, view, false, puglKeyCallback);
    767   emscripten_set_keyup_callback(className, view, false, puglKeyCallback);
    768 #endif
    769   emscripten_set_touchstart_callback(className, view, false, puglTouchCallback);
    770   emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
    771   emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
    772   emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglTouchCallback);
    773   emscripten_set_mousedown_callback(className, view, false, puglMouseCallback);
    774   emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglMouseCallback);
    775   emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglMouseCallback);
    776   emscripten_set_mouseenter_callback(className, view, false, puglMouseCallback);
    777   emscripten_set_mouseleave_callback(className, view, false, puglMouseCallback);
    778   emscripten_set_focusin_callback(className, view, false, puglFocusCallback);
    779   emscripten_set_focusout_callback(className, view, false, puglFocusCallback);
    780 #ifndef PUGL_WASM_NO_MOUSEWHEEL_INPUT
    781   emscripten_set_wheel_callback(className, view, false, puglWheelCallback);
    782 #endif
    783   emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglPointerLockChangeCallback);
    784   emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglUiCallback);
    785   emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglFullscreenChangeCallback);
    786   emscripten_set_visibilitychange_callback(view, false, puglVisibilityChangeCallback);
    787 
    788   printf("TODO: %s %d\n", __func__, __LINE__);
    789   return PUGL_SUCCESS;
    790 }
    791 
    792 PuglStatus
    793 puglShow(PuglView* const view, PuglShowCommand)
    794 {
    795   view->impl->visible = true;
    796   view->impl->needsRepaint = true;
    797   return puglPostRedisplay(view);
    798 }
    799 
    800 PuglStatus
    801 puglHide(PuglView* const view)
    802 {
    803   view->impl->visible = false;
    804   return PUGL_FAILURE;
    805 }
    806 
    807 void
    808 puglFreeViewInternals(PuglView* const view)
    809 {
    810   printf("DONE: %s %d\n", __func__, __LINE__);
    811   if (view && view->impl) {
    812     if (view->backend) {
    813       // unregister the window events, to make sure no callbacks to old views are triggered
    814       emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    815       emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    816       emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    817       emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    818       emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    819       emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    820       emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    821       emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, NULL);
    822       emscripten_set_visibilitychange_callback(NULL, false, NULL);
    823       view->backend->destroy(view);
    824     }
    825     free(view->impl->clipboardData);
    826     free(view->impl->timers);
    827     free(view->impl);
    828   }
    829 }
    830 
    831 void
    832 puglFreeWorldInternals(PuglWorld* const world)
    833 {
    834   printf("DONE: %s %d\n", __func__, __LINE__);
    835   free(world->impl);
    836 }
    837 
    838 PuglStatus
    839 puglGrabFocus(PuglView*)
    840 {
    841   return PUGL_FAILURE;
    842 }
    843 
    844 double
    845 puglGetScaleFactor(const PuglView* const view)
    846 {
    847   printf("DONE: %s %d\n", __func__, __LINE__);
    848   return view->world->impl->scaleFactor;
    849 }
    850 
    851 double
    852 puglGetTime(const PuglWorld*)
    853 {
    854   return emscripten_get_now() / 1e3;
    855 }
    856 
    857 PuglStatus
    858 puglUpdate(PuglWorld* const world, const double timeout)
    859 {
    860   for (size_t i = 0; i < world->numViews; ++i) {
    861     PuglView* const view = world->views[i];
    862 
    863     if (!view->impl->visible) {
    864       continue;
    865     }
    866 
    867     puglDispatchSimpleEvent(view, PUGL_UPDATE);
    868 
    869     if (!view->impl->needsRepaint) {
    870       continue;
    871     }
    872 
    873     view->impl->needsRepaint = false;
    874 
    875     PuglEvent event     = {{PUGL_EXPOSE, 0}};
    876     event.expose.x      = view->lastConfigure.x;
    877     event.expose.y      = view->lastConfigure.y;
    878     event.expose.width  = view->lastConfigure.width;
    879     event.expose.height = view->lastConfigure.height;
    880     puglDispatchEvent(view, &event);
    881   }
    882 
    883   return PUGL_SUCCESS;
    884 }
    885 
    886 PuglStatus
    887 puglPostRedisplay(PuglView* const view)
    888 {
    889   view->impl->needsRepaint = true;
    890   return PUGL_SUCCESS;
    891 }
    892 
    893 PuglStatus
    894 puglPostRedisplayRect(PuglView* const view, const PuglRect rect)
    895 {
    896   view->impl->needsRepaint = true;
    897   return PUGL_FAILURE;
    898 }
    899 
    900 PuglNativeView
    901 puglGetNativeView(PuglView* const view)
    902 {
    903   return 0;
    904 }
    905 
    906 PuglStatus
    907 puglViewStringChanged(PuglView*, const PuglStringHint key, const char* const value)
    908 {
    909   switch (key) {
    910   case PUGL_CLASS_NAME:
    911     break;
    912   case PUGL_WINDOW_TITLE:
    913     emscripten_set_window_title(value);
    914     break;
    915   }
    916 
    917   return PUGL_SUCCESS;
    918 }
    919 
    920 PuglStatus
    921 puglSetSizeHint(PuglView* const    view,
    922                 const PuglSizeHint hint,
    923                 const PuglSpan     width,
    924                 const PuglSpan     height)
    925 {
    926   view->sizeHints[hint].width  = width;
    927   view->sizeHints[hint].height = height;
    928   return PUGL_SUCCESS;
    929 }
    930 
    931 static EM_BOOL
    932 puglTimerLoopCallback(double timeout, void* const arg)
    933 {
    934   PuglTimer*     const timer = (PuglTimer*)arg;
    935   PuglInternals* const impl  = timer->view->impl;
    936 
    937   // only handle active timers
    938   for (uint32_t i=0; i<impl->numTimers; ++i)
    939   {
    940     if (impl->timers[i].id == timer->id)
    941     {
    942       PuglEvent event = {{PUGL_TIMER, 0}};
    943       event.timer.id  = timer->id;
    944       puglDispatchEventWithContext(timer->view, &event);
    945       return EM_TRUE;
    946     }
    947   }
    948 
    949   return EM_FALSE;
    950 
    951   // unused
    952   (void)timeout;
    953 }
    954 
    955 PuglStatus
    956 puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout)
    957 {
    958   printf("DONE: %s %d\n", __func__, __LINE__);
    959   PuglInternals* const impl = view->impl;
    960   const uint32_t timerIndex = impl->numTimers++;
    961 
    962   if (impl->timers == NULL)
    963     impl->timers = (PuglTimer*)malloc(sizeof(PuglTimer));
    964   else
    965     impl->timers = (PuglTimer*)realloc(impl->timers, sizeof(PuglTimer) * timerIndex);
    966 
    967   PuglTimer* const timer = &impl->timers[timerIndex];
    968   timer->view = view;
    969   timer->id = id;
    970 
    971   emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1e3, timer);
    972   return PUGL_SUCCESS;
    973 }
    974 
    975 PuglStatus
    976 puglStopTimer(PuglView* const view, const uintptr_t id)
    977 {
    978   printf("DONE: %s %d\n", __func__, __LINE__);
    979   PuglInternals* const impl = view->impl;
    980 
    981   if (impl->timers == NULL || impl->numTimers == 0)
    982     return PUGL_FAILURE;
    983 
    984   for (uint32_t i=0; i<impl->numTimers; ++i)
    985   {
    986     if (impl->timers[i].id == id)
    987     {
    988       memmove(impl->timers + i, impl->timers + (i + 1), sizeof(PuglTimer) * (impl->numTimers - 1));
    989       --impl->numTimers;
    990       return PUGL_SUCCESS;
    991     }
    992   }
    993 
    994   return PUGL_FAILURE;
    995 }
    996 
    997 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
    998 EM_JS(char*, puglGetAsyncClipboardData, (), {
    999   var text = Asyncify.handleSleep(function(wakeUp) {
   1000     navigator.clipboard.readText()
   1001       .then(function(text) {
   1002         wakeUp(text);
   1003       })
   1004       .catch(function() {
   1005         wakeUp("");
   1006       });
   1007   });
   1008   if (!text.length) {
   1009     return null;
   1010   }
   1011   var length = lengthBytesUTF8(text) + 1;
   1012   var str = _malloc(length);
   1013   stringToUTF8(text, str, length);
   1014   return str;
   1015 });
   1016 #endif
   1017 
   1018 PuglStatus
   1019 puglPaste(PuglView* const view)
   1020 {
   1021 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
   1022   // abort early if we already know it is not supported
   1023   if (view->impl->supportsClipboardRead == PUGL_FALSE) {
   1024     return PUGL_UNSUPPORTED;
   1025   }
   1026 
   1027   free(view->impl->clipboardData);
   1028   view->impl->clipboardData = puglGetAsyncClipboardData();
   1029 #endif
   1030 
   1031   if (view->impl->clipboardData == NULL) {
   1032     return PUGL_FAILURE;
   1033   }
   1034 
   1035   const PuglDataOfferEvent offer = {
   1036     PUGL_DATA_OFFER,
   1037     0,
   1038     emscripten_get_now() / 1e3,
   1039   };
   1040 
   1041   PuglEvent offerEvent;
   1042   offerEvent.offer = offer;
   1043   puglDispatchEvent(view, &offerEvent);
   1044   return PUGL_SUCCESS;
   1045 }
   1046 
   1047 PuglStatus
   1048 puglAcceptOffer(PuglView* const                 view,
   1049                 const PuglDataOfferEvent* const offer,
   1050                 const uint32_t                  typeIndex)
   1051 {
   1052   if (typeIndex != 0) {
   1053     return PUGL_UNSUPPORTED;
   1054   }
   1055 
   1056   const PuglDataEvent data = {
   1057     PUGL_DATA,
   1058     0,
   1059     emscripten_get_now() / 1e3,
   1060     0,
   1061   };
   1062 
   1063   PuglEvent dataEvent;
   1064   dataEvent.data = data;
   1065   puglDispatchEvent(view, &dataEvent);
   1066   return PUGL_SUCCESS;
   1067 }
   1068 
   1069 uint32_t
   1070 puglGetNumClipboardTypes(const PuglView* const view)
   1071 {
   1072   return view->impl->clipboardData != NULL ? 1u : 0u;
   1073 }
   1074 
   1075 const char*
   1076 puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex)
   1077 {
   1078   return (typeIndex == 0 && view->impl->clipboardData != NULL)
   1079            ? "text/plain"
   1080            : NULL;
   1081 }
   1082 
   1083 const void*
   1084 puglGetClipboard(PuglView* const view,
   1085                  const uint32_t  typeIndex,
   1086                  size_t* const   len)
   1087 {
   1088   return view->impl->clipboardData;
   1089 }
   1090 
   1091 PuglStatus
   1092 puglSetClipboard(PuglView* const   view,
   1093                  const char* const type,
   1094                  const void* const data,
   1095                  const size_t      len)
   1096 {
   1097   // only utf8 text supported for now
   1098   if (type != NULL && strcmp(type, "text/plain") != 0) {
   1099     return PUGL_UNSUPPORTED;
   1100   }
   1101 
   1102   const char* const className = view->world->strings[PUGL_CLASS_NAME];
   1103   const char* const text      = (const char*)data;
   1104 
   1105 #ifdef PUGL_WASM_ASYNC_CLIPBOARD
   1106   // abort early if we already know it is not supported
   1107   if (view->impl->supportsClipboardWrite == PUGL_FALSE) {
   1108     return PUGL_UNSUPPORTED;
   1109   }
   1110 #else
   1111   puglSetString(&view->impl->clipboardData, text);
   1112 #endif
   1113 
   1114   EM_ASM({
   1115     if (typeof(navigator.clipboard) !== 'undefined' && typeof(navigator.clipboard.writeText) === 'function' && window.isSecureContext) {
   1116       navigator.clipboard.writeText(UTF8ToString($1));
   1117     } else {
   1118       var canvasClipboardObjName = UTF8ToString($0) + "_clipboard";
   1119       var canvasClipboardElem = document.getElementById(canvasClipboardObjName);
   1120 
   1121       if (!canvasClipboardElem) {
   1122         canvasClipboardElem = document.createElement('textarea');
   1123         canvasClipboardElem.id = canvasClipboardObjName;
   1124         canvasClipboardElem.style.position = 'fixed';
   1125         canvasClipboardElem.style.whiteSpace = 'pre';
   1126         canvasClipboardElem.style.zIndex = '-1';
   1127         canvasClipboardElem.setAttribute('readonly', true);
   1128         document.body.appendChild(canvasClipboardElem);
   1129       }
   1130 
   1131       canvasClipboardElem.textContent = UTF8ToString($1);
   1132       canvasClipboardElem.select();
   1133       document.execCommand("copy");
   1134     }
   1135   }, className, text);
   1136 
   1137   // FIXME proper return status
   1138   return PUGL_SUCCESS;
   1139 }
   1140 
   1141 PuglStatus
   1142 puglSetCursor(PuglView* const view, const PuglCursor cursor)
   1143 {
   1144   printf("TODO: %s %d\n", __func__, __LINE__);
   1145   return PUGL_FAILURE;
   1146 }
   1147 
   1148 PuglStatus
   1149 puglSetTransientParent(PuglView* const view, const PuglNativeView parent)
   1150 {
   1151   printf("TODO: %s %d\n", __func__, __LINE__);
   1152   view->transientParent = parent;
   1153   return PUGL_FAILURE;
   1154 }
   1155 
   1156 PuglStatus
   1157 puglSetPosition(PuglView* const view, const int x, const int y)
   1158 {
   1159   printf("TODO: %s %d\n", __func__, __LINE__);
   1160 
   1161   if (x > INT16_MAX || y > INT16_MAX) {
   1162     return PUGL_BAD_PARAMETER;
   1163   }
   1164 
   1165   if (!view->impl->created) {
   1166     // Set defaults to be used when realized
   1167     view->defaultX = x;
   1168     view->defaultY = y;
   1169     return PUGL_SUCCESS;
   1170   }
   1171 
   1172   view->lastConfigure.x = (PuglCoord)x;
   1173   view->lastConfigure.y = (PuglCoord)y;
   1174   return puglPostRedisplay(view);
   1175 }