From 9dfe26d752963805a44e142744089d16783557db Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 30 Oct 2026 12:58:36 -0700 Subject: [PATCH] Fix browser clipboard paste handling --- src/windy/platforms/emscripten/emdefs.nim | 19 ++++++++++++ src/windy/platforms/emscripten/platform.nim | 33 +++++++++++++++++---- tests/manual_inputs.nim | 6 ++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/windy/platforms/emscripten/emdefs.nim b/src/windy/platforms/emscripten/emdefs.nim index 2a3212f..7393d50 100644 --- a/src/windy/platforms/emscripten/emdefs.nim +++ b/src/windy/platforms/emscripten/emdefs.nim @@ -123,6 +123,24 @@ EM_JS(void, open_url, (const char* url), { window.open(urlUtf8, '_blank'); }); +EM_JS(void, setup_paste_handler, (const char* target, void* userData), { + var canvas = document.querySelector(UTF8ToString(target)); + canvas.addEventListener('paste', function(event) { + if (!event.clipboardData) return; + var text = event.clipboardData.getData('text/plain'); + var size = lengthBytesUTF8(text) + 1; + var pointer = _malloc(size); + stringToUTF8(text, pointer, size); + try { + if (Module._windy_paste_callback(userData, pointer)) { + event.preventDefault(); + } + } finally { + _free(pointer); + } + }); +}); + EM_JS(void, setup_drag_drop_handlers_internal, (const char* target, void* userData), { const canvas = target ? document.querySelector(UTF8ToString(target)) : Module.canvas; if (!canvas) { @@ -329,6 +347,7 @@ proc get_device_pixel_ratio*(): cdouble {.importc.} proc open_temp_text_file*(title, text: cstring) {.importc.} proc open_url*(url: cstring) {.importc.} proc setup_drag_drop_handlers_internal*(target: cstring, userData: pointer) {.importc.} +proc setup_paste_handler*(target: cstring, userData: pointer) {.importc.} proc set_cursor*(cursor: cstring) {.importc.} proc getLocalStorageLength*(key: cstring): cint {.importc: "get_local_storage_length".} proc getLocalStorageInto*(output: cstring, maxLen: cint, key: cstring): cint {.importc: "get_local_storage_into".} diff --git a/src/windy/platforms/emscripten/platform.nim b/src/windy/platforms/emscripten/platform.nim index efc0387..abbd620 100644 --- a/src/windy/platforms/emscripten/platform.nim +++ b/src/windy/platforms/emscripten/platform.nim @@ -76,6 +76,7 @@ var frameDispatchActive: bool pendingResize: bool pendingFocusChange: bool + clipboardText: string proc handleButtonPress(window: Window, button: Button) proc handleButtonRelease(window: Window, button: Button) @@ -375,7 +376,8 @@ proc buttonToggle*(window: Window): ButtonView = # Clipboard functions proc clipboardContent*(): string = - "" # Would need JavaScript interop + ## Returns the text supplied by the latest browser paste event. + clipboardText proc `clipboardContent=`*(content: string) = discard # Would need JavaScript interop @@ -508,11 +510,8 @@ proc setClipboardString*(s: string) = ) proc getClipboardString*(): string = - # TODO: Implement clipboard with Emscripten. - raise newException( - Exception, - "getClipboardString is not supported on emscripten" - ) + ## Returns the text supplied by the latest browser paste event. + clipboardText proc getClipboardContentKinds*(): set[ClipboardContentKind] = ## Image clipboard is not implemented on Emscripten yet. @@ -795,6 +794,10 @@ proc onKeyDown(eventType: cint, keyEvent: ptr EmscriptenKeyboardEvent, userData: button = keyEventToButton(keyEvent) key = $cast[cstring](addr keyEvent.key[0]) window.syncModifiers(keyEvent, button, pressed = true) + if window.runeInputEnabled and button == KeyV and + (keyEvent.metaKey or (keyEvent.ctrlKey and not keyEvent.altKey)): + # Let the browser deliver clipboard text before notifying the editor. + return 0 if button != ButtonUnknown: window.handleButtonPress(button) if window.runeInputEnabled and not keyEvent.metaKey and @@ -902,6 +905,7 @@ proc setupEventHandlers(window: Window) = # Drag and drop handler setup_drag_drop_handlers_internal(window.canvas, cast[pointer](window)) + setup_paste_handler(window.canvas, cast[pointer](window)) proc handleButtonPress(window: Window, button: Button) = handleButtonPressTemplate() @@ -912,6 +916,23 @@ proc handleButtonRelease(window: Window, button: Button) = proc handleRune(window: Window, rune: Rune) = handleRuneTemplate() +proc windy_paste_callback(userData: pointer, text: cstring): cint {. + exportc, cdecl, codegenDecl: "EMSCRIPTEN_KEEPALIVE $# $#$#" +.} = + ## Delivers browser clipboard text as one editor paste operation. + let window = cast[Window](userData) + if window == nil or not window.runeInputEnabled: + return 0 + clipboardText = $text + let previous = window.state.buttonDown + window.state.buttonDown.excl(KeyLeftAlt) + window.state.buttonDown.excl(KeyRightAlt) + window.state.buttonDown.incl(KeyLeftControl) + window.handleButtonPress(KeyV) + window.handleButtonRelease(KeyV) + window.state.buttonDown = previous + return 1 + proc windy_file_drop_callback(userData: pointer, fileNamePtr: cstring, fileDataPtr: pointer, fileDataLen: cint) {.exportc, cdecl, codegenDecl: "EMSCRIPTEN_KEEPALIVE $# $#$#".} = ## callback to handle the file drop event. ## EMSCRIPTEN_KEEPALIVE is required to avoid dead code elimination. diff --git a/tests/manual_inputs.nim b/tests/manual_inputs.nim index bec9dcc..9f497df 100644 --- a/tests/manual_inputs.nim +++ b/tests/manual_inputs.nim @@ -36,6 +36,12 @@ window.onButtonPress = proc(button: Button) = window.runeInputEnabled = not window.runeInputEnabled of KeyEscape: text.setLen(0) + of KeyV: + if window.runeInputEnabled and + (set[Button](window.buttonDown) * { + KeyLeftControl, KeyRightControl, KeyLeftSuper, KeyRightSuper + }) != {}: + text.add(getClipboardString().toRunes()) else: discard updateTitle()