1/* 2 * Window-handling utility funcs 3 */ 4 5#include "ctwm.h" 6 7#include <stdio.h> 8#include <stdlib.h> 9 10#include <X11/Xatom.h> 11 12#include "add_window.h" // NoName 13#include "ctwm_atoms.h" 14#include "drawing.h" 15#include "events.h" 16#include "event_internal.h" // Temp? 17#ifdef EWMH 18# include "ewmh_atoms.h" 19#endif 20#include "icons.h" 21#include "list.h" 22#include "occupation.h" 23#include "otp.h" 24#include "r_area.h" 25#include "r_area_list.h" 26#include "r_layout.h" 27#include "screen.h" 28#include "util.h" 29#include "win_decorations.h" 30#include "win_ops.h" 31#include "win_utils.h" 32#include "workspace_utils.h" 33 34 35/* 36 * Fill in size hints for a window from WM_NORMAL_HINTS prop. 37 * 38 * Formerly in add_window.c 39 */ 40void 41GetWindowSizeHints(TwmWindow *tmp) 42{ 43 long supplied = 0; 44 XSizeHints *hints = &tmp->hints; 45 46 if(!XGetWMNormalHints(dpy, tmp->w, hints, &supplied)) { 47 hints->flags = 0; 48 } 49 50 if(hints->flags & PResizeInc) { 51 if(hints->width_inc == 0) { 52 hints->width_inc = 1; 53 } 54 if(hints->height_inc == 0) { 55 hints->height_inc = 1; 56 } 57 } 58 59 if(!(supplied & PWinGravity) && (hints->flags & USPosition)) { 60 static int gravs[] = { SouthEastGravity, SouthWestGravity, 61 NorthEastGravity, NorthWestGravity 62 }; 63 int right = tmp->attr.x + tmp->attr.width + 2 * tmp->old_bw; 64 int bottom = tmp->attr.y + tmp->attr.height + 2 * tmp->old_bw; 65 hints->win_gravity = 66 gravs[((Scr->rooth - bottom < 67 tmp->title_height + 2 * tmp->frame_bw3D) ? 0 : 2) | 68 ((Scr->rootw - right < 69 tmp->title_height + 2 * tmp->frame_bw3D) ? 0 : 1)]; 70 hints->flags |= PWinGravity; 71 } 72 73 /* Check for min size < max size */ 74 if((hints->flags & (PMinSize | PMaxSize)) == (PMinSize | PMaxSize)) { 75 if(hints->max_width < hints->min_width) { 76 if(hints->max_width > 0) { 77 hints->min_width = hints->max_width; 78 } 79 else if(hints->min_width > 0) { 80 hints->max_width = hints->min_width; 81 } 82 else { 83 hints->max_width = hints->min_width = 1; 84 } 85 } 86 87 if(hints->max_height < hints->min_height) { 88 if(hints->max_height > 0) { 89 hints->min_height = hints->max_height; 90 } 91 else if(hints->min_height > 0) { 92 hints->max_height = hints->min_height; 93 } 94 else { 95 hints->max_height = hints->min_height = 1; 96 } 97 } 98 } 99} 100 101 102/* 103 * Fill in info from WM_PROTOCOLS property 104 * 105 * Formerly in add_window.c 106 */ 107void 108FetchWmProtocols(TwmWindow *tmp) 109{ 110 unsigned long flags = 0L; 111 Atom *protocols = NULL; 112 int n; 113 114 if(XGetWMProtocols(dpy, tmp->w, &protocols, &n)) { 115 int i; 116 Atom *ap; 117 118 for(i = 0, ap = protocols; i < n; i++, ap++) { 119 if(*ap == XA_WM_TAKE_FOCUS) { 120 flags |= DoesWmTakeFocus; 121 } 122 if(*ap == XA_WM_SAVE_YOURSELF) { 123 flags |= DoesWmSaveYourself; 124 } 125 if(*ap == XA_WM_DELETE_WINDOW) { 126 flags |= DoesWmDeleteWindow; 127 } 128 } 129 if(protocols) { 130 XFree(protocols); 131 } 132 } 133 tmp->protocols = flags; 134} 135 136 137/* 138 * Figure signs for calculating location offsets for a window dependent 139 * on its gravity. 140 * 141 * Depending on how its gravity is set, offsets to window coordinates for 142 * e.g. border widths may need to move either down (right) or up (left). 143 * Or possibly not at all. So we write multipliers into passed vars for 144 * callers. 145 * 146 * Formerly in add_window.c 147 */ 148void 149GetGravityOffsets(TwmWindow *tmp, int *xp, int *yp) 150{ 151 static struct _gravity_offset { 152 int x, y; 153 } gravity_offsets[] = { 154 [ForgetGravity] = { 0, 0 }, 155 [NorthWestGravity] = { -1, -1 }, 156 [NorthGravity] = { 0, -1 }, 157 [NorthEastGravity] = { 1, -1 }, 158 [WestGravity] = { -1, 0 }, 159 [CenterGravity] = { 0, 0 }, 160 [EastGravity] = { 1, 0 }, 161 [SouthWestGravity] = { -1, 1 }, 162 [SouthGravity] = { 0, 1 }, 163 [SouthEastGravity] = { 1, 1 }, 164 [StaticGravity] = { 0, 0 }, 165 }; 166 int g = ((tmp->hints.flags & PWinGravity) 167 ? tmp->hints.win_gravity : NorthWestGravity); 168 169 if(g < ForgetGravity || g > StaticGravity) { 170 *xp = *yp = 0; 171 } 172 else { 173 *xp = gravity_offsets[g].x; 174 *yp = gravity_offsets[g].y; 175 } 176} 177 178 179/* 180 * Finds the TwmWindow structure associated with a Window (if any), or 181 * NULL. 182 * 183 * This is a relatively cheap function since it does not involve 184 * communication with the server. Probably faster than walking the list 185 * of TwmWindows, since the lookup is by a hash table. 186 * 187 * Formerly in add_window.c 188 */ 189TwmWindow * 190GetTwmWindow(Window w) 191{ 192 TwmWindow *twmwin; 193 int stat; 194 195 stat = XFindContext(dpy, w, TwmContext, (XPointer *)&twmwin); 196 if(stat == XCNOENT) { 197 twmwin = NULL; 198 } 199 200 return twmwin; 201} 202 203 204/*********************************************************************** 205 * 206 * Procedure: 207 * GetWMPropertyString - Get Window Manager text property and 208 * convert it to a string. 209 * 210 * Returned Value: 211 * (char *) - pointer to the malloc'd string or NULL 212 * 213 * Inputs: 214 * w - the id of the window whose property is to be retrieved 215 * prop - property atom (typically WM_NAME or WM_ICON_NAME) 216 * 217 *********************************************************************** 218 * 219 * Formerly in util.c 220 */ 221char * 222GetWMPropertyString(Window w, Atom prop) 223{ 224 XTextProperty text_prop; 225 char *stringptr; 226 227 XGetTextProperty(dpy, w, &text_prop, prop); 228 if(text_prop.value == NULL) { 229 return NULL; 230 } 231 232 if(text_prop.encoding == XA_STRING 233 || text_prop.encoding == XA_UTF8_STRING 234 || text_prop.encoding == XA_COMPOUND_TEXT) { 235 /* property is encoded as compound text - convert to locale string */ 236 char **text_list; 237 int text_list_count; 238 int status; 239 240 /* Check historical strictness */ 241 if(Scr->StrictWinNameEncoding) { 242 bool fail = false; 243 244 if((prop == XA_WM_NAME || prop == XA_WM_ICON_NAME) 245 && text_prop.encoding != XA_STRING 246 && text_prop.encoding != XA_COMPOUND_TEXT) { 247 fail = true; 248 } 249 250#ifdef EWMH 251 if((prop == XA__NET_WM_NAME || prop == XA__NET_WM_ICON_NAME) 252 && text_prop.encoding != XA_UTF8_STRING) { 253 fail = true; 254 } 255#endif // EWMH 256 257 if(fail) { 258 fprintf(stderr, "%s: Invalid encoding for property %s " 259 "of window 0x%lx\n", ProgramName, 260 XGetAtomName(dpy, prop), w); 261 XFree(text_prop.value); 262 return NULL; 263 } 264 } 265 266 267 status = XmbTextPropertyToTextList(dpy, &text_prop, &text_list, 268 &text_list_count); 269 if(text_list_count == 0 270 || text_list == NULL 271 || text_list[0] == NULL) { 272 // Got nothing 273 XFree(text_prop.value); 274 return NULL; 275 } 276 else if(status < 0 || text_list_count < 0) { 277 // Got an error statuf 278 switch(status) { 279 case XConverterNotFound: 280 fprintf(stderr, 281 "%s: Converter not found; unable to convert property %s of window ID %lx.\n", 282 ProgramName, XGetAtomName(dpy, prop), w); 283 break; 284 case XNoMemory: 285 fprintf(stderr, 286 "%s: Insufficient memory; unable to convert property %s of window ID %lx.\n", 287 ProgramName, XGetAtomName(dpy, prop), w); 288 break; 289 case XLocaleNotSupported: 290 fprintf(stderr, 291 "%s: Locale not supported; unable to convert property %s of window ID %lx.\n", 292 ProgramName, XGetAtomName(dpy, prop), w); 293 break; 294 } 295 stringptr = NULL; 296 /* 297 don't call XFreeStringList - text_list appears to have 298 invalid address if status is bad 299 XFreeStringList(text_list); 300 */ 301 } 302 else { 303 // Actually got the data! 304 stringptr = strdup(text_list[0]); 305 XFreeStringList(text_list); 306 } 307 } 308 else { 309 /* property is encoded in a format we don't understand */ 310 fprintf(stderr, 311 "%s: Encoding not STRING or COMPOUND_TEXT; unable to decode property %s of window ID %lx.\n", 312 ProgramName, XGetAtomName(dpy, prop), w); 313 stringptr = NULL; 314 } 315 XFree(text_prop.value); 316 317 return stringptr; 318} 319 320 321/* 322 * Cleanup something stored that we got from the above originally. 323 * 324 * Formerly in util.c 325 */ 326void 327FreeWMPropertyString(char *prop) 328{ 329 if(prop && prop != NoName) { 330 free(prop); 331 } 332} 333 334 335/* 336 * Window mapped on some virtual screen? 337 * 338 * Formerly in util.c 339 */ 340bool 341visible(const TwmWindow *tmp_win) 342{ 343 return (tmp_win->vs != NULL); 344} 345 346 347/* 348 * Various code paths do a dance of "mask off notifications of event type 349 * ; do something that triggers that event (but we're doing it, so we 350 * don't need the notification) ; restore previous mask". So have some 351 * util funcs to make it more visually obvious. 352 * 353 * e.g.: 354 * long prev_mask = mask_out_event(w, PropertyChangeMask); 355 * do_something_that_changes_properties(); 356 * restore_mask(prev_mask); 357 * 358 * We're cheating a little with the -1 return on mask_out_event(), as 359 * that's theoretically valid for the data type. It's not as far as I 360 * can tell for X or us though; having all the bits set (well, I guess 361 * I'm assuming 2s-complement too) is pretty absurd, and there are only 362 * 25 defined bits in Xlib, so even on 32-bit systems, it shouldn't fill 363 * up long. 364 */ 365long 366mask_out_event(Window w, long ignore_event) 367{ 368 XWindowAttributes wattr; 369 370 /* Get current mask */ 371 if(XGetWindowAttributes(dpy, w, &wattr) == 0) { 372 return -1; 373 } 374 375 /* 376 * If we're ignoring nothing, nothing to do. This is probably not 377 * strictly speaking a useful thing to ask for in general, but it's 378 * the right thing for us to do if we're asked to do nothing. 379 */ 380 if(ignore_event == 0) { 381 return wattr.your_event_mask; 382 } 383 384 /* Delegate */ 385 return mask_out_event_mask(w, ignore_event, wattr.your_event_mask); 386} 387 388long 389mask_out_event_mask(Window w, long ignore_event, long curmask) 390{ 391 /* Set to the current, minus what we're wanting to ignore */ 392 XSelectInput(dpy, w, (curmask & ~ignore_event)); 393 394 /* Return what it was */ 395 return curmask; 396} 397 398int 399restore_mask(Window w, long restore) 400{ 401 return XSelectInput(dpy, w, restore); 402} 403 404 405/* 406 * Setting and getting WM_STATE property. 407 * 408 * x-ref ICCCM section 4.1.3.1 409 * https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 410 * 411 * XXX These should probably be named more alike, as they're 412 * complementary ops. 413 */ 414void 415SetMapStateProp(TwmWindow *tmp_win, int state) 416{ 417 unsigned long data[2]; /* "suggested" by ICCCM version 1 */ 418 419 data[0] = (unsigned long) state; 420 data[1] = (unsigned long)(tmp_win->iconify_by_unmapping ? None : 421 (tmp_win->icon ? tmp_win->icon->w : None)); 422 423 XChangeProperty(dpy, tmp_win->w, XA_WM_STATE, XA_WM_STATE, 32, 424 PropModeReplace, (unsigned char *) data, 2); 425} 426 427 428bool 429GetWMState(Window w, int *statep, Window *iwp) 430{ 431 Atom actual_type; 432 int actual_format; 433 unsigned long nitems, bytesafter; 434 unsigned long *datap = NULL; 435 bool retval = false; 436 437 if(XGetWindowProperty(dpy, w, XA_WM_STATE, 0L, 2L, False, XA_WM_STATE, 438 &actual_type, &actual_format, &nitems, &bytesafter, 439 (unsigned char **) &datap) != Success || !datap) { 440 return false; 441 } 442 443 if(nitems <= 2) { /* "suggested" by ICCCM version 1 */ 444 *statep = (int) datap[0]; 445 *iwp = (Window) datap[1]; 446 retval = true; 447 } 448 449 XFree(datap); 450 return retval; 451} 452 453 454/* 455 * Display a window's position in the dimensions window. This is used 456 * during various window positioning (during new window popups, moves, 457 * etc). 458 * 459 * This reuses the same window for the position as is used during 460 * resizing for the dimesions of the window in DisplaySize(). The 461 * innards of the funcs can probably be collapsed together a little, and 462 * the higher-level knowledge of Scr->SizeWindow (e.g., for unmapping 463 * after ths op is done) should probably be encapsulated a bit better. 464 */ 465void 466DisplayPosition(const TwmWindow *_unused_tmp_win, int x, int y) 467{ 468 char str [100]; 469 char signx = '+'; 470 char signy = '+'; 471 472 if(x < 0) { 473 x = -x; 474 signx = '-'; 475 } 476 if(y < 0) { 477 y = -y; 478 signy = '-'; 479 } 480 sprintf(str, " %c%-4d %c%-4d ", signx, x, signy, y); 481 XRaiseWindow(dpy, Scr->SizeWindow); 482 483 Draw3DBorder(Scr->SizeWindow, 0, 0, 484 Scr->SizeStringOffset + Scr->SizeStringWidth + SIZE_HINDENT, 485 Scr->SizeFont.height + SIZE_VINDENT * 2, 486 2, Scr->DefaultC, off, false, false); 487 488 FB(Scr->DefaultC.fore, Scr->DefaultC.back); 489 XmbDrawImageString(dpy, Scr->SizeWindow, Scr->SizeFont.font_set, 490 Scr->NormalGC, Scr->SizeStringOffset, 491 Scr->SizeFont.ascent + SIZE_VINDENT, str, 13); 492} 493 494void 495MoveResizeSizeWindow(int x, int y, unsigned int width, unsigned int height) 496{ 497 XResizeWindow(dpy, Scr->SizeWindow, width, height); 498 499 if(Scr->CenterFeedbackWindow) { 500 RArea monitor = RLayoutGetAreaAtXY(Scr->BorderedLayout, x, y); 501 502 XMoveWindow(dpy, Scr->SizeWindow, 503 monitor.x + monitor.width / 2 - width / 2, 504 monitor.y + monitor.height / 2 - height / 2); 505 } 506} 507 508 509/* 510 * Various funcs for adjusting coordinates for windows based on 511 * resistances etc. 512 * 513 * XXX In desperate need of better commenting. 514 */ 515static void 516_tryToPack(RArea *final, const RArea *cur_win) 517{ 518 if(final->x >= cur_win->x + cur_win->width) { 519 return; 520 } 521 if(final->y >= cur_win->y + cur_win->height) { 522 return; 523 } 524 if(final->x + final->width <= cur_win->x) { 525 return; 526 } 527 if(final->y + final->height <= cur_win->y) { 528 return; 529 } 530 531 if(final->x + Scr->MovePackResistance > cur_win->x + 532 cur_win->width) { /* left */ 533 final->x = MAX(final->x, cur_win->x + cur_win->width); 534 return; 535 } 536 if(final->x + final->width < cur_win->x + 537 Scr->MovePackResistance) { /* right */ 538 final->x = MIN(final->x, cur_win->x - final->width); 539 return; 540 } 541 if(final->y + Scr->MovePackResistance > cur_win->y + 542 cur_win->height) { /* top */ 543 final->y = MAX(final->y, cur_win->y + cur_win->height); 544 return; 545 } 546 if(final->y + final->height < cur_win->y + 547 Scr->MovePackResistance) { /* bottom */ 548 final->y = MIN(final->y, cur_win->y - final->height); 549 } 550} 551 552static bool 553_tryToPackVsEachMonitor(const RArea *monitor_area, void *vfinal) 554{ 555 _tryToPack((RArea *)vfinal, monitor_area); 556 return false; 557} 558 559void 560TryToPack(TwmWindow *tmp_win, int *x, int *y) 561{ 562 TwmWindow *t; 563 RArea cur_win; 564 RArea final = RAreaNew(*x, *y, 565 tmp_win->frame_width + 2 * tmp_win->frame_bw, 566 tmp_win->frame_height + 2 * tmp_win->frame_bw); 567 568 /* Global layout is not a single rectangle, check against the 569 * monitor borders */ 570 if(Scr->BorderedLayout->horiz->len > 1) { 571 RAreaListForeach( 572 Scr->BorderedLayout->monitors, _tryToPackVsEachMonitor, &final); 573 } 574 575 for(t = Scr->FirstWindow; t != NULL; t = t->next) { 576 if(t == tmp_win) { 577 continue; 578 } 579#ifdef WINBOX 580 if(t->winbox != tmp_win->winbox) { 581 continue; 582 } 583#endif 584 if(t->vs != tmp_win->vs) { 585 continue; 586 } 587 if(!t->mapped) { 588 continue; 589 } 590 591 cur_win = RAreaNew(t->frame_x, t->frame_y, 592 t->frame_width + 2 * t->frame_bw, 593 t->frame_height + 2 * t->frame_bw); 594 595 _tryToPack(&final, &cur_win); 596 } 597 598 *x = final.x; 599 *y = final.y; 600} 601 602 603/* 604 * Directionals for TryToPush_be(). These differ from the specs for 605 * jump/pack/fill in functions. because there's an indeterminate option. 606 */ 607typedef enum { 608 PD_ANY, 609 PD_BOTTOM, 610 PD_LEFT, 611 PD_RIGHT, 612 PD_TOP, 613} PushDirection; 614static void TryToPush_be(TwmWindow *tmp_win, int x, int y, PushDirection dir); 615 616void 617TryToPush(TwmWindow *tmp_win, int x, int y) 618{ 619 TryToPush_be(tmp_win, x, y, PD_ANY); 620} 621 622static void 623TryToPush_be(TwmWindow *tmp_win, int x, int y, PushDirection dir) 624{ 625 TwmWindow *t; 626 int newx, newy, ndir; 627 bool move; 628 int w, h; 629 int winw = tmp_win->frame_width + 2 * tmp_win->frame_bw; 630 int winh = tmp_win->frame_height + 2 * tmp_win->frame_bw; 631 632 for(t = Scr->FirstWindow; t != NULL; t = t->next) { 633 if(t == tmp_win) { 634 continue; 635 } 636#ifdef WINBOX 637 if(t->winbox != tmp_win->winbox) { 638 continue; 639 } 640#endif 641 if(t->vs != tmp_win->vs) { 642 continue; 643 } 644 if(!t->mapped) { 645 continue; 646 } 647 648 w = t->frame_width + 2 * t->frame_bw; 649 h = t->frame_height + 2 * t->frame_bw; 650 if(x >= t->frame_x + w) { 651 continue; 652 } 653 if(y >= t->frame_y + h) { 654 continue; 655 } 656 if(x + winw <= t->frame_x) { 657 continue; 658 } 659 if(y + winh <= t->frame_y) { 660 continue; 661 } 662 663 move = false; 664 if((dir == PD_ANY || dir == PD_LEFT) && 665 (x + Scr->MovePackResistance > t->frame_x + w)) { 666 newx = x - w; 667 newy = t->frame_y; 668 ndir = PD_LEFT; 669 move = true; 670 } 671 else if((dir == PD_ANY || dir == PD_RIGHT) && 672 (x + winw < t->frame_x + Scr->MovePackResistance)) { 673 newx = x + winw; 674 newy = t->frame_y; 675 ndir = PD_RIGHT; 676 move = true; 677 } 678 else if((dir == PD_ANY || dir == PD_TOP) && 679 (y + Scr->MovePackResistance > t->frame_y + h)) { 680 newx = t->frame_x; 681 newy = y - h; 682 ndir = PD_TOP; 683 move = true; 684 } 685 else if((dir == PD_ANY || dir == PD_BOTTOM) && 686 (y + winh < t->frame_y + Scr->MovePackResistance)) { 687 newx = t->frame_x; 688 newy = y + winh; 689 ndir = PD_BOTTOM; 690 move = true; 691 } 692 if(move) { 693 TryToPush_be(t, newx, newy, ndir); 694 TryToPack(t, &newx, &newy); 695 ConstrainByBorders(tmp_win, 696 &newx, t->frame_width + 2 * t->frame_bw, 697 &newy, t->frame_height + 2 * t->frame_bw); 698 SetupWindow(t, newx, newy, t->frame_width, t->frame_height, -1); 699 } 700 } 701} 702 703 704void 705TryToGrid(TwmWindow *tmp_win, int *x, int *y) 706{ 707 int w = tmp_win->frame_width + 2 * tmp_win->frame_bw; 708 int h = tmp_win->frame_height + 2 * tmp_win->frame_bw; 709 int grav = ((tmp_win->hints.flags & PWinGravity) 710 ? tmp_win->hints.win_gravity : NorthWestGravity); 711 712 switch(grav) { 713 case ForgetGravity : 714 case StaticGravity : 715 case NorthWestGravity : 716 case NorthGravity : 717 case WestGravity : 718 case CenterGravity : 719 *x = ((*x - Scr->BorderLeft) / Scr->XMoveGrid) * Scr->XMoveGrid 720 + Scr->BorderLeft; 721 *y = ((*y - Scr->BorderTop) / Scr->YMoveGrid) * Scr->YMoveGrid 722 + Scr->BorderTop; 723 break; 724 case NorthEastGravity : 725 case EastGravity : 726 *x = (((*x + w - Scr->BorderLeft) / Scr->XMoveGrid) * 727 Scr->XMoveGrid) - w + Scr->BorderLeft; 728 *y = ((*y - Scr->BorderTop) / Scr->YMoveGrid) * 729 Scr->YMoveGrid + Scr->BorderTop; 730 break; 731 case SouthWestGravity : 732 case SouthGravity : 733 *x = ((*x - Scr->BorderLeft) / Scr->XMoveGrid) * Scr->XMoveGrid 734 + Scr->BorderLeft; 735 *y = (((*y + h - Scr->BorderTop) / Scr->YMoveGrid) * Scr->YMoveGrid) 736 - h + Scr->BorderTop; 737 break; 738 case SouthEastGravity : 739 *x = (((*x + w - Scr->BorderLeft) / Scr->XMoveGrid) * 740 Scr->XMoveGrid) - w + Scr->BorderLeft; 741 *y = (((*y + h - Scr->BorderTop) / Scr->YMoveGrid) * 742 Scr->YMoveGrid) - h + Scr->BorderTop; 743 break; 744 } 745} 746 747 748 749#ifdef WINBOX 750/* 751 * Functions related to keeping windows from being placed off-screen (or 752 * off-screen too far). Involved in handling of params like DontMoveOff 753 * and MoveOffResistance, etc. 754 */ 755static void ConstrainLeftTop(int *value, int border); 756static void ConstrainRightBottom(int *value, int size1, int border, int size2); 757#endif 758 759bool 760ConstrainByLayout(RLayout *layout, int move_off_res, int *left, int width, 761 int *top, int height) 762{ 763 RArea area = RAreaNew(*left, *top, width, height); 764 int limit; 765 bool clipped = false; 766 767 limit = RLayoutFindBottomEdge(layout, &area) - height + 1; 768 if(area.y > limit) { 769 if(move_off_res >= 0 && area.y >= limit + move_off_res) { 770 area.y -= move_off_res; 771 } 772 else { 773 area.y = limit; 774 clipped = true; 775 } 776 } 777 778 limit = RLayoutFindRightEdge(layout, &area) - width + 1; 779 if(area.x > limit) { 780 if(move_off_res >= 0 && area.x >= limit + move_off_res) { 781 area.x -= move_off_res; 782 } 783 else { 784 area.x = limit; 785 clipped = true; 786 } 787 } 788 789 limit = RLayoutFindLeftEdge(layout, &area); 790 if(area.x < limit) { 791 if(move_off_res >= 0 && area.x <= limit - move_off_res) { 792 area.x += move_off_res; 793 } 794 else { 795 area.x = limit; 796 clipped = true; 797 } 798 } 799 800 limit = RLayoutFindTopEdge(layout, &area); 801 if(area.y < limit) { 802 if(move_off_res >= 0 && area.y <= limit - move_off_res) { 803 area.y += move_off_res; 804 } 805 else { 806 area.y = limit; 807 clipped = true; 808 } 809 } 810 811 *left = area.x; 812 *top = area.y; 813 814 return clipped; 815} 816 817void 818ConstrainByBorders1(int *left, int width, int *top, int height) 819{ 820 ConstrainByLayout(Scr->BorderedLayout, Scr->MoveOffResistance, 821 left, width, top, height); 822} 823 824void 825ConstrainByBorders(TwmWindow *twmwin, int *left, int width, 826 int *top, int height) 827{ 828 if(false) { 829 // Dummy 830 } 831#ifdef WINBOX 832 else if(twmwin->winbox) { 833 XWindowAttributes attr; 834 XGetWindowAttributes(dpy, twmwin->winbox->window, &attr); 835 ConstrainRightBottom(left, width, 0, attr.width); 836 ConstrainLeftTop(left, 0); 837 ConstrainRightBottom(top, height, 0, attr.height); 838 ConstrainLeftTop(top, 0); 839 } 840#endif 841 else { 842 ConstrainByBorders1(left, width, top, height); 843 } 844} 845 846#ifdef WINBOX 847static void 848ConstrainLeftTop(int *value, int border) 849{ 850 if(*value < border) { 851 if(Scr->MoveOffResistance < 0 || 852 *value > border - Scr->MoveOffResistance) { 853 *value = border; 854 } 855 else if(Scr->MoveOffResistance > 0 && 856 *value <= border - Scr->MoveOffResistance) { 857 *value = *value + Scr->MoveOffResistance; 858 } 859 } 860} 861 862static void 863ConstrainRightBottom(int *value, int size1, int border, int size2) 864{ 865 if(*value + size1 > size2 - border) { 866 if(Scr->MoveOffResistance < 0 || 867 *value + size1 < size2 - border + Scr->MoveOffResistance) { 868 *value = size2 - size1 - border; 869 } 870 else if(Scr->MoveOffResistance > 0 && 871 *value + size1 >= size2 - border + Scr->MoveOffResistance) { 872 *value = *value - Scr->MoveOffResistance; 873 } 874 } 875} 876#endif 877 878 879/* 880 * Zoom over to a particular window. 881 */ 882void 883WarpToWindow(TwmWindow *t, bool must_raise) 884{ 885 int x, y; 886 887 if(t->ring.cursor_valid) { 888 x = t->ring.curs_x; 889 y = t->ring.curs_y; 890#ifdef DEBUG 891 fprintf(stderr, "WarpToWindow: cursor_valid; x == %d, y == %d\n", x, y); 892#endif 893 894 /* 895 * XXX is this correct with 3D borders? Easier check possible? 896 * frame_bw is for the left border. 897 */ 898 if(x < t->frame_bw) { 899 x = t->frame_bw; 900 } 901 if(x >= t->frame_width + t->frame_bw) { 902 x = t->frame_width + t->frame_bw - 1; 903 } 904 if(y < t->title_height + t->frame_bw) { 905 y = t->title_height + t->frame_bw; 906 } 907 if(y >= t->frame_height + t->frame_bw) { 908 y = t->frame_height + t->frame_bw - 1; 909 } 910#ifdef DEBUG 911 fprintf(stderr, "WarpToWindow: adjusted ; x := %d, y := %d\n", x, y); 912#endif 913 } 914 else { 915 x = t->frame_width / 2; 916 y = t->frame_height / 2; 917#ifdef DEBUG 918 fprintf(stderr, "WarpToWindow: middle; x := %d, y := %d\n", x, y); 919#endif 920 } 921#if 0 922 int dest_x, dest_y; 923 Window child; 924 925 /* 926 * Check if the proposed position actually is visible. If not, raise the window. 927 * "If the coordinates are contained in a mapped 928 * child of dest_w, that child is returned to child_return." 929 * We'll need to check for the right child window; the frame probably. 930 * (What about XXX window boxes?) 931 * 932 * Alternatively, use XQueryPointer() which returns the root window 933 * the pointer is in, but XXX that won't work for VirtualScreens. 934 */ 935 if(XTranslateCoordinates(dpy, t->frame, Scr->Root, x, y, &dest_x, &dest_y, 936 &child)) { 937 if(child != t->frame) { 938 must_raise = true; 939 } 940 } 941#endif 942 if(t->auto_raise || must_raise) { 943 AutoRaiseWindow(t); 944 } 945 if(! visible(t)) { 946 WorkSpace *wlist; 947 948 for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL; 949 wlist = wlist->next) { 950 if(OCCUPY(t, wlist)) { 951 break; 952 } 953 } 954 if(wlist != NULL) { 955 GotoWorkSpace(Scr->currentvs, wlist); 956 } 957 } 958 959 XWarpPointer(dpy, None, Scr->Root, 0, 0, 0, 0, x + t->frame_x, y + t->frame_y); 960 SetFocus(t, EventTime); 961 962#ifdef DEBUG 963 { 964 Window root_return; 965 Window child_return; 966 int root_x_return; 967 int root_y_return; 968 int win_x_return; 969 int win_y_return; 970 unsigned int mask_return; 971 972 if(XQueryPointer(dpy, t->frame, &root_return, &child_return, &root_x_return, 973 &root_y_return, &win_x_return, &win_y_return, &mask_return)) { 974 fprintf(stderr, 975 "XQueryPointer: root_return=%x, child_return=%x, root_x_return=%d, root_y_return=%d, win_x_return=%d, win_y_return=%d\n", 976 root_return, child_return, root_x_return, root_y_return, win_x_return, 977 win_y_return); 978 } 979 } 980#endif 981} 982 983 984/* 985 * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all 986 * client messages will have the following form: 987 * 988 * event type ClientMessage 989 * message type XA_WM_PROTOCOLS 990 * window tmp->w 991 * format 32 992 * data[0] message atom 993 * data[1] time stamp 994 */ 995void 996send_clientmessage(Window w, Atom a, Time timestamp) 997{ 998 XClientMessageEvent ev; 999 1000 ev.type = ClientMessage; 1001 ev.window = w; 1002 ev.message_type = XA_WM_PROTOCOLS; 1003 ev.format = 32; 1004 ev.data.l[0] = a; 1005 ev.data.l[1] = timestamp; 1006 XSendEvent(dpy, w, False, 0L, (XEvent *) &ev); 1007} 1008 1009 1010/* 1011 * Create synthetic WM_HINTS info for windows. When a window specifies 1012 * stuff, we should probably pay attention to it (though we don't 1013 * always; x-ref comments in AddWindow() especially about focus). 1014 * However, when it doesn't tell us anything at all, we should assume 1015 * something useful. "Window managers are free to assume convenient 1016 * values for all fields of the WM_HINTS property if a window is mapped 1017 * without one." (ICCCM Ch. 4, 1018 * <https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#Client_Properties>). 1019 * 1020 * Specifically, we assume it wants us to give it focus. It's fairly 1021 * bogus for a window not to tell us anything, but e.g current versions 1022 * of Chrome do (don't do) just that. So we better make up something 1023 * useful. 1024 * 1025 * Should probably be some configurability for this, so make the func 1026 * take the window, even though we don't currently do anything useful 1027 * with it... 1028 */ 1029XWMHints * 1030gen_synthetic_wmhints(TwmWindow *win) 1031{ 1032 XWMHints *hints; 1033 1034 hints = XAllocWMHints(); 1035 if(!hints) { 1036 return NULL; 1037 } 1038 1039 /* 1040 * Reasonable defaults. Takes input, in normal state. 1041 * 1042 * XXX Make configurable? 1043 */ 1044 hints->flags = InputHint | StateHint; 1045 hints->input = True; 1046 hints->initial_state = NormalState; 1047 1048 return hints; 1049} 1050 1051 1052/** 1053 * Perform whatever adaptations of WM_HINTS info we do. 1054 * 1055 * Most of these relate to focus, but we also fiddle with group 1056 * membership. 1057 */ 1058XWMHints * 1059munge_wmhints(TwmWindow *win, XWMHints *hints) 1060{ 1061 /* 1062 * If we have WM_HINTS, but they don't tell us anything about focus, 1063 * force it to true for our purposes. 1064 * 1065 * CL: Having with not willing focus cause problems with AutoSqueeze 1066 * and a few others things. So I suppress it. And the whole focus 1067 * thing is buggy anyway. 1068 */ 1069 if(!(hints->flags & InputHint)) { 1070 hints->input = True; 1071 } 1072 1073 /* 1074 * Now we're expecting to give the window focus if it asked for it 1075 * via WM_HINTS, if it didn't say anything one way or the other in 1076 * WM_HINTS, or if it didn't give us any WM_HINTS at all. But if it 1077 * explicitly asked not to, we don't give it unless overridden by 1078 * config. 1079 */ 1080 if(Scr->ForceFocus || IsInList(Scr->ForceFocusL, win)) { 1081 hints->input = True; 1082 } 1083 1084 1085 /* Setup group bits */ 1086 if(hints->flags & WindowGroupHint) { 1087 win->group = hints->window_group; 1088 if(win->group) { 1089 /* 1090 * GTK windows often have a spurious "group leader" window which is 1091 * never reported to us and therefore does not really exist. This 1092 * is annoying because we treat group members a lot like transient 1093 * windows. Look for that here. It is in fact a duplicate of the 1094 * WM_CLIENT_LEADER property. 1095 */ 1096 if(win->group != win->w && !GetTwmWindow(win->group)) { 1097 win->group = 0; 1098 } 1099 } 1100 } 1101 else { 1102 win->group = 0; 1103 } 1104 1105 return hints; 1106} 1107 1108 1109/** 1110 * [Re]set a window's name. This goes over the available naming sources 1111 * for the window and points the TwmWindow::name at the appropriate one. 1112 * It may also set a property to signal other EWMH-aware clients when 1113 * we're naming it a way they can't see themselves. 1114 * 1115 * \note This should rarely be called directly; apply_window_name() 1116 * should be used instead. It's split out because we need to do this 1117 * step individually in AddWindow(). 1118 * 1119 * \note Note also that we never need to worry about freeing the 1120 * TwmWindow::name; it always points to one of the TwmWindow::names 1121 * values (which are free'd by the event handler when they change) or to 1122 * NoName (which is static). So we can just casually flip it around at 1123 * will. 1124 */ 1125bool 1126set_window_name(TwmWindow *win) 1127{ 1128 char *newname = NULL; 1129#define TRY(fld) { \ 1130 if(newname == NULL && win->names.fld != NULL) { \ 1131 newname = win->names.fld; \ 1132 } \ 1133 } 1134 TRY(ctwm_wm_name) 1135#ifdef EWMH 1136 TRY(net_wm_name) 1137#endif 1138 TRY(wm_name) 1139#undef TRY 1140 1141 if(newname == NULL) { 1142 newname = NoName; 1143 } 1144 if(win->name == newname) { 1145 return false; // Nothing to do 1146 } 1147 1148 // Now we know what to call it 1149 win->name = newname; 1150 1151#ifdef EWMH 1152 // EWMH says we set an additional property on any windows where what 1153 // we consider the name isn't what's in _NET_WM_NAME, so pagers etc 1154 // can call it the same as we do. 1155 // 1156 // The parts of the text describing it conflict a little; at one 1157 // place, it implies this should be set unless we're using 1158 // _NET_WM_NAME, in another it seems to suggest WM_NAME should be 1159 // considered applicable too. I choose to implement it excluding 1160 // both, so this only gets set if we're overriding either standard 1161 // naming (probably rare). 1162 if(win->name != win->names.net_wm_name && win->name != win->names.wm_name) { 1163 // XXX We're not doing any checking of the encoding here... I 1164 // don't see that Xlib helps us any, so we probably have to fall 1165 // back to iconv? That came into the base in POSIX 2008, but was 1166 // in XSI back into the 90's I believe? 1167 XChangeProperty(dpy, win->w, XA__NET_WM_VISIBLE_NAME, XA_UTF8_STRING, 1168 8, PropModeReplace, (unsigned char *)win->name, 1169 strlen(win->name)); 1170 } 1171 else { 1172 XDeleteProperty(dpy, win->w, XA__NET_WM_VISIBLE_NAME); 1173 } 1174#endif // EWMH 1175 1176 // We set a name 1177 return true; 1178} 1179 1180 1181/** 1182 * [Re]set and apply changes to a window's name. This is called after 1183 * we've received a new WM_NAME (or other name-setting) property, to 1184 * update our titlebars, icon managers, etc. 1185 */ 1186void 1187apply_window_name(TwmWindow *win) 1188{ 1189 /* [Re]set ->name */ 1190 if(set_window_name(win) == false) { 1191 // No change 1192 return; 1193 } 1194 win->nameChanged = true; 1195 1196 1197 /* Update the active name */ 1198 { 1199 XRectangle inc_rect; 1200 XRectangle logical_rect; 1201 1202 XmbTextExtents(Scr->TitleBarFont.font_set, 1203 win->name, strlen(win->name), 1204 &inc_rect, &logical_rect); 1205 win->name_width = logical_rect.width; 1206 } 1207 1208 /* recompute the priority if necessary */ 1209 if(Scr->AutoPriority) { 1210 OtpRecomputePrefs(win); 1211 } 1212 1213 SetupWindow(win, win->frame_x, win->frame_y, 1214 win->frame_width, win->frame_height, -1); 1215 1216 if(win->title_w) { 1217 XClearArea(dpy, win->title_w, 0, 0, 0, 0, True); 1218 } 1219 if(Scr->AutoOccupy) { 1220 WmgrRedoOccupation(win); 1221 } 1222 1223#if 0 1224 /* Experimental, not yet working. */ 1225 { 1226 ColorPair cp; 1227 int f, b; 1228 1229 f = GetColorFromList(Scr->TitleForegroundL, win->name, 1230 &win->class, &cp.fore); 1231 b = GetColorFromList(Scr->TitleBackgroundL, win->name, 1232 &win->class, &cp.back); 1233 if(f || b) { 1234 if(Scr->use3Dtitles && !Scr->BeNiceToColormap) { 1235 GetShadeColors(&cp); 1236 } 1237 win->title = cp; 1238 } 1239 f = GetColorFromList(Scr->BorderColorL, win->name, 1240 &win->class, &cp.fore); 1241 b = GetColorFromList(Scr->BorderColorL, win->name, 1242 &win->class, &cp.back); 1243 if(f || b) { 1244 if(Scr->use3Dborders && !Scr->BeNiceToColormap) { 1245 GetShadeColors(&cp); 1246 } 1247 win->borderC = cp; 1248 } 1249 1250 f = GetColorFromList(Scr->BorderTileForegroundL, win->name, 1251 &win->class, &cp.fore); 1252 b = GetColorFromList(Scr->BorderTileBackgroundL, win->name, 1253 &win->class, &cp.back); 1254 if(f || b) { 1255 if(Scr->use3Dborders && !Scr->BeNiceToColormap) { 1256 GetShadeColors(&cp); 1257 } 1258 win->border_tile = cp; 1259 } 1260 } 1261#endif 1262 1263 /* 1264 * If we haven't set a separate icon name, we use the window name, so 1265 * we need to update it. 1266 */ 1267 if(win->names.icon_set == false) { 1268 apply_window_icon_name(win); 1269 } 1270 AutoPopupMaybe(win); 1271 1272 return; 1273} 1274 1275 1276/** 1277 * [Re]set a window's icon name. As with the window name version in 1278 * set_window_name(), this is mostly separate so the AddWindow() process 1279 * can call it. 1280 * 1281 * \note As with TwmWindow::name, we never want to try free()'ing or the 1282 * like TwmWindow::icon_name. 1283 * 1284 * \sa set_window_name() for details; this is just the icon name 1285 * equivalent of it. 1286 */ 1287bool 1288set_window_icon_name(TwmWindow *win) 1289{ 1290 char *newname = NULL; 1291#define TRY(fld) { \ 1292 if(newname == NULL && win->names.fld != NULL) { \ 1293 newname = win->names.fld; \ 1294 win->names.icon_set = true; \ 1295 } \ 1296 } 1297 TRY(ctwm_wm_icon_name) 1298#ifdef EWMH 1299 TRY(net_wm_icon_name) 1300#endif 1301 TRY(wm_icon_name) 1302#undef TRY 1303 1304 // Our fallback for icon names is the window name. Flag when we're 1305 // doing that, so the window name handler can know when it needs to 1306 // call us. 1307 if(newname == NULL) { 1308 newname = win->name; 1309 win->names.icon_set = false; 1310 } 1311 if(win->icon_name == newname) { 1312 return false; // Nothing to do 1313 } 1314 1315 // A name is chosen 1316 win->icon_name = newname; 1317 1318#ifdef EWMH 1319 // EWMH asks for _NET_WM_VISIBLE_ICON_NAME in various cases where 1320 // we're not using 'standard' properties' values. x-ref comments above in 1321 // set_window_name() about the parallel property for the window name 1322 // for various caveats. 1323 if(win->icon_name != win->names.net_wm_icon_name 1324 && win->icon_name != win->names.wm_icon_name) { 1325 // XXX Still encoding questionable; x-ref above. 1326 XChangeProperty(dpy, win->w, XA__NET_WM_VISIBLE_ICON_NAME, 1327 XA_UTF8_STRING, 1328 8, PropModeReplace, (unsigned char *)win->icon_name, 1329 strlen(win->icon_name)); 1330 } 1331 else { 1332 XDeleteProperty(dpy, win->w, XA__NET_WM_VISIBLE_ICON_NAME); 1333 } 1334#endif // EWMH 1335 1336 // Did it 1337 return true; 1338} 1339 1340 1341/** 1342 * [Re]set and apply changes to a window's icon name. This is called 1343 * after we've received a new WM_ICON_NAME (or other name-setting) 1344 * property, to update our titlebars, icon managers, etc. 1345 * 1346 * \sa apply_window_name() which does the same for the window title. 1347 */ 1348void 1349apply_window_icon_name(TwmWindow *win) 1350{ 1351 /* [Re]set ->icon_name */ 1352 if(set_window_icon_name(win) == false) { 1353 // No change 1354 return; 1355 } 1356 1357 1358 /* Lot less to do for icons... */ 1359 RedoIcon(Tmp_win); 1360 AutoPopupMaybe(Tmp_win); 1361 1362 return; 1363} 1364