aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/pugl_shader_demo.c62
-rw-r--r--src/implementation.c48
-rw-r--r--src/implementation.h8
-rw-r--r--src/win.c15
-rw-r--r--src/x11.c100
-rw-r--r--src/x11.h1
-rw-r--r--test/test_show_hide.c2
-rw-r--r--test/test_update.c2
8 files changed, 177 insertions, 61 deletions
diff --git a/examples/pugl_shader_demo.c b/examples/pugl_shader_demo.c
index aa5c38e..2fd9a57 100644
--- a/examples/pugl_shader_demo.c
+++ b/examples/pugl_shader_demo.c
@@ -398,6 +398,41 @@ teardownGl(PuglTestApp* app)
deleteProgram(app->drawRect);
}
+static double
+updateTimeout(const PuglTestApp* const app)
+{
+ if (!puglGetVisible(app->view)) {
+ return -1.0; // View is invisible (minimized), wait until something happens
+ }
+
+ if (!app->opts.sync) {
+ return 0.0; // VSync explicitly disabled, run as fast as possible
+ }
+
+ /* To minimize input latency and get smooth performance during window
+ resizing, we want to poll for events as long as possible before starting
+ to draw the next frame. This ensures that as many events are consumed as
+ possible before starting to draw, or, equivalently, that the next rendered
+ frame represents the latest events possible. This is particularly
+ important for mouse input and "live" window resizing, where many events
+ tend to pile up within a frame.
+
+ To do this, we keep track of the time when the last frame was finished
+ drawing, and how long it took to expose (and assume this is relatively
+ stable). Then, we can calculate how much time there is from now until the
+ time when we should start drawing to not miss the deadline, and use that
+ as the timeout for puglUpdate().
+ */
+
+ const int refreshRate = puglGetViewHint(app->view, PUGL_REFRESH_RATE);
+ const double now = puglGetTime(app->world);
+ const double nextFrameEndTime = app->lastFrameEndTime + (1.0 / refreshRate);
+ const double nextExposeTime = nextFrameEndTime - app->lastDrawDuration;
+ const double timeUntilNext = nextExposeTime - now;
+
+ return timeUntilNext;
+}
+
int
main(int argc, char** argv)
{
@@ -426,36 +461,11 @@ main(int argc, char** argv)
printViewHints(app.view);
puglShow(app.view);
- // Calculate ideal frame duration to drive the main loop at a good rate
- const int refreshRate = puglGetViewHint(app.view, PUGL_REFRESH_RATE);
- const double frameDuration = 1.0 / (double)refreshRate;
-
// Grind away, drawing continuously
const double startTime = puglGetTime(app.world);
PuglFpsPrinter fpsPrinter = {startTime};
while (!app.quit) {
- /* To minimize input latency and get smooth performance during window
- resizing, we want to poll for events as long as possible before
- starting to draw the next frame. This ensures that as many events
- are consumed as possible before starting to draw, or, equivalently,
- that the next rendered frame represents the latest events possible.
- This is particularly important for mouse input and "live" window
- resizing, where many events tend to pile up within a frame.
-
- To do this, we keep track of the time when the last frame was
- finished drawing, and how long it took to expose (and assume this is
- relatively stable). Then, we can calculate how much time there is
- from now until the time when we should start drawing to not miss the
- deadline, and use that as the timeout for puglUpdate().
- */
-
- const double now = puglGetTime(app.world);
- const double nextFrameEndTime = app.lastFrameEndTime + frameDuration;
- const double nextExposeTime = nextFrameEndTime - app.lastDrawDuration;
- const double timeUntilNext = nextExposeTime - now;
- const double timeout = app.opts.sync ? timeUntilNext : 0.0;
-
- puglUpdate(app.world, fmax(0.0, timeout));
+ puglUpdate(app.world, fmax(0.0, updateTimeout(&app)));
puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
}
diff --git a/src/implementation.c b/src/implementation.c
index ad0de63..d7763a7 100644
--- a/src/implementation.c
+++ b/src/implementation.c
@@ -373,23 +373,25 @@ puglDispatchSimpleEvent(PuglView* view, const PuglEventType type)
}
void
-puglDispatchEventInContext(PuglView* view, const PuglEvent* event)
+puglConfigure(PuglView* view, const PuglEvent* event)
{
- if (event->type == PUGL_CONFIGURE) {
- view->frame.x = event->configure.x;
- view->frame.y = event->configure.y;
- view->frame.width = event->configure.width;
- view->frame.height = event->configure.height;
+ assert(event->type == PUGL_CONFIGURE);
- if (puglMustConfigure(view, &event->configure)) {
- view->eventFunc(view, event);
- view->lastConfigure = event->configure;
- }
- } else if (event->type == PUGL_EXPOSE) {
- if (event->expose.width > 0 && event->expose.height > 0) {
- view->eventFunc(view, event);
- }
- } else {
+ view->frame.x = event->configure.x;
+ view->frame.y = event->configure.y;
+ view->frame.width = event->configure.width;
+ view->frame.height = event->configure.height;
+
+ if (puglMustConfigure(view, &event->configure)) {
+ view->eventFunc(view, event);
+ view->lastConfigure = event->configure;
+ }
+}
+
+void
+puglExpose(PuglView* view, const PuglEvent* event)
+{
+ if (event->expose.width > 0.0 && event->expose.height > 0.0) {
view->eventFunc(view, event);
}
}
@@ -409,13 +411,25 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event)
case PUGL_CONFIGURE:
if (puglMustConfigure(view, &event->configure)) {
view->backend->enter(view, NULL);
- puglDispatchEventInContext(view, event);
+ puglConfigure(view, event);
view->backend->leave(view, NULL);
}
break;
+ case PUGL_MAP:
+ if (!view->visible) {
+ view->visible = true;
+ view->eventFunc(view, event);
+ }
+ break;
+ case PUGL_UNMAP:
+ if (view->visible) {
+ view->visible = false;
+ view->eventFunc(view, event);
+ }
+ break;
case PUGL_EXPOSE:
view->backend->enter(view, &event->expose);
- puglDispatchEventInContext(view, event);
+ puglExpose(view, event);
view->backend->leave(view, &event->expose);
break;
default:
diff --git a/src/implementation.h b/src/implementation.h
index 8c5398c..936322d 100644
--- a/src/implementation.h
+++ b/src/implementation.h
@@ -58,9 +58,13 @@ puglDecodeUTF8(const uint8_t* buf);
void
puglDispatchSimpleEvent(PuglView* view, PuglEventType type);
-/// Dispatch `event` to `view` while already in the graphics context
+/// Process configure event while already in the graphics context
void
-puglDispatchEventInContext(PuglView* view, const PuglEvent* event);
+puglConfigure(PuglView* view, const PuglEvent* event);
+
+/// Process expose event while already in the graphics context
+void
+puglExpose(PuglView* view, const PuglEvent* event);
/// Dispatch `event` to `view`, entering graphics context if necessary
void
diff --git a/src/win.c b/src/win.c
index 448e053..d1a66cd 100644
--- a/src/win.c
+++ b/src/win.c
@@ -579,14 +579,17 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_INTERNALPAINT);
}
- if ((bool)wParam != view->visible) {
- view->visible = wParam;
- event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP;
- }
+ event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP;
break;
case WM_SIZE:
- handleConfigure(view, &event);
- InvalidateRect(view->impl->hwnd, NULL, false);
+ if (wParam == SIZE_MINIMIZED) {
+ event.type = PUGL_UNMAP;
+ } else if (!view->visible) {
+ event.type = PUGL_MAP;
+ } else {
+ handleConfigure(view, &event);
+ InvalidateRect(view->impl->hwnd, NULL, false);
+ }
break;
case WM_SIZING:
if (view->minAspectX) {
diff --git a/src/x11.c b/src/x11.c
index 559c05b..50695ff 100644
--- a/src/x11.c
+++ b/src/x11.c
@@ -48,6 +48,7 @@
#include <sys/select.h>
#include <sys/time.h>
+#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
@@ -126,6 +127,8 @@ puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0);
impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION =
XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0);
+ impl->atoms.NET_WM_STATE_HIDDEN =
+ XInternAtom(display, "_NET_WM_STATE_HIDDEN", 0);
// Open input method
XSetLocaleModifiers("");
@@ -327,6 +330,7 @@ puglRealize(PuglView* const view)
attr.event_mask |= KeyReleaseMask;
attr.event_mask |= LeaveWindowMask;
attr.event_mask |= PointerMotionMask;
+ attr.event_mask |= PropertyChangeMask;
attr.event_mask |= StructureNotifyMask;
attr.event_mask |= VisibilityChangeMask;
@@ -550,6 +554,33 @@ translateModifiers(const unsigned xstate)
((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0u));
}
+static PuglStatus
+getAtomProperty(PuglView* const view,
+ const Window window,
+ const Atom property,
+ unsigned long* numValues,
+ Atom** values)
+{
+ Atom actualType = 0;
+ int actualFormat = 0;
+ unsigned long bytesAfter = 0;
+
+ return (XGetWindowProperty(view->impl->display,
+ window,
+ property,
+ 0,
+ LONG_MAX,
+ False,
+ XA_ATOM,
+ &actualType,
+ &actualFormat,
+ numValues,
+ &bytesAfter,
+ (unsigned char**)values) == Success)
+ ? PUGL_SUCCESS
+ : PUGL_FAILURE;
+}
+
static PuglEvent
translateClientMessage(PuglView* const view, const XClientMessageEvent message)
{
@@ -571,6 +602,39 @@ translateClientMessage(PuglView* const view, const XClientMessageEvent message)
}
static PuglEvent
+translatePropertyNotify(PuglView* const view, XPropertyEvent message)
+{
+ const PuglX11Atoms* const atoms = &view->world->impl->atoms;
+
+ PuglEvent event = {{PUGL_NOTHING, 0}};
+ bool hidden = false;
+ if (message.atom == atoms->NET_WM_STATE) {
+ unsigned long numHints = 0;
+ Atom* hints = NULL;
+ if (getAtomProperty(
+ view, view->impl->win, message.atom, &numHints, &hints)) {
+ return event;
+ }
+
+ for (unsigned long i = 0; i < numHints; ++i) {
+ if (hints[i] == atoms->NET_WM_STATE_HIDDEN) {
+ hidden = true;
+ }
+ }
+
+ if (hidden && view->visible) {
+ event.type = PUGL_UNMAP;
+ } else if (!hidden && !view->visible) {
+ event.type = PUGL_MAP;
+ }
+
+ XFree(hints);
+ }
+
+ return event;
+}
+
+static PuglEvent
translateEvent(PuglView* const view, XEvent xevent)
{
PuglEvent event = {{PUGL_NOTHING, 0}};
@@ -580,15 +644,21 @@ translateEvent(PuglView* const view, XEvent xevent)
case ClientMessage:
event = translateClientMessage(view, xevent.xclient);
break;
+ case PropertyNotify:
+ event = translatePropertyNotify(view, xevent.xproperty);
+ break;
case VisibilityNotify:
- view->visible = xevent.xvisibility.state != VisibilityFullyObscured;
+ if (xevent.xvisibility.state == VisibilityFullyObscured) {
+ event.type = PUGL_UNMAP;
+ } else {
+ event.type = PUGL_MAP;
+ }
break;
case MapNotify:
event.type = PUGL_MAP;
break;
case UnmapNotify:
- event.type = PUGL_UNMAP;
- view->visible = false;
+ event.type = PUGL_UNMAP;
break;
case ConfigureNotify:
event.type = PUGL_CONFIGURE;
@@ -986,21 +1056,35 @@ flushExposures(PuglWorld* const world)
for (size_t i = 0; i < world->numViews; ++i) {
PuglView* const view = world->views[i];
+ // Send update event so the application can trigger redraws
if (view->visible) {
puglDispatchSimpleEvent(view, PUGL_UPDATE);
}
+ // Copy and reset pending events (in case their handlers write new ones)
const PuglEvent configure = view->impl->pendingConfigure;
const PuglEvent expose = view->impl->pendingExpose;
view->impl->pendingConfigure.type = PUGL_NOTHING;
view->impl->pendingExpose.type = PUGL_NOTHING;
- if (configure.type || expose.type) {
- view->backend->enter(view, expose.type ? &expose.expose : NULL);
- puglDispatchEventInContext(view, &configure);
- puglDispatchEventInContext(view, &expose);
- view->backend->leave(view, expose.type ? &expose.expose : NULL);
+ if (expose.type) {
+ view->backend->enter(view, &expose.expose);
+
+ if (configure.type) {
+ puglConfigure(view, &configure);
+ }
+
+ puglExpose(view, &expose);
+ view->backend->leave(view, &expose.expose);
+ } else if (configure.type) {
+ view->backend->enter(view, NULL);
+
+ if (configure.type) {
+ puglConfigure(view, &configure);
+ }
+
+ view->backend->leave(view, NULL);
}
}
}
diff --git a/src/x11.h b/src/x11.h
index 313b4f5..daf177a 100644
--- a/src/x11.h
+++ b/src/x11.h
@@ -38,6 +38,7 @@ typedef struct {
Atom NET_WM_NAME;
Atom NET_WM_STATE;
Atom NET_WM_STATE_DEMANDS_ATTENTION;
+ Atom NET_WM_STATE_HIDDEN;
} PuglX11Atoms;
typedef struct {
diff --git a/test/test_show_hide.c b/test/test_show_hide.c
index 2294709..b624735 100644
--- a/test/test_show_hide.c
+++ b/test/test_show_hide.c
@@ -75,7 +75,7 @@ onEvent(PuglView* view, const PuglEvent* event)
test->state = EXPOSED;
break;
case PUGL_UNMAP:
- assert(test->state == EXPOSED);
+ assert(test->state == MAPPED || test->state == EXPOSED);
test->state = UNMAPPED;
break;
case PUGL_DESTROY:
diff --git a/test/test_update.c b/test/test_update.c
index d9c02ab..39415bc 100644
--- a/test/test_update.c
+++ b/test/test_update.c
@@ -75,8 +75,8 @@ onEvent(PuglView* view, const PuglEvent* event)
case PUGL_UPDATE:
if (test->state == EXPOSED1) {
- puglPostRedisplay(view);
test->state = UPDATED;
+ puglPostRedisplay(view);
}
break;