summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndre Weissflog <floooh@gmail.com>2019-12-02 17:56:31 +0100
committerGitHub <noreply@github.com>2019-12-02 17:56:31 +0100
commitcaea66533e96dd85d46086e4c3455d01a698aad3 (patch)
tree9fc672ecd3d8478dfd30e162aa0952fcce08d2a7
parentc3020f9876a96044ad6c8ef43884c03e460159a8 (diff)
Initial clipboard support (#237)
* WIP clipboard support code dump. HTML5 clipboard/permissions API is a mess, so that's not an option. * sokol_app.h: clipboard on emscripten working * sokol_imgui.h: init-flag to disable Ctrl-hotkeys * sokol_app.h: new sapp_consume_event() function, rename clipboard event * add some todos to sokol_app.h * macOS: send CLIPBOARD_PASTED event * sokol_app.h: add Win32 clipboard support * sokol_app.h: documentation for clipboard support * sokol_app.h: dummy clipboard support * Start adding clipboard support to sokol_imgui. Current problem: on HTML5, copy/paste must happen inside JS event handler. * mention new sokol_app.h clipboard support in README * readme tweaks * sokol_app.h: MSVC warnings fixed * sokol_imgui.h: fix key debuffering * sokol_app.h: documentation fixes
-rw-r--r--README.md22
-rw-r--r--sokol_app.h321
-rw-r--r--util/sokol_imgui.h135
3 files changed, 457 insertions, 21 deletions
diff --git a/README.md b/README.md
index 4c061bd1..476b6efa 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Simple
[STB-style](https://github.com/nothings/stb/blob/master/docs/stb_howto.txt)
cross-platform libraries for C and C++, written in C.
-[See what's new](#updates) (**08-Sep-2019**: clamp-to-border texture sampling in sokol_gfx.h)
+[See what's new](#updates) (**02-Dec-2019**: initial clipboard support in sokol_app.h)
[Live Samples](https://floooh.github.io/sokol-html5/index.html) via WASM.
@@ -465,6 +465,26 @@ Mainly some "missing features" for desktop apps:
# Updates
+- **02-Dec-2019**: Initial clipboard support in sokol_app.h for Windows, macOS
+ and HTML5. This allows to read and write UTF-8 encoded strings from and
+ to the target platform's shared clipboard.
+
+ A 'real-world' example usage is in the [Visual6502 Remix project](https://github.com/floooh/v6502r).
+
+ Unfortunately clipboard support on the HTML5 platform comes with a lot of
+ platform-specific caveats which can't be solved in sokol_app.h alone
+ because of the restrictions the web platform puts on clipboard access and
+ different behaviours and support levels of the various HTML5 clipboard
+ APIs. I'm not really happy with the current HTML5 clipboard
+ implementation. It sorta works, but it sure ain't pretty :)
+
+ Maybe the situation will improve in a few years when all browsers agree
+ on and support the new [permission-based clipboard
+ API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).
+
+ For documention of the clipboard feature, search for CLIPBOARD SUPPORT
+ in sokol_app.h
+
- **08-Sep-2019**: sokol_gfx.h now supports clamp-to-border texture sampling:
- the enum ```sg_wrap``` has a new member ```SG_WRAP_CLAMP_TO_BORDER```
- there's a new enum ```sg_border_color```
diff --git a/sokol_app.h b/sokol_app.h
index ddcf2e7d..c91b7d52 100644
--- a/sokol_app.h
+++ b/sokol_app.h
@@ -101,8 +101,13 @@
screen keyboard | --- | --- | --- | YES | TODO | --- | YES
swap interval | YES | YES | YES | YES | TODO | TODO | YES
high-dpi | YES | YES | TODO | YES | YES | TODO | YES
+ clipboard | YES | YES | TODO | --- | --- | --- | YES
- - what about bluetooth keyboard / mouse on mobile platforms?
+ TODO
+ ====
+ - Linux clipboard support
+ - document sapp_consume_event()
+ - sapp_consume_event() on non-web platforms?
STEP BY STEP
============
@@ -275,6 +280,58 @@
"Really Quit?" dialog box). Note that the cleanup-callback isn't
called on the web and mobile platforms.
+ CLIPBOARD SUPPORT
+ =================
+ Applications can send and receive UTF-8 encoded text data from and to the
+ system clipboard. By default, clipboard support is disabled and
+ must be enabled at startup via the following sapp_desc struct
+ members:
+
+ sapp_desc.enable_clipboard - set to true to enable clipboard support
+ sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes
+
+ Enabling the clipboard will dynamically allocate a clipboard buffer
+ for UTF-8 encoded text data of the requested size in bytes, the default
+ size if 8 KBytes. Strings that don't fit into the clipboard buffer
+ (including the terminating zero) will be silently clipped, so it's
+ important that you provide a big enough clipboard size for your
+ use case.
+
+ To send data to the clipboard, call sapp_set_clipboard_string() with
+ a pointer to an UTF-8 encoded, null-terminated C-string.
+
+ NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be
+ called from inside a 'short-lived event handler', and there are a few
+ other HTML5-specific caveats to workaround. You'll basically have to
+ tinker until it works in all browsers :/ (maybe the situation will
+ improve when all browsers agree on and implement the new
+ HTML5 navigator.clipboard API).
+
+ To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED
+ event in your event handler function, and then call sapp_get_clipboard_string()
+ to obtain the updated UTF-8 encoded text.
+
+ NOTE that behaviour of sapp_get_clipboard_string() is slightly different
+ depending on platform:
+
+ - on the HTML5 platform, the internal clipboard buffer will only be updated
+ right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent,
+ and sapp_get_clipboard_string() will simply return the current content
+ of the clipboard buffer
+ - on 'native' platforms, the call to sapp_get_clipboard_string() will
+ update the internal clipboard buffer with the most recent data
+ from the system clipboard
+
+ Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event,
+ and then call sapp_get_clipboard_string() right in the event handler.
+
+ The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app
+ as follows:
+
+ - on macOS: when the Cmd+V key is pressed down
+ - on HTML5: when the browser sends a 'paste' event to the global 'window' object
+ - on all other platforms: when the Ctrl+V key is pressed down
+
HIGH-DPI RENDERING
==================
You can set the sapp_desc.high_dpi flag during initialization to request
@@ -526,6 +583,7 @@ typedef enum sapp_event_type {
SAPP_EVENTTYPE_RESUMED,
SAPP_EVENTTYPE_UPDATE_CURSOR,
SAPP_EVENTTYPE_QUIT_REQUESTED,
+ SAPP_EVENTTYPE_CLIPBOARD_PASTED,
_SAPP_EVENTTYPE_NUM,
_SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF
} sapp_event_type;
@@ -719,6 +777,8 @@ typedef struct sapp_desc {
bool alpha; /* whether the framebuffer should have an alpha channel (ignored on some platforms) */
const char* window_title; /* the window title as UTF-8 encoded string */
bool user_cursor; /* if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR */
+ bool enable_clipboard; /* enable clipboard access, default is false */
+ int clipboard_size; /* max size of clipboard content in bytes */
const char* html5_canvas_name; /* the name (id) of the HTML5 canvas element, default is "canvas" */
bool html5_canvas_resize; /* if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked */
@@ -760,8 +820,14 @@ SOKOL_API_DECL void sapp_request_quit(void);
SOKOL_API_DECL void sapp_cancel_quit(void);
/* intiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */
SOKOL_API_DECL void sapp_quit(void);
+/* call from inside event callback to consume the current event (don't forward to platform) */
+SOKOL_API_DECL void sapp_consume_event(void);
/* get the current frame counter (for comparison with sapp_event.frame_count) */
SOKOL_API_DECL uint64_t sapp_frame_count(void);
+/* write string into clipboard */
+SOKOL_API_DECL void sapp_set_clipboard_string(const char* str);
+/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */
+SOKOL_API_DECL const char* sapp_get_clipboard_string(void);
/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */
SOKOL_API_DECL int sapp_run(const sapp_desc* desc);
@@ -933,6 +999,7 @@ typedef struct {
bool cleanup_called;
bool quit_requested;
bool quit_ordered;
+ bool event_consumed;
const char* html5_canvas_name;
bool html5_ask_leave_site;
char window_title[_SAPP_MAX_TITLE_LENGTH]; /* UTF-8 */
@@ -945,6 +1012,9 @@ typedef struct {
sapp_event event;
sapp_desc desc;
sapp_keycode keycodes[SAPP_MAX_KEYCODES];
+ bool clipboard_enabled;
+ int clipboard_size;
+ char* clipboard;
} _sapp_state;
static _sapp_state _sapp;
@@ -994,7 +1064,7 @@ _SOKOL_PRIVATE void _sapp_call_cleanup(void) {
}
}
-_SOKOL_PRIVATE void _sapp_call_event(const sapp_event* e) {
+_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) {
if (!_sapp.cleanup_called) {
if (_sapp.desc.event_cb) {
_sapp.desc.event_cb(e);
@@ -1003,6 +1073,13 @@ _SOKOL_PRIVATE void _sapp_call_event(const sapp_event* e) {
_sapp.desc.event_userdata_cb(e, _sapp.desc.user_data);
}
}
+ if (_sapp.event_consumed) {
+ _sapp.event_consumed = false;
+ return true;
+ }
+ else {
+ return false;
+ }
}
_SOKOL_PRIVATE void _sapp_strcpy(const char* src, char* dst, int max_len) {
@@ -1034,6 +1111,11 @@ _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) {
_sapp.swap_interval = _sapp_def(_sapp.desc.swap_interval, 1);
_sapp.html5_canvas_name = _sapp_def(_sapp.desc.html5_canvas_name, "canvas");
_sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site;
+ _sapp.clipboard_enabled = _sapp.desc.enable_clipboard;
+ if (_sapp.clipboard_enabled) {
+ _sapp.clipboard_size = _sapp_def(_sapp.desc.clipboard_size, 8192);
+ _sapp.clipboard = (char*) SOKOL_CALLOC(1, _sapp.clipboard_size);
+ }
if (_sapp.desc.window_title) {
_sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title));
}
@@ -1043,6 +1125,14 @@ _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) {
_sapp.dpi_scale = 1.0f;
}
+_SOKOL_PRIVATE void _sapp_discard_state(void) {
+ if (_sapp.clipboard_enabled) {
+ SOKOL_ASSERT(_sapp.clipboard);
+ SOKOL_FREE((void*)_sapp.clipboard);
+ }
+ memset(&_sapp, 0, sizeof(_sapp));
+}
+
_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) {
memset(&_sapp.event, 0, sizeof(_sapp.event));
_sapp.event.type = type;
@@ -1254,6 +1344,7 @@ _SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
NSApp.delegate = _sapp_macos_app_dlg_obj;
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
+ _sapp_discard_state();
}
/* MacOS entry function */
@@ -1592,9 +1683,10 @@ _SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) {
as a workaround, to prevent key presses from sticking we'll send
a keyup event following right after the keydown if SUPER is also pressed
*/
- _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, _sapp_translate_key(event.keyCode), event.isARepeat, mods);
+ const sapp_keycode key_code = _sapp_translate_key(event.keyCode);
+ _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods);
if (0 != (mods & SAPP_MODIFIER_SUPER)) {
- _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, _sapp_translate_key(event.keyCode), event.isARepeat, mods);
+ _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, key_code, event.isARepeat, mods);
}
const NSString* chars = event.characters;
const NSUInteger len = chars.length;
@@ -1611,6 +1703,11 @@ _SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) {
_sapp_call_event(&_sapp.event);
}
}
+ /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */
+ if (_sapp.clipboard_enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) {
+ _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
+ _sapp_call_event(&_sapp.event);
+ }
}
}
- (void)keyUp:(NSEvent*)event {
@@ -1655,6 +1752,31 @@ _SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) {
}
@end
+void _sapp_macos_set_clipboard_string(const char* str) {
+ @autoreleasepool {
+ NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+ [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
+ [pasteboard setString:@(str) forType:NSPasteboardTypeString];
+ }
+}
+
+const char* _sapp_macos_get_clipboard_string(void) {
+ SOKOL_ASSERT(_sapp.clipboard);
+ @autoreleasepool {
+ _sapp.clipboard[0] = 0;
+ NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+ if (![[pasteboard types] containsObject:NSPasteboardTypeString]) {
+ return _sapp.clipboard;
+ }
+ NSString* str = [pasteboard stringForType:NSPasteboardTypeString];
+ if (!str) {
+ return _sapp.clipboard;
+ }
+ _sapp_strcpy([str UTF8String], _sapp.clipboard, _sapp.clipboard_size);
+ }
+ return _sapp.clipboard;
+}
+
#endif /* MacOS */
/*== iOS =====================================================================*/
@@ -1708,6 +1830,7 @@ _SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
static int argc = 1;
static char* argv[] = { (char*)"sokol_app" };
UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class]));
+ _sapp_discard_state();
}
/* iOS entry function */
@@ -2073,6 +2196,16 @@ EM_JS(void, sapp_js_unfocus_textfield, (void), {
document.getElementById("_sokol_app_input_element").blur();
});
+EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) {
+ if (_sapp.clipboard_enabled) {
+ _sapp_strcpy(str, _sapp.clipboard, _sapp.clipboard_size);
+ if (_sapp_events_enabled()) {
+ _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
+ _sapp_call_event(&_sapp.event);
+ }
+ }
+}
+
/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */
EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) {
return _sapp.html5_ask_leave_site ? 1 : 0;
@@ -2087,6 +2220,35 @@ EM_JS(void, sapp_js_hook_beforeunload, (void), {
});
});
+EM_JS(void, sapp_js_init_clipboard, (void), {
+ window.addEventListener('paste', function(event) {
+ var pasted_str = event.clipboardData.getData('text');
+ ccall('_sapp_emsc_onpaste', 'void', ['string'], [pasted_str]);
+ });
+});
+
+EM_JS(void, sapp_js_write_clipboard, (const char* c_str), {
+ var str = UTF8ToString(c_str);
+ var ta = document.createElement('textarea');
+ ta.setAttribute('autocomplete', 'off');
+ ta.setAttribute('autocorrect', 'off');
+ ta.setAttribute('autocapitalize', 'off');
+ ta.setAttribute('spellcheck', 'false');
+ ta.style.left = -100 + 'px';
+ ta.style.top = -100 + 'px';
+ ta.style.height = 1;
+ ta.style.width = 1;
+ ta.value = str;
+ document.body.appendChild(ta);
+ ta.select();
+ document.execCommand('copy');
+ document.body.removeChild(ta);
+});
+
+_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) {
+ sapp_js_write_clipboard(str);
+}
+
/* called from the emscripten event handler to update the keyboard visibility
state, this must happen from an JS input event handler, otherwise
the request will be ignored by the browser
@@ -2317,6 +2479,10 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard
}
if (type == SAPP_EVENTTYPE_CHAR) {
_sapp.event.char_code = emsc_event->charCode;
+ /* workaround to make Cmd+V work on Safari */
+ if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) {
+ retval = false;
+ }
}
else {
_sapp.event.key_code = _sapp_translate_key(emsc_event->keyCode);
@@ -2397,10 +2563,15 @@ _SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboard
break;
}
}
- _sapp_call_event(&_sapp.event);
+ if (_sapp_call_event(&_sapp.event)) {
+ /* consume event via sapp_consume_event() */
+ retval = true;
+ }
if (send_keyup_followup) {
_sapp.event.type = SAPP_EVENTTYPE_KEY_UP;
- _sapp_call_event(&_sapp.event);
+ if (_sapp_call_event(&_sapp.event)) {
+ retval = true;
+ }
}
}
}
@@ -2567,9 +2738,16 @@ _SOKOL_PRIVATE void _sapp_emsc_init_keytable(void) {
_sapp.keycodes[224] = SAPP_KEYCODE_LEFT_SUPER;
}
+_SOKOL_PRIVATE void _sapp_emsc_init_clipboard(void) {
+ sapp_js_init_clipboard();
+}
+
_SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
_sapp_init_state(desc);
_sapp_emsc_init_keytable();
+ if (_sapp.clipboard_enabled) {
+ _sapp_emsc_init_clipboard();
+ }
double w, h;
if (_sapp.desc.html5_canvas_resize) {
w = (double) _sapp.desc.width;
@@ -2636,6 +2814,8 @@ _SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0);
sapp_js_hook_beforeunload();
+
+ // NOT A BUG: do not call _sapp_discard_state()
}
#if !defined(SOKOL_NO_ENTRY)
@@ -3894,6 +4074,12 @@ _SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int
}
}
+_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) {
+ SOKOL_ASSERT(src && dst && (dst_num_bytes > 1));
+ memset(dst, 0, dst_num_bytes);
+ return 0 != WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL);
+}
+
_SOKOL_PRIVATE void _sapp_win32_show_mouse(bool shown) {
ShowCursor((BOOL)shown);
}
@@ -4101,6 +4287,15 @@ _SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool rep
_sapp.event.key_code = _sapp.keycodes[vk];
_sapp.event.key_repeat = repeat;
_sapp_call_event(&_sapp.event);
+ /* check if a CLIPBOARD_PASTED event must be sent too */
+ if (_sapp.clipboard_enabled &&
+ (type == SAPP_EVENTTYPE_KEY_DOWN) &&
+ (_sapp.event.modifiers = SAPP_MODIFIER_CTRL) &&
+ (_sapp.event.key_code == SAPP_KEYCODE_V))
+ {
+ _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
+ _sapp_call_event(&_sapp.event);
+ }
}
}
@@ -4352,6 +4547,69 @@ _SOKOL_PRIVATE void _sapp_win32_init_dpi(void) {
}
}
+_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) {
+ SOKOL_ASSERT(str);
+ SOKOL_ASSERT(_sapp_win32_hwnd);
+ SOKOL_ASSERT(_sapp.clipboard_enabled && (_sapp.clipboard_size > 0));
+
+ wchar_t* wchar_buf = 0;
+ const int wchar_buf_size = _sapp.clipboard_size * sizeof(wchar_t);
+ HANDLE object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size);
+ if (!object) {
+ goto error;
+ }
+ wchar_buf = (wchar_t*) GlobalLock(object);
+ if (!wchar_buf) {
+ goto error;
+ }
+ if (!_sapp_win32_utf8_to_wide(str, wchar_buf, wchar_buf_size)) {
+ goto error;
+ }
+ GlobalUnlock(wchar_buf);
+ wchar_buf = 0;
+ if (!OpenClipboard(_sapp_win32_hwnd)) {
+ goto error;
+ }
+ EmptyClipboard();
+ SetClipboardData(CF_UNICODETEXT, object);
+ CloseClipboard();
+ return true;
+
+error:
+ if (wchar_buf) {
+ GlobalUnlock(object);
+ }
+ if (object) {
+ GlobalFree(object);
+ }
+ return false;
+}
+
+_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) {
+ SOKOL_ASSERT(_sapp.clipboard_enabled && _sapp.clipboard);
+ SOKOL_ASSERT(_sapp_win32_hwnd);
+ if (!OpenClipboard(_sapp_win32_hwnd)) {
+ /* silently ignore any errors and just return the current
+ content of the local clipboard buffer
+ */
+ return _sapp.clipboard;
+ }
+ HANDLE object = GetClipboardData(CF_UNICODETEXT);
+ if (!object) {
+ CloseClipboard();
+ return _sapp.clipboard;
+ }
+ const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object);
+ if (!wchar_buf) {
+ CloseClipboard();
+ return _sapp.clipboard;
+ }
+ _sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard, _sapp.clipboard_size);
+ GlobalUnlock(object);
+ CloseClipboard();
+ return _sapp.clipboard;
+}
+
_SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
_sapp_init_state(desc);
_sapp_win32_init_keytable();
@@ -4416,6 +4674,7 @@ _SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
_sapp_wgl_shutdown();
#endif
_sapp_win32_destroy_window();
+ _sapp_discard_state();
}
static char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) {
@@ -5191,6 +5450,8 @@ void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size
activity->callbacks->onLowMemory = _sapp_android_on_low_memory;
SOKOL_LOG("NativeActivity successfully created");
+
+ /* NOT A BUG: do NOT call sapp_discard_state() */
}
#endif /* Android */
@@ -6673,6 +6934,15 @@ _SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key,
_sapp.event.key_repeat = repeat;
_sapp.event.modifiers = mods;
_sapp_call_event(&_sapp.event);
+ /* check if a CLIPBOARD_PASTED event must be sent too */
+ if (_sapp.clipboard_enabled &&
+ (type == SAPP_EVENTTYPE_KEY_DOWN) &&
+ (_sapp.event.modifiers = SAPP_MODIFIER_CTRL) &&
+ (_sapp.event.key_code == SAPP_KEYCODE_V))
+ {
+ _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED);
+ _sapp_call_event(&_sapp.event);
+ }
}
}
@@ -7019,6 +7289,7 @@ _SOKOL_PRIVATE void _sapp_run(const sapp_desc* desc) {
_sapp_glx_destroy_context();
_sapp_x11_destroy_window();
XCloseDisplay(_sapp_x11_display);
+ _sapp_discard_state();
}
#if !defined(SOKOL_NO_ENTRY)
@@ -7134,6 +7405,44 @@ SOKOL_API_IMPL void sapp_quit(void) {
_sapp.quit_ordered = true;
}
+SOKOL_API_IMPL void sapp_consume_event(void) {
+ _sapp.event_consumed = true;
+}
+
+/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */
+SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) {
+ if (!_sapp.clipboard_enabled) {
+ return;
+ }
+ SOKOL_ASSERT(str);
+ #if defined(__APPLE__) && defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
+ _sapp_macos_set_clipboard_string(str);
+ #elif defined(__EMSCRIPTEN__)
+ _sapp_emsc_set_clipboard_string(str);
+ #elif defined(_WIN32)
+ _sapp_win32_set_clipboard_string(str);
+ #else
+ /* not implemented */
+ #endif
+ _sapp_strcpy(str, _sapp.clipboard, _sapp.clipboard_size);
+}
+
+SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) {
+ if (!_sapp.clipboard_enabled) {
+ return "";
+ }
+ #if defined(__APPLE__) && defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
+ return _sapp_macos_get_clipboard_string();
+ #elif defined(__EMSCRIPTEN__)
+ return _sapp.clipboard;
+ #elif defined(_WIN32)
+ return _sapp_win32_get_clipboard_string();
+ #else
+ /* not implemented */
+ return _sapp.clipboard;
+ #endif
+}
+
SOKOL_API_IMPL const void* sapp_metal_get_device(void) {
SOKOL_ASSERT(_sapp.valid);
#if defined(SOKOL_METAL)
diff --git a/util/sokol_imgui.h b/util/sokol_imgui.h
index 7e796172..d8b3d8c7 100644
--- a/util/sokol_imgui.h
+++ b/util/sokol_imgui.h
@@ -219,6 +219,7 @@ typedef struct simgui_desc_t {
float dpi_scale;
const char* ini_filename;
bool no_default_font;
+ bool disable_hotkeys; /* don't let ImGui handle Ctrl-A,C,V,X,Y,Z */
} simgui_desc_t;
SOKOL_API_DECL void simgui_setup(const simgui_desc_t* desc);
@@ -251,6 +252,10 @@ SOKOL_API_DECL void simgui_shutdown(void);
#include <stddef.h> /* offsetof */
#include <string.h> /* memset */
+#if !defined(SOKOL_IMGUI_NO_SOKOL_APP) && defined(__EMSCRIPTEN__)
+#include <emscripten.h>
+#endif
+
#ifndef SOKOL_API_IMPL
#define SOKOL_API_IMPL
#endif
@@ -278,6 +283,8 @@ typedef struct {
ImVec2 disp_size;
} _simgui_vs_params_t;
+#define SIMGUI_MAX_KEY_VALUE (512) // same as ImGuis IO.KeysDown array
+
typedef struct {
simgui_desc_t desc;
sg_buffer vbuf;
@@ -288,6 +295,9 @@ typedef struct {
#if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
bool btn_down[SAPP_MAX_MOUSEBUTTONS];
bool btn_up[SAPP_MAX_MOUSEBUTTONS];
+ uint8_t keys_down[SIMGUI_MAX_KEY_VALUE]; // bits 0..3 or modifiers, != 0 is key-down
+ uint8_t keys_up[SIMGUI_MAX_KEY_VALUE]; // same is keys_down
+ bool is_osx; // return true if running on OSX (or HTML5 OSX), needed for copy/paste
#endif
} _simgui_state_t;
static _simgui_state_t _simgui;
@@ -701,12 +711,46 @@ static const uint8_t _simgui_fs_bin[] = {
#error "sokol_imgui.h: No sokol_gfx.h backend selected (SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11 or SOKOL_METAL)"
#endif
+#if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
+static void _simgui_set_clipboard(void* user_data, const char* text) {
+ sapp_set_clipboard_string(text);
+}
+
+static const char* _simgui_get_clipboard(void* user_data) {
+ return sapp_get_clipboard_string();
+}
+
+#if defined(__EMSCRIPTEN__)
+EM_JS(int, simgui_js_is_osx, (void), {
+ if (navigator.userAgent.includes('Macintosh')) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+});
+#endif
+
+static bool _simgui_is_osx(void) {
+ #if defined(__EMSCRIPTEN__)
+ return simgui_js_is_osx();
+ #elif defined(__APPLE__)
+ return true;
+ #else
+ return false;
+ #endif
+}
+#endif
+
SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) {
SOKOL_ASSERT(desc);
memset(&_simgui, 0, sizeof(_simgui));
_simgui.desc = *desc;
_simgui.desc.max_vertices = _simgui_def(_simgui.desc.max_vertices, 65536);
_simgui.desc.dpi_scale = _simgui_def(_simgui.desc.dpi_scale, 1.0f);
+ #if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
+ _simgui.is_osx = _simgui_is_osx();
+ #endif
/* can keep color_format, depth_format and sample_count as is,
since sokol_gfx.h will do its own default-value handling
*/
@@ -728,6 +772,7 @@ SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) {
}
#endif
io->IniFilename = _simgui.desc.ini_filename;
+ io->ConfigMacOSXBehaviors = _simgui_is_osx();
io->BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
#if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
io->KeyMap[ImGuiKey_Tab] = SAPP_KEYCODE_TAB;
@@ -744,12 +789,18 @@ SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) {
io->KeyMap[ImGuiKey_Space] = SAPP_KEYCODE_SPACE;
io->KeyMap[ImGuiKey_Enter] = SAPP_KEYCODE_ENTER;
io->KeyMap[ImGuiKey_Escape] = SAPP_KEYCODE_ESCAPE;
- io->KeyMap[ImGuiKey_A] = SAPP_KEYCODE_A;
- io->KeyMap[ImGuiKey_C] = SAPP_KEYCODE_C;
- io->KeyMap[ImGuiKey_V] = SAPP_KEYCODE_V;
- io->KeyMap[ImGuiKey_X] = SAPP_KEYCODE_X;
- io->KeyMap[ImGuiKey_Y] = SAPP_KEYCODE_Y;
- io->KeyMap[ImGuiKey_Z] = SAPP_KEYCODE_Z;
+ if (!_simgui.desc.disable_hotkeys) {
+ io->KeyMap[ImGuiKey_A] = SAPP_KEYCODE_A;
+ io->KeyMap[ImGuiKey_C] = SAPP_KEYCODE_C;
+ io->KeyMap[ImGuiKey_V] = SAPP_KEYCODE_V;
+ io->KeyMap[ImGuiKey_X] = SAPP_KEYCODE_X;
+ io->KeyMap[ImGuiKey_Y] = SAPP_KEYCODE_Y;
+ io->KeyMap[ImGuiKey_Z] = SAPP_KEYCODE_Z;
+ }
+ #if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
+ io->SetClipboardTextFn = _simgui_set_clipboard;
+ io->GetClipboardTextFn = _simgui_get_clipboard;
+ #endif
#endif
/* create sokol-gfx resources */
@@ -872,6 +923,13 @@ SOKOL_API_IMPL void simgui_shutdown(void) {
sg_destroy_buffer(_simgui.vbuf);
}
+_SOKOL_PRIVATE void _simgui_set_imgui_modifiers(ImGuiIO* io, uint8_t mods) {
+ io->KeyAlt = (mods & SAPP_MODIFIER_ALT) != 0;
+ io->KeyCtrl = (mods & SAPP_MODIFIER_CTRL) != 0;
+ io->KeyShift = (mods & SAPP_MODIFIER_SHIFT) != 0;
+ io->KeySuper = (mods & SAPP_MODIFIER_SUPER) != 0;
+}
+
SOKOL_API_IMPL void simgui_new_frame(int width, int height, double delta_time) {
#if defined(__cplusplus)
ImGuiIO* io = &ImGui::GetIO();
@@ -892,6 +950,18 @@ SOKOL_API_IMPL void simgui_new_frame(int width, int height, double delta_time) {
io->MouseDown[i] = false;
}
}
+ for (int i = 0; i < SIMGUI_MAX_KEY_VALUE; i++) {
+ if (_simgui.keys_down[i]) {
+ io->KeysDown[i] = true;
+ _simgui_set_imgui_modifiers(io, _simgui.keys_down[i]);
+ _simgui.keys_down[i] = 0;
+ }
+ else if (_simgui.keys_up[i]) {
+ io->KeysDown[i] = false;
+ _simgui_set_imgui_modifiers(io, _simgui.keys_up[i]);
+ _simgui.keys_up[i] = 0;
+ }
+ }
if (io->WantTextInput && !sapp_keyboard_shown()) {
sapp_show_keyboard(true);
}
@@ -1009,6 +1079,15 @@ SOKOL_API_IMPL void simgui_render(void) {
}
#if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
+_SOKOL_PRIVATE bool _simgui_is_ctrl(uint8_t modifiers) {
+ if (_simgui.is_osx) {
+ return 0 != (modifiers & SAPP_MODIFIER_SUPER);
+ }
+ else {
+ return 0 != (modifiers & SAPP_MODIFIER_CTRL);
+ }
+}
+
SOKOL_API_IMPL bool simgui_handle_event(const sapp_event* ev) {
const float dpi_scale = _simgui.desc.dpi_scale;
#if defined(__cplusplus)
@@ -1016,10 +1095,7 @@ SOKOL_API_IMPL bool simgui_handle_event(const sapp_event* ev) {
#else
ImGuiIO* io = igGetIO();
#endif
- io->KeyAlt = (ev->modifiers & SAPP_MODIFIER_ALT) != 0;
- io->KeyCtrl = (ev->modifiers & SAPP_MODIFIER_CTRL) != 0;
- io->KeyShift = (ev->modifiers & SAPP_MODIFIER_SHIFT) != 0;
- io->KeySuper = (ev->modifiers & SAPP_MODIFIER_SUPER) != 0;
+ _simgui_set_imgui_modifiers(io, ev->modifiers);
switch (ev->type) {
case SAPP_EVENTTYPE_MOUSE_DOWN:
io->MousePos.x = ev->mouse_x / dpi_scale;
@@ -1069,17 +1145,43 @@ SOKOL_API_IMPL bool simgui_handle_event(const sapp_event* ev) {
_simgui.btn_up[0] = _simgui.btn_down[0] = false;
break;
case SAPP_EVENTTYPE_KEY_DOWN:
- io->KeysDown[ev->key_code] = true;
+ /* intercept Ctrl-V, this is handled via EVENTTYPE_CLIPBOARD_PASTED */
+ if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_V)) {
+ break;
+ }
+ /* on web platform, don't forward Ctrl-X, Ctrl-V to the browser */
+ if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_X)) {
+ sapp_consume_event();
+ }
+ if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_C)) {
+ sapp_consume_event();
+ }
+ _simgui.keys_down[ev->key_code] = 0x80 | ev->modifiers;
break;
case SAPP_EVENTTYPE_KEY_UP:
- io->KeysDown[ev->key_code] = false;
+ /* intercept Ctrl-V, this is handled via EVENTTYPE_CLIPBOARD_PASTED */
+ if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_V)) {
+ break;
+ }
+ /* on web platform, don't forward Ctrl-X, Ctrl-V to the browser */
+ if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_X)) {
+ sapp_consume_event();
+ }
+ if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_C)) {
+ sapp_consume_event();
+ }
+ _simgui.keys_up[ev->key_code] = 0x80 | ev->modifiers;
break;
case SAPP_EVENTTYPE_CHAR:
/* on some platforms, special keys may be reported as
characters, which may confuse some ImGui widgets,
- drop those
+ drop those, also don't forward characters if some
+ modifiers have been pressed
*/
- if ((ev->char_code >= 32) && (ev->char_code != 127)) {
+ if ((ev->char_code >= 32) &&
+ (ev->char_code != 127) &&
+ (0 == (ev->modifiers & (SAPP_MODIFIER_ALT|SAPP_MODIFIER_CTRL|SAPP_MODIFIER_SUPER))))
+ {
#if defined(__cplusplus)
io->AddInputCharacter((ImWchar)ev->char_code);
#else
@@ -1087,6 +1189,11 @@ SOKOL_API_IMPL bool simgui_handle_event(const sapp_event* ev) {
#endif
}
break;
+ case SAPP_EVENTTYPE_CLIPBOARD_PASTED:
+ /* simulate a Ctrl-V key down/up */
+ _simgui.keys_down[SAPP_KEYCODE_V] = _simgui.keys_up[SAPP_KEYCODE_V] =
+ 0x80 | (_simgui.is_osx ? SAPP_MODIFIER_SUPER:SAPP_MODIFIER_CTRL);
+ break;
default:
break;
}