From bf74a3c7d446e04628f0293ec806522be92d3c10 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 27 May 2025 01:30:50 -0500 Subject: [PATCH] move keyboard and mouse functions that are main thread only to sdl_video --- scripts/core/_sdl_video.js | 190 ++++++++++++++++++++++++++++ source/qjs_sdl.c | 34 +---- source/qjs_sdl_video.c | 249 +++++++++++++++++++++++++++++++++++++ 3 files changed, 440 insertions(+), 33 deletions(-) diff --git a/scripts/core/_sdl_video.js b/scripts/core/_sdl_video.js index e433ddbd..cfcd3b07 100644 --- a/scripts/core/_sdl_video.js +++ b/scripts/core/_sdl_video.js @@ -97,6 +97,12 @@ $_.receiver(function(msg) { case 'cursor': response = handle_cursor(msg); break; + case 'mouse': + response = handle_mouse(msg); + break; + case 'keyboard': + response = handle_keyboard(msg); + break; default: response = {error: "Unknown kind: " + msg.kind}; } @@ -635,6 +641,190 @@ function handle_cursor(msg) { // Utility function to create window and renderer prosperon.endowments = prosperon.endowments || {}; +// Mouse operations +function handle_mouse(msg) { + var mouse = prosperon.endowments.mouse; + + switch (msg.op) { + case 'show': + if (msg.data === undefined) return {error: "Missing show parameter"}; + mouse.show(msg.data); + return {success: true}; + + case 'capture': + if (msg.data === undefined) return {error: "Missing capture parameter"}; + mouse.capture(msg.data); + return {success: true}; + + case 'get_state': + return {data: mouse.get_state()}; + + case 'get_global_state': + return {data: mouse.get_global_state()}; + + case 'get_relative_state': + return {data: mouse.get_relative_state()}; + + case 'warp_global': + if (!msg.data) return {error: "Missing position"}; + mouse.warp_global(msg.data); + return {success: true}; + + case 'warp_in_window': + if (!msg.data || !msg.data.window_id || !msg.data.pos) + return {error: "Missing window_id or position"}; + var window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + mouse.warp_in_window(window, msg.data.pos); + return {success: true}; + + case 'cursor_visible': + return {data: mouse.cursor_visible()}; + + case 'get_cursor': + var cursor = mouse.get_cursor(); + if (!cursor) return {data: null}; + // Find or create cursor ID + for (var id in resources.cursor) { + if (resources.cursor[id] === cursor) { + return {data: id}; + } + } + // Not tracked, add it + var cursor_id = allocate_id(); + resources.cursor[cursor_id] = cursor; + return {data: cursor_id}; + + case 'get_default_cursor': + var cursor = mouse.get_default_cursor(); + if (!cursor) return {data: null}; + // Find or create cursor ID + for (var id in resources.cursor) { + if (resources.cursor[id] === cursor) { + return {data: id}; + } + } + // Not tracked, add it + var cursor_id = allocate_id(); + resources.cursor[cursor_id] = cursor; + return {data: cursor_id}; + + case 'create_system_cursor': + if (msg.data === undefined) return {error: "Missing cursor type"}; + var cursor = mouse.create_system_cursor(msg.data); + var cursor_id = allocate_id(); + resources.cursor[cursor_id] = cursor; + return {id: cursor_id}; + + case 'get_focus': + var window = mouse.get_focus(); + if (!window) return {data: null}; + // Find window ID + for (var id in resources.window) { + if (resources.window[id] === window) { + return {data: id}; + } + } + // Not tracked, add it + var win_id = allocate_id(); + resources.window[win_id] = window; + return {data: win_id}; + + default: + return {error: "Unknown mouse operation: " + msg.op}; + } +} + +// Keyboard operations +function handle_keyboard(msg) { + var keyboard = prosperon.endowments.keyboard; + + switch (msg.op) { + case 'get_state': + return {data: keyboard.get_state()}; + + case 'get_focus': + var window = keyboard.get_focus(); + if (!window) return {data: null}; + // Find window ID + for (var id in resources.window) { + if (resources.window[id] === window) { + return {data: id}; + } + } + // Not tracked, add it + var win_id = allocate_id(); + resources.window[win_id] = window; + return {data: win_id}; + + case 'start_text_input': + var window = null; + if (msg.data && msg.data.window_id) { + window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + } + keyboard.start_text_input(window); + return {success: true}; + + case 'stop_text_input': + var window = null; + if (msg.data && msg.data.window_id) { + window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + } + keyboard.stop_text_input(window); + return {success: true}; + + case 'text_input_active': + var window = null; + if (msg.data && msg.data.window_id) { + window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + } + return {data: keyboard.text_input_active(window)}; + + case 'get_text_input_area': + var window = null; + if (msg.data && msg.data.window_id) { + window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + } + return {data: keyboard.get_text_input_area(window)}; + + case 'set_text_input_area': + if (!msg.data || !msg.data.rect) return {error: "Missing rect"}; + var window = null; + if (msg.data.window_id) { + window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + } + keyboard.set_text_input_area(msg.data.rect, msg.data.cursor || 0, window); + return {success: true}; + + case 'clear_composition': + var window = null; + if (msg.data && msg.data.window_id) { + window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + } + keyboard.clear_composition(window); + return {success: true}; + + case 'screen_keyboard_shown': + if (!msg.data || !msg.data.window_id) return {error: "Missing window_id"}; + var window = resources.window[msg.data.window_id]; + if (!window) return {error: "Invalid window id"}; + return {data: keyboard.screen_keyboard_shown(window)}; + + case 'reset': + keyboard.reset(); + return {success: true}; + + default: + return {error: "Unknown keyboard operation: " + msg.op}; + } +} + // Export resource info for debugging prosperon.sdl_video = { resources: resources, diff --git a/source/qjs_sdl.c b/source/qjs_sdl.c index 5627b024..157ff3ca 100644 --- a/source/qjs_sdl.c +++ b/source/qjs_sdl.c @@ -48,15 +48,7 @@ static JSValue js_keymod(JSContext *js) } // INPUT FUNCTIONS -JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0]))) - -JSC_CCALL(input_mouse_show, - if (JS_ToBool(js,argv[0])) - SDL_ShowCursor(); - else - SDL_HideCursor(); -) - +// Thread-safe keyboard functions remain here JSC_CCALL(input_keyname, return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0]))); ) @@ -65,27 +57,6 @@ JSC_CCALL(input_keymod, return js_keymod(js); ) -JSC_CCALL(input_mousestate, - float x,y; - SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y); - JSValue m = JS_NewObject(js); - JS_SetPropertyStr(js,m,"x", number2js(js,x)); - JS_SetPropertyStr(js,m,"y", number2js(js,y)); - - if (flags & SDL_BUTTON_LMASK) - JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1)); - if (flags & SDL_BUTTON_MMASK) - JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1)); - if (flags & SDL_BUTTON_RMASK) - JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1)); - if (flags & SDL_BUTTON_X1MASK) - JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1)); - if (flags & SDL_BUTTON_X2MASK) - JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1)); - - return m; -) - // watch events extern char **event_watchers; extern SDL_Mutex *event_watchers_mutex; @@ -139,11 +110,8 @@ JSC_CCALL(input_unwatch, ) static const JSCFunctionListEntry js_input_funcs[] = { - MIST_FUNC_DEF(input, mouse_show, 1), - MIST_FUNC_DEF(input, mouse_lock, 1), MIST_FUNC_DEF(input, keyname, 1), MIST_FUNC_DEF(input, keymod, 0), - MIST_FUNC_DEF(input, mousestate, 0), MIST_FUNC_DEF(input, watch, 1), MIST_FUNC_DEF(input, unwatch, 1), }; diff --git a/source/qjs_sdl_video.c b/source/qjs_sdl_video.c index f60db021..317f2cb0 100644 --- a/source/qjs_sdl_video.c +++ b/source/qjs_sdl_video.c @@ -1780,6 +1780,245 @@ JSC_CCALL(sdl_createWindowAndRenderer, return ret; ) +// Mouse functions that must run on main thread +JSC_CCALL(mouse_show, + if (JS_ToBool(js,argv[0])) + SDL_ShowCursor(); + else + SDL_HideCursor(); +) + +JSC_CCALL(mouse_capture, + SDL_CaptureMouse(JS_ToBool(js,argv[0])); +) + +JSC_CCALL(mouse_get_state, + float x,y; + SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y); + JSValue m = JS_NewObject(js); + JS_SetPropertyStr(js,m,"x", number2js(js,x)); + JS_SetPropertyStr(js,m,"y", number2js(js,y)); + + if (flags & SDL_BUTTON_LMASK) + JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_MMASK) + JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_RMASK) + JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X1MASK) + JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X2MASK) + JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1)); + + return m; +) + +JSC_CCALL(mouse_get_global_state, + float x,y; + SDL_MouseButtonFlags flags = SDL_GetGlobalMouseState(&x,&y); + JSValue m = JS_NewObject(js); + JS_SetPropertyStr(js,m,"x", number2js(js,x)); + JS_SetPropertyStr(js,m,"y", number2js(js,y)); + + if (flags & SDL_BUTTON_LMASK) + JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_MMASK) + JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_RMASK) + JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X1MASK) + JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X2MASK) + JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1)); + + return m; +) + +JSC_CCALL(mouse_get_relative_state, + float x,y; + SDL_MouseButtonFlags flags = SDL_GetRelativeMouseState(&x,&y); + JSValue m = JS_NewObject(js); + JS_SetPropertyStr(js,m,"x", number2js(js,x)); + JS_SetPropertyStr(js,m,"y", number2js(js,y)); + + if (flags & SDL_BUTTON_LMASK) + JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_MMASK) + JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_RMASK) + JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X1MASK) + JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X2MASK) + JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1)); + + return m; +) + +JSC_CCALL(mouse_warp_global, + HMM_Vec2 pos = js2vec2(js, argv[0]); + SDL_WarpMouseGlobal(pos.x, pos.y); +) + +JSC_CCALL(mouse_warp_in_window, + SDL_Window *window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + HMM_Vec2 pos = js2vec2(js, argv[1]); + SDL_WarpMouseInWindow(window, pos.x, pos.y); +) + +JSC_CCALL(mouse_cursor_visible, + return JS_NewBool(js, SDL_CursorVisible()); +) + +JSC_CCALL(mouse_get_cursor, + SDL_Cursor *cursor = SDL_GetCursor(); + if (!cursor) return JS_NULL; + return SDL_Cursor2js(js, cursor); +) + +JSC_CCALL(mouse_get_default_cursor, + SDL_Cursor *cursor = SDL_GetDefaultCursor(); + if (!cursor) return JS_NULL; + return SDL_Cursor2js(js, cursor); +) + +JSC_CCALL(mouse_create_system_cursor, + int id = js2number(js, argv[0]); + SDL_Cursor *cursor = SDL_CreateSystemCursor(id); + if (!cursor) return JS_ThrowReferenceError(js, "Failed to create system cursor: %s", SDL_GetError()); + return SDL_Cursor2js(js, cursor); +) + +JSC_CCALL(mouse_get_focus, + SDL_Window *window = SDL_GetMouseFocus(); + if (!window) return JS_NULL; + return SDL_Window2js(js, window); +) + +static const JSCFunctionListEntry js_mouse_funcs[] = { + MIST_FUNC_DEF(mouse, show, 1), + MIST_FUNC_DEF(mouse, capture, 1), + MIST_FUNC_DEF(mouse, get_state, 0), + MIST_FUNC_DEF(mouse, get_global_state, 0), + MIST_FUNC_DEF(mouse, get_relative_state, 0), + MIST_FUNC_DEF(mouse, warp_global, 1), + MIST_FUNC_DEF(mouse, warp_in_window, 2), + MIST_FUNC_DEF(mouse, cursor_visible, 0), + MIST_FUNC_DEF(mouse, get_cursor, 0), + MIST_FUNC_DEF(mouse, get_default_cursor, 0), + MIST_FUNC_DEF(mouse, create_system_cursor, 1), + MIST_FUNC_DEF(mouse, get_focus, 0), +}; + +// Keyboard functions that must run on main thread +JSC_CCALL(keyboard_get_state, + int numkeys; + const bool *keystate = SDL_GetKeyboardState(&numkeys); + JSValue arr = JS_NewArray(js); + for (int i = 0; i < numkeys; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewBool(js, keystate[i])); + } + return arr; +) + +JSC_CCALL(keyboard_get_focus, + SDL_Window *window = SDL_GetKeyboardFocus(); + if (!window) return JS_NULL; + return SDL_Window2js(js, window); +) + +JSC_CCALL(keyboard_start_text_input, + SDL_Window *window = NULL; + if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) { + window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + } + SDL_StartTextInput(window); +) + +JSC_CCALL(keyboard_stop_text_input, + SDL_Window *window = NULL; + if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) { + window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + } + SDL_StopTextInput(window); +) + +JSC_CCALL(keyboard_text_input_active, + SDL_Window *window = NULL; + if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) { + window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + } + return JS_NewBool(js, SDL_TextInputActive(window)); +) + +JSC_CCALL(keyboard_get_text_input_area, + SDL_Window *window = NULL; + if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) { + window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + } + SDL_Rect rect; + int cursor; + if (!SDL_GetTextInputArea(window, &rect, &cursor)) + return JS_ThrowReferenceError(js, "Failed to get text input area: %s", SDL_GetError()); + + JSValue obj = JS_NewObject(js); + SDL_FRect frect; + SDL_RectToFRect(&rect, &frect); + JS_SetPropertyStr(js, obj, "rect", rect2js(js, frect)); + JS_SetPropertyStr(js, obj, "cursor", JS_NewInt32(js, cursor)); + return obj; +) + +JSC_CCALL(keyboard_set_text_input_area, + SDL_Window *window = NULL; + rect r = js2rect(js, argv[0]); + int cursor = argc > 1 ? js2number(js, argv[1]) : 0; + if (argc > 2 && !JS_IsNull(argv[2]) && !JS_IsUndefined(argv[2])) { + window = js2SDL_Window(js, argv[2]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + } + if (!SDL_SetTextInputArea(window, &r, cursor)) + return JS_ThrowReferenceError(js, "Failed to set text input area: %s", SDL_GetError()); +) + +JSC_CCALL(keyboard_clear_composition, + SDL_Window *window = NULL; + if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) { + window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + } + if (!SDL_ClearComposition(window)) + return JS_ThrowReferenceError(js, "Failed to clear composition: %s", SDL_GetError()); +) + +JSC_CCALL(keyboard_screen_keyboard_shown, + SDL_Window *window = js2SDL_Window(js, argv[0]); + if (!window) return JS_ThrowReferenceError(js, "Invalid window"); + return JS_NewBool(js, SDL_ScreenKeyboardShown(window)); +) + +JSC_CCALL(keyboard_reset, + SDL_ResetKeyboard(); +) + +static const JSCFunctionListEntry js_keyboard_funcs[] = { + MIST_FUNC_DEF(keyboard, get_state, 0), + MIST_FUNC_DEF(keyboard, get_focus, 0), + MIST_FUNC_DEF(keyboard, start_text_input, 1), + MIST_FUNC_DEF(keyboard, stop_text_input, 1), + MIST_FUNC_DEF(keyboard, text_input_active, 1), + MIST_FUNC_DEF(keyboard, get_text_input_area, 1), + MIST_FUNC_DEF(keyboard, set_text_input_area, 3), + MIST_FUNC_DEF(keyboard, clear_composition, 1), + MIST_FUNC_DEF(keyboard, screen_keyboard_shown, 1), + MIST_FUNC_DEF(keyboard, reset, 0), +}; + // Hook function to set up endowments for the video actor static void video_actor_hook(JSContext *js) { // Get prosperon object @@ -1827,6 +2066,16 @@ static void video_actor_hook(JSContext *js) { JS_SetPropertyStr(js, endowments, "setCursor", JS_NewCFunction(js, js_sdl_set_cursor, "setCursor", 1)); + // Create mouse module + JSValue mouse_mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mouse_mod, js_mouse_funcs, countof(js_mouse_funcs)); + JS_SetPropertyStr(js, endowments, "mouse", mouse_mod); + + // Create keyboard module + JSValue keyboard_mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, keyboard_mod, js_keyboard_funcs, countof(js_keyboard_funcs)); + JS_SetPropertyStr(js, endowments, "keyboard", keyboard_mod); + JS_FreeValue(js, endowments); JS_FreeValue(js, prosperon); }