aboutsummaryrefslogtreecommitdiffstats
path: root/pugl
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2020-03-16 20:32:28 +0100
committerDavid Robillard <d@drobilla.net>2020-03-16 21:21:15 +0100
commita54361853bdfa08437c2858e603ce6202fb341b2 (patch)
tree5e86a841cc14dd4ca04d7b7b54b05f9c37e6feaf /pugl
parent7de08cd2a57d26f546060183944632da71f643f2 (diff)
downloadpugl-a54361853bdfa08437c2858e603ce6202fb341b2.tar.gz
pugl-a54361853bdfa08437c2858e603ce6202fb341b2.tar.bz2
pugl-a54361853bdfa08437c2858e603ce6202fb341b2.zip
Add timer events
Diffstat (limited to 'pugl')
-rw-r--r--pugl/detail/mac.h1
-rw-r--r--pugl/detail/mac.m44
-rw-r--r--pugl/detail/win.c22
-rw-r--r--pugl/detail/x11.c139
-rw-r--r--pugl/detail/x11.h11
-rw-r--r--pugl/pugl.h61
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