DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1234 lines
38KB

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