/* * Copyright 1992, 2005 Stefan Monnier. * * Author: Stefan Monnier [ monnier@lia.di.epfl.ch ] * Adapted for use with more than one virtual screen by * Olaf "Rhialto" Seibert . * * $Id: otp.c,v 1.1.1.2 2023/07/05 07:36:07 nia Exp $ * * handles all the OnTopPriority-related issues. * */ #include "ctwm.h" #include #include #include #include #include "otp.h" #include "ctwm_atoms.h" #include "screen.h" #include "util.h" #include "icons.h" #include "list.h" #include "events.h" #include "event_handlers.h" #include "vscreen.h" #include "win_utils.h" #define DEBUG_OTP 0 #if DEBUG_OTP #define DPRINTF(x) fprintf x #else #define DPRINTF(x) #endif #if defined(NDEBUG) # define CHECK_OTP 0 #else # define CHECK_OTP 1 #endif /* number of priorities known to ctwm: [0..ONTOP_MAX] */ #define OTP_ZERO 8 #define OTP_MAX (OTP_ZERO * 2) /* Shorten code a little */ #define PRI(owl) OwlEffectivePriority(owl) #define PRI_CP(from, to) do { \ to->pri_base = from->pri_base; \ to->pri_aflags = from->pri_aflags; \ } while(0) struct OtpWinList { OtpWinList *above; OtpWinList *below; TwmWindow *twm_win; WinType type; bool switching; int pri_base; // Base priority unsigned pri_aflags; // Flags that might alter it; OTP_AFLAG_* bool stashed_aflags; }; struct OtpPreferences { name_list *priorityL[OTP_MAX + 1]; int priority; name_list *switchingL; bool switching; }; typedef struct Box { int x; int y; int width; int height; } Box; static bool OtpCheckConsistencyVS(VirtualScreen *currentvs, Window vroot); static void OwlPrettyPrint(const OtpWinList *start); static void OwlSetAflagMask(OtpWinList *owl, unsigned mask, unsigned setto); static void OwlSetAflag(OtpWinList *owl, unsigned flag); static void OwlClearAflag(OtpWinList *owl, unsigned flag); static void OwlStashAflags(OtpWinList *owl); static unsigned OwlGetStashedAflags(OtpWinList *owl, bool *gotit); static int OwlEffectivePriority(OtpWinList *owl); static Box BoxOfOwl(OtpWinList *owl) { Box b; switch(owl->type) { case IconWin: { Icon *icon = owl->twm_win->icon; b.x = icon->w_x; b.y = icon->w_y; b.width = icon->w_width; b.height = icon->w_height; break; } case WinWin: { TwmWindow *twm_win = owl->twm_win; b.x = twm_win->frame_x; b.y = twm_win->frame_y; b.width = twm_win->frame_width; b.height = twm_win->frame_height; break; } default: assert(false); } return b; } static bool BoxesIntersect(Box *b1, Box *b2) { bool interX = (b1->x + b1->width > b2->x) && (b2->x + b2->width > b1->x); bool interY = (b1->y + b1->height > b2->y) && (b2->y + b2->height > b1->y); return (interX && interY); } static bool isIntersectingWith(OtpWinList *owl1, OtpWinList *owl2) { Box b1 = BoxOfOwl(owl1); Box b2 = BoxOfOwl(owl2); return BoxesIntersect(&b1, &b2); } static bool isOnScreen(OtpWinList *owl) { TwmWindow *twm_win = owl->twm_win; return (((owl->type == IconWin) ? twm_win->iconified : twm_win->mapped) && OCCUPY(twm_win, Scr->currentvs->wsw->currentwspc)); } bool isTransientOf(TwmWindow *trans, TwmWindow *main) { return (trans->istransient && trans->transientfor == main->w); } bool isGroupLeader(TwmWindow *twm_win) { return ((twm_win->group == 0) || (twm_win->group == twm_win->w)); } bool isGroupLeaderOf(TwmWindow *leader, TwmWindow *twm_win) { return (isGroupLeader(leader) && !isGroupLeader(twm_win) && (leader->group == twm_win->group)); } bool isSmallTransientOf(TwmWindow *trans, TwmWindow *main) { int trans_area, main_area; if(isTransientOf(trans, main)) { assert(trans->frame); trans_area = trans->frame_width * trans->frame_height; main_area = main->frame_width * main->frame_height; return (trans_area < ((main_area * Scr->TransientOnTop) / 100)); } else { return false; } } static Window WindowOfOwl(OtpWinList *owl) { return (owl->type == IconWin) ? owl->twm_win->icon->w : owl->twm_win->frame; } bool OtpCheckConsistency(void) { #if DEBUG_OTP VirtualScreen *tvs; bool result = true; for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) { fprintf(stderr, "OtpCheckConsistencyVS: vs:(x,y)=(%d,%d)\n", tvs->x, tvs->y); result = result && OtpCheckConsistencyVS(tvs, tvs->window); } return result; #else return OtpCheckConsistencyVS(Scr->currentvs, Scr->Root); #endif } static bool OtpCheckConsistencyVS(VirtualScreen *currentvs, Window vroot) { #if CHECK_OTP OtpWinList *owl; TwmWindow *twm_win; Window root, parent, *children; unsigned int nchildren; int priority = 0; int stack = -1; int nwins = 0; XQueryTree(dpy, vroot, &root, &parent, &children, &nchildren); #if DEBUG_OTP { int i; fprintf(stderr, "XQueryTree: %d children:\n", nchildren); for(i = 0; i < nchildren; i++) { fprintf(stderr, "[%d]=%x ", i, (unsigned int)children[i]); } fprintf(stderr, "\n"); } #endif for(owl = Scr->bottomOwl; owl != NULL; owl = owl->above) { twm_win = owl->twm_win; /* check the back arrows are correct */ assert(((owl->type == IconWin) && (owl == twm_win->icon->otp)) || ((owl->type == WinWin) && (owl == twm_win->otp))); /* check the doubly linked list's consistency */ if(owl->below == NULL) { assert(owl == Scr->bottomOwl); } else { assert(owl->below->above == owl); } /* Code already ensures this */ assert(owl->pri_base <= OTP_MAX); /* List should be bottom->top, so effective pri better ascend */ { const int nextpri = PRI(owl); if(nextpri < priority) { fprintf(stderr, "%s(): Priority went backward " "(%d:'%s' -> %d:'%s')\n", __func__, priority, owl->below->twm_win->name, nextpri, owl->twm_win->name); OwlPrettyPrint(Scr->bottomOwl); abort(); } priority = nextpri; } #if DEBUG_OTP fprintf(stderr, "checking owl: pri %d w=%x stack=%d", priority, (unsigned int)WindowOfOwl(owl), stack); if(twm_win) { fprintf(stderr, " title=%s occupation=%x ", twm_win->name, (unsigned int)twm_win->occupation); if(owl->twm_win->vs) { fprintf(stderr, " vs:(x,y)=(%d,%d)", twm_win->vs->x, twm_win->vs->y); } else { fprintf(stderr, " vs:NULL"); } if(owl->twm_win->parent_vs) { fprintf(stderr, " parent_vs:(x,y)=(%d,%d)", twm_win->parent_vs->x, twm_win->parent_vs->y); } else { fprintf(stderr, " parent_vs:NULL"); } } fprintf(stderr, " %s\n", (owl->type == WinWin ? "Window" : "Icon")); #endif /* count the number of twm windows */ if(owl->type == WinWin) { nwins++; } #ifdef WINBOX if(twm_win->winbox) { /* * We can't check windows in a WindowBox, since they are * not direct children of the Root window. */ DPRINTF((stderr, "Can't check this window, it is in a WinBox\n")); continue; } #endif /* * Check only windows from the current vitual screen; the others * won't show up in the tree from XQueryTree(). */ if(currentvs == twm_win->parent_vs) { /* check the window's existence. */ Window windowOfOwl = WindowOfOwl(owl); #if DEBUG_OTP int i; for(i = 0; i < nchildren && windowOfOwl != children[i];) { i++; } fprintf(stderr, "search for owl in stack -> i=%d\n", i); assert(i > stack && "Window not in good place in stack"); assert(i < nchildren && "Window was not found in stack"); if(0) { char buf[128]; snprintf(buf, 128, "xwininfo -all -id %d", (int)windowOfOwl); system(buf); } /* we know that this always increases stack (assert i>stack) */ stack = i; #else /* DEBUG_OTP */ /* check against the Xserver's stack */ do { stack++; DPRINTF((stderr, "stack++: children[%d] = %x\n", stack, (unsigned int)children[stack])); assert(stack < nchildren); } while(windowOfOwl != children[stack]); #endif /* DEBUG_OTP */ } } XFree(children); /* by decrementing nwins, check that all the wins are in our list */ for(twm_win = Scr->FirstWindow; twm_win != NULL; twm_win = twm_win->next) { nwins--; } /* if we just removed a win, it might still be somewhere, hence the -1 */ assert((nwins <= 0) && (nwins >= -1)); #endif return true; } static void RemoveOwl(OtpWinList *owl) { if(owl->above != NULL) { owl->above->below = owl->below; } if(owl->below != NULL) { owl->below->above = owl->above; } else { Scr->bottomOwl = owl->above; } owl->below = NULL; owl->above = NULL; } /** * For the purpose of putting a window above another, * they need to have the same parent, i.e. be in the same * VirtualScreen. */ static OtpWinList *GetOwlAtOrBelowInVS(OtpWinList *owl, VirtualScreen *vs) { while(owl != NULL && owl->twm_win->parent_vs != vs) { owl = owl->below; } return owl; } #ifdef WINBOX /* * Windows in a box don't really occur in the stacking order of the * root window. * In the OWL list, keep them just on top of their box, in their * respective order of course. * Therefore we may need to update the owl we're going to be above. */ static OtpWinList *GetOwlAtOrBelowInWinbox(OtpWinList **owlp, WindowBox *wb) { OtpWinList *owl = *owlp; while(owl != NULL && owl->twm_win->winbox != wb) { owl = owl->below; } if(owl == NULL) { /* we have gone below the box: put it just on top of it */ *owlp = wb->twmwin->otp; } else { *owlp = owl; } return owl; } #endif static void InsertOwlAbove(OtpWinList *owl, OtpWinList *other_owl) { #if DEBUG_OTP fprintf(stderr, "InsertOwlAbove owl->pri=%d w=0x%x parent_vs:(x,y)=(%d,%d)", PRI(owl), (unsigned int)WindowOfOwl(owl), owl->twm_win->parent_vs->x, owl->twm_win->parent_vs->y); if(other_owl != NULL) { fprintf(stderr, "\n other_owl->pri=%d w=0x%x parent_vs:(x,y)=(%d,%d)", PRI(other_owl), (unsigned int)WindowOfOwl(other_owl), owl->twm_win->parent_vs->x, owl->twm_win->parent_vs->y); } fprintf(stderr, "\n"); #endif assert(owl->above == NULL); assert(owl->below == NULL); if(other_owl == NULL) { DPRINTF((stderr, "Bottom-most window overall\n")); /* special case for the lowest window overall */ assert(PRI(owl) <= PRI(Scr->bottomOwl)); /* pass the action to the Xserver */ XLowerWindow(dpy, WindowOfOwl(owl)); /* update the list */ owl->above = Scr->bottomOwl; owl->above->below = owl; Scr->bottomOwl = owl; } else { #ifdef WINBOX WindowBox *winbox = owl->twm_win->winbox; #endif OtpWinList *vs_owl; if(false) { // dummy } #ifdef WINBOX else if(winbox != NULL) { vs_owl = GetOwlAtOrBelowInWinbox(&other_owl, winbox); } #endif else { vs_owl = GetOwlAtOrBelowInVS(other_owl, owl->twm_win->parent_vs); } assert(PRI(owl) >= PRI(other_owl)); if(other_owl->above != NULL) { assert(PRI(owl) <= PRI(other_owl->above)); } if(vs_owl == NULL) { DPRINTF((stderr, "Bottom-most window in VirtualScreen or window box\n")); /* special case for the lowest window in this virtual screen or window box */ /* pass the action to the Xserver */ XLowerWindow(dpy, WindowOfOwl(owl)); } else { XWindowChanges xwc; int xwcm; DPRINTF((stderr, "General case\n")); /* general case */ assert(PRI(vs_owl) <= PRI(other_owl)); assert(owl->twm_win->parent_vs == vs_owl->twm_win->parent_vs); /* pass the action to the Xserver */ xwcm = CWStackMode | CWSibling; xwc.sibling = WindowOfOwl(vs_owl); xwc.stack_mode = Above; XConfigureWindow(dpy, WindowOfOwl(owl), xwcm, &xwc); } /* update the list */ owl->below = other_owl; owl->above = other_owl->above; owl->below->above = owl; if(owl->above != NULL) { owl->above->below = owl; } } } /* should owl stay above other_owl if other_owl was raised ? */ static bool shouldStayAbove(OtpWinList *owl, OtpWinList *other_owl) { return ((owl->type == WinWin) && (other_owl->type == WinWin) && isSmallTransientOf(owl->twm_win, other_owl->twm_win)); } static void RaiseSmallTransientsOfAbove(OtpWinList *owl, OtpWinList *other_owl) { OtpWinList *trans_owl, *tmp_owl; /* the icons have no transients and we can't have windows below NULL */ if((owl->type != WinWin) || other_owl == NULL) { return; } /* beware: we modify the list as we scan it. This is the reason for tmp */ for(trans_owl = other_owl->below; trans_owl != NULL; trans_owl = tmp_owl) { tmp_owl = trans_owl->below; if(shouldStayAbove(trans_owl, owl)) { RemoveOwl(trans_owl); PRI_CP(owl, trans_owl); InsertOwlAbove(trans_owl, other_owl); } } } static OtpWinList *OwlRightBelow(int priority) { OtpWinList *owl1, *owl2; /* in case there isn't anything below */ if(priority <= PRI(Scr->bottomOwl)) { return NULL; } for(owl1 = Scr->bottomOwl, owl2 = owl1->above; (owl2 != NULL) && (PRI(owl2) < priority); owl1 = owl2, owl2 = owl2->above) { /* nada */; } assert(owl2 == owl1->above); assert(PRI(owl1) < priority); assert((owl2 == NULL) || (PRI(owl2) >= priority)); return owl1; } static void InsertOwl(OtpWinList *owl, int where) { OtpWinList *other_owl; int priority; DPRINTF((stderr, "InsertOwl %s\n", (where == Above) ? "Above" : (where == Below) ? "Below" : "???")); assert(owl->above == NULL); assert(owl->below == NULL); assert((where == Above) || (where == Below)); priority = PRI(owl) - (where == Above ? 0 : 1); if(Scr->bottomOwl == NULL) { /* for the first window: just insert it in the list */ Scr->bottomOwl = owl; } else { other_owl = OwlRightBelow(priority + 1); /* make sure other_owl is not one of the transients */ while((other_owl != NULL) && shouldStayAbove(other_owl, owl)) { PRI_CP(owl, other_owl); other_owl = other_owl->below; } /* raise the transient windows that should stay on top */ RaiseSmallTransientsOfAbove(owl, other_owl); /* now go ahead and put the window where it should go */ InsertOwlAbove(owl, other_owl); } } static void SetOwlPriority(OtpWinList *owl, int new_pri, int where) { DPRINTF((stderr, "SetOwlPriority(%d)\n", new_pri)); /* make sure the values are within bounds */ if(new_pri < 0) { new_pri = 0; } if(new_pri > OTP_MAX) { new_pri = OTP_MAX; } RemoveOwl(owl); owl->pri_base = new_pri; InsertOwl(owl, where); assert(owl->pri_base == new_pri); } /* * Shift transients of a window to a new [base] priority, preparatory to * moving that window itself there. */ static void TryToMoveTransientsOfTo(OtpWinList *owl, int priority, int where) { OtpWinList *other_owl; /* the icons have no transients */ if(owl->type != WinWin) { return; } /* * We start looking for transients of owl at the bottom of its OTP * layer. */ other_owl = OwlRightBelow(PRI(owl)); other_owl = (other_owl == NULL) ? Scr->bottomOwl : other_owl->above; assert(PRI(other_owl) >= PRI(owl)); /* !beware! we're changing the list as we scan it, hence the tmp_owl */ while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) { OtpWinList *tmp_owl = other_owl->above; if((other_owl->type == WinWin) && isTransientOf(other_owl->twm_win, owl->twm_win)) { /* Copy in our flags so it winds up in the right place */ other_owl->pri_aflags = owl->pri_aflags; SetOwlPriority(other_owl, priority, where); } other_owl = tmp_owl; } } static void TryToSwitch(OtpWinList *owl, int where) { int priority; if(!owl->switching) { return; } /* * Switching is purely an adjustment to the base priority, so we * don't need to figure stuff based on the effective. */ priority = OTP_MAX - owl->pri_base; if(((where == Above) && (priority > owl->pri_base)) || ((where == Below) && (priority < owl->pri_base))) { /* * TTMTOT() before changing pri_base since it uses the current * state to find the transients. */ TryToMoveTransientsOfTo(owl, priority, where); owl->pri_base = priority; } } static void RaiseOwl(OtpWinList *owl) { TryToSwitch(owl, Above); RemoveOwl(owl); InsertOwl(owl, Above); } static void LowerOwl(OtpWinList *owl) { TryToSwitch(owl, Below); RemoveOwl(owl); InsertOwl(owl, Below); } static bool isHiddenBy(OtpWinList *owl, OtpWinList *other_owl) { /* doesn't check that owl is on screen */ return (isOnScreen(other_owl) && isIntersectingWith(owl, other_owl)); } static void TinyRaiseOwl(OtpWinList *owl) { OtpWinList *other_owl = owl->above; while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) { if(isHiddenBy(owl, other_owl) && !shouldStayAbove(other_owl, owl)) { RemoveOwl(owl); RaiseSmallTransientsOfAbove(owl, other_owl); InsertOwlAbove(owl, other_owl); return; } else { other_owl = other_owl->above; } } } static void TinyLowerOwl(OtpWinList *owl) { OtpWinList *other_owl = owl->below; while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) { if(isHiddenBy(owl, other_owl)) { RemoveOwl(owl); InsertOwlAbove(owl, other_owl->below); return; } else { other_owl = other_owl->below; } } } static void RaiseLowerOwl(OtpWinList *owl) { OtpWinList *other_owl; int priority; /* * abs(effective pri) * * XXX Why? This seems like it's encoding the assumption * "f.raiselower should assume any negative [user-level] priorities * are a result of a window that should be positive being switched, * and we should switch it positive before raising if we need to", or * some such. */ priority = MAX(PRI(owl), OTP_MAX - PRI(owl)); for(other_owl = owl->above; (other_owl != NULL) && (PRI(other_owl) <= priority); other_owl = other_owl->above) { if(isHiddenBy(owl, other_owl) && !shouldStayAbove(other_owl, owl)) { RaiseOwl(owl); return; } } LowerOwl(owl); } void OtpRaise(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; assert(owl != NULL); RaiseOwl(owl); OtpCheckConsistency(); #ifdef EWMH EwmhSet_NET_CLIENT_LIST_STACKING(); #endif /* EWMH */ } void OtpLower(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; assert(owl != NULL); LowerOwl(owl); OtpCheckConsistency(); #ifdef EWMH EwmhSet_NET_CLIENT_LIST_STACKING(); #endif /* EWMH */ } void OtpRaiseLower(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; assert(owl != NULL); RaiseLowerOwl(owl); OtpCheckConsistency(); #ifdef EWMH EwmhSet_NET_CLIENT_LIST_STACKING(); #endif /* EWMH */ } void OtpTinyRaise(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; assert(owl != NULL); TinyRaiseOwl(owl); OtpCheckConsistency(); #ifdef EWMH EwmhSet_NET_CLIENT_LIST_STACKING(); #endif /* EWMH */ } void OtpTinyLower(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; assert(owl != NULL); TinyLowerOwl(owl); OtpCheckConsistency(); #ifdef EWMH EwmhSet_NET_CLIENT_LIST_STACKING(); #endif /* EWMH */ } /* * XCirculateSubwindows() is complicated by the fact that it restacks only * in case of overlapping windows. Therefore it seems easier to not * try to emulate that but to leave it to the X server. * * If XCirculateSubwindows() actually does something, it sends a * CirculateNotify event, but you only receive it if * SubstructureNotifyMask is selected on the root window. * However... if that is done from the beginning, for some reason all * windows disappear when ctwm starts or exits. * Maybe SubstructureNotifyMask interferes with SubstructureRedirectMask? * * To get around that, the SubstructureNotifyMask is selected only * temporarily here when wanted. */ void OtpCirculateSubwindows(VirtualScreen *vs, int direction) { Window w = vs->window; XWindowAttributes winattrs; Bool circulated; DPRINTF((stderr, "OtpCirculateSubwindows %d\n", direction)); XGetWindowAttributes(dpy, w, &winattrs); XSelectInput(dpy, w, winattrs.your_event_mask | SubstructureNotifyMask); XCirculateSubwindows(dpy, w, direction); XSelectInput(dpy, w, winattrs.your_event_mask); /* * Now we should get the CirculateNotify event. * It usually seems to arrive soon enough, but just to be sure, look * ahead in the message queue to see if it can be expedited. */ circulated = XCheckTypedWindowEvent(dpy, w, CirculateNotify, &Event); if(circulated) { HandleCirculateNotify(); } } /* * Update our list of Owls after the Circulate action, and also * enforce the priority by possibly restacking the window again. */ void OtpHandleCirculateNotify(VirtualScreen *vs, TwmWindow *twm_win, WinType wintype, int place) { switch(place) { case PlaceOnTop: OtpRaise(twm_win, wintype); break; case PlaceOnBottom: OtpLower(twm_win, wintype); break; default: DPRINTF((stderr, "OtpHandleCirculateNotify: place=%d\n", place)); assert(0 && "OtpHandleCirculateNotify: place equals PlaceOnTop nor PlaceOnBottom"); } } void OtpSetPriority(TwmWindow *twm_win, WinType wintype, int new_pri, int where) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; int priority = OTP_ZERO + new_pri; DPRINTF((stderr, "OtpSetPriority: new_pri=%d\n", new_pri)); assert(owl != NULL); #ifdef WINBOX if(twm_win->winbox != NULL || twm_win->iswinbox) { return; } #endif if(ABS(new_pri) > OTP_ZERO) { DPRINTF((stderr, "invalid OnTopPriority value: %d\n", new_pri)); } else { TryToMoveTransientsOfTo(owl, priority, where); SetOwlPriority(owl, priority, where); } OtpCheckConsistency(); } void OtpChangePriority(TwmWindow *twm_win, WinType wintype, int relpriority) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; int priority = owl->pri_base + relpriority; int where; #ifdef WINBOX if(twm_win->winbox != NULL || twm_win->iswinbox) { return; } #endif where = relpriority < 0 ? Below : Above; TryToMoveTransientsOfTo(owl, priority, where); SetOwlPriority(owl, priority, where); OtpCheckConsistency(); } void OtpSwitchPriority(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; int priority = OTP_MAX - owl->pri_base; int where; assert(owl != NULL); #ifdef WINBOX if(twm_win->winbox != NULL || twm_win->iswinbox) { return; } #endif where = priority < OTP_ZERO ? Below : Above; TryToMoveTransientsOfTo(owl, priority, where); SetOwlPriority(owl, priority, where); OtpCheckConsistency(); } void OtpToggleSwitching(TwmWindow *twm_win, WinType wintype) { OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; assert(owl != NULL); #ifdef WINBOX if(twm_win->winbox != NULL || twm_win->iswinbox) { return; } #endif owl->switching = !owl->switching; OtpCheckConsistency(); } /* * This is triggered as a result of a StackMode ConfigureRequest. We * choose to interpret this as restacking relative to the base * priorities, since all the alterations are EWMH-related, and those * should probably override. * * XXX Or should they? Maybe we should alter until our effective is * positioned as desired relative to their effective? This may also need * revisiting if we grow alterations that aren't a result of EWMH stuff. */ void OtpForcePlacement(TwmWindow *twm_win, int where, TwmWindow *other_win) { OtpWinList *owl = twm_win->otp; OtpWinList *other_owl = other_win->otp; assert(twm_win->otp != NULL); assert(other_win->otp != NULL); if(where == BottomIf) { where = Below; } if(where != Below) { where = Above; } /* remove the owl to change it */ RemoveOwl(owl); /* * Base our priority base off that other win. Don't use PRI_CP since * we shouldn't suddenly get its flags as well. */ owl->pri_base = other_owl->pri_base; /* put the owl back into the list */ if(where == Below) { other_owl = other_owl->below; } InsertOwlAbove(owl, other_owl); OtpCheckConsistency(); } static void ApplyPreferences(OtpPreferences *prefs, OtpWinList *owl) { int i; TwmWindow *twm_win = owl->twm_win; /* check PrioritySwitch */ if(LookInList(prefs->switchingL, twm_win->name, &twm_win->class)) { owl->switching = !prefs->switching; } /* check OnTopPriority */ for(i = 0; i <= OTP_MAX; i++) { if(LookInList(prefs->priorityL[i], twm_win->name, &twm_win->class)) { owl->pri_base = i; } } } /* * Reset stuff based on preferences; called during property changes if * AutoPriority set. */ static void RecomputeOwlPrefs(OtpPreferences *prefs, OtpWinList *owl) { int old_pri; old_pri = owl->pri_base; ApplyPreferences(prefs, owl); if(old_pri != owl->pri_base) { RemoveOwl(owl); InsertOwl(owl, Above); /* * Stash flags if we don't have any yet, since we just changed * the priority. */ if(!owl->stashed_aflags) { OwlStashAflags(owl); } #ifdef EWMH /* Let other things know we did something with stacking */ EwmhSet_NET_WM_STATE(owl->twm_win, EWMH_STATE_ABOVE); #endif } } void OtpRecomputePrefs(TwmWindow *twm_win) { assert(twm_win->otp != NULL); RecomputeOwlPrefs(Scr->OTP, twm_win->otp); if(twm_win->icon != NULL) { RecomputeOwlPrefs(Scr->IconOTP, twm_win->icon->otp); } OtpCheckConsistency(); } static void free_OtpWinList(OtpWinList *owl) { assert(owl->above == NULL); assert(owl->below == NULL); free(owl); } void OtpRemove(TwmWindow *twm_win, WinType wintype) { OtpWinList **owlp; owlp = (wintype == IconWin) ? &twm_win->icon->otp : &twm_win->otp; assert(*owlp != NULL); RemoveOwl(*owlp); free_OtpWinList(*owlp); *owlp = NULL; OtpCheckConsistency(); } static OtpWinList *new_OtpWinList(TwmWindow *twm_win, WinType wintype, bool switching, int priority) { OtpWinList *owl = malloc(sizeof(OtpWinList)); owl->above = NULL; owl->below = NULL; owl->twm_win = twm_win; owl->type = wintype; owl->switching = switching; owl->pri_base = priority; owl->pri_aflags = 0; /* * We never need to stash anything for icons, they don't persist * across restart anyway. So pretend we did stash already to * discourage other code from trying to stash. */ owl->stashed_aflags = (wintype == WinWin ? false : true); return owl; } static OtpWinList *AddNewOwl(TwmWindow *twm_win, WinType wintype, OtpWinList *parent) { OtpWinList *owl; OtpPreferences *prefs = (wintype == IconWin) ? Scr->IconOTP : Scr->OTP; /* make the new owl */ owl = new_OtpWinList(twm_win, wintype, prefs->switching, prefs->priority); /* inherit the default attributes from the parent window if appropriate */ if(parent != NULL) { PRI_CP(parent, owl); owl->switching = parent->switching; } /* now see if the preferences have something to say */ if(!(parent != NULL && twm_win->istransient)) { ApplyPreferences(prefs, owl); } #ifdef EWMH /* If nothing came in, EWMH might have something to say */ if(owl->pri_base == 0) { owl->pri_base = EwmhGetInitPriority(twm_win) + OTP_ZERO; } #endif /* * Initialize flags. Right now, the only stashed flags are related * to EWMH requests, so in a sense this whole thing could be dropped * under #ifdef. But I'll assume that might not always be the case, * so for now the !(EWMH) case is just a twisty noop. */ { bool gotflags = false; unsigned aflags = 0, fromstash = 0; aflags = OwlGetStashedAflags(owl, &gotflags); #ifdef EWMH fromstash = (OTP_AFLAG_ABOVE | OTP_AFLAG_BELOW); #endif if(gotflags) { /* * Got stashed OTP flags; use 'em. Explicitly mask in only * the flags we're caring about; the others aren't telling us * info we need to persist. */ aflags &= fromstash; } #ifdef EWMH /* FULLSCREEN we get from the normal EWMH prop no matter what */ if(twm_win->ewmhFlags & EWMH_STATE_FULLSCREEN) { aflags |= OTP_AFLAG_FULLSCREEN; } if(!gotflags) { /* Nothing from OTP about above/below; check EWMH */ aflags = 0; if(twm_win->ewmhFlags & EWMH_STATE_ABOVE) { aflags |= OTP_AFLAG_ABOVE; } if(twm_win->ewmhFlags & EWMH_STATE_BELOW) { aflags |= OTP_AFLAG_BELOW; } } #endif // EWMH /* Set whatever we figured */ owl->pri_aflags |= aflags; owl->stashed_aflags = gotflags; /* If we set a priority or flags, we should stash away flags */ if((PRI(owl) != OTP_ZERO || owl->pri_aflags != 0) && !owl->stashed_aflags) { OwlStashAflags(owl); } } /* finally put the window where it should go */ InsertOwl(owl, Above); return owl; } void OtpAdd(TwmWindow *twm_win, WinType wintype) { TwmWindow *other_win; OtpWinList *parent = NULL; OtpWinList **owlp; owlp = (wintype == IconWin) ? &twm_win->icon->otp : &twm_win->otp; assert(*owlp == NULL); if(false) { // dummy } #ifdef WINBOX else if(twm_win->winbox) { /* windows in boxes *must* inherit priority from the box */ parent = twm_win->winbox->twmwin->otp; parent->switching = false; } #endif /* in case it's a transient, find the parent */ else if(wintype == WinWin && (twm_win->istransient || !isGroupLeader(twm_win))) { other_win = Scr->FirstWindow; while(other_win != NULL && !isTransientOf(twm_win, other_win) && !isGroupLeaderOf(other_win, twm_win)) { other_win = other_win->next; } if(other_win != NULL) { parent = other_win->otp; } } /* make the new owl */ *owlp = AddNewOwl(twm_win, wintype, parent); assert(*owlp != NULL); OtpCheckConsistency(); } void OtpReassignIcon(TwmWindow *twm_win, Icon *old_icon) { if(old_icon != NULL) { /* Transfer OWL to new Icon */ Icon *new_icon = twm_win->icon; if(new_icon != NULL) { new_icon->otp = old_icon->otp; old_icon->otp = NULL; } } else { /* Create a new OWL for this Icon */ OtpAdd(twm_win, IconWin); } } void OtpFreeIcon(TwmWindow *twm_win) { /* Remove the icon's OWL, if any */ Icon *cur_icon = twm_win->icon; if(cur_icon != NULL) { OtpRemove(twm_win, IconWin); } } name_list **OtpScrSwitchingL(ScreenInfo *scr, WinType wintype) { OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; assert(prefs != NULL); return &(prefs->switchingL); } void OtpScrSetSwitching(ScreenInfo *scr, WinType wintype, bool switching) { #ifndef NDEBUG OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; assert(prefs != NULL); #endif scr->OTP->switching = switching; } void OtpScrSetZero(ScreenInfo *scr, WinType wintype, int priority) { OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; assert(prefs != NULL); if(ABS(priority) > OTP_ZERO) { fprintf(stderr, "invalid OnTopPriority value: %d\n", priority); return; } prefs->priority = priority + OTP_ZERO; } name_list **OtpScrPriorityL(ScreenInfo *scr, WinType wintype, int priority) { OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; assert(prefs != NULL); if(ABS(priority) > OTP_ZERO) { fprintf(stderr, "invalid OnTopPriority value: %d\n", priority); priority = 0; } return &(prefs->priorityL[priority + OTP_ZERO]); } static OtpPreferences *new_OtpPreferences(void) { OtpPreferences *pref = malloc(sizeof(OtpPreferences)); int i; /* initialize default values */ for(i = 0; i <= OTP_MAX; i++) { pref->priorityL[i] = NULL; } pref->priority = OTP_ZERO; pref->switchingL = NULL; pref->switching = false; return pref; } static void free_OtpPreferences(OtpPreferences *pref) { int i; FreeList(&pref->switchingL); for(i = 0; i <= OTP_MAX; i++) { FreeList(&pref->priorityL[i]); } free(pref); } void OtpScrInitData(ScreenInfo *scr) { if(scr->OTP != NULL) { free_OtpPreferences(scr->OTP); } if(scr->IconOTP != NULL) { free_OtpPreferences(scr->IconOTP); } scr->OTP = new_OtpPreferences(); scr->IconOTP = new_OtpPreferences(); } int ReparentWindow(Display *display, TwmWindow *twm_win, WinType wintype, Window parent, int x, int y) { int result; OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; OtpWinList *other = owl->below; assert(owl != NULL); DPRINTF((stderr, "ReparentWindow: w=%x type=%d\n", (unsigned int)WindowOfOwl(owl), wintype)); result = XReparentWindow(display, WindowOfOwl(owl), parent, x, y); /* The raise was already done by XReparentWindow, so this call just re-places the window at the right spot in the list and enforces priority settings. */ RemoveOwl(owl); InsertOwlAbove(owl, other); OtpCheckConsistency(); return result; } void ReparentWindowAndIcon(Display *display, TwmWindow *twm_win, Window parent, int win_x, int win_y, int icon_x, int icon_y) { OtpWinList *win_owl = twm_win->otp; assert(twm_win->icon != NULL); OtpWinList *icon_owl = twm_win->icon->otp; assert(win_owl != NULL); assert(icon_owl != NULL); OtpWinList *below_win = win_owl->below; OtpWinList *below_icon = icon_owl->below; DPRINTF((stderr, "ReparentWindowAndIcon %x\n", (unsigned int)twm_win->frame)); XReparentWindow(display, twm_win->frame, parent, win_x, win_y); XReparentWindow(display, twm_win->icon->w, parent, icon_x, icon_y); /* The raise was already done by XReparentWindow, so this call just re-places the window at the right spot in the list and enforces priority settings. */ RemoveOwl(win_owl); RemoveOwl(icon_owl); if(below_win != icon_owl) { /* * Only insert the window above something if it isn't the icon, * because that isn't back yet. */ InsertOwlAbove(win_owl, below_win); InsertOwlAbove(icon_owl, below_icon); } else { /* In such a case, do it in the opposite order. */ InsertOwlAbove(icon_owl, below_icon); InsertOwlAbove(win_owl, below_win); } OtpCheckConsistency(); return; } /* Iterators. */ TwmWindow *OtpBottomWin(void) { OtpWinList *owl = Scr->bottomOwl; while(owl && owl->type != WinWin) { owl = owl->above; } return owl ? owl->twm_win : NULL; } TwmWindow *OtpTopWin(void) { OtpWinList *owl = Scr->bottomOwl, *top = NULL; while(owl) { if(owl->type == WinWin) { top = owl; } owl = owl->above; } return top ? top->twm_win : NULL; } TwmWindow *OtpNextWinUp(TwmWindow *twm_win) { OtpWinList *owl = twm_win->otp->above; while(owl && owl->type != WinWin) { owl = owl->above; } return owl ? owl->twm_win : NULL; } TwmWindow *OtpNextWinDown(TwmWindow *twm_win) { OtpWinList *owl = twm_win->otp->below; while(owl && owl->type != WinWin) { owl = owl->below; } return owl ? owl->twm_win : NULL; } /* * Outputting info to understand the state of OTP stuff. */ /// Pretty-print a whole OWL stack. Works upward from the arg; /// generally, you'd call this with Scr->bottomOwl. static void OwlPrettyPrint(const OtpWinList *start) { fprintf(stderr, "%s():\n", __func__); for(const OtpWinList *owl = start ; owl != NULL ; owl = owl->above) { fprintf(stderr, " pri=%2d (%+d) %s 0x%lx:'%1.50s'\n", OtpEffectivePriority(owl->twm_win), OtpEffectiveDisplayPriority(owl->twm_win), (owl->type == WinWin ? "win" : "ico"), owl->twm_win->w, owl->twm_win->name); fprintf(stderr, " basepri=%d %s%s%s\n", owl->pri_base, #ifdef EWMH (owl->pri_aflags & OTP_AFLAG_ABOVE ? " _ABOVE" : ""), (owl->pri_aflags & OTP_AFLAG_BELOW ? " _BELOW" : ""), (owl->pri_aflags & OTP_AFLAG_FULLSCREEN ? " _FULLSCREEN" : "") #else "", "", "" #endif ); if(owl->twm_win->istransient) { const TwmWindow *parent = GetTwmWindow(owl->twm_win->transientfor); fprintf(stderr, " transient for 0x%lx:%1.50s\n", parent->w, parent->name); } } fprintf(stderr, " Done.\n"); } /* * Stuff for messing with pri_aflags */ /* Set the masked bits to exactly what's given */ void OtpSetAflagMask(TwmWindow *twm_win, unsigned mask, unsigned setto) { assert(twm_win != NULL); assert(twm_win->otp != NULL); OwlSetAflagMask(twm_win->otp, mask, setto); } static void OwlSetAflagMask(OtpWinList *owl, unsigned mask, unsigned setto) { assert(owl != NULL); owl->pri_aflags &= ~mask; owl->pri_aflags |= (setto & mask); OwlStashAflags(owl); } /* Set/clear individual ones */ void OtpSetAflag(TwmWindow *twm_win, unsigned flag) { assert(twm_win != NULL); assert(twm_win->otp != NULL); OwlSetAflag(twm_win->otp, flag); } static void OwlSetAflag(OtpWinList *owl, unsigned flag) { assert(owl != NULL); owl->pri_aflags |= flag; OwlStashAflags(owl); } void OtpClearAflag(TwmWindow *twm_win, unsigned flag) { assert(twm_win != NULL); assert(twm_win->otp != NULL); OwlClearAflag(twm_win->otp, flag); } static void OwlClearAflag(OtpWinList *owl, unsigned flag) { assert(owl != NULL); owl->pri_aflags &= ~flag; OwlStashAflags(owl); } /* * Stash up flags in a property. We use this to keep track of whether we * have above/below flags set in the OTP info, so we can know what to set * when we restart. Otherwise we can't tell whether stuff like EWMH * _NET_WM_STATE flags are saying 'above' because the above flag got set * at some point, or whether other OTP config happens to have already * raised it. */ void OtpStashAflagsFirstTime(TwmWindow *twm_win) { if(!twm_win->otp->stashed_aflags) { OwlStashAflags(twm_win->otp); } } static void OwlStashAflags(OtpWinList *owl) { unsigned long of_prop = owl->pri_aflags; /* Only "real" windows need stashed flags */ if(owl->type != WinWin) { return; } XChangeProperty(dpy, owl->twm_win->w, XA_CTWM_OTP_AFLAGS, XA_INTEGER, 32, PropModeReplace, (unsigned char *)&of_prop, 1); owl->stashed_aflags = true; } static unsigned OwlGetStashedAflags(OtpWinList *owl, bool *gotit) { /* Lotta dummy args */ int ret; Atom act_type; int d_fmt; unsigned long nitems, d_after; unsigned long aflags, *aflags_p; /* Only on real windows */ if(owl->type != WinWin) { *gotit = false; return 0; } ret = XGetWindowProperty(dpy, owl->twm_win->w, XA_CTWM_OTP_AFLAGS, 0, 1, False, XA_INTEGER, &act_type, &d_fmt, &nitems, &d_after, (unsigned char **)&aflags_p); if(ret == Success && act_type == XA_INTEGER && aflags_p != NULL) { aflags = *aflags_p; XFree(aflags_p); *gotit = true; } else { *gotit = false; aflags = 0; } return aflags; } /* * Figure where a window should be stacked based on the current world, * and move it there. This function pretty much assumes it's not already * there; callers should generally be figuring that out before calling * this. */ void OtpRestackWindow(TwmWindow *twm_win) { OtpWinList *owl = twm_win->otp; RemoveOwl(owl); InsertOwl(owl, Above); OtpCheckConsistency(); } /** * Focus/unfocus backend. This is used on windows whose stacking is * focus-dependent (e.g., EWMH fullscreen), to move them and their * transients around. For these windows, getting/losing focus is * practically the same as a f.setpriority, except it's on the calculated * rather than the base parts. And it's hard to re-use our existing * functions to do it because we have to move Scr->Focus before the main * window changes, but then it's too late to see where all the transients * were. * * There are a number of unpleasant assumptions in here relating to where * the transients are, and IWBNI we could be smarter and quicker about * dealing with them. But this gets us past the simple to cause * assertion failures, anyway... */ static void OtpFocusWindowBE(TwmWindow *twm_win, int oldprio) { OtpWinList *owl = twm_win->otp; // This one comes off the list, and goes back in its new place. RemoveOwl(owl); InsertOwl(owl, Above); // Now root around for any transients of it, and // nudge them into the new location. The whole Above/Below thing is // kinda a heavy-handed guess, but... // // This is nearly a reimplementation of TryToMoveTransientsOfTo(), // but the assumption that we can find the transients by starting // from where the old priority was in the list turns out to be deeply // broken. So just walk the whole thing. Which isn't ideal, but... // // We also need to do loop detection, since otherwise we'll get stuck // when a window has multiple transients to move around. Since we // read from the bottom up, if a window is moving up the stack, then // its transients move up, and we run into them again and again. // // XXX It should not be this freakin' hard to find a window's // transients. We should fix that more globally. // XXX Let's just get a friggin' vector implementation already... size_t tlsz = 32; // Should hardly ever be too small size_t tlused = 0; OtpWinList **tlst = calloc(tlsz, sizeof(OtpWinList *)); if(tlst == NULL) { fprintf(stderr, "%s(): realloc() failed\n", __func__); abort(); } // Loop through and find them all OtpWinList *trans = Scr->bottomOwl; while((trans != NULL)) { // Gotta pre-stash, since we're sometimes about to move trans. OtpWinList *next = trans->above; if((trans->type == WinWin) && isTransientOf(trans->twm_win, twm_win)) { // Got one, stash it tlst[tlused++] = trans; // Grow? if(tlused == tlsz) { tlsz *= 2; OtpWinList **tln = realloc(tlst, (tlsz * sizeof(OtpWinList *))); if(tln == NULL) { fprintf(stderr, "%s(): realloc() failed\n", __func__); abort(); } tlst = tln; } } // And onward trans = next; } // Now loop over them and shuffle them up for(int i = 0 ; i < tlused ; i++) { RemoveOwl(tlst[i]); InsertOwl(tlst[i], Above); } free(tlst); OtpCheckConsistency(); } /** * Unfocus a window. This needs to know internals of OTP because of * focus-dependent stacking of it and its transients. */ void OtpUnfocusWindow(TwmWindow *twm_win) { // Stash where it currently appears to be. We assume all its // transients currently have the same effective priority. See also // TryToMoveTransientsOfTo() which makes the same assumption. I'm // not sure that's entirely warranted... int oldprio = PRI(twm_win->otp); // Now tell ourselves it's unfocused assert(Scr->Focus == twm_win); Scr->Focus = NULL; // And do the work OtpFocusWindowBE(twm_win, oldprio); } /** * Focus a window. This needs to know internals of OTP because of * focus-dependent stacking of it and its transients. */ void OtpFocusWindow(TwmWindow *twm_win) { // X-ref OtoUnfocusWindow() comments. int oldprio = PRI(twm_win->otp); assert(Scr->Focus != twm_win); Scr->Focus = twm_win; OtpFocusWindowBE(twm_win, oldprio); } /* * Calculating effective priority. Take the base priority (what gets * set/altered by various OTP config and functions), and then tack on * whatever alterations more ephemeral things might apply. This * currently pretty much means EWMH bits. */ int OtpEffectiveDisplayPriority(TwmWindow *twm_win) { assert(twm_win != NULL); assert(twm_win->otp != NULL); return(OwlEffectivePriority(twm_win->otp) - OTP_ZERO); } int OtpEffectivePriority(TwmWindow *twm_win) { assert(twm_win != NULL); assert(twm_win->otp != NULL); return OwlEffectivePriority(twm_win->otp); } static int OwlEffectivePriority(OtpWinList *owl) { int pri; assert(owl != NULL); pri = owl->pri_base; #ifdef EWMH /* ABOVE/BELOW states shift a bit relative to the base */ if(owl->pri_aflags & OTP_AFLAG_ABOVE) { pri += EWMH_PRI_ABOVE; } if(owl->pri_aflags & OTP_AFLAG_BELOW) { pri -= EWMH_PRI_ABOVE; } /* * Special magic: EWMH says that _BELOW + _DOCK = (just _BELOW). * So if both are set, and its base is where we'd expect just a _DOCK * to be, try cancelling that out. */ { EwmhWindowType ewt = owl->twm_win->ewmhWindowType; if((owl->pri_aflags & OTP_AFLAG_BELOW) && (ewt == wt_Dock) && (owl->pri_base == EWMH_PRI_DOCK + OTP_ZERO)) { pri -= EWMH_PRI_DOCK; } } /* * If FULLSCREEN and focused, jam to (nearly; let the user still win * if they try) the top. We also need to handle transients; they * might not have focus, but still need to be on top of the window * they're coming up transient for, or else they'll be hidden * forever. */ if(owl->pri_aflags & OTP_AFLAG_FULLSCREEN) { if(Scr->Focus == owl->twm_win) { // It's focused, shift it up pri = EWMH_PRI_FULLSCREEN + OTP_ZERO; } else if(owl->twm_win->istransient) { // It's a transient of something else; if that something else // has the fullscreen/focus combo, we should pop this up top // too. Technically, we should perhaps test whether its // parent is also OTP_AFLAG_FULLSCREEN, but if the transient // has it, the parent probably does too. Worry about that // detail if it ever becomes a problem. TwmWindow *parent = GetTwmWindow(owl->twm_win->transientfor); if(Scr->Focus == parent) { // Shift this up so we stay on top pri = EWMH_PRI_FULLSCREEN + OTP_ZERO; } } } #endif /* Constrain */ pri = MAX(pri, 0); pri = MIN(pri, OTP_MAX); return pri; } /* * Does the priority of a window depend on its focus state? External * code needs to know, to know when it might need restacking. */ bool OtpIsFocusDependent(TwmWindow *twm_win) { assert(twm_win != NULL); assert(twm_win->otp != NULL); #ifdef EWMH /* * EWMH says _FULLSCREEN and focused windows get shoved to the top; * this implies that _FULLSCREEN and _not_ focused don't. So if the * focus is changing, that means we may need to restack. */ if(twm_win->otp->pri_aflags & OTP_AFLAG_FULLSCREEN) { return true; } #endif return false; }