aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2023-01-07 19:27:14 -0500
committerDavid Robillard <d@drobilla.net>2023-01-07 20:27:35 -0500
commit4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b (patch)
tree63307cb14d43c7391b34f94ca0e532d8e9e01a09 /src
parent677e13dcbb5b64ce85093b9ea5c14025964e35b9 (diff)
downloadpugl-4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b.tar.gz
pugl-4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b.tar.bz2
pugl-4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b.zip
Add support for special view types and styles
Diffstat (limited to 'src')
-rw-r--r--src/common.c12
-rw-r--r--src/mac.m207
-rw-r--r--src/types.h1
-rw-r--r--src/win.c132
-rw-r--r--src/win.h24
-rw-r--r--src/x11.c236
-rw-r--r--src/x11.h10
7 files changed, 516 insertions, 106 deletions
diff --git a/src/common.c b/src/common.c
index 3c2929f..0249e0d 100644
--- a/src/common.c
+++ b/src/common.c
@@ -106,6 +106,7 @@ puglSetDefaultHints(PuglHints hints)
hints[PUGL_RESIZABLE] = PUGL_FALSE;
hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE;
hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE;
+ hints[PUGL_VIEW_TYPE] = PUGL_DONT_CARE;
}
PuglView*
@@ -255,11 +256,18 @@ puglGetTransientParent(const PuglView* const view)
bool
puglGetVisible(const PuglView* view)
{
- return view->stage == PUGL_VIEW_STAGE_MAPPED;
+ return view->stage == PUGL_VIEW_STAGE_MAPPED &&
+ !(view->lastConfigure.style & PUGL_VIEW_STYLE_HIDDEN);
}
void*
-puglGetContext(PuglView* view)
+puglGetContext(PuglView* const view)
{
return view->backend->getContext(view);
}
+
+PuglViewStyleFlags
+puglGetViewStyle(const PuglView* const view)
+{
+ return view->lastConfigure.style;
+}
diff --git a/src/mac.m b/src/mac.m
index 00b886c..72f5f9c 100644
--- a/src/mac.m
+++ b/src/mac.m
@@ -173,6 +173,27 @@ updateViewRect(PuglView* view)
}
}
+static PuglViewStyleFlags
+getCurrentViewStyleFlags(PuglView* const view)
+{
+ const bool isResizing = view->resizing;
+
+ if (!view->impl->window) {
+ return (isResizing ? PUGL_VIEW_STYLE_RESIZING : 0U);
+ }
+
+ const NSWindowStyleMask styleMask = [view->impl->window styleMask];
+
+ const bool isFullScreen = styleMask & NSWindowStyleMaskFullScreen;
+ const bool isMiniaturized = [view->impl->window isMiniaturized];
+ const bool isZoomed = [view->impl->window isZoomed];
+
+ return (isFullScreen ? PUGL_VIEW_STYLE_FULLSCREEN : 0U) |
+ (isMiniaturized ? PUGL_VIEW_STYLE_HIDDEN : 0U) |
+ (isZoomed ? (PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE) : 0U) |
+ (isResizing ? PUGL_VIEW_STYLE_RESIZING : 0U);
+}
+
@implementation PuglWindow {
@public
PuglView* puglview;
@@ -201,6 +222,24 @@ updateViewRect(PuglView* view)
[self setContentSize:sizePoints(view, view->frame.width, view->frame.height)];
}
+- (PuglStatus)dispatchCurrentConfiguration
+{
+ const PuglConfigureEvent ev = {
+ PUGL_CONFIGURE,
+ 0,
+ puglview->frame.x,
+ puglview->frame.y,
+ puglview->frame.width,
+ puglview->frame.height,
+ getCurrentViewStyleFlags(puglview),
+ };
+
+ PuglEvent configureEvent;
+ configureEvent.configure = ev;
+
+ return puglDispatchEvent(puglview, &configureEvent);
+}
+
- (BOOL)canBecomeKeyWindow
{
return YES;
@@ -216,24 +255,25 @@ updateViewRect(PuglView* view)
[super setIsVisible:flag];
if (flag && puglview->stage < PUGL_VIEW_STAGE_MAPPED) {
- const PuglConfigureEvent ev = {
- PUGL_CONFIGURE,
- 0,
- puglview->frame.x,
- puglview->frame.y,
- puglview->frame.width,
- puglview->frame.height,
- };
-
- PuglEvent configureEvent;
- configureEvent.configure = ev;
- puglDispatchEvent(puglview, &configureEvent);
+ [self dispatchCurrentConfiguration];
puglDispatchSimpleEvent(puglview, PUGL_MAP);
} else if (!flag && puglview->stage == PUGL_VIEW_STAGE_MAPPED) {
puglDispatchSimpleEvent(puglview, PUGL_UNMAP);
}
}
+- (void)setIsZoomed:(BOOL)flag
+{
+ [super setIsZoomed:flag];
+
+ const bool wasZoomed = (puglview->lastConfigure.style &
+ (PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE));
+
+ if (flag != wasZoomed) {
+ [self dispatchCurrentConfiguration];
+ }
+}
+
@end
@implementation PuglWrapperView {
@@ -263,6 +303,7 @@ updateViewRect(PuglView* view)
puglview->frame.y,
puglview->frame.width,
puglview->frame.height,
+ getCurrentViewStyleFlags(puglview),
};
PuglEvent configureEvent;
@@ -821,6 +862,11 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
puglDispatchSimpleEvent(puglview, PUGL_LOOP_ENTER);
}
+- (void)viewDidEndLiveResize
+{
+ puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE);
+}
+
- (void)viewWillDraw
{
puglDispatchSimpleEvent(puglview, PUGL_UPDATE);
@@ -837,16 +883,13 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
puglDispatchEvent(puglview, &timerEvent);
}
-- (void)viewDidEndLiveResize
-{
- puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE);
-}
-
@end
@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
- (instancetype)initWithPuglWindow:(PuglWindow*)window;
+- (void)beginLiveResize:(NSNotification*)notification;
+- (void)endLiveResize:(NSNotification*)notification;
@end
@@ -863,6 +906,22 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
return self;
}
+- (void)beginLiveResize:(NSNotification*)notification
+{
+ (void)notification;
+
+ window->puglview->resizing = true;
+ [window dispatchCurrentConfiguration];
+}
+
+- (void)endLiveResize:(NSNotification*)notification
+{
+ (void)notification;
+
+ window->puglview->resizing = false;
+ [window dispatchCurrentConfiguration];
+}
+
- (BOOL)windowShouldClose:(id)sender
{
(void)sender;
@@ -896,6 +955,56 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
puglDispatchEvent(window->puglview, &ev);
}
+- (void)windowWillStartLiveResize:(NSNotification*)notification
+{
+ [self beginLiveResize:notification];
+}
+
+- (void)windowDidEndLiveResize:(NSNotification*)notification
+{
+ [self endLiveResize:notification];
+}
+
+- (void)windowWillMiniaturize:(NSNotification*)notification
+{
+ [self beginLiveResize:notification];
+}
+
+- (void)windowDidMiniaturize:(NSNotification*)notification
+{
+ [self endLiveResize:notification];
+}
+
+- (void)windowWillDeminiaturize:(NSNotification*)notification
+{
+ [self beginLiveResize:notification];
+}
+
+- (void)windowDidDeminiaturize:(NSNotification*)notification
+{
+ [self endLiveResize:notification];
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification*)notification
+{
+ [self beginLiveResize:notification];
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification*)notification
+{
+ [self endLiveResize:notification];
+}
+
+- (void)windowWillExitFullScreen:(NSNotification*)notification
+{
+ [self beginLiveResize:notification];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification*)notification
+{
+ [self endLiveResize:notification];
+}
+
@end
PuglWorldInternals*
@@ -1278,11 +1387,69 @@ puglHasFocus(const PuglView* view)
[[impl->wrapperView window] firstResponder] == impl->wrapperView);
}
+static bool
+styleIsMaximized(const PuglViewStyleFlags flags)
+{
+ return (flags & PUGL_VIEW_STYLE_TALL) && (flags & PUGL_VIEW_STYLE_WIDE);
+}
+
PuglStatus
-puglRequestAttention(PuglView* view)
+puglSetViewStyle(PuglView* const view, const PuglViewStyleFlags flags)
{
- if (![view->impl->window isKeyWindow]) {
- [view->world->impl->app requestUserAttention:NSInformationalRequest];
+ NSWindow* const window = view->impl->window;
+ const PuglViewStyleFlags oldFlags = puglGetViewStyle(view);
+ if (!window) {
+ return PUGL_FAILURE;
+ }
+
+ for (uint32_t mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; mask <<= 1U) {
+ const bool oldValue = oldFlags & mask;
+ const bool newValue = flags & mask;
+ if (oldValue == newValue) {
+ continue;
+ }
+
+ switch (mask) {
+ case PUGL_VIEW_STYLE_MODAL:
+ case PUGL_VIEW_STYLE_TALL:
+ case PUGL_VIEW_STYLE_WIDE:
+ break;
+
+ case PUGL_VIEW_STYLE_HIDDEN:
+ if (newValue) {
+ [window miniaturize:window];
+ } else {
+ [window deminiaturize:window];
+ }
+ break;
+
+ case PUGL_VIEW_STYLE_FULLSCREEN:
+ if (newValue != ([window styleMask] & NSFullScreenWindowMask)) {
+ [window toggleFullScreen:nil];
+ }
+ break;
+
+ case PUGL_VIEW_STYLE_ABOVE:
+ case PUGL_VIEW_STYLE_BELOW:
+ break;
+
+ case PUGL_VIEW_STYLE_DEMANDING:
+ if (![window isKeyWindow]) {
+ [view->world->impl->app requestUserAttention:NSInformationalRequest];
+ }
+ break;
+
+ case PUGL_VIEW_STYLE_RESIZING:
+ case PUGL_VIEW_STYLE_MAPPED:
+ break;
+ }
+ }
+
+ // Handle maximization (MacOS doesn't have tall/wide styles)
+ const bool oldMaximized = styleIsMaximized(oldFlags);
+ const bool newMaximized = styleIsMaximized(flags);
+ if (oldMaximized != newMaximized) {
+ [window zoom:window];
}
return PUGL_SUCCESS;
diff --git a/src/types.h b/src/types.h
index 072d3fe..2f6721b 100644
--- a/src/types.h
+++ b/src/types.h
@@ -56,6 +56,7 @@ struct PuglViewImpl {
PuglHints hints;
PuglViewSize sizeHints[PUGL_NUM_SIZE_HINTS];
PuglViewStage stage;
+ bool resizing;
};
/// Cross-platform world definition
diff --git a/src/win.c b/src/win.c
index 79d38c2..b410607 100644
--- a/src/win.c
+++ b/src/win.c
@@ -110,13 +110,24 @@ puglRegisterWindowClass(const char* name)
static unsigned
puglWinGetWindowFlags(const PuglView* const view)
{
+ const unsigned commonFlags = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+ if (view->parent) {
+ return commonFlags | WS_CHILD;
+ }
+
+ if (view->impl->fullscreen) {
+ return commonFlags | WS_POPUPWINDOW;
+ }
+
+ const unsigned typeFlags =
+ (view->hints[PUGL_VIEW_TYPE] == PUGL_VIEW_TYPE_DIALOG)
+ ? (WS_DLGFRAME | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU)
+ : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX);
+
const bool resizable = !!view->hints[PUGL_RESIZABLE];
const unsigned sizeFlags = resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0U;
- return (WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
- (view->parent
- ? WS_CHILD
- : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | sizeFlags)));
+ return commonFlags | typeFlags | sizeFlags;
}
static unsigned
@@ -564,6 +575,13 @@ handleConfigure(PuglView* view, PuglEvent* event)
event->configure.width = (PuglSpan)width;
event->configure.height = (PuglSpan)height;
+ event->configure.style =
+ ((view->resizing ? PUGL_VIEW_STYLE_RESIZING : 0U) |
+ (view->impl->fullscreen ? PUGL_VIEW_STYLE_FULLSCREEN : 0U) |
+ (view->impl->minimized ? PUGL_VIEW_STYLE_HIDDEN : 0U) |
+ (view->impl->maximized ? (PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE)
+ : 0U));
+
return rect;
}
@@ -635,11 +653,12 @@ constrainAspect(const PuglView* const view,
static LRESULT
handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
{
- PuglEvent event = {{PUGL_NOTHING, 0}};
- RECT rect = {0, 0, 0, 0};
- POINT pt = {0, 0};
- MINMAXINFO* mmi = NULL;
- void* dummy_ptr = NULL;
+ PuglEvent event = {{PUGL_NOTHING, 0}};
+ RECT rect = {0, 0, 0, 0};
+ POINT pt = {0, 0};
+ MINMAXINFO* mmi = NULL;
+ void* dummy_ptr = NULL;
+ WINDOWPLACEMENT placement = {sizeof(WINDOWPLACEMENT), 0, 0, pt, pt};
if (InSendMessageEx(dummy_ptr)) {
event.any.flags |= PUGL_IS_SEND_EVENT;
@@ -667,20 +686,19 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP;
break;
- case WM_SIZE:
- 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_DISPLAYCHANGE:
view->impl->scaleFactor = puglWinGetViewScaleFactor(view);
break;
case WM_WINDOWPOSCHANGED:
+ view->impl->minimized = false;
+ view->impl->maximized = false;
+ if (GetWindowPlacement(view->impl->hwnd, &placement)) {
+ if (placement.showCmd == SW_SHOWMINIMIZED) {
+ view->impl->minimized = true;
+ } else if (placement.showCmd == SW_SHOWMAXIMIZED) {
+ view->impl->maximized = true;
+ }
+ }
handleConfigure(view, &event);
break;
case WM_SIZING:
@@ -690,6 +708,10 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
}
break;
case WM_ENTERSIZEMOVE:
+ view->resizing = true;
+ puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER);
+ handleConfigure(view, &event);
+ break;
case WM_ENTERMENULOOP:
puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER);
break;
@@ -701,6 +723,10 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
}
break;
case WM_EXITSIZEMOVE:
+ view->resizing = false;
+ puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE);
+ handleConfigure(view, &event);
+ break;
case WM_EXITMENULOOP:
puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE);
break;
@@ -855,13 +881,73 @@ puglHasFocus(const PuglView* view)
return GetFocus() == view->impl->hwnd;
}
+static bool
+styleIsMaximized(const PuglViewStyleFlags flags)
+{
+ return (flags & PUGL_VIEW_STYLE_TALL) && (flags & PUGL_VIEW_STYLE_WIDE);
+}
+
PuglStatus
-puglRequestAttention(PuglView* view)
+puglSetViewStyle(PuglView* const view, const PuglViewStyleFlags flags)
{
- FLASHWINFO info = {
- sizeof(FLASHWINFO), view->impl->hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, 1, 0};
+ PuglInternals* const impl = view->impl;
+ const PuglViewStyleFlags oldFlags = puglGetViewStyle(view);
+
+ for (uint32_t mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; mask <<= 1U) {
+ const bool oldValue = oldFlags & mask;
+ const bool newValue = flags & mask;
+ if (oldValue == newValue) {
+ continue;
+ }
+
+ switch (mask) {
+ case PUGL_VIEW_STYLE_MODAL:
+ case PUGL_VIEW_STYLE_TALL:
+ case PUGL_VIEW_STYLE_WIDE:
+ break;
+
+ case PUGL_VIEW_STYLE_HIDDEN:
+ ShowWindow(impl->hwnd, newValue ? SW_SHOWMINIMIZED : SW_RESTORE);
+ break;
+
+ case PUGL_VIEW_STYLE_FULLSCREEN:
+ impl->fullscreen = newValue;
+ SetWindowLong(impl->hwnd, GWL_STYLE, (LONG)puglWinGetWindowFlags(view));
+ SetWindowLong(
+ impl->hwnd, GWL_EXSTYLE, (LONG)puglWinGetWindowExFlags(view));
+ if (newValue) {
+ GetWindowPlacement(impl->hwnd, &impl->oldPlacement);
+ ShowWindow(impl->hwnd, SW_SHOWMAXIMIZED);
+ } else {
+ impl->oldPlacement.showCmd = SW_RESTORE;
+ SetWindowPlacement(impl->hwnd, &impl->oldPlacement);
+ }
+ break;
- FlashWindowEx(&info);
+ case PUGL_VIEW_STYLE_ABOVE:
+ case PUGL_VIEW_STYLE_BELOW:
+ break;
+
+ case PUGL_VIEW_STYLE_DEMANDING: {
+ FLASHWINFO info = {
+ sizeof(FLASHWINFO), impl->hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, 1, 0};
+
+ FlashWindowEx(&info);
+ break;
+ }
+
+ case PUGL_VIEW_STYLE_RESIZING:
+ break;
+ }
+ }
+
+ // Handle maximization (Windows doesn't have tall/wide styles)
+ const bool oldMaximized = styleIsMaximized(oldFlags);
+ const bool newMaximized = styleIsMaximized(flags);
+ if (oldMaximized != newMaximized) {
+ ShowWindow(impl->hwnd, newMaximized ? SW_SHOWMAXIMIZED : SW_RESTORE);
+ puglPostRedisplay(view);
+ }
return PUGL_SUCCESS;
}
diff --git a/src/win.h b/src/win.h
index 4a89e11..37ffa72 100644
--- a/src/win.h
+++ b/src/win.h
@@ -19,16 +19,20 @@ struct PuglWorldInternalsImpl {
};
struct PuglInternalsImpl {
- PuglWinPFD pfd;
- int pfId;
- HWND hwnd;
- HCURSOR cursor;
- HDC hdc;
- PuglBlob clipboard;
- PuglSurface* surface;
- double scaleFactor;
- bool flashing;
- bool mouseTracked;
+ PuglWinPFD pfd;
+ int pfId;
+ HWND hwnd;
+ HCURSOR cursor;
+ HDC hdc;
+ WINDOWPLACEMENT oldPlacement;
+ PuglBlob clipboard;
+ PuglSurface* surface;
+ double scaleFactor;
+ bool flashing;
+ bool mouseTracked;
+ bool minimized;
+ bool maximized;
+ bool fullscreen;
};
PUGL_API
diff --git a/src/x11.c b/src/x11.c
index 30a15be..372c167 100644
--- a/src/x11.c
+++ b/src/x11.c
@@ -155,7 +155,8 @@ puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
impl->display = display;
impl->scaleFactor = puglX11GetDisplayScaleFactor(display);
- // Intern the various atoms we will need
+ // Intern the various atoms we'll need
+
impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0);
impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0);
@@ -164,10 +165,31 @@ puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
impl->atoms.NET_CLOSE_WINDOW = XInternAtom(display, "_NET_CLOSE_WINDOW", 0);
impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0);
impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0);
+
+ impl->atoms.NET_WM_STATE_ABOVE =
+ XInternAtom(display, "_NET_WM_STATE_ABOVE", 0);
+ impl->atoms.NET_WM_STATE_BELOW =
+ XInternAtom(display, "_NET_WM_STATE_BELOW", 0);
impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION =
XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0);
+ impl->atoms.NET_WM_STATE_FULLSCREEN =
+ XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", 0);
impl->atoms.NET_WM_STATE_HIDDEN =
XInternAtom(display, "_NET_WM_STATE_HIDDEN", 0);
+ impl->atoms.NET_WM_STATE_MAXIMIZED_HORZ =
+ XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0);
+ impl->atoms.NET_WM_STATE_MAXIMIZED_VERT =
+ XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", 0);
+ impl->atoms.NET_WM_STATE_MODAL =
+ XInternAtom(display, "_NET_WM_STATE_MODAL", 0);
+ impl->atoms.NET_WM_WINDOW_TYPE =
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
+ impl->atoms.NET_WM_WINDOW_TYPE_DIALOG =
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", 0);
+ impl->atoms.NET_WM_WINDOW_TYPE_NORMAL =
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", 0);
+ impl->atoms.NET_WM_WINDOW_TYPE_UTILITY =
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", 0);
impl->atoms.TARGETS = XInternAtom(display, "TARGETS", 0);
impl->atoms.text_uri_list = XInternAtom(display, "text/uri-list", 0);
@@ -207,6 +229,96 @@ puglInitViewInternals(PuglWorld* const world)
return impl;
}
+static Atom
+styleFlagToAtom(PuglWorld* const world, const PuglViewStyleFlag flag)
+{
+ const PuglX11Atoms* const atoms = &world->impl->atoms;
+
+ switch (flag) {
+ case PUGL_VIEW_STYLE_MODAL:
+ return atoms->NET_WM_STATE_MODAL;
+ case PUGL_VIEW_STYLE_TALL:
+ return atoms->NET_WM_STATE_MAXIMIZED_VERT;
+ case PUGL_VIEW_STYLE_WIDE:
+ return atoms->NET_WM_STATE_MAXIMIZED_HORZ;
+ case PUGL_VIEW_STYLE_HIDDEN:
+ return atoms->NET_WM_STATE_HIDDEN;
+ case PUGL_VIEW_STYLE_FULLSCREEN:
+ return atoms->NET_WM_STATE_FULLSCREEN;
+ case PUGL_VIEW_STYLE_ABOVE:
+ return atoms->NET_WM_STATE_ABOVE;
+ case PUGL_VIEW_STYLE_BELOW:
+ return atoms->NET_WM_STATE_BELOW;
+ case PUGL_VIEW_STYLE_DEMANDING:
+ return atoms->NET_WM_STATE_DEMANDS_ATTENTION;
+ case PUGL_VIEW_STYLE_RESIZING:
+ break;
+ }
+
+ return 0;
+}
+
+static Atom
+viewTypeToAtom(PuglWorld* const world, const PuglViewType type)
+{
+ const PuglX11Atoms* const atoms = &world->impl->atoms;
+
+ switch (type) {
+ case PUGL_VIEW_TYPE_NORMAL:
+ return atoms->NET_WM_WINDOW_TYPE_NORMAL;
+ case PUGL_VIEW_TYPE_UTILITY:
+ return atoms->NET_WM_WINDOW_TYPE_UTILITY;
+ case PUGL_VIEW_TYPE_DIALOG:
+ return atoms->NET_WM_WINDOW_TYPE_DIALOG;
+ }
+
+ return 0;
+}
+
+PuglStatus
+puglSetViewStyle(PuglView* const view, const PuglViewStyleFlags flags)
+{
+ PuglWorld* const world = view->world;
+ PuglInternals* const impl = view->impl;
+ Display* const display = view->world->impl->display;
+ const PuglX11Atoms* const atoms = &view->world->impl->atoms;
+ const PuglViewStyleFlags oldFlags = puglGetViewStyle(view);
+
+ for (uint32_t mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; mask <<= 1U) {
+ const Atom stateAtom = styleFlagToAtom(world, (PuglViewStyleFlag)mask);
+ const bool oldValue = oldFlags & mask;
+ const bool newValue = flags & mask;
+ if (!stateAtom || oldValue == newValue) {
+ continue;
+ }
+
+ if (stateAtom == atoms->NET_WM_STATE_HIDDEN) {
+ // KDE annoyingly doesn't support clients setting the hidden hint
+ XIconifyWindow(display, impl->win, impl->screen);
+ } else {
+ XEvent event = {ClientMessage};
+ event.xclient.window = impl->win;
+ event.xclient.format = 32;
+ event.xclient.message_type = atoms->NET_WM_STATE;
+ event.xclient.data.l[0] = newValue ? WM_STATE_ADD : WM_STATE_REMOVE;
+ event.xclient.data.l[1] = (long)stateAtom;
+ event.xclient.data.l[2] = 0;
+ event.xclient.data.l[3] = 1;
+ event.xclient.data.l[4] = 0;
+
+ if (!XSendEvent(display,
+ RootWindow(display, impl->screen),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &event)) {
+ return PUGL_UNKNOWN_ERROR;
+ }
+ }
+ }
+
+ return PUGL_SUCCESS;
+}
+
static PuglStatus
pollX11Socket(PuglWorld* const world, const double timeout)
{
@@ -435,6 +547,20 @@ puglRealize(PuglView* const view)
return st;
}
+ // Set window type
+ if (view->hints[PUGL_VIEW_TYPE] != PUGL_DONT_CARE) {
+ const PuglViewType viewType = (PuglViewType)view->hints[PUGL_VIEW_TYPE];
+ const Atom windowType = viewTypeToAtom(world, viewType);
+ XChangeProperty(display,
+ impl->win,
+ atoms->NET_WM_WINDOW_TYPE,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ (const unsigned char*)&windowType,
+ 1);
+ }
+
#ifdef HAVE_XRANDR
int ignored = 0;
if (XRRQueryExtension(display, &ignored, &ignored)) {
@@ -787,6 +913,40 @@ translateClientMessage(PuglView* const view, XClientMessageEvent message)
return event;
}
+static PuglViewStyleFlags
+getCurrentViewStyleFlags(PuglView* const view)
+{
+ const PuglX11Atoms* const atoms = &view->world->impl->atoms;
+
+ unsigned long numHints = 0;
+ Atom* hints = NULL;
+ PuglViewStyleFlags state = 0U;
+ if (!getAtomProperty(
+ view, view->impl->win, atoms->NET_WM_STATE, &numHints, &hints)) {
+ for (unsigned long i = 0; i < numHints; ++i) {
+ if (hints[i] == atoms->NET_WM_STATE_MAXIMIZED_VERT) {
+ state |= PUGL_VIEW_STYLE_TALL;
+ } else if (hints[i] == atoms->NET_WM_STATE_MAXIMIZED_HORZ) {
+ state |= PUGL_VIEW_STYLE_WIDE;
+ } else if (hints[i] == atoms->NET_WM_STATE_HIDDEN) {
+ state |= PUGL_VIEW_STYLE_HIDDEN;
+ } else if (hints[i] == atoms->NET_WM_STATE_FULLSCREEN) {
+ state |= PUGL_VIEW_STYLE_FULLSCREEN;
+ } else if (hints[i] == atoms->NET_WM_STATE_MODAL) {
+ state |= PUGL_VIEW_STYLE_MODAL;
+ } else if (hints[i] == atoms->NET_WM_STATE_ABOVE) {
+ state |= PUGL_VIEW_STYLE_ABOVE;
+ } else if (hints[i] == atoms->NET_WM_STATE_BELOW) {
+ state |= PUGL_VIEW_STYLE_BELOW;
+ } else if (hints[i] == atoms->NET_WM_STATE_DEMANDS_ATTENTION) {
+ state |= PUGL_VIEW_STYLE_DEMANDING;
+ }
+ }
+ }
+
+ return state;
+}
+
static PuglEvent
getCurrentConfiguration(PuglView* const view)
{
@@ -794,42 +954,47 @@ getCurrentConfiguration(PuglView* const view)
XWindowAttributes attrs;
XGetWindowAttributes(view->world->impl->display, view->impl->win, &attrs);
- // Build a configure event with the current position and size
+ // Build a configure event based on the current window configuration
PuglEvent configureEvent = {{PUGL_CONFIGURE, 0}};
configureEvent.configure.x = (PuglCoord)attrs.x;
configureEvent.configure.y = (PuglCoord)attrs.y;
configureEvent.configure.width = (PuglSpan)attrs.width;
configureEvent.configure.height = (PuglSpan)attrs.height;
+ configureEvent.configure.style = getCurrentViewStyleFlags(view);
return configureEvent;
}
static PuglEvent
+makeConfigureEvent(PuglView* const view)
+{
+ PuglEvent event = view->impl->pendingConfigure;
+
+ if (event.type != PUGL_CONFIGURE) {
+ event = getCurrentConfiguration(view);
+ }
+
+ return event;
+}
+
+static PuglEvent
translatePropertyNotify(PuglView* const view, XPropertyEvent message)
{
- const PuglX11Atoms* const atoms = &view->world->impl->atoms;
+ const PuglInternals* const impl = view->impl;
+ const PuglX11Atoms* const atoms = &view->world->impl->atoms;
+ PuglEvent event = {{PUGL_NOTHING, 0}};
- PuglEvent event = {{PUGL_NOTHING, 0}};
if (message.atom == atoms->NET_WM_STATE) {
+ // Get all the current states set in the window hints
unsigned long numHints = 0;
Atom* hints = NULL;
- if (getAtomProperty(
- view, view->impl->win, message.atom, &numHints, &hints)) {
+ if (getAtomProperty(view, impl->win, message.atom, &numHints, &hints)) {
return event;
}
- bool hidden = false;
- for (unsigned long i = 0; i < numHints; ++i) {
- if (hints[i] == atoms->NET_WM_STATE_HIDDEN) {
- hidden = true;
- }
- }
-
- if (hidden && view->stage == PUGL_VIEW_STAGE_MAPPED) {
- event.type = PUGL_UNMAP;
- } else if (!hidden && view->stage == PUGL_VIEW_STAGE_CONFIGURED) {
- event.type = PUGL_MAP;
- }
+ // Make a configure event based on the current configuration to update
+ event = makeConfigureEvent(view);
+ event.configure.style = getCurrentViewStyleFlags(view);
XFree(hints);
}
@@ -851,9 +1016,7 @@ translateEvent(PuglView* const view, XEvent xevent)
event = translatePropertyNotify(view, xevent.xproperty);
break;
case VisibilityNotify:
- event.type = (xevent.xvisibility.state == VisibilityFullyObscured)
- ? PUGL_UNMAP
- : PUGL_MAP;
+ event = makeConfigureEvent(view);
break;
case MapNotify:
event.type = PUGL_MAP;
@@ -862,7 +1025,7 @@ translateEvent(PuglView* const view, XEvent xevent)
event.type = PUGL_UNMAP;
break;
case ConfigureNotify:
- event.type = PUGL_CONFIGURE;
+ event = makeConfigureEvent(view);
event.configure.x = (PuglCoord)xevent.xconfigure.x;
event.configure.y = (PuglCoord)xevent.xconfigure.y;
event.configure.width = (PuglSpan)xevent.xconfigure.width;
@@ -1014,35 +1177,6 @@ puglHasFocus(const PuglView* const view)
}
PuglStatus
-puglRequestAttention(PuglView* const view)
-{
- PuglInternals* const impl = view->impl;
- Display* const display = view->world->impl->display;
- const PuglX11Atoms* const atoms = &view->world->impl->atoms;
- XEvent event = PUGL_INIT_STRUCT;
-
- event.type = ClientMessage;
- event.xclient.window = impl->win;
- event.xclient.format = 32;
- event.xclient.message_type = atoms->NET_WM_STATE;
- event.xclient.data.l[0] = WM_STATE_ADD;
- event.xclient.data.l[1] = (long)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(display, impl->screen);
-
- return XSendEvent(display,
- root,
- False,
- SubstructureNotifyMask | SubstructureRedirectMask,
- &event)
- ? PUGL_SUCCESS
- : PUGL_UNKNOWN_ERROR;
-}
-
-PuglStatus
puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout)
{
#ifdef HAVE_XSYNC
diff --git a/src/x11.h b/src/x11.h
index de2e0d2..a048495 100644
--- a/src/x11.h
+++ b/src/x11.h
@@ -27,8 +27,18 @@ typedef struct {
Atom NET_CLOSE_WINDOW;
Atom NET_WM_NAME;
Atom NET_WM_STATE;
+ Atom NET_WM_STATE_ABOVE;
+ Atom NET_WM_STATE_BELOW;
Atom NET_WM_STATE_DEMANDS_ATTENTION;
+ Atom NET_WM_STATE_FULLSCREEN;
Atom NET_WM_STATE_HIDDEN;
+ Atom NET_WM_STATE_MAXIMIZED_HORZ;
+ Atom NET_WM_STATE_MAXIMIZED_VERT;
+ Atom NET_WM_STATE_MODAL;
+ Atom NET_WM_WINDOW_TYPE;
+ Atom NET_WM_WINDOW_TYPE_DIALOG;
+ Atom NET_WM_WINDOW_TYPE_NORMAL;
+ Atom NET_WM_WINDOW_TYPE_UTILITY;
Atom TARGETS;
Atom text_uri_list;
} PuglX11Atoms;