1/* 2 * Copyright 1992, 2005 Stefan Monnier. 3 * 4 * Author: Stefan Monnier [ monnier@lia.di.epfl.ch ] 5 * Adapted for use with more than one virtual screen by 6 * Olaf "Rhialto" Seibert <rhialto@falu.nl>. 7 * 8 * $Id: otp.c,v 1.1.1.2 2023/07/05 07:36:07 nia Exp $ 9 * 10 * handles all the OnTopPriority-related issues. 11 * 12 */ 13 14#include "ctwm.h" 15 16#include <stdio.h> 17#include <stdlib.h> 18#include <assert.h> 19#include <X11/Xatom.h> 20 21#include "otp.h" 22#include "ctwm_atoms.h" 23#include "screen.h" 24#include "util.h" 25#include "icons.h" 26#include "list.h" 27#include "events.h" 28#include "event_handlers.h" 29#include "vscreen.h" 30#include "win_utils.h" 31 32#define DEBUG_OTP 0 33#if DEBUG_OTP 34#define DPRINTF(x) fprintf x 35#else 36#define DPRINTF(x) 37#endif 38 39#if defined(NDEBUG) 40# define CHECK_OTP 0 41#else 42# define CHECK_OTP 1 43#endif 44 45/* number of priorities known to ctwm: [0..ONTOP_MAX] */ 46#define OTP_ZERO 8 47#define OTP_MAX (OTP_ZERO * 2) 48 49/* Shorten code a little */ 50#define PRI(owl) OwlEffectivePriority(owl) 51#define PRI_CP(from, to) do { \ 52 to->pri_base = from->pri_base; \ 53 to->pri_aflags = from->pri_aflags; \ 54 } while(0) 55 56struct OtpWinList { 57 OtpWinList *above; 58 OtpWinList *below; 59 TwmWindow *twm_win; 60 WinType type; 61 bool switching; 62 int pri_base; // Base priority 63 unsigned pri_aflags; // Flags that might alter it; OTP_AFLAG_* 64 bool stashed_aflags; 65}; 66 67struct OtpPreferences { 68 name_list *priorityL[OTP_MAX + 1]; 69 int priority; 70 name_list *switchingL; 71 bool switching; 72}; 73 74typedef struct Box { 75 int x; 76 int y; 77 int width; 78 int height; 79} Box; 80 81 82static bool OtpCheckConsistencyVS(VirtualScreen *currentvs, Window vroot); 83static void OwlPrettyPrint(const OtpWinList *start); 84static void OwlSetAflagMask(OtpWinList *owl, unsigned mask, unsigned setto); 85static void OwlSetAflag(OtpWinList *owl, unsigned flag); 86static void OwlClearAflag(OtpWinList *owl, unsigned flag); 87static void OwlStashAflags(OtpWinList *owl); 88static unsigned OwlGetStashedAflags(OtpWinList *owl, bool *gotit); 89static int OwlEffectivePriority(OtpWinList *owl); 90 91static Box BoxOfOwl(OtpWinList *owl) 92{ 93 Box b; 94 95 switch(owl->type) { 96 case IconWin: { 97 Icon *icon = owl->twm_win->icon; 98 99 b.x = icon->w_x; 100 b.y = icon->w_y; 101 b.width = icon->w_width; 102 b.height = icon->w_height; 103 break; 104 } 105 case WinWin: { 106 TwmWindow *twm_win = owl->twm_win; 107 108 b.x = twm_win->frame_x; 109 b.y = twm_win->frame_y; 110 b.width = twm_win->frame_width; 111 b.height = twm_win->frame_height; 112 break; 113 } 114 default: 115 assert(false); 116 } 117 return b; 118} 119 120 121static bool BoxesIntersect(Box *b1, Box *b2) 122{ 123 bool interX = (b1->x + b1->width > b2->x) && (b2->x + b2->width > b1->x); 124 bool interY = (b1->y + b1->height > b2->y) && (b2->y + b2->height > b1->y); 125 126 return (interX && interY); 127} 128 129 130static bool isIntersectingWith(OtpWinList *owl1, OtpWinList *owl2) 131{ 132 Box b1 = BoxOfOwl(owl1); 133 Box b2 = BoxOfOwl(owl2); 134 135 return BoxesIntersect(&b1, &b2); 136} 137 138 139static bool isOnScreen(OtpWinList *owl) 140{ 141 TwmWindow *twm_win = owl->twm_win; 142 143 return (((owl->type == IconWin) ? twm_win->iconified : twm_win->mapped) 144 && OCCUPY(twm_win, Scr->currentvs->wsw->currentwspc)); 145} 146 147 148bool isTransientOf(TwmWindow *trans, TwmWindow *main) 149{ 150 return (trans->istransient && trans->transientfor == main->w); 151} 152 153bool isGroupLeader(TwmWindow *twm_win) 154{ 155 return ((twm_win->group == 0) 156 || (twm_win->group == twm_win->w)); 157} 158 159bool isGroupLeaderOf(TwmWindow *leader, TwmWindow *twm_win) 160{ 161 return (isGroupLeader(leader) 162 && !isGroupLeader(twm_win) 163 && (leader->group == twm_win->group)); 164} 165 166bool isSmallTransientOf(TwmWindow *trans, TwmWindow *main) 167{ 168 int trans_area, main_area; 169 170 if(isTransientOf(trans, main)) { 171 assert(trans->frame); 172 trans_area = trans->frame_width * trans->frame_height; 173 main_area = main->frame_width * main->frame_height; 174 175 return (trans_area < ((main_area * Scr->TransientOnTop) / 100)); 176 } 177 else { 178 return false; 179 } 180} 181 182static Window WindowOfOwl(OtpWinList *owl) 183{ 184 return (owl->type == IconWin) 185 ? owl->twm_win->icon->w : owl->twm_win->frame; 186} 187 188bool OtpCheckConsistency(void) 189{ 190#if DEBUG_OTP 191 VirtualScreen *tvs; 192 bool result = true; 193 194 for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) { 195 fprintf(stderr, "OtpCheckConsistencyVS: vs:(x,y)=(%d,%d)\n", 196 tvs->x, tvs->y); 197 result = result && OtpCheckConsistencyVS(tvs, tvs->window); 198 } 199 return result; 200#else 201 return OtpCheckConsistencyVS(Scr->currentvs, Scr->Root); 202#endif 203} 204 205static bool OtpCheckConsistencyVS(VirtualScreen *currentvs, Window vroot) 206{ 207#if CHECK_OTP 208 OtpWinList *owl; 209 TwmWindow *twm_win; 210 Window root, parent, *children; 211 unsigned int nchildren; 212 int priority = 0; 213 int stack = -1; 214 int nwins = 0; 215 216 XQueryTree(dpy, vroot, &root, &parent, &children, &nchildren); 217 218#if DEBUG_OTP 219 { 220 int i; 221 fprintf(stderr, "XQueryTree: %d children:\n", nchildren); 222 223 for(i = 0; i < nchildren; i++) { 224 fprintf(stderr, "[%d]=%x ", i, (unsigned int)children[i]); 225 } 226 fprintf(stderr, "\n"); 227 } 228#endif 229 230 for(owl = Scr->bottomOwl; owl != NULL; owl = owl->above) { 231 twm_win = owl->twm_win; 232 233 /* check the back arrows are correct */ 234 assert(((owl->type == IconWin) && (owl == twm_win->icon->otp)) 235 || ((owl->type == WinWin) && (owl == twm_win->otp))); 236 237 /* check the doubly linked list's consistency */ 238 if(owl->below == NULL) { 239 assert(owl == Scr->bottomOwl); 240 } 241 else { 242 assert(owl->below->above == owl); 243 } 244 245 /* Code already ensures this */ 246 assert(owl->pri_base <= OTP_MAX); 247 248 /* List should be bottom->top, so effective pri better ascend */ 249 { 250 const int nextpri = PRI(owl); 251 if(nextpri < priority) { 252 fprintf(stderr, "%s(): Priority went backward " 253 "(%d:'%s' -> %d:'%s')\n", 254 __func__, 255 priority, owl->below->twm_win->name, 256 nextpri, owl->twm_win->name); 257 OwlPrettyPrint(Scr->bottomOwl); 258 abort(); 259 } 260 priority = nextpri; 261 } 262 263#if DEBUG_OTP 264 265 fprintf(stderr, "checking owl: pri %d w=%x stack=%d", 266 priority, (unsigned int)WindowOfOwl(owl), stack); 267 if(twm_win) { 268 fprintf(stderr, " title=%s occupation=%x ", 269 twm_win->name, 270 (unsigned int)twm_win->occupation); 271 if(owl->twm_win->vs) { 272 fprintf(stderr, " vs:(x,y)=(%d,%d)", 273 twm_win->vs->x, 274 twm_win->vs->y); 275 } 276 else { 277 fprintf(stderr, " vs:NULL"); 278 } 279 if(owl->twm_win->parent_vs) { 280 fprintf(stderr, " parent_vs:(x,y)=(%d,%d)", 281 twm_win->parent_vs->x, 282 twm_win->parent_vs->y); 283 } 284 else { 285 fprintf(stderr, " parent_vs:NULL"); 286 } 287 } 288 fprintf(stderr, " %s\n", (owl->type == WinWin ? "Window" : "Icon")); 289#endif 290 291 /* count the number of twm windows */ 292 if(owl->type == WinWin) { 293 nwins++; 294 } 295 296#ifdef WINBOX 297 if(twm_win->winbox) { 298 /* 299 * We can't check windows in a WindowBox, since they are 300 * not direct children of the Root window. 301 */ 302 DPRINTF((stderr, "Can't check this window, it is in a WinBox\n")); 303 continue; 304 } 305#endif 306 307 /* 308 * Check only windows from the current vitual screen; the others 309 * won't show up in the tree from XQueryTree(). 310 */ 311 if(currentvs == twm_win->parent_vs) { 312 /* check the window's existence. */ 313 Window windowOfOwl = WindowOfOwl(owl); 314 315#if DEBUG_OTP 316 int i; 317 for(i = 0; i < nchildren && windowOfOwl != children[i];) { 318 i++; 319 } 320 fprintf(stderr, "search for owl in stack -> i=%d\n", i); 321 assert(i > stack && "Window not in good place in stack"); 322 assert(i < nchildren && "Window was not found in stack"); 323 if(0) { 324 char buf[128]; 325 snprintf(buf, 128, "xwininfo -all -id %d", (int)windowOfOwl); 326 system(buf); 327 } 328 329 /* we know that this always increases stack (assert i>stack) */ 330 stack = i; 331#else /* DEBUG_OTP */ 332 /* check against the Xserver's stack */ 333 do { 334 stack++; 335 DPRINTF((stderr, "stack++: children[%d] = %x\n", stack, 336 (unsigned int)children[stack])); 337 assert(stack < nchildren); 338 } 339 while(windowOfOwl != children[stack]); 340#endif /* DEBUG_OTP */ 341 } 342 } 343 344 XFree(children); 345 346 /* by decrementing nwins, check that all the wins are in our list */ 347 for(twm_win = Scr->FirstWindow; twm_win != NULL; twm_win = twm_win->next) { 348 nwins--; 349 } 350 /* if we just removed a win, it might still be somewhere, hence the -1 */ 351 assert((nwins <= 0) && (nwins >= -1)); 352#endif 353 return true; 354} 355 356 357static void RemoveOwl(OtpWinList *owl) 358{ 359 if(owl->above != NULL) { 360 owl->above->below = owl->below; 361 } 362 if(owl->below != NULL) { 363 owl->below->above = owl->above; 364 } 365 else { 366 Scr->bottomOwl = owl->above; 367 } 368 owl->below = NULL; 369 owl->above = NULL; 370} 371 372 373/** 374 * For the purpose of putting a window above another, 375 * they need to have the same parent, i.e. be in the same 376 * VirtualScreen. 377 */ 378static OtpWinList *GetOwlAtOrBelowInVS(OtpWinList *owl, VirtualScreen *vs) 379{ 380 while(owl != NULL && owl->twm_win->parent_vs != vs) { 381 owl = owl->below; 382 } 383 384 return owl; 385} 386 387#ifdef WINBOX 388/* 389 * Windows in a box don't really occur in the stacking order of the 390 * root window. 391 * In the OWL list, keep them just on top of their box, in their 392 * respective order of course. 393 * Therefore we may need to update the owl we're going to be above. 394 */ 395static OtpWinList *GetOwlAtOrBelowInWinbox(OtpWinList **owlp, WindowBox *wb) 396{ 397 OtpWinList *owl = *owlp; 398 399 while(owl != NULL && owl->twm_win->winbox != wb) { 400 owl = owl->below; 401 } 402 403 if(owl == NULL) { 404 /* we have gone below the box: put it just on top of it */ 405 *owlp = wb->twmwin->otp; 406 } 407 else { 408 *owlp = owl; 409 } 410 return owl; 411} 412#endif 413 414 415static void InsertOwlAbove(OtpWinList *owl, OtpWinList *other_owl) 416{ 417#if DEBUG_OTP 418 fprintf(stderr, "InsertOwlAbove owl->pri=%d w=0x%x parent_vs:(x,y)=(%d,%d)", 419 PRI(owl), 420 (unsigned int)WindowOfOwl(owl), 421 owl->twm_win->parent_vs->x, 422 owl->twm_win->parent_vs->y); 423 if(other_owl != NULL) { 424 fprintf(stderr, "\n other_owl->pri=%d w=0x%x parent_vs:(x,y)=(%d,%d)", 425 PRI(other_owl), 426 (unsigned int)WindowOfOwl(other_owl), 427 owl->twm_win->parent_vs->x, 428 owl->twm_win->parent_vs->y); 429 } 430 fprintf(stderr, "\n"); 431#endif 432 433 assert(owl->above == NULL); 434 assert(owl->below == NULL); 435 436 437 if(other_owl == NULL) { 438 DPRINTF((stderr, "Bottom-most window overall\n")); 439 /* special case for the lowest window overall */ 440 assert(PRI(owl) <= PRI(Scr->bottomOwl)); 441 442 /* pass the action to the Xserver */ 443 XLowerWindow(dpy, WindowOfOwl(owl)); 444 445 /* update the list */ 446 owl->above = Scr->bottomOwl; 447 owl->above->below = owl; 448 Scr->bottomOwl = owl; 449 } 450 else { 451#ifdef WINBOX 452 WindowBox *winbox = owl->twm_win->winbox; 453#endif 454 OtpWinList *vs_owl; 455 456 if(false) { 457 // dummy 458 } 459#ifdef WINBOX 460 else if(winbox != NULL) { 461 vs_owl = GetOwlAtOrBelowInWinbox(&other_owl, winbox); 462 } 463#endif 464 else { 465 466 vs_owl = GetOwlAtOrBelowInVS(other_owl, owl->twm_win->parent_vs); 467 } 468 469 assert(PRI(owl) >= PRI(other_owl)); 470 if(other_owl->above != NULL) { 471 assert(PRI(owl) <= PRI(other_owl->above)); 472 } 473 474 if(vs_owl == NULL) { 475 DPRINTF((stderr, "Bottom-most window in VirtualScreen or window box\n")); 476 /* special case for the lowest window in this virtual screen or window box */ 477 478 /* pass the action to the Xserver */ 479 XLowerWindow(dpy, WindowOfOwl(owl)); 480 } 481 else { 482 XWindowChanges xwc; 483 int xwcm; 484 485 DPRINTF((stderr, "General case\n")); 486 /* general case */ 487 assert(PRI(vs_owl) <= PRI(other_owl)); 488 assert(owl->twm_win->parent_vs == vs_owl->twm_win->parent_vs); 489 490 /* pass the action to the Xserver */ 491 xwcm = CWStackMode | CWSibling; 492 xwc.sibling = WindowOfOwl(vs_owl); 493 xwc.stack_mode = Above; 494 XConfigureWindow(dpy, WindowOfOwl(owl), xwcm, &xwc); 495 } 496 497 /* update the list */ 498 owl->below = other_owl; 499 owl->above = other_owl->above; 500 owl->below->above = owl; 501 if(owl->above != NULL) { 502 owl->above->below = owl; 503 } 504 } 505} 506 507 508/* should owl stay above other_owl if other_owl was raised ? */ 509static bool shouldStayAbove(OtpWinList *owl, OtpWinList *other_owl) 510{ 511 return ((owl->type == WinWin) 512 && (other_owl->type == WinWin) 513 && isSmallTransientOf(owl->twm_win, other_owl->twm_win)); 514} 515 516 517static void RaiseSmallTransientsOfAbove(OtpWinList *owl, OtpWinList *other_owl) 518{ 519 OtpWinList *trans_owl, *tmp_owl; 520 521 /* the icons have no transients and we can't have windows below NULL */ 522 if((owl->type != WinWin) || other_owl == NULL) { 523 return; 524 } 525 526 /* beware: we modify the list as we scan it. This is the reason for tmp */ 527 for(trans_owl = other_owl->below; trans_owl != NULL; trans_owl = tmp_owl) { 528 tmp_owl = trans_owl->below; 529 if(shouldStayAbove(trans_owl, owl)) { 530 RemoveOwl(trans_owl); 531 PRI_CP(owl, trans_owl); 532 InsertOwlAbove(trans_owl, other_owl); 533 } 534 } 535} 536 537 538static OtpWinList *OwlRightBelow(int priority) 539{ 540 OtpWinList *owl1, *owl2; 541 542 /* in case there isn't anything below */ 543 if(priority <= PRI(Scr->bottomOwl)) { 544 return NULL; 545 } 546 547 for(owl1 = Scr->bottomOwl, owl2 = owl1->above; 548 (owl2 != NULL) && (PRI(owl2) < priority); 549 owl1 = owl2, owl2 = owl2->above) { 550 /* nada */; 551 } 552 553 assert(owl2 == owl1->above); 554 assert(PRI(owl1) < priority); 555 assert((owl2 == NULL) || (PRI(owl2) >= priority)); 556 557 558 return owl1; 559} 560 561static void InsertOwl(OtpWinList *owl, int where) 562{ 563 OtpWinList *other_owl; 564 int priority; 565 566 DPRINTF((stderr, "InsertOwl %s\n", 567 (where == Above) ? "Above" : 568 (where == Below) ? "Below" : 569 "???")); 570 assert(owl->above == NULL); 571 assert(owl->below == NULL); 572 assert((where == Above) || (where == Below)); 573 574 priority = PRI(owl) - (where == Above ? 0 : 1); 575 576 if(Scr->bottomOwl == NULL) { 577 /* for the first window: just insert it in the list */ 578 Scr->bottomOwl = owl; 579 } 580 else { 581 other_owl = OwlRightBelow(priority + 1); 582 583 /* make sure other_owl is not one of the transients */ 584 while((other_owl != NULL) 585 && shouldStayAbove(other_owl, owl)) { 586 PRI_CP(owl, other_owl); 587 588 other_owl = other_owl->below; 589 } 590 591 /* raise the transient windows that should stay on top */ 592 RaiseSmallTransientsOfAbove(owl, other_owl); 593 594 /* now go ahead and put the window where it should go */ 595 InsertOwlAbove(owl, other_owl); 596 } 597} 598 599 600static void SetOwlPriority(OtpWinList *owl, int new_pri, int where) 601{ 602 DPRINTF((stderr, "SetOwlPriority(%d)\n", new_pri)); 603 604 /* make sure the values are within bounds */ 605 if(new_pri < 0) { 606 new_pri = 0; 607 } 608 if(new_pri > OTP_MAX) { 609 new_pri = OTP_MAX; 610 } 611 612 RemoveOwl(owl); 613 owl->pri_base = new_pri; 614 InsertOwl(owl, where); 615 616 assert(owl->pri_base == new_pri); 617} 618 619 620/* 621 * Shift transients of a window to a new [base] priority, preparatory to 622 * moving that window itself there. 623 */ 624static void TryToMoveTransientsOfTo(OtpWinList *owl, int priority, int where) 625{ 626 OtpWinList *other_owl; 627 628 /* the icons have no transients */ 629 if(owl->type != WinWin) { 630 return; 631 } 632 633 /* 634 * We start looking for transients of owl at the bottom of its OTP 635 * layer. 636 */ 637 other_owl = OwlRightBelow(PRI(owl)); 638 other_owl = (other_owl == NULL) ? Scr->bottomOwl : other_owl->above; 639 assert(PRI(other_owl) >= PRI(owl)); 640 641 /* !beware! we're changing the list as we scan it, hence the tmp_owl */ 642 while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) { 643 OtpWinList *tmp_owl = other_owl->above; 644 if((other_owl->type == WinWin) 645 && isTransientOf(other_owl->twm_win, owl->twm_win)) { 646 /* Copy in our flags so it winds up in the right place */ 647 other_owl->pri_aflags = owl->pri_aflags; 648 SetOwlPriority(other_owl, priority, where); 649 } 650 other_owl = tmp_owl; 651 } 652} 653 654static void TryToSwitch(OtpWinList *owl, int where) 655{ 656 int priority; 657 658 if(!owl->switching) { 659 return; 660 } 661 662 /* 663 * Switching is purely an adjustment to the base priority, so we 664 * don't need to figure stuff based on the effective. 665 */ 666 priority = OTP_MAX - owl->pri_base; 667 if(((where == Above) && (priority > owl->pri_base)) || 668 ((where == Below) && (priority < owl->pri_base))) { 669 /* 670 * TTMTOT() before changing pri_base since it uses the current 671 * state to find the transients. 672 */ 673 TryToMoveTransientsOfTo(owl, priority, where); 674 owl->pri_base = priority; 675 } 676} 677 678static void RaiseOwl(OtpWinList *owl) 679{ 680 TryToSwitch(owl, Above); 681 RemoveOwl(owl); 682 InsertOwl(owl, Above); 683} 684 685 686static void LowerOwl(OtpWinList *owl) 687{ 688 TryToSwitch(owl, Below); 689 RemoveOwl(owl); 690 InsertOwl(owl, Below); 691} 692 693static bool isHiddenBy(OtpWinList *owl, OtpWinList *other_owl) 694{ 695 /* doesn't check that owl is on screen */ 696 return (isOnScreen(other_owl) 697 && isIntersectingWith(owl, other_owl)); 698} 699 700static void TinyRaiseOwl(OtpWinList *owl) 701{ 702 OtpWinList *other_owl = owl->above; 703 704 while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) { 705 if(isHiddenBy(owl, other_owl) 706 && !shouldStayAbove(other_owl, owl)) { 707 RemoveOwl(owl); 708 RaiseSmallTransientsOfAbove(owl, other_owl); 709 InsertOwlAbove(owl, other_owl); 710 return; 711 } 712 else { 713 other_owl = other_owl->above; 714 } 715 } 716} 717 718static void TinyLowerOwl(OtpWinList *owl) 719{ 720 OtpWinList *other_owl = owl->below; 721 722 while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) { 723 if(isHiddenBy(owl, other_owl)) { 724 RemoveOwl(owl); 725 InsertOwlAbove(owl, other_owl->below); 726 return; 727 } 728 else { 729 other_owl = other_owl->below; 730 } 731 } 732} 733 734static void RaiseLowerOwl(OtpWinList *owl) 735{ 736 OtpWinList *other_owl; 737 int priority; 738 739 /* 740 * abs(effective pri) 741 * 742 * XXX Why? This seems like it's encoding the assumption 743 * "f.raiselower should assume any negative [user-level] priorities 744 * are a result of a window that should be positive being switched, 745 * and we should switch it positive before raising if we need to", or 746 * some such. 747 */ 748 priority = MAX(PRI(owl), OTP_MAX - PRI(owl)); 749 750 for(other_owl = owl->above; 751 (other_owl != NULL) && (PRI(other_owl) <= priority); 752 other_owl = other_owl->above) { 753 if(isHiddenBy(owl, other_owl) 754 && !shouldStayAbove(other_owl, owl)) { 755 RaiseOwl(owl); 756 return; 757 } 758 } 759 LowerOwl(owl); 760} 761 762 763void OtpRaise(TwmWindow *twm_win, WinType wintype) 764{ 765 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 766 assert(owl != NULL); 767 768 RaiseOwl(owl); 769 770 OtpCheckConsistency(); 771#ifdef EWMH 772 EwmhSet_NET_CLIENT_LIST_STACKING(); 773#endif /* EWMH */ 774} 775 776 777void OtpLower(TwmWindow *twm_win, WinType wintype) 778{ 779 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 780 assert(owl != NULL); 781 782 LowerOwl(owl); 783 784 OtpCheckConsistency(); 785#ifdef EWMH 786 EwmhSet_NET_CLIENT_LIST_STACKING(); 787#endif /* EWMH */ 788} 789 790 791void OtpRaiseLower(TwmWindow *twm_win, WinType wintype) 792{ 793 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 794 assert(owl != NULL); 795 796 RaiseLowerOwl(owl); 797 798 OtpCheckConsistency(); 799#ifdef EWMH 800 EwmhSet_NET_CLIENT_LIST_STACKING(); 801#endif /* EWMH */ 802} 803 804 805void OtpTinyRaise(TwmWindow *twm_win, WinType wintype) 806{ 807 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 808 assert(owl != NULL); 809 810 TinyRaiseOwl(owl); 811 812 OtpCheckConsistency(); 813#ifdef EWMH 814 EwmhSet_NET_CLIENT_LIST_STACKING(); 815#endif /* EWMH */ 816} 817 818 819void OtpTinyLower(TwmWindow *twm_win, WinType wintype) 820{ 821 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 822 assert(owl != NULL); 823 824 TinyLowerOwl(owl); 825 826 OtpCheckConsistency(); 827#ifdef EWMH 828 EwmhSet_NET_CLIENT_LIST_STACKING(); 829#endif /* EWMH */ 830} 831 832 833/* 834 * XCirculateSubwindows() is complicated by the fact that it restacks only 835 * in case of overlapping windows. Therefore it seems easier to not 836 * try to emulate that but to leave it to the X server. 837 * 838 * If XCirculateSubwindows() actually does something, it sends a 839 * CirculateNotify event, but you only receive it if 840 * SubstructureNotifyMask is selected on the root window. 841 * However... if that is done from the beginning, for some reason all 842 * windows disappear when ctwm starts or exits. 843 * Maybe SubstructureNotifyMask interferes with SubstructureRedirectMask? 844 * 845 * To get around that, the SubstructureNotifyMask is selected only 846 * temporarily here when wanted. 847 */ 848 849void OtpCirculateSubwindows(VirtualScreen *vs, int direction) 850{ 851 Window w = vs->window; 852 XWindowAttributes winattrs; 853 Bool circulated; 854 855 DPRINTF((stderr, "OtpCirculateSubwindows %d\n", direction)); 856 857 XGetWindowAttributes(dpy, w, &winattrs); 858 XSelectInput(dpy, w, winattrs.your_event_mask | SubstructureNotifyMask); 859 XCirculateSubwindows(dpy, w, direction); 860 XSelectInput(dpy, w, winattrs.your_event_mask); 861 /* 862 * Now we should get the CirculateNotify event. 863 * It usually seems to arrive soon enough, but just to be sure, look 864 * ahead in the message queue to see if it can be expedited. 865 */ 866 circulated = XCheckTypedWindowEvent(dpy, w, CirculateNotify, &Event); 867 if(circulated) { 868 HandleCirculateNotify(); 869 } 870} 871 872/* 873 * Update our list of Owls after the Circulate action, and also 874 * enforce the priority by possibly restacking the window again. 875 */ 876 877void OtpHandleCirculateNotify(VirtualScreen *vs, TwmWindow *twm_win, 878 WinType wintype, int place) 879{ 880 switch(place) { 881 case PlaceOnTop: 882 OtpRaise(twm_win, wintype); 883 break; 884 case PlaceOnBottom: 885 OtpLower(twm_win, wintype); 886 break; 887 default: 888 DPRINTF((stderr, "OtpHandleCirculateNotify: place=%d\n", place)); 889 assert(0 && 890 "OtpHandleCirculateNotify: place equals PlaceOnTop nor PlaceOnBottom"); 891 } 892} 893 894void OtpSetPriority(TwmWindow *twm_win, WinType wintype, int new_pri, int where) 895{ 896 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 897 int priority = OTP_ZERO + new_pri; 898 899 DPRINTF((stderr, "OtpSetPriority: new_pri=%d\n", new_pri)); 900 assert(owl != NULL); 901 902#ifdef WINBOX 903 if(twm_win->winbox != NULL || twm_win->iswinbox) { 904 return; 905 } 906#endif 907 908 if(ABS(new_pri) > OTP_ZERO) { 909 DPRINTF((stderr, "invalid OnTopPriority value: %d\n", new_pri)); 910 } 911 else { 912 TryToMoveTransientsOfTo(owl, priority, where); 913 SetOwlPriority(owl, priority, where); 914 } 915 916 OtpCheckConsistency(); 917} 918 919 920void OtpChangePriority(TwmWindow *twm_win, WinType wintype, int relpriority) 921{ 922 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 923 int priority = owl->pri_base + relpriority; 924 int where; 925 926#ifdef WINBOX 927 if(twm_win->winbox != NULL || twm_win->iswinbox) { 928 return; 929 } 930#endif 931 932 where = relpriority < 0 ? Below : Above; 933 934 TryToMoveTransientsOfTo(owl, priority, where); 935 SetOwlPriority(owl, priority, where); 936 937 OtpCheckConsistency(); 938} 939 940 941void OtpSwitchPriority(TwmWindow *twm_win, WinType wintype) 942{ 943 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 944 int priority = OTP_MAX - owl->pri_base; 945 int where; 946 947 assert(owl != NULL); 948 949#ifdef WINBOX 950 if(twm_win->winbox != NULL || twm_win->iswinbox) { 951 return; 952 } 953#endif 954 955 where = priority < OTP_ZERO ? Below : Above; 956 TryToMoveTransientsOfTo(owl, priority, where); 957 SetOwlPriority(owl, priority, where); 958 959 OtpCheckConsistency(); 960} 961 962 963void OtpToggleSwitching(TwmWindow *twm_win, WinType wintype) 964{ 965 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 966 assert(owl != NULL); 967 968#ifdef WINBOX 969 if(twm_win->winbox != NULL || twm_win->iswinbox) { 970 return; 971 } 972#endif 973 974 owl->switching = !owl->switching; 975 976 OtpCheckConsistency(); 977} 978 979 980/* 981 * This is triggered as a result of a StackMode ConfigureRequest. We 982 * choose to interpret this as restacking relative to the base 983 * priorities, since all the alterations are EWMH-related, and those 984 * should probably override. 985 * 986 * XXX Or should they? Maybe we should alter until our effective is 987 * positioned as desired relative to their effective? This may also need 988 * revisiting if we grow alterations that aren't a result of EWMH stuff. 989 */ 990void OtpForcePlacement(TwmWindow *twm_win, int where, TwmWindow *other_win) 991{ 992 OtpWinList *owl = twm_win->otp; 993 OtpWinList *other_owl = other_win->otp; 994 995 assert(twm_win->otp != NULL); 996 assert(other_win->otp != NULL); 997 998 if(where == BottomIf) { 999 where = Below; 1000 } 1001 if(where != Below) { 1002 where = Above; 1003 } 1004 1005 /* remove the owl to change it */ 1006 RemoveOwl(owl); 1007 1008 /* 1009 * Base our priority base off that other win. Don't use PRI_CP since 1010 * we shouldn't suddenly get its flags as well. 1011 */ 1012 owl->pri_base = other_owl->pri_base; 1013 1014 /* put the owl back into the list */ 1015 if(where == Below) { 1016 other_owl = other_owl->below; 1017 } 1018 InsertOwlAbove(owl, other_owl); 1019 1020 OtpCheckConsistency(); 1021} 1022 1023 1024static void ApplyPreferences(OtpPreferences *prefs, OtpWinList *owl) 1025{ 1026 int i; 1027 TwmWindow *twm_win = owl->twm_win; 1028 1029 /* check PrioritySwitch */ 1030 if(LookInList(prefs->switchingL, twm_win->name, &twm_win->class)) { 1031 owl->switching = !prefs->switching; 1032 } 1033 1034 /* check OnTopPriority */ 1035 for(i = 0; i <= OTP_MAX; i++) { 1036 if(LookInList(prefs->priorityL[i], 1037 twm_win->name, &twm_win->class)) { 1038 owl->pri_base = i; 1039 } 1040 } 1041} 1042 1043 1044/* 1045 * Reset stuff based on preferences; called during property changes if 1046 * AutoPriority set. 1047 */ 1048static void RecomputeOwlPrefs(OtpPreferences *prefs, OtpWinList *owl) 1049{ 1050 int old_pri; 1051 1052 old_pri = owl->pri_base; 1053 ApplyPreferences(prefs, owl); 1054 if(old_pri != owl->pri_base) { 1055 RemoveOwl(owl); 1056 InsertOwl(owl, Above); 1057 1058 /* 1059 * Stash flags if we don't have any yet, since we just changed 1060 * the priority. 1061 */ 1062 if(!owl->stashed_aflags) { 1063 OwlStashAflags(owl); 1064 } 1065 1066#ifdef EWMH 1067 /* Let other things know we did something with stacking */ 1068 EwmhSet_NET_WM_STATE(owl->twm_win, EWMH_STATE_ABOVE); 1069#endif 1070 } 1071} 1072 1073void OtpRecomputePrefs(TwmWindow *twm_win) 1074{ 1075 assert(twm_win->otp != NULL); 1076 1077 RecomputeOwlPrefs(Scr->OTP, twm_win->otp); 1078 if(twm_win->icon != NULL) { 1079 RecomputeOwlPrefs(Scr->IconOTP, twm_win->icon->otp); 1080 } 1081 1082 OtpCheckConsistency(); 1083} 1084 1085 1086static void free_OtpWinList(OtpWinList *owl) 1087{ 1088 assert(owl->above == NULL); 1089 assert(owl->below == NULL); 1090 free(owl); 1091} 1092 1093 1094void OtpRemove(TwmWindow *twm_win, WinType wintype) 1095{ 1096 OtpWinList **owlp; 1097 owlp = (wintype == IconWin) ? &twm_win->icon->otp : &twm_win->otp; 1098 1099 assert(*owlp != NULL); 1100 1101 RemoveOwl(*owlp); 1102 free_OtpWinList(*owlp); 1103 *owlp = NULL; 1104 1105 OtpCheckConsistency(); 1106} 1107 1108 1109static OtpWinList *new_OtpWinList(TwmWindow *twm_win, 1110 WinType wintype, 1111 bool switching, 1112 int priority) 1113{ 1114 OtpWinList *owl = malloc(sizeof(OtpWinList)); 1115 1116 owl->above = NULL; 1117 owl->below = NULL; 1118 owl->twm_win = twm_win; 1119 owl->type = wintype; 1120 owl->switching = switching; 1121 owl->pri_base = priority; 1122 owl->pri_aflags = 0; 1123 1124 /* 1125 * We never need to stash anything for icons, they don't persist 1126 * across restart anyway. So pretend we did stash already to 1127 * discourage other code from trying to stash. 1128 */ 1129 owl->stashed_aflags = (wintype == WinWin ? false : true); 1130 1131 return owl; 1132} 1133 1134static OtpWinList *AddNewOwl(TwmWindow *twm_win, WinType wintype, 1135 OtpWinList *parent) 1136{ 1137 OtpWinList *owl; 1138 OtpPreferences *prefs = (wintype == IconWin) ? Scr->IconOTP : Scr->OTP; 1139 1140 /* make the new owl */ 1141 owl = new_OtpWinList(twm_win, wintype, 1142 prefs->switching, prefs->priority); 1143 1144 /* inherit the default attributes from the parent window if appropriate */ 1145 if(parent != NULL) { 1146 PRI_CP(parent, owl); 1147 owl->switching = parent->switching; 1148 } 1149 1150 /* now see if the preferences have something to say */ 1151 if(!(parent != NULL && twm_win->istransient)) { 1152 ApplyPreferences(prefs, owl); 1153 } 1154 1155#ifdef EWMH 1156 /* If nothing came in, EWMH might have something to say */ 1157 if(owl->pri_base == 0) { 1158 owl->pri_base = EwmhGetInitPriority(twm_win) + OTP_ZERO; 1159 } 1160#endif 1161 1162 /* 1163 * Initialize flags. Right now, the only stashed flags are related 1164 * to EWMH requests, so in a sense this whole thing could be dropped 1165 * under #ifdef. But I'll assume that might not always be the case, 1166 * so for now the !(EWMH) case is just a twisty noop. 1167 */ 1168 { 1169 bool gotflags = false; 1170 unsigned aflags = 0, fromstash = 0; 1171 1172 aflags = OwlGetStashedAflags(owl, &gotflags); 1173 1174#ifdef EWMH 1175 fromstash = (OTP_AFLAG_ABOVE | OTP_AFLAG_BELOW); 1176#endif 1177 1178 if(gotflags) { 1179 /* 1180 * Got stashed OTP flags; use 'em. Explicitly mask in only 1181 * the flags we're caring about; the others aren't telling us 1182 * info we need to persist. 1183 */ 1184 aflags &= fromstash; 1185 } 1186 1187#ifdef EWMH 1188 /* FULLSCREEN we get from the normal EWMH prop no matter what */ 1189 if(twm_win->ewmhFlags & EWMH_STATE_FULLSCREEN) { 1190 aflags |= OTP_AFLAG_FULLSCREEN; 1191 } 1192 1193 if(!gotflags) { 1194 /* Nothing from OTP about above/below; check EWMH */ 1195 aflags = 0; 1196 if(twm_win->ewmhFlags & EWMH_STATE_ABOVE) { 1197 aflags |= OTP_AFLAG_ABOVE; 1198 } 1199 if(twm_win->ewmhFlags & EWMH_STATE_BELOW) { 1200 aflags |= OTP_AFLAG_BELOW; 1201 } 1202 } 1203#endif // EWMH 1204 1205 /* Set whatever we figured */ 1206 owl->pri_aflags |= aflags; 1207 owl->stashed_aflags = gotflags; 1208 1209 /* If we set a priority or flags, we should stash away flags */ 1210 if((PRI(owl) != OTP_ZERO || owl->pri_aflags != 0) 1211 && !owl->stashed_aflags) { 1212 OwlStashAflags(owl); 1213 } 1214 } 1215 1216 /* finally put the window where it should go */ 1217 InsertOwl(owl, Above); 1218 1219 return owl; 1220} 1221 1222void OtpAdd(TwmWindow *twm_win, WinType wintype) 1223{ 1224 TwmWindow *other_win; 1225 OtpWinList *parent = NULL; 1226 OtpWinList **owlp; 1227 owlp = (wintype == IconWin) ? &twm_win->icon->otp : &twm_win->otp; 1228 1229 assert(*owlp == NULL); 1230 1231 if(false) { 1232 // dummy 1233 } 1234#ifdef WINBOX 1235 else if(twm_win->winbox) { 1236 /* windows in boxes *must* inherit priority from the box */ 1237 parent = twm_win->winbox->twmwin->otp; 1238 parent->switching = false; 1239 } 1240#endif 1241 /* in case it's a transient, find the parent */ 1242 else if(wintype == WinWin && (twm_win->istransient 1243 || !isGroupLeader(twm_win))) { 1244 other_win = Scr->FirstWindow; 1245 while(other_win != NULL 1246 && !isTransientOf(twm_win, other_win) 1247 && !isGroupLeaderOf(other_win, twm_win)) { 1248 other_win = other_win->next; 1249 } 1250 if(other_win != NULL) { 1251 parent = other_win->otp; 1252 } 1253 } 1254 1255 /* make the new owl */ 1256 *owlp = AddNewOwl(twm_win, wintype, parent); 1257 1258 assert(*owlp != NULL); 1259 OtpCheckConsistency(); 1260} 1261 1262void OtpReassignIcon(TwmWindow *twm_win, Icon *old_icon) 1263{ 1264 if(old_icon != NULL) { 1265 /* Transfer OWL to new Icon */ 1266 Icon *new_icon = twm_win->icon; 1267 if(new_icon != NULL) { 1268 new_icon->otp = old_icon->otp; 1269 old_icon->otp = NULL; 1270 } 1271 } 1272 else { 1273 /* Create a new OWL for this Icon */ 1274 OtpAdd(twm_win, IconWin); 1275 } 1276} 1277 1278void OtpFreeIcon(TwmWindow *twm_win) 1279{ 1280 /* Remove the icon's OWL, if any */ 1281 Icon *cur_icon = twm_win->icon; 1282 if(cur_icon != NULL) { 1283 OtpRemove(twm_win, IconWin); 1284 } 1285} 1286 1287name_list **OtpScrSwitchingL(ScreenInfo *scr, WinType wintype) 1288{ 1289 OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; 1290 1291 assert(prefs != NULL); 1292 1293 return &(prefs->switchingL); 1294} 1295 1296 1297void OtpScrSetSwitching(ScreenInfo *scr, WinType wintype, bool switching) 1298{ 1299#ifndef NDEBUG 1300 OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; 1301 1302 assert(prefs != NULL); 1303#endif 1304 1305 scr->OTP->switching = switching; 1306} 1307 1308 1309void OtpScrSetZero(ScreenInfo *scr, WinType wintype, int priority) 1310{ 1311 OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; 1312 1313 assert(prefs != NULL); 1314 1315 if(ABS(priority) > OTP_ZERO) { 1316 fprintf(stderr, "invalid OnTopPriority value: %d\n", priority); 1317 return; 1318 } 1319 1320 prefs->priority = priority + OTP_ZERO; 1321} 1322 1323 1324name_list **OtpScrPriorityL(ScreenInfo *scr, WinType wintype, int priority) 1325{ 1326 OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP; 1327 1328 assert(prefs != NULL); 1329 1330 if(ABS(priority) > OTP_ZERO) { 1331 fprintf(stderr, "invalid OnTopPriority value: %d\n", priority); 1332 priority = 0; 1333 } 1334 return &(prefs->priorityL[priority + OTP_ZERO]); 1335} 1336 1337 1338static OtpPreferences *new_OtpPreferences(void) 1339{ 1340 OtpPreferences *pref = malloc(sizeof(OtpPreferences)); 1341 int i; 1342 1343 /* initialize default values */ 1344 for(i = 0; i <= OTP_MAX; i++) { 1345 pref->priorityL[i] = NULL; 1346 } 1347 pref->priority = OTP_ZERO; 1348 pref->switchingL = NULL; 1349 pref->switching = false; 1350 1351 return pref; 1352} 1353 1354static void free_OtpPreferences(OtpPreferences *pref) 1355{ 1356 int i; 1357 1358 FreeList(&pref->switchingL); 1359 for(i = 0; i <= OTP_MAX; i++) { 1360 FreeList(&pref->priorityL[i]); 1361 } 1362 1363 free(pref); 1364} 1365 1366void OtpScrInitData(ScreenInfo *scr) 1367{ 1368 if(scr->OTP != NULL) { 1369 free_OtpPreferences(scr->OTP); 1370 } 1371 if(scr->IconOTP != NULL) { 1372 free_OtpPreferences(scr->IconOTP); 1373 } 1374 scr->OTP = new_OtpPreferences(); 1375 scr->IconOTP = new_OtpPreferences(); 1376} 1377 1378int ReparentWindow(Display *display, TwmWindow *twm_win, WinType wintype, 1379 Window parent, int x, int y) 1380{ 1381 int result; 1382 OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp; 1383 OtpWinList *other = owl->below; 1384 assert(owl != NULL); 1385 1386 DPRINTF((stderr, "ReparentWindow: w=%x type=%d\n", 1387 (unsigned int)WindowOfOwl(owl), wintype)); 1388 result = XReparentWindow(display, WindowOfOwl(owl), parent, x, y); 1389 /* The raise was already done by XReparentWindow, so this call 1390 just re-places the window at the right spot in the list 1391 and enforces priority settings. */ 1392 RemoveOwl(owl); 1393 InsertOwlAbove(owl, other); 1394 OtpCheckConsistency(); 1395 return result; 1396} 1397 1398void 1399ReparentWindowAndIcon(Display *display, TwmWindow *twm_win, 1400 Window parent, int win_x, int win_y, 1401 int icon_x, int icon_y) 1402{ 1403 OtpWinList *win_owl = twm_win->otp; 1404 assert(twm_win->icon != NULL); 1405 OtpWinList *icon_owl = twm_win->icon->otp; 1406 assert(win_owl != NULL); 1407 assert(icon_owl != NULL); 1408 OtpWinList *below_win = win_owl->below; 1409 OtpWinList *below_icon = icon_owl->below; 1410 1411 DPRINTF((stderr, "ReparentWindowAndIcon %x\n", (unsigned int)twm_win->frame)); 1412 XReparentWindow(display, twm_win->frame, parent, win_x, win_y); 1413 XReparentWindow(display, twm_win->icon->w, parent, icon_x, icon_y); 1414 /* The raise was already done by XReparentWindow, so this call 1415 just re-places the window at the right spot in the list 1416 and enforces priority settings. */ 1417 RemoveOwl(win_owl); 1418 RemoveOwl(icon_owl); 1419 if(below_win != icon_owl) { 1420 /* 1421 * Only insert the window above something if it isn't the icon, 1422 * because that isn't back yet. 1423 */ 1424 InsertOwlAbove(win_owl, below_win); 1425 InsertOwlAbove(icon_owl, below_icon); 1426 } 1427 else { 1428 /* In such a case, do it in the opposite order. */ 1429 InsertOwlAbove(icon_owl, below_icon); 1430 InsertOwlAbove(win_owl, below_win); 1431 } 1432 OtpCheckConsistency(); 1433 return; 1434} 1435 1436/* Iterators. */ 1437TwmWindow *OtpBottomWin(void) 1438{ 1439 OtpWinList *owl = Scr->bottomOwl; 1440 while(owl && owl->type != WinWin) { 1441 owl = owl->above; 1442 } 1443 return owl ? owl->twm_win : NULL; 1444} 1445 1446TwmWindow *OtpTopWin(void) 1447{ 1448 OtpWinList *owl = Scr->bottomOwl, *top = NULL; 1449 while(owl) { 1450 if(owl->type == WinWin) { 1451 top = owl; 1452 } 1453 owl = owl->above; 1454 } 1455 return top ? top->twm_win : NULL; 1456} 1457 1458TwmWindow *OtpNextWinUp(TwmWindow *twm_win) 1459{ 1460 OtpWinList *owl = twm_win->otp->above; 1461 while(owl && owl->type != WinWin) { 1462 owl = owl->above; 1463 } 1464 return owl ? owl->twm_win : NULL; 1465} 1466 1467TwmWindow *OtpNextWinDown(TwmWindow *twm_win) 1468{ 1469 OtpWinList *owl = twm_win->otp->below; 1470 while(owl && owl->type != WinWin) { 1471 owl = owl->below; 1472 } 1473 return owl ? owl->twm_win : NULL; 1474} 1475 1476 1477 1478/* 1479 * Outputting info to understand the state of OTP stuff. 1480 */ 1481 1482/// Pretty-print a whole OWL stack. Works upward from the arg; 1483/// generally, you'd call this with Scr->bottomOwl. 1484static void 1485OwlPrettyPrint(const OtpWinList *start) 1486{ 1487 fprintf(stderr, "%s():\n", __func__); 1488 1489 for(const OtpWinList *owl = start ; owl != NULL ; owl = owl->above) { 1490 fprintf(stderr, " pri=%2d (%+d) %s 0x%lx:'%1.50s'\n", 1491 OtpEffectivePriority(owl->twm_win), 1492 OtpEffectiveDisplayPriority(owl->twm_win), 1493 (owl->type == WinWin ? "win" : "ico"), 1494 owl->twm_win->w, owl->twm_win->name); 1495 fprintf(stderr, " basepri=%d %s%s%s\n", 1496 owl->pri_base, 1497#ifdef EWMH 1498 (owl->pri_aflags & OTP_AFLAG_ABOVE ? " _ABOVE" : ""), 1499 (owl->pri_aflags & OTP_AFLAG_BELOW ? " _BELOW" : ""), 1500 (owl->pri_aflags & OTP_AFLAG_FULLSCREEN ? " _FULLSCREEN" : "") 1501#else 1502 "", "", "" 1503#endif 1504 ); 1505 if(owl->twm_win->istransient) { 1506 const TwmWindow *parent = GetTwmWindow(owl->twm_win->transientfor); 1507 fprintf(stderr, " transient for 0x%lx:%1.50s\n", 1508 parent->w, parent->name); 1509 } 1510 } 1511 1512 fprintf(stderr, " Done.\n"); 1513} 1514 1515 1516 1517/* 1518 * Stuff for messing with pri_aflags 1519 */ 1520/* Set the masked bits to exactly what's given */ 1521void 1522OtpSetAflagMask(TwmWindow *twm_win, unsigned mask, unsigned setto) 1523{ 1524 assert(twm_win != NULL); 1525 assert(twm_win->otp != NULL); 1526 OwlSetAflagMask(twm_win->otp, mask, setto); 1527} 1528 1529static void 1530OwlSetAflagMask(OtpWinList *owl, unsigned mask, unsigned setto) 1531{ 1532 assert(owl != NULL); 1533 1534 owl->pri_aflags &= ~mask; 1535 owl->pri_aflags |= (setto & mask); 1536 OwlStashAflags(owl); 1537} 1538 1539/* Set/clear individual ones */ 1540void 1541OtpSetAflag(TwmWindow *twm_win, unsigned flag) 1542{ 1543 assert(twm_win != NULL); 1544 assert(twm_win->otp != NULL); 1545 1546 OwlSetAflag(twm_win->otp, flag); 1547} 1548 1549static void 1550OwlSetAflag(OtpWinList *owl, unsigned flag) 1551{ 1552 assert(owl != NULL); 1553 1554 owl->pri_aflags |= flag; 1555 OwlStashAflags(owl); 1556} 1557 1558void 1559OtpClearAflag(TwmWindow *twm_win, unsigned flag) 1560{ 1561 assert(twm_win != NULL); 1562 assert(twm_win->otp != NULL); 1563 1564 OwlClearAflag(twm_win->otp, flag); 1565} 1566 1567static void 1568OwlClearAflag(OtpWinList *owl, unsigned flag) 1569{ 1570 assert(owl != NULL); 1571 1572 owl->pri_aflags &= ~flag; 1573 OwlStashAflags(owl); 1574} 1575 1576/* 1577 * Stash up flags in a property. We use this to keep track of whether we 1578 * have above/below flags set in the OTP info, so we can know what to set 1579 * when we restart. Otherwise we can't tell whether stuff like EWMH 1580 * _NET_WM_STATE flags are saying 'above' because the above flag got set 1581 * at some point, or whether other OTP config happens to have already 1582 * raised it. 1583 */ 1584void 1585OtpStashAflagsFirstTime(TwmWindow *twm_win) 1586{ 1587 if(!twm_win->otp->stashed_aflags) { 1588 OwlStashAflags(twm_win->otp); 1589 } 1590} 1591 1592static void 1593OwlStashAflags(OtpWinList *owl) 1594{ 1595 unsigned long of_prop = owl->pri_aflags; 1596 1597 /* Only "real" windows need stashed flags */ 1598 if(owl->type != WinWin) { 1599 return; 1600 } 1601 1602 XChangeProperty(dpy, owl->twm_win->w, XA_CTWM_OTP_AFLAGS, XA_INTEGER, 1603 32, PropModeReplace, (unsigned char *)&of_prop, 1); 1604 1605 owl->stashed_aflags = true; 1606} 1607 1608static unsigned 1609OwlGetStashedAflags(OtpWinList *owl, bool *gotit) 1610{ 1611 /* Lotta dummy args */ 1612 int ret; 1613 Atom act_type; 1614 int d_fmt; 1615 unsigned long nitems, d_after; 1616 unsigned long aflags, *aflags_p; 1617 1618 /* Only on real windows */ 1619 if(owl->type != WinWin) { 1620 *gotit = false; 1621 return 0; 1622 } 1623 1624 ret = XGetWindowProperty(dpy, owl->twm_win->w, XA_CTWM_OTP_AFLAGS, 0, 1, 1625 False, XA_INTEGER, &act_type, &d_fmt, &nitems, 1626 &d_after, (unsigned char **)&aflags_p); 1627 if(ret == Success && act_type == XA_INTEGER && aflags_p != NULL) { 1628 aflags = *aflags_p; 1629 XFree(aflags_p); 1630 *gotit = true; 1631 } 1632 else { 1633 *gotit = false; 1634 aflags = 0; 1635 } 1636 1637 return aflags; 1638} 1639 1640 1641/* 1642 * Figure where a window should be stacked based on the current world, 1643 * and move it there. This function pretty much assumes it's not already 1644 * there; callers should generally be figuring that out before calling 1645 * this. 1646 */ 1647void 1648OtpRestackWindow(TwmWindow *twm_win) 1649{ 1650 OtpWinList *owl = twm_win->otp; 1651 1652 RemoveOwl(owl); 1653 InsertOwl(owl, Above); 1654 OtpCheckConsistency(); 1655} 1656 1657 1658 1659/** 1660 * Focus/unfocus backend. This is used on windows whose stacking is 1661 * focus-dependent (e.g., EWMH fullscreen), to move them and their 1662 * transients around. For these windows, getting/losing focus is 1663 * practically the same as a f.setpriority, except it's on the calculated 1664 * rather than the base parts. And it's hard to re-use our existing 1665 * functions to do it because we have to move Scr->Focus before the main 1666 * window changes, but then it's too late to see where all the transients 1667 * were. 1668 * 1669 * There are a number of unpleasant assumptions in here relating to where 1670 * the transients are, and IWBNI we could be smarter and quicker about 1671 * dealing with them. But this gets us past the simple to cause 1672 * assertion failures, anyway... 1673 */ 1674static void 1675OtpFocusWindowBE(TwmWindow *twm_win, int oldprio) 1676{ 1677 OtpWinList *owl = twm_win->otp; 1678 1679 // This one comes off the list, and goes back in its new place. 1680 RemoveOwl(owl); 1681 InsertOwl(owl, Above); 1682 1683 // Now root around for any transients of it, and 1684 // nudge them into the new location. The whole Above/Below thing is 1685 // kinda a heavy-handed guess, but... 1686 // 1687 // This is nearly a reimplementation of TryToMoveTransientsOfTo(), 1688 // but the assumption that we can find the transients by starting 1689 // from where the old priority was in the list turns out to be deeply 1690 // broken. So just walk the whole thing. Which isn't ideal, but... 1691 // 1692 // We also need to do loop detection, since otherwise we'll get stuck 1693 // when a window has multiple transients to move around. Since we 1694 // read from the bottom up, if a window is moving up the stack, then 1695 // its transients move up, and we run into them again and again. 1696 // 1697 // XXX It should not be this freakin' hard to find a window's 1698 // transients. We should fix that more globally. 1699 1700 // XXX Let's just get a friggin' vector implementation already... 1701 size_t tlsz = 32; // Should hardly ever be too small 1702 size_t tlused = 0; 1703 OtpWinList **tlst = calloc(tlsz, sizeof(OtpWinList *)); 1704 if(tlst == NULL) { 1705 fprintf(stderr, "%s(): realloc() failed\n", __func__); 1706 abort(); 1707 } 1708 1709 // Loop through and find them all 1710 OtpWinList *trans = Scr->bottomOwl; 1711 while((trans != NULL)) { 1712 // Gotta pre-stash, since we're sometimes about to move trans. 1713 OtpWinList *next = trans->above; 1714 1715 if((trans->type == WinWin) 1716 && isTransientOf(trans->twm_win, twm_win)) { 1717 // Got one, stash it 1718 tlst[tlused++] = trans; 1719 1720 // Grow? 1721 if(tlused == tlsz) { 1722 tlsz *= 2; 1723 OtpWinList **tln = realloc(tlst, (tlsz * sizeof(OtpWinList *))); 1724 if(tln == NULL) { 1725 fprintf(stderr, "%s(): realloc() failed\n", __func__); 1726 abort(); 1727 } 1728 tlst = tln; 1729 } 1730 } 1731 1732 // And onward 1733 trans = next; 1734 } 1735 1736 1737 // Now loop over them and shuffle them up 1738 for(int i = 0 ; i < tlused ; i++) { 1739 RemoveOwl(tlst[i]); 1740 InsertOwl(tlst[i], Above); 1741 } 1742 1743 free(tlst); 1744 1745 OtpCheckConsistency(); 1746} 1747 1748/** 1749 * Unfocus a window. This needs to know internals of OTP because of 1750 * focus-dependent stacking of it and its transients. 1751 */ 1752void 1753OtpUnfocusWindow(TwmWindow *twm_win) 1754{ 1755 // Stash where it currently appears to be. We assume all its 1756 // transients currently have the same effective priority. See also 1757 // TryToMoveTransientsOfTo() which makes the same assumption. I'm 1758 // not sure that's entirely warranted... 1759 int oldprio = PRI(twm_win->otp); 1760 1761 // Now tell ourselves it's unfocused 1762 assert(Scr->Focus == twm_win); 1763 Scr->Focus = NULL; 1764 1765 // And do the work 1766 OtpFocusWindowBE(twm_win, oldprio); 1767} 1768 1769/** 1770 * Focus a window. This needs to know internals of OTP because of 1771 * focus-dependent stacking of it and its transients. 1772 */ 1773void 1774OtpFocusWindow(TwmWindow *twm_win) 1775{ 1776 // X-ref OtoUnfocusWindow() comments. 1777 int oldprio = PRI(twm_win->otp); 1778 1779 assert(Scr->Focus != twm_win); 1780 Scr->Focus = twm_win; 1781 1782 OtpFocusWindowBE(twm_win, oldprio); 1783} 1784 1785 1786 1787/* 1788 * Calculating effective priority. Take the base priority (what gets 1789 * set/altered by various OTP config and functions), and then tack on 1790 * whatever alterations more ephemeral things might apply. This 1791 * currently pretty much means EWMH bits. 1792 */ 1793int 1794OtpEffectiveDisplayPriority(TwmWindow *twm_win) 1795{ 1796 assert(twm_win != NULL); 1797 assert(twm_win->otp != NULL); 1798 1799 return(OwlEffectivePriority(twm_win->otp) - OTP_ZERO); 1800} 1801 1802int 1803OtpEffectivePriority(TwmWindow *twm_win) 1804{ 1805 assert(twm_win != NULL); 1806 assert(twm_win->otp != NULL); 1807 1808 return OwlEffectivePriority(twm_win->otp); 1809} 1810 1811static int 1812OwlEffectivePriority(OtpWinList *owl) 1813{ 1814 int pri; 1815 1816 assert(owl != NULL); 1817 1818 pri = owl->pri_base; 1819 1820#ifdef EWMH 1821 /* ABOVE/BELOW states shift a bit relative to the base */ 1822 if(owl->pri_aflags & OTP_AFLAG_ABOVE) { 1823 pri += EWMH_PRI_ABOVE; 1824 } 1825 if(owl->pri_aflags & OTP_AFLAG_BELOW) { 1826 pri -= EWMH_PRI_ABOVE; 1827 } 1828 1829 /* 1830 * Special magic: EWMH says that _BELOW + _DOCK = (just _BELOW). 1831 * So if both are set, and its base is where we'd expect just a _DOCK 1832 * to be, try cancelling that out. 1833 */ 1834 { 1835 EwmhWindowType ewt = owl->twm_win->ewmhWindowType; 1836 if((owl->pri_aflags & OTP_AFLAG_BELOW) && (ewt == wt_Dock) && 1837 (owl->pri_base == EWMH_PRI_DOCK + OTP_ZERO)) { 1838 pri -= EWMH_PRI_DOCK; 1839 } 1840 } 1841 1842 /* 1843 * If FULLSCREEN and focused, jam to (nearly; let the user still win 1844 * if they try) the top. We also need to handle transients; they 1845 * might not have focus, but still need to be on top of the window 1846 * they're coming up transient for, or else they'll be hidden 1847 * forever. 1848 */ 1849 if(owl->pri_aflags & OTP_AFLAG_FULLSCREEN) { 1850 if(Scr->Focus == owl->twm_win) { 1851 // It's focused, shift it up 1852 pri = EWMH_PRI_FULLSCREEN + OTP_ZERO; 1853 } 1854 else if(owl->twm_win->istransient) { 1855 // It's a transient of something else; if that something else 1856 // has the fullscreen/focus combo, we should pop this up top 1857 // too. Technically, we should perhaps test whether its 1858 // parent is also OTP_AFLAG_FULLSCREEN, but if the transient 1859 // has it, the parent probably does too. Worry about that 1860 // detail if it ever becomes a problem. 1861 TwmWindow *parent = GetTwmWindow(owl->twm_win->transientfor); 1862 if(Scr->Focus == parent) { 1863 // Shift this up so we stay on top 1864 pri = EWMH_PRI_FULLSCREEN + OTP_ZERO; 1865 } 1866 } 1867 } 1868#endif 1869 1870 /* Constrain */ 1871 pri = MAX(pri, 0); 1872 pri = MIN(pri, OTP_MAX); 1873 1874 return pri; 1875} 1876 1877 1878/* 1879 * Does the priority of a window depend on its focus state? External 1880 * code needs to know, to know when it might need restacking. 1881 */ 1882bool 1883OtpIsFocusDependent(TwmWindow *twm_win) 1884{ 1885 assert(twm_win != NULL); 1886 assert(twm_win->otp != NULL); 1887 1888#ifdef EWMH 1889 /* 1890 * EWMH says _FULLSCREEN and focused windows get shoved to the top; 1891 * this implies that _FULLSCREEN and _not_ focused don't. So if the 1892 * focus is changing, that means we may need to restack. 1893 */ 1894 if(twm_win->otp->pri_aflags & OTP_AFLAG_FULLSCREEN) { 1895 return true; 1896 } 1897#endif 1898 1899 return false; 1900} 1901