diff options
author | David Robillard <d@drobilla.net> | 2023-01-07 19:27:14 -0500 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2023-01-07 20:27:35 -0500 |
commit | 4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b (patch) | |
tree | 63307cb14d43c7391b34f94ca0e532d8e9e01a09 /src | |
parent | 677e13dcbb5b64ce85093b9ea5c14025964e35b9 (diff) | |
download | pugl-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.c | 12 | ||||
-rw-r--r-- | src/mac.m | 207 | ||||
-rw-r--r-- | src/types.h | 1 | ||||
-rw-r--r-- | src/win.c | 132 | ||||
-rw-r--r-- | src/win.h | 24 | ||||
-rw-r--r-- | src/x11.c | 236 | ||||
-rw-r--r-- | src/x11.h | 10 |
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; +} @@ -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 @@ -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; } @@ -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 @@ -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 @@ -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; |