From 80191fb070d60e7bffd78c2ef9e43b2610f2b8ff Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 21 Jul 2019 19:42:50 +0200 Subject: Add puglRequestAttention() --- pugl/pugl.h | 10 ++++++++++ pugl/pugl.hpp | 1 + pugl/pugl_osx.m | 26 ++++++++++++++++++++++++++ pugl/pugl_win.c | 21 +++++++++++++++++++++ pugl/pugl_x11.c | 33 +++++++++++++++++++++++++++++++++ pugl/pugl_x11.h | 2 ++ pugl_test.c | 8 +++++++- 7 files changed, 100 insertions(+), 1 deletion(-) diff --git a/pugl/pugl.h b/pugl/pugl.h index c8e62d4..d744477 100644 --- a/pugl/pugl.h +++ b/pugl/pugl.h @@ -598,6 +598,16 @@ puglIgnoreKeyRepeat(PuglView* view, bool ignore); PUGL_API void puglGrabFocus(PuglView* view); +/** + Request user attention. + + This hints to the system that the window or application requires attention + from the user. The exact effect depends on the platform, but is usually + something like flashing a task bar entry. +*/ +PUGL_API void +puglRequestAttention(PuglView* view); + /** Block and wait for an event to be ready. diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp index 8232887..36e7e13 100644 --- a/pugl/pugl.hpp +++ b/pugl/pugl.hpp @@ -83,6 +83,7 @@ public: virtual void* getContext() { return puglGetContext(_view); } virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); } virtual void grabFocus() { puglGrabFocus(_view); } + virtual void requestAttention() { puglRequestAttention(_view); } virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); } virtual PuglStatus processEvents() { return puglProcessEvents(_view); } virtual void postRedisplay() { puglPostRedisplay(_view); } diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m index 73fe654..7a804b7 100644 --- a/pugl/pugl_osx.m +++ b/pugl/pugl_osx.m @@ -101,6 +101,7 @@ struct PuglInternalsImpl { PuglView* puglview; NSTrackingArea* trackingArea; NSTimer* timer; + NSTimer* urgentTimer; } @end @@ -544,6 +545,11 @@ handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type) puglPostRedisplay(puglview); } +- (void) urgentTick +{ + [NSApp requestUserAttention:NSInformationalRequest]; +} + - (void) viewDidEndLiveResize { [super viewDidEndLiveResize]; @@ -582,6 +588,12 @@ handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type) - (void) windowDidBecomeKey:(NSNotification*)notification { + PuglOpenGLView* glview = window->puglview->impl->glview; + if (window->puglview->impl->glview->urgentTimer) { + [glview->urgentTimer invalidate]; + glview->urgentTimer = NULL; + } + const PuglEventFocus ev = { PUGL_FOCUS_IN, window->puglview, 0, false }; puglDispatchEvent(window->puglview, (const PuglEvent*)&ev); } @@ -740,6 +752,20 @@ puglGrabFocus(PuglView* view) [view->impl->window makeKeyWindow]; } +void +puglRequestAttention(PuglView* view) +{ + if (![view->impl->window isKeyWindow]) { + [NSApp requestUserAttention:NSInformationalRequest]; + view->impl->glview->urgentTimer = + [NSTimer scheduledTimerWithTimeInterval:2.0 + target:view->impl->glview + selector:@selector(urgentTick) + userInfo:nil + repeats:YES]; + } +} + PuglStatus puglWaitForEvent(PuglView* view) { diff --git a/pugl/pugl_win.c b/pugl/pugl_win.c index 2e05c71..c91d383 100644 --- a/pugl/pugl_win.c +++ b/pugl/pugl_win.c @@ -45,6 +45,7 @@ #define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) #define PUGL_RESIZE_TIMER_ID 9461 +#define PUGL_URGENT_TIMER_ID 9462 #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_ACCELERATION_ARB 0x2003 @@ -631,6 +632,13 @@ handleCrossing(PuglView* view, const PuglEventType type, POINT pos) puglDispatchEvent(view, (const PuglEvent*)&ev); } +static void +stopFlashing(PuglView* view) +{ + KillTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID); + FlashWindow(view->impl->hwnd, FALSE); +} + static LRESULT handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) { @@ -671,6 +679,8 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) case WM_TIMER: if (wParam == PUGL_RESIZE_TIMER_ID) { puglPostRedisplay(view); + } else if (wParam == PUGL_URGENT_TIMER_ID) { + FlashWindow(view->impl->hwnd, TRUE); } break; case WM_EXITSIZEMOVE: @@ -704,6 +714,7 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) tme.hwndTrack = view->impl->hwnd; TrackMouseEvent(&tme); + stopFlashing(view); handleCrossing(view, PUGL_ENTER_NOTIFY, pt); view->impl->mouseTracked = true; } @@ -778,6 +789,7 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_SETFOCUS: + stopFlashing(view); event.type = PUGL_FOCUS_IN; break; case WM_KILLFOCUS: @@ -802,6 +814,15 @@ puglGrabFocus(PuglView* view) SetFocus(view->impl->hwnd); } +void +puglRequestAttention(PuglView* view) +{ + if (!view->impl->mouseTracked || GetFocus() != view->impl->hwnd) { + FlashWindow(view->impl->hwnd, TRUE); + SetTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID, 500, NULL); + } +} + PuglStatus puglWaitForEvent(PuglView* view) { diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c index 8bfcf17..49a1a9c 100644 --- a/pugl/pugl_x11.c +++ b/pugl/pugl_x11.c @@ -50,6 +50,12 @@ # define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif +enum WmClientStateMessageAction { + WM_STATE_REMOVE, + WM_STATE_ADD, + WM_STATE_TOGGLE +}; + PuglInternals* puglInitInternals(void) { @@ -80,6 +86,9 @@ puglCreateWindow(PuglView* view, const char* title) // Intern the various atoms we will need impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); + 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); if (view->ctx_type == PUGL_GL) { #ifdef PUGL_HAVE_GL @@ -435,6 +444,30 @@ puglGrabFocus(PuglView* view) view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime); } +void +puglRequestAttention(PuglView* view) +{ + PuglInternals* const impl = view->impl; + XEvent event = {0}; + + event.type = ClientMessage; + event.xclient.window = impl->win; + event.xclient.format = 32; + event.xclient.message_type = impl->atoms.NET_WM_STATE; + event.xclient.data.l[0] = WM_STATE_ADD; + event.xclient.data.l[1] = impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + + const Window root = RootWindow(impl->display, impl->screen); + XSendEvent(impl->display, + root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + (XEvent*)&event); +} + PuglStatus puglWaitForEvent(PuglView* view) { diff --git a/pugl/pugl_x11.h b/pugl/pugl_x11.h index f33d8de..6a38c62 100644 --- a/pugl/pugl_x11.h +++ b/pugl/pugl_x11.h @@ -33,5 +33,7 @@ struct PuglInternalsImpl { struct { Atom WM_PROTOCOLS; Atom WM_DELETE_WINDOW; + Atom NET_WM_STATE; + Atom NET_WM_STATE_DEMANDS_ATTENTION; } atoms; }; diff --git a/pugl_test.c b/pugl_test.c index 8425611..6520b07 100644 --- a/pugl_test.c +++ b/pugl_test.c @@ -297,7 +297,8 @@ main(int argc, char** argv) puglShowWindow(view); - float lastReportTime = (float)puglGetTime(view); + float lastReportTime = (float)puglGetTime(view); + bool requestedAttention = false; while (!quit) { const float thisTime = (float)puglGetTime(view); @@ -309,6 +310,11 @@ main(int argc, char** argv) puglProcessEvents(view); + if (!requestedAttention && thisTime > 5) { + puglRequestAttention(view); + requestedAttention = true; + } + if (continuous && thisTime > lastReportTime + 5) { const double fps = framesDrawn / (thisTime - lastReportTime); fprintf(stderr, -- cgit v1.2.1