diff options
author | David Robillard <d@drobilla.net> | 2020-03-16 20:32:28 +0100 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2020-03-16 21:21:15 +0100 |
commit | a54361853bdfa08437c2858e603ce6202fb341b2 (patch) | |
tree | 5e86a841cc14dd4ca04d7b7b54b05f9c37e6feaf /pugl | |
parent | 7de08cd2a57d26f546060183944632da71f643f2 (diff) | |
download | pugl-a54361853bdfa08437c2858e603ce6202fb341b2.tar.gz pugl-a54361853bdfa08437c2858e603ce6202fb341b2.tar.bz2 pugl-a54361853bdfa08437c2858e603ce6202fb341b2.zip |
Add timer events
Diffstat (limited to 'pugl')
-rw-r--r-- | pugl/detail/mac.h | 1 | ||||
-rw-r--r-- | pugl/detail/mac.m | 44 | ||||
-rw-r--r-- | pugl/detail/win.c | 22 | ||||
-rw-r--r-- | pugl/detail/x11.c | 139 | ||||
-rw-r--r-- | pugl/detail/x11.h | 11 | ||||
-rw-r--r-- | pugl/pugl.h | 61 |
6 files changed, 276 insertions, 2 deletions
diff --git a/pugl/detail/mac.h b/pugl/detail/mac.h index adeebe9..d167f76 100644 --- a/pugl/detail/mac.h +++ b/pugl/detail/mac.h @@ -33,6 +33,7 @@ NSMutableAttributedString* markedText; NSTimer* timer; NSTimer* urgentTimer; + NSMutableDictionary* userTimers; bool reshaped; } diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m index 23fad7b..1c81abe 100644 --- a/pugl/detail/mac.m +++ b/pugl/detail/mac.m @@ -640,6 +640,14 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) [puglview->world->impl->app requestUserAttention:NSInformationalRequest]; } +- (void) timerTick:(NSTimer*)userTimer +{ + const NSNumber* userInfo = userTimer.userInfo; + const PuglEventTimer ev = {PUGL_TIMER, 0, userInfo.unsignedLongValue}; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + - (void) viewDidEndLiveResize { [super viewDidEndLiveResize]; @@ -763,6 +771,7 @@ puglCreateWindow(PuglView* view, const char* title) // Create wrapper view to handle input impl->wrapperView = [PuglWrapperView alloc]; impl->wrapperView->puglview = view; + impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init]; impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init]; [impl->wrapperView setAutoresizesSubviews:YES]; [impl->wrapperView initWithFrame: @@ -920,6 +929,41 @@ puglRequestAttention(PuglView* view) return PUGL_SUCCESS; } +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ + puglStopTimer(view, id); + + NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; + + NSTimer* timer = [NSTimer timerWithTimeInterval:timeout + target:view->impl->wrapperView + selector:@selector(timerTick:) + userInfo:idNumber + repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; + + view->impl->wrapperView->userTimers[idNumber] = timer; + + return PUGL_SUCCESS; +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ + NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; + NSTimer* timer = view->impl->wrapperView->userTimers[idNumber]; + + if (timer) { + [view->impl->wrapperView->userTimers removeObjectForKey:timer]; + [timer invalidate]; + return PUGL_SUCCESS; + } + + return PUGL_UNKNOWN_ERROR; +} + PuglStatus puglSendEvent(PuglView* view, const PuglEvent* event) { if (event->type == PUGL_CLIENT) { diff --git a/pugl/detail/win.c b/pugl/detail/win.c index cb4dfad..971ecdd 100644 --- a/pugl/detail/win.c +++ b/pugl/detail/win.c @@ -51,6 +51,7 @@ #define PUGL_LOCAL_CLIENT_MSG (WM_USER + 52) #define PUGL_RESIZE_TIMER_ID 9461 #define PUGL_URGENT_TIMER_ID 9462 +#define PUGL_USER_TIMER_MIN 9470 typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void); @@ -589,6 +590,9 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); } else if (wParam == PUGL_URGENT_TIMER_ID) { FlashWindow(view->impl->hwnd, TRUE); + } else if (wParam >= PUGL_USER_TIMER_MIN) { + const PuglEventTimer ev = {PUGL_TIMER, 0, wParam - PUGL_USER_TIMER_MIN}; + puglDispatchEvent(view, (const PuglEvent*)&ev); } break; case WM_EXITSIZEMOVE: @@ -742,6 +746,24 @@ puglRequestAttention(PuglView* view) } PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ + const UINT msec = (UINT)floor(timeout * 1000.0); + + return (SetTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id, msec, NULL) + ? PUGL_SUCCESS + : PUGL_UNKNOWN_ERROR); +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ + return (KillTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id) + ? PUGL_SUCCESS + : PUGL_UNKNOWN_ERROR); +} + +PuglStatus puglSendEvent(PuglView* view, const PuglEvent* event) { if (event->type == PUGL_CLIENT) { diff --git a/pugl/detail/x11.c b/pugl/detail/x11.c index 3684e46..9613dfb 100644 --- a/pugl/detail/x11.c +++ b/pugl/detail/x11.c @@ -35,6 +35,10 @@ #include <X11/Xutil.h> #include <X11/keysym.h> +#ifdef HAVE_XSYNC +# include <X11/extensions/sync.h> +#endif + #include <sys/select.h> #include <sys/time.h> @@ -65,6 +69,37 @@ static const long eventMask = EnterWindowMask | LeaveWindowMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask); +static bool +puglInitXSync(PuglWorldInternals* impl) +{ +#ifdef HAVE_XSYNC + int syncMajor; + int syncMinor; + int errorBase; + XSyncSystemCounter* counters; + int numCounters; + + if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) && + XSyncInitialize(impl->display, &syncMajor, &syncMinor) && + (counters = XSyncListSystemCounters(impl->display, &numCounters))) { + + for (int n = 0; n < numCounters; ++n) { + if (!strcmp(counters[n].name, "SERVERTIME")) { + impl->serverTimeCounter = counters[n].counter; + impl->syncSupported = true; + break; + } + } + + XSyncFreeSystemCounterList(counters); + } +#else + (void)impl; +#endif + + return false; +} + PuglWorldInternals* puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags) { @@ -100,6 +135,7 @@ puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags) impl->xim = XOpenIM(display, NULL, NULL, NULL); } + puglInitXSync(impl); XFlush(display); return impl; @@ -301,6 +337,7 @@ puglFreeWorldInternals(PuglWorld* world) XCloseIM(world->impl->xim); } XCloseDisplay(world->impl->display); + free(world->impl->timers); free(world->impl); } @@ -588,6 +625,80 @@ puglRequestAttention(PuglView* view) return PUGL_SUCCESS; } +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ +#ifdef HAVE_XSYNC + if (view->world->impl->syncSupported) { + XSyncValue value; + XSyncIntToValue(&value, (int)floor(timeout * 1000.0)); + + PuglWorldInternals* w = view->world->impl; + Display* const display = w->display; + const XSyncCounter counter = w->serverTimeCounter; + const XSyncTrigger trigger = {counter, XSyncRelative, value, 0}; + XSyncAlarmAttributes attr = {trigger, value, True, XSyncAlarmActive}; + const XSyncAlarm alarm = XSyncCreateAlarm(display, 0x17, &attr); + const PuglTimer timer = {alarm, view, id}; + + if (alarm != None) { + for (size_t i = 0; i < w->numTimers; ++i) { + if (w->timers[i].view == view && w->timers[i].id == id) { + // Replace existing timer + XSyncDestroyAlarm(w->display, w->timers[i].alarm); + w->timers[i] = timer; + return PUGL_SUCCESS; + } + } + + // Add new timer + const size_t size = ++w->numTimers * sizeof(timer); + w->timers = (PuglTimer*)realloc(w->timers, size); + w->timers[w->numTimers - 1] = timer; + return PUGL_SUCCESS; + } + } +#else + (void)view; + (void)id; + (void)timeout; +#endif + + return PUGL_FAILURE; +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ +#ifdef HAVE_XSYNC + PuglWorldInternals* w = view->world->impl; + + for (size_t i = 0; i < w->numTimers; ++i) { + if (w->timers[i].view == view && w->timers[i].id == id) { + XSyncDestroyAlarm(w->display, w->timers[i].alarm); + + if (i == w->numTimers - 1) { + memset(&w->timers[i], 0, sizeof(PuglTimer)); + } else { + memmove(w->timers + i, + w->timers + i + 1, + sizeof(PuglTimer) * (w->numTimers - i - 1)); + + memset(&w->timers[i], 0, sizeof(PuglTimer)); + } + + --w->numTimers; + return PUGL_SUCCESS; + } + } +#else + (void)view; + (void)id; +#endif + + return PUGL_FAILURE; +} + static XEvent puglEventToX(PuglView* view, const PuglEvent* event) { @@ -765,6 +876,30 @@ flushExposures(PuglWorld* world) } } +static bool +handleTimerEvent(PuglWorld* world, XEvent xevent) +{ +#ifdef HAVE_XSYNC + if (xevent.type == world->impl->syncEventBase + XSyncAlarmNotify) { + XSyncAlarmNotifyEvent* notify = ((XSyncAlarmNotifyEvent*)&xevent); + + for (size_t i = 0; i < world->impl->numTimers; ++i) { + if (world->impl->timers[i].alarm == notify->alarm) { + const PuglEventTimer ev = {PUGL_TIMER, 0, world->impl->timers[i].id}; + puglDispatchEvent(world->impl->timers[i].view, (const PuglEvent*)&ev); + } + } + + return true; + } +#else + (void)world; + (void)xevent; +#endif + + return false; +} + static PuglStatus puglDispatchX11Events(PuglWorld* world) { @@ -779,6 +914,10 @@ puglDispatchX11Events(PuglWorld* world) XEvent xevent; XNextEvent(display, &xevent); + if (handleTimerEvent(world, xevent)) { + continue; + } + PuglView* view = puglFindView(world, xevent.xany.window); if (!view) { continue; diff --git a/pugl/detail/x11.h b/pugl/detail/x11.h index fe8ce01..6f86a90 100644 --- a/pugl/detail/x11.h +++ b/pugl/detail/x11.h @@ -38,10 +38,21 @@ typedef struct { Atom NET_WM_STATE_DEMANDS_ATTENTION; } PuglX11Atoms; +typedef struct { + XID alarm; + PuglView* view; + uint64_t id; +} PuglTimer; + struct PuglWorldInternalsImpl { Display* display; PuglX11Atoms atoms; XIM xim; + PuglTimer* timers; + size_t numTimers; + XID serverTimeCounter; + int syncEventBase; + bool syncSupported; bool dispatchingEvents; }; diff --git a/pugl/pugl.h b/pugl/pugl.h index a796e56..8e20f35 100644 --- a/pugl/pugl.h +++ b/pugl/pugl.h @@ -185,7 +185,8 @@ typedef enum { PUGL_SCROLL, ///< Scrolled, a #PuglEventScroll PUGL_FOCUS_IN, ///< Keyboard focus entered view, a #PuglEventFocus PUGL_FOCUS_OUT, ///< Keyboard focus left view, a #PuglEventFocus - PUGL_CLIENT ///< Custom client message, a #PuglEventClient + PUGL_CLIENT, ///< Custom client message, a #PuglEventClient + PUGL_TIMER ///< Timer triggered, a #PuglEventTimer } PuglEventType; /** @@ -405,6 +406,22 @@ typedef struct { } PuglEventClient; /** + Timer event. + + This event is sent at the regular interval specified in the call to + puglStartTimer() that activated it. + + The #id is the application-specific ID given to puglStartTimer() which + distinguishes this timer from others. It should always be checked in the + event handler, even in applications that register only one timer. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_TIMER + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values + uintptr_t id; ///< Timer ID +} PuglEventTimer; + +/** View event. This is a union of all event types. The #type must be checked to determine @@ -428,6 +445,7 @@ typedef union { PuglEventScroll scroll; ///< #PUGL_SCROLL PuglEventFocus focus; ///< #PUGL_FOCUS_IN, #PUGL_FOCUS_OUT PuglEventClient client; ///< #PUGL_CLIENT + PuglEventTimer timer; ///< #PUGL_TIMER } PuglEvent; /** @@ -977,7 +995,7 @@ puglPostRedisplayRect(PuglView* view, PuglRect rect); @} @anchor interaction @name Interaction - Functions for interacting with the user. + Functions for interacting with the user and window system. @{ */ @@ -1035,6 +1053,45 @@ PUGL_API PuglStatus puglRequestAttention(PuglView* view); /** + Activate a repeating timer event. + + This starts a timer which will send a #PuglEventTimer to `view` every + `timeout` seconds. This can be used to perform some action in a view at a + regular interval with relatively low frequency. Note that the frequency of + timer events may be limited by how often puglUpdate() is called. + + If the given timer already exists, it is replaced. + + @param view The view to begin seding #PUGL_TIMER events to. + + @param id The identifier for this timer. This is an application-specific ID + that should be a low number, typically the value of a constant or `enum` + that starts from 0. There is a platform-specific limit to the number of + supported timers, and overhead associated with each, so applications should + create only a few timers and perform several tasks in one if necessary. + + @param timeout The period, in seconds, of this timer. This is not + guaranteed to have a resolution better than 10ms (the maximum timer + resolution on Windows) and may be rounded up if it is too short. On X11 and + MacOS, a resolution of about 1ms can usually be relied on. + + @return #PUGL_SUCCESS, #PUGL_FAILURE if timers are not supported on this + system, or an error code. +*/ +PUGL_API PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout); + +/** + Stop an active timer. + + @param view The view that the timer is set for. + @param id The ID previously passed to puglStartTimer(). + @return #PUGL_SUCCESS, or #PUGL_FAILURE if no such timer was found. +*/ +PUGL_API PuglStatus +puglStopTimer(PuglView* view, uintptr_t id); + +/** Send an event to a view via the window system. If supported, the event will be delivered to the view via the event loop |