From 84aafe86188a923c7cbcfbf51c70ff12b2182c4e Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 8 Jan 2021 17:38:09 +0100 Subject: [PATCH] Implement blocking WaitEvent call When possible make a blocking call to wait for the next event to avoid using CPU when waiting for an event. The blocking call use a new optional video driver event, WaitNextEvent, if available. It is called only if an window already shown is available. If present the window is designated using the variable wakeup_window to receive a wakeup event if needed. To ensure that the main thread sees events send from different threads we check if the main thread is making a blocking call. If yes we send a wake-up event to the designated window to wake-up the main thread. The wake-up event is sent only if the PushEvent call is coming from a different thread. Before sending the wake-up event the ID of the thread making the blocking call is saved using the variable blocking_thread_id and it is compared to the current thread's id to decide if the wake-up event should be sent. Two new optional video device methods are introduced: WaitNextEvent SendWakeupEvent in addition the mutex wakeup_lock which is defined and initialized but only for the drivers supporting the methods above. If the methods are not present the system behaves as previously performing a periodic polling of the events queue. The blocking call is disabled if a joystick or sensor is detected and falls back to previous behavior. --- src/events/SDL_events.c | 84 ++++++++ src/video/SDL_sysvideo.h | 5 + src/video/cocoa/SDL_cocoaevents.h | 2 + src/video/cocoa/SDL_cocoaevents.m | 47 ++++- src/video/cocoa/SDL_cocoavideo.m | 9 + src/video/windows/SDL_windowsevents.c | 30 +++ src/video/windows/SDL_windowsevents.h | 2 + src/video/windows/SDL_windowsvideo.c | 9 + src/video/x11/SDL_x11events.c | 34 ++++ src/video/x11/SDL_x11events.h | 2 + src/video/x11/SDL_x11video.c | 22 ++- src/video/x11/SDL_x11video.h | 2 + test/CMakeLists.txt | 1 + test/Makefile.in | 4 + test/checkkeysthreads.c | 275 ++++++++++++++++++++++++++ 15 files changed, 521 insertions(+), 7 deletions(-) create mode 100644 test/checkkeysthreads.c diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 6f07116bc..ccda5b8de 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -707,9 +707,74 @@ SDL_PollEvent(SDL_Event * event) return SDL_WaitEventTimeout(event, 0); } +static int +SDL_WaitEvent_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event) +{ + for (;;) { + if (!_this->wakeup_lock || SDL_LockMutex(_this->wakeup_lock) == 0) { + int status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + /* If status == 0 we are going to block so wakeup will be needed. */ + if (status == 0) { + _this->wakeup_window = wakeup_window; + _this->blocking_thread_id = SDL_ThreadID(); + } else { + _this->wakeup_window = NULL; + _this->blocking_thread_id = 0; + } + if (_this->wakeup_lock) { + SDL_UnlockMutex(_this->wakeup_lock); + } + if (status < 0) { + break; + } + if (status > 0) { + SDL_SendPendingSignalEvents(); /* in case we had a signal handler fire, etc. */ + return 1; + } + /* No events found in the queue, call WaitNextEvent to wait for an event. */ + _this->WaitNextEvent(_this); + /* Set wakeup_window to NULL without holding the lock. */ + _this->wakeup_window = NULL; + } + } + return 0; +} + int SDL_WaitEvent(SDL_Event * event) { + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + SDL_bool need_polling; + SDL_Window *wakeup_window = NULL; + +#if !SDL_JOYSTICK_DISABLED + need_polling = \ + (!SDL_disabled_events[SDL_JOYAXISMOTION >> 8] || SDL_JoystickEventState(SDL_QUERY)) \ + && (SDL_NumJoysticks() > 0); +#endif + +#if !SDL_SENSOR_DISABLED + need_polling = need_polling || (!SDL_disabled_events[SDL_SENSORUPDATE >> 8] && \ + (SDL_NumSensors() > 0)); +#endif + + if (!need_polling && _this) { + /* Look if a shown window is available to send the wakeup event. */ + SDL_Window *window; + for (window = _this->windows; window; window = window->next) { + if (window->flags & SDL_WINDOW_SHOWN) { + wakeup_window = window; + } + } + need_polling = (wakeup_window == NULL); + } + + /* To use WaitNextEvent we need to ensure that also SendWakeupEvent is + available so that we can wake up the thread when is blocking waiting + for the next event. */ + if (!need_polling && _this && _this->WaitNextEvent && _this->SendWakeupEvent) { + return SDL_WaitEvent_Device(_this, wakeup_window, event); + } return SDL_WaitEventTimeout(event, -1); } @@ -744,6 +809,24 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout) } } +static int +SDL_SendWakeupEvent() +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + if (!_this || !_this->SendWakeupEvent) { + return 0; + } + if (!_this->wakeup_lock || SDL_LockMutex(_this->wakeup_lock) == 0) { + if (_this->wakeup_window && _this->blocking_thread_id != 0 && _this->blocking_thread_id != SDL_ThreadID()) { + _this->SendWakeupEvent(_this, _this->wakeup_window); + } + if (_this->wakeup_lock) { + SDL_UnlockMutex(_this->wakeup_lock); + } + } + return 0; +} + int SDL_PushEvent(SDL_Event * event) { @@ -793,6 +876,7 @@ SDL_PushEvent(SDL_Event * event) return -1; } + SDL_SendWakeupEvent(); SDL_GestureProcessEvent(event); return 1; diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 38ed97157..798e35a7b 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -286,6 +286,8 @@ struct SDL_VideoDevice /* * Event manager functions */ + void (*WaitNextEvent) (_THIS); + void (*SendWakeupEvent) (_THIS, SDL_Window *window); void (*PumpEvents) (_THIS); /* Suspend the screensaver */ @@ -320,6 +322,9 @@ struct SDL_VideoDevice /* Data common to all drivers */ SDL_bool is_dummy; SDL_bool suspend_screensaver; + SDL_Window *wakeup_window; + SDL_threadID blocking_thread_id; + SDL_mutex *wakeup_lock; /* Initialized only if WaitNextEvent/SendWakeupEvent are supported */ int num_displays; SDL_VideoDisplay *displays; SDL_Window *windows; diff --git a/src/video/cocoa/SDL_cocoaevents.h b/src/video/cocoa/SDL_cocoaevents.h index 7653c4550..376cf7e38 100644 --- a/src/video/cocoa/SDL_cocoaevents.h +++ b/src/video/cocoa/SDL_cocoaevents.h @@ -25,6 +25,8 @@ extern void Cocoa_RegisterApp(void); extern void Cocoa_PumpEvents(_THIS); +extern void Cocoa_WaitNextEvent(_THIS); +extern void Cocoa_SendWakeupEvent(_THIS, SDL_Window *window); extern void Cocoa_SuspendScreenSaver(_THIS); #endif /* SDL_cocoaevents_h_ */ diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m index 0de74df31..000662e25 100644 --- a/src/video/cocoa/SDL_cocoaevents.m +++ b/src/video/cocoa/SDL_cocoaevents.m @@ -445,9 +445,8 @@ Cocoa_RegisterApp(void) } }} -void -Cocoa_PumpEvents(_THIS) -{ @autoreleasepool +int +Cocoa_PumpEventsUntilDate(_THIS, NSDate *expiration, bool accumulate) { #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 /* Update activity every 30 seconds to prevent screensaver */ @@ -463,9 +462,9 @@ Cocoa_PumpEvents(_THIS) #endif for ( ; ; ) { - NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ]; + NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:expiration inMode:NSDefaultRunLoopMode dequeue:YES ]; if ( event == nil ) { - break; + return 0; } if (!s_bShouldHandleEventsInSDLApplication) { @@ -474,9 +473,47 @@ Cocoa_PumpEvents(_THIS) // Pass events down to SDLApplication to be handled in sendEvent: [NSApp sendEvent:event]; + if ( !accumulate) { + break; + } + } + return 1; +} + +void +Cocoa_WaitNextEvent(_THIS) +{ @autoreleasepool +{ + NSDate *theDistantFuture = [NSDate distantFuture]; + while (Cocoa_PumpEventsUntilDate(_this, theDistantFuture, false) == 0) { } }} +void +Cocoa_PumpEvents(_THIS) +{ @autoreleasepool +{ + Cocoa_PumpEventsUntilDate(_this, [NSDate distantPast], true); +}} + +void Cocoa_SendWakeupEvent(_THIS, SDL_Window *window) +{ @autoreleasepool +{ + NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow; + + NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined + location: NSMakePoint(0,0) + modifierFlags: 0 + timestamp: 0.0 + windowNumber: [nswindow getWindowNumber] + context: nil + subtype: 0 + data1: 0 + data2: 0]; + + [NSApp postEvent: event atStart: YES]; +}} + void Cocoa_SuspendScreenSaver(_THIS) { @autoreleasepool diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index f51c98c42..c1dbac1fb 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -45,6 +45,7 @@ Cocoa_Available(void) static void Cocoa_DeleteDevice(SDL_VideoDevice * device) { + SDL_DestroyMutex(device->wakeup_lock); SDL_free(device->driverdata); SDL_free(device); } @@ -70,6 +71,12 @@ Cocoa_CreateDevice(int devindex) return NULL; } device->driverdata = data; + device->wakeup_lock = SDL_CreateMutex(); + if (!device->wakeup_lock) { + SDL_OutOfMemory(); + SDL_free(device); + return (0); + } /* Set the function pointers */ device->VideoInit = Cocoa_VideoInit; @@ -80,6 +87,8 @@ Cocoa_CreateDevice(int devindex) device->GetDisplayModes = Cocoa_GetDisplayModes; device->SetDisplayMode = Cocoa_SetDisplayMode; device->PumpEvents = Cocoa_PumpEvents; + device->WaitNextEvent = Cocoa_WaitNextEvent; + device->SendWakeupEvent = Cocoa_SendWakeupEvent; device->SuspendScreenSaver = Cocoa_SuspendScreenSaver; device->CreateSDLWindow = Cocoa_CreateWindow; diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index d79d42c22..1d9908855 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -79,6 +79,8 @@ #define WM_UNICHAR 0x0109 #endif +#define WM_SDL_WAKEUP (WM_USER + 201) + static SDL_Scancode VKeytoScancode(WPARAM vkey) { @@ -1089,6 +1091,10 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } } break; + + case WM_SDL_WAKEUP: + /* Used only to wakeup event loop but do not translate into an event. */ + break; } /* If there's a window proc, assume it's going to handle messages */ @@ -1125,6 +1131,30 @@ void SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata) g_WindowsMessageHookData = userdata; } +void +WIN_WaitNextEvent(_THIS) +{ + MSG msg; + if (g_WindowsEnableMessageLoop) { + if (GetMessage(&msg, 0, 0, 0)) { + if (g_WindowsMessageHook) { + g_WindowsMessageHook(g_WindowsMessageHookData, msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + + /* Always translate the message in case it's a non-SDL window (e.g. with Qt integration) */ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +void +WIN_SendWakeupEvent(_THIS, SDL_Window *window) +{ + HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd; + PostMessage(hwnd, WM_SDL_WAKEUP, 0, 0); +} + void WIN_PumpEvents(_THIS) { diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h index 80d429fca..eaf853242 100644 --- a/src/video/windows/SDL_windowsevents.h +++ b/src/video/windows/SDL_windowsevents.h @@ -30,6 +30,8 @@ extern HINSTANCE SDL_Instance; extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); extern void WIN_PumpEvents(_THIS); +extern void WIN_SendWakeupEvent(_THIS, SDL_Window *window); +extern void WIN_WaitNextEvent(_THIS); #endif /* SDL_windowsevents_h_ */ diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 02598ebe2..85642bb35 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -94,6 +94,7 @@ WIN_DeleteDevice(SDL_VideoDevice * device) SDL_UnloadObject(data->shcoreDLL); } + SDL_DestroyMutex(device->wakeup_lock); SDL_free(device->driverdata); SDL_free(device); } @@ -119,6 +120,12 @@ WIN_CreateDevice(int devindex) return NULL; } device->driverdata = data; + device->wakeup_lock = SDL_CreateMutex(); + if (!device->wakeup_lock) { + SDL_OutOfMemory(); + SDL_free(device); + return (0); + } data->userDLL = SDL_LoadObject("USER32.DLL"); if (data->userDLL) { @@ -145,6 +152,8 @@ WIN_CreateDevice(int devindex) device->GetDisplayModes = WIN_GetDisplayModes; device->SetDisplayMode = WIN_SetDisplayMode; device->PumpEvents = WIN_PumpEvents; + device->WaitNextEvent = WIN_WaitNextEvent; + device->SendWakeupEvent = WIN_SendWakeupEvent; device->SuspendScreenSaver = WIN_SuspendScreenSaver; device->CreateSDLWindow = WIN_CreateWindow; diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 24e9b0c15..5a26da6e4 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -1432,6 +1432,40 @@ X11_Pending(Display * display) return (0); } +void +X11_SendWakeupEvent(_THIS, SDL_Window *window) +{ + SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; + Display *req_display = data->request_display; + Window xwindow = ((SDL_WindowData *) window->driverdata)->xwindow; + XClientMessageEvent event; + + memset(&event, 0, sizeof(XClientMessageEvent)); + event.type = ClientMessage; + event.display = req_display; + event.send_event = True; + event.message_type = data->_SDL_WAKEUP; + event.format = 8; + + X11_XSendEvent(req_display, xwindow, False, NoEventMask, (XEvent *) &event); + /* XSendEvent returns a status and it could be BadValue or BadWindow. If an + error happens it is an SDL's internal error and there is nothing we can do here. */ + X11_XFlush(req_display); +} + +void +X11_WaitNextEvent(_THIS) +{ + /* Keep processing pending events */ + X11_DispatchEvent(_this); + +#ifdef SDL_USE_IME + if(SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE){ + SDL_IME_PumpEvents(); + } +#endif +} + void X11_PumpEvents(_THIS) { diff --git a/src/video/x11/SDL_x11events.h b/src/video/x11/SDL_x11events.h index df0782cb5..ab5cc5d8e 100644 --- a/src/video/x11/SDL_x11events.h +++ b/src/video/x11/SDL_x11events.h @@ -24,6 +24,8 @@ #define SDL_x11events_h_ extern void X11_PumpEvents(_THIS); +extern void X11_WaitNextEvent(_THIS); +extern void X11_SendWakeupEvent(_THIS, SDL_Window *window); extern void X11_SuspendScreenSaver(_THIS); #endif /* SDL_x11events_h_ */ diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 76cce5831..8806c1223 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -117,6 +117,7 @@ X11_DeleteDevice(SDL_VideoDevice * device) X11_XCloseDisplay(data->display); } SDL_free(data->windowlist); + SDL_DestroyMutex(device->wakeup_lock); SDL_free(device->driverdata); SDL_free(device); @@ -181,6 +182,12 @@ X11_CreateDevice(int devindex) return NULL; } device->driverdata = data; + device->wakeup_lock = SDL_CreateMutex(); + if (!device->wakeup_lock) { + SDL_free(device); + SDL_OutOfMemory(); + return (0); + } data->global_mouse_changed = SDL_TRUE; @@ -193,6 +200,7 @@ X11_CreateDevice(int devindex) } */ data->display = X11_XOpenDisplay(display); + data->request_display = X11_XOpenDisplay(display); #ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC /* On some systems if linking without -lX11, it fails and you get following message. * Xlib: connection to ":0.0" refused by server @@ -201,12 +209,19 @@ X11_CreateDevice(int devindex) * It succeeds if retrying 1 second later * or if running xhost +localhost on shell. */ - if (data->display == NULL) { + if (data->display == NULL || data->request_display == NULL) { SDL_Delay(1000); data->display = X11_XOpenDisplay(display); + data->request_display = X11_XOpenDisplay(display); } #endif - if (data->display == NULL) { + if (data->display == NULL || data->request_display == NULL) { + if (data->display) { + X11_XCloseDisplay(data->display); + } + if (data->request_display) { + X11_XCloseDisplay(data->request_display); + } SDL_free(device->driverdata); SDL_free(device); SDL_SetError("Couldn't open X11 display"); @@ -231,6 +246,8 @@ X11_CreateDevice(int devindex) device->SetDisplayMode = X11_SetDisplayMode; device->SuspendScreenSaver = X11_SuspendScreenSaver; device->PumpEvents = X11_PumpEvents; + device->WaitNextEvent = X11_WaitNextEvent; + device->SendWakeupEvent = X11_SendWakeupEvent; device->CreateSDLWindow = X11_CreateWindow; device->CreateSDLWindowFrom = X11_CreateWindowFrom; @@ -431,6 +448,7 @@ X11_VideoInit(_THIS) GET_ATOM(_NET_WM_USER_TIME); GET_ATOM(_NET_ACTIVE_WINDOW); GET_ATOM(_NET_FRAME_EXTENTS); + GET_ATOM(_SDL_WAKEUP); GET_ATOM(UTF8_STRING); GET_ATOM(PRIMARY); GET_ATOM(XdndEnter); diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 04bedfbd1..c08142f17 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -75,6 +75,7 @@ typedef struct SDL_VideoData { Display *display; + Display *request_display; char *classname; pid_t pid; XIM im; @@ -111,6 +112,7 @@ typedef struct SDL_VideoData Atom _NET_WM_USER_TIME; Atom _NET_ACTIVE_WINDOW; Atom _NET_FRAME_EXTENTS; + Atom _SDL_WAKEUP; Atom UTF8_STRING; Atom PRIMARY; Atom XdndEnter; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a380bc804..1c5ce9082 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ if(WINDOWS) endif() add_executable(checkkeys checkkeys.c) +add_executable(checkkeysthreads checkkeysthreads.c) add_executable(loopwave loopwave.c) add_executable(loopwavequeue loopwavequeue.c) add_executable(testresample testresample.c) diff --git a/test/Makefile.in b/test/Makefile.in index 9a62156ec..b9b4f7f0e 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -9,6 +9,7 @@ LIBS = @LIBS@ TARGETS = \ checkkeys$(EXE) \ + checkkeysthreads$(EXE) \ controllermap$(EXE) \ loopwave$(EXE) \ loopwavequeue$(EXE) \ @@ -80,6 +81,9 @@ Makefile: $(srcdir)/Makefile.in checkkeys$(EXE): $(srcdir)/checkkeys.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) +checkkeysthreads$(EXE): $(srcdir)/checkkeysthreads.c + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + loopwave$(EXE): $(srcdir)/loopwave.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) diff --git a/test/checkkeysthreads.c b/test/checkkeysthreads.c new file mode 100644 index 000000000..9a78331c9 --- /dev/null +++ b/test/checkkeysthreads.c @@ -0,0 +1,275 @@ +/* + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ + +/* Simple program: Loop, watching keystrokes + Note that you need to call SDL_PollEvent() or SDL_WaitEvent() to + pump the event loop and catch keystrokes. +*/ + +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#include "SDL.h" + +int done; + +/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ +static void +quit(int rc) +{ + SDL_Quit(); + exit(rc); +} + +static void +print_string(char **text, size_t *maxlen, const char *fmt, ...) +{ + int len; + va_list ap; + + va_start(ap, fmt); + len = SDL_vsnprintf(*text, *maxlen, fmt, ap); + if (len > 0) { + *text += len; + if ( ((size_t) len) < *maxlen ) { + *maxlen -= (size_t) len; + } else { + *maxlen = 0; + } + } + va_end(ap); +} + +static void +print_modifiers(char **text, size_t *maxlen) +{ + int mod; + print_string(text, maxlen, " modifiers:"); + mod = SDL_GetModState(); + if (!mod) { + print_string(text, maxlen, " (none)"); + return; + } + if (mod & KMOD_LSHIFT) + print_string(text, maxlen, " LSHIFT"); + if (mod & KMOD_RSHIFT) + print_string(text, maxlen, " RSHIFT"); + if (mod & KMOD_LCTRL) + print_string(text, maxlen, " LCTRL"); + if (mod & KMOD_RCTRL) + print_string(text, maxlen, " RCTRL"); + if (mod & KMOD_LALT) + print_string(text, maxlen, " LALT"); + if (mod & KMOD_RALT) + print_string(text, maxlen, " RALT"); + if (mod & KMOD_LGUI) + print_string(text, maxlen, " LGUI"); + if (mod & KMOD_RGUI) + print_string(text, maxlen, " RGUI"); + if (mod & KMOD_NUM) + print_string(text, maxlen, " NUM"); + if (mod & KMOD_CAPS) + print_string(text, maxlen, " CAPS"); + if (mod & KMOD_MODE) + print_string(text, maxlen, " MODE"); +} + +static void +PrintModifierState() +{ + char message[512]; + char *spot; + size_t left; + + spot = message; + left = sizeof(message); + + print_modifiers(&spot, &left); + SDL_Log("Initial state:%s\n", message); +} + +static void +PrintKey(SDL_Keysym * sym, SDL_bool pressed, SDL_bool repeat) +{ + char message[512]; + char *spot; + size_t left; + + spot = message; + left = sizeof(message); + + /* Print the keycode, name and state */ + if (sym->sym) { + print_string(&spot, &left, + "Key %s: scancode %d = %s, keycode 0x%08X = %s ", + pressed ? "pressed " : "released", + sym->scancode, + SDL_GetScancodeName(sym->scancode), + sym->sym, SDL_GetKeyName(sym->sym)); + } else { + print_string(&spot, &left, + "Unknown Key (scancode %d = %s) %s ", + sym->scancode, + SDL_GetScancodeName(sym->scancode), + pressed ? "pressed " : "released"); + } + print_modifiers(&spot, &left); + if (repeat) { + print_string(&spot, &left, " (repeat)"); + } + SDL_Log("%s\n", message); + fflush(stderr); +} + +static void +PrintText(char *eventtype, char *text) +{ + char *spot, expanded[1024]; + + expanded[0] = '\0'; + for ( spot = text; *spot; ++spot ) + { + size_t length = SDL_strlen(expanded); + SDL_snprintf(expanded + length, sizeof(expanded) - length, "\\x%.2x", (unsigned char)*spot); + } + SDL_Log("%s Text (%s): \"%s%s\"\n", eventtype, expanded, *text == '"' ? "\\" : "", text); +} + +void +loop() +{ + SDL_Event event; + /* Check for events */ + /*SDL_WaitEvent(&event); emscripten does not like waiting*/ + + fprintf(stderr, "starting loop\n"); fflush(stderr); + // while (SDL_PollEvent(&event)) { + while (!done && SDL_WaitEvent(&event)) { + fprintf(stderr, "got event type: %d\n", event.type); fflush(stderr); + switch (event.type) { + case SDL_KEYDOWN: + case SDL_KEYUP: + PrintKey(&event.key.keysym, (event.key.state == SDL_PRESSED) ? SDL_TRUE : SDL_FALSE, (event.key.repeat) ? SDL_TRUE : SDL_FALSE); + break; + case SDL_TEXTEDITING: + PrintText("EDIT", event.text.text); + break; + case SDL_TEXTINPUT: + PrintText("INPUT", event.text.text); + break; + case SDL_MOUSEBUTTONDOWN: + /* Left button quits the app, other buttons toggles text input */ + fprintf(stderr, "mouse button down button: %d (LEFT=%d)\n", event.button.button, SDL_BUTTON_LEFT); fflush(stderr); + if (event.button.button == SDL_BUTTON_LEFT) { + done = 1; + } else { + if (SDL_IsTextInputActive()) { + SDL_Log("Stopping text input\n"); + SDL_StopTextInput(); + } else { + SDL_Log("Starting text input\n"); + SDL_StartTextInput(); + } + } + break; + case SDL_QUIT: + done = 1; + break; + default: + break; + } + fprintf(stderr, "waiting new event\n"); fflush(stderr); + } + fprintf(stderr, "exiting event loop\n"); fflush(stderr); +#ifdef __EMSCRIPTEN__ + if (done) { + emscripten_cancel_main_loop(); + } +#endif +} + +/* Very simple thread - counts 0 to 9 delaying 50ms between increments */ +static int ping_thread(void *ptr) +{ + int cnt; + SDL_Event sdlevent; + memset(&sdlevent, sizeof(SDL_Event), 0); + for (cnt = 0; cnt < 10; ++cnt) { + fprintf(stderr, "sending event (%d/%d) from thread.\n", cnt + 1, 10); fflush(stderr); + sdlevent.type = SDL_KEYDOWN; + sdlevent.key.keysym.sym = SDLK_1; + SDL_PushEvent(&sdlevent); + SDL_Delay(1000 + rand() % 1000); + } + return cnt; +} + +int +main(int argc, char *argv[]) +{ + SDL_Window *window; + + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + /* Initialize SDL */ + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); + return (1); + } + + /* Set 640x480 video mode */ + window = SDL_CreateWindow("CheckKeys Test", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 640, 480, 0); + if (!window) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create 640x480 window: %s\n", + SDL_GetError()); + quit(2); + } + +#if __IPHONEOS__ + /* Creating the context creates the view, which we need to show keyboard */ + SDL_GL_CreateContext(window); +#endif + + SDL_StartTextInput(); + + /* Print initial modifier state */ + SDL_PumpEvents(); + PrintModifierState(); + + /* Watch keystrokes */ + done = 0; + + SDL_Thread *thread; + thread = SDL_CreateThread(ping_thread, "PingThread", (void *)NULL); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(loop, 0, 1); +#else + while (!done) { + loop(); + } +#endif + + SDL_WaitThread(thread, NULL); + SDL_Quit(); + return (0); +} + +/* vi: set ts=4 sw=4 expandtab: */ -- 2.17.1