1/* 2 * Functions related to manipulating windows. 3 * 4 * This doesn't include stuff related to changing their occupation (in 5 * functions_workspaces.c), or moving/resizing (in 6 * functions_win_moveresize.c), or a few other cases of things 7 * peripherally involving windows. 8 */ 9 10#include "ctwm.h" 11 12#include <stdlib.h> 13 14#include "colormaps.h" 15#include "ctwm_atoms.h" 16#include "events.h" 17#include "event_handlers.h" 18#include "functions.h" 19#include "functions_defs.h" 20#include "functions_internal.h" 21#include "icons.h" 22#include "occupation.h" 23#include "otp.h" 24#include "parse.h" 25#include "screen.h" 26#include "win_decorations.h" 27#include "win_iconify.h" 28#include "win_ops.h" 29#include "win_utils.h" 30#include "workspace_manager.h" 31 32 33 34/* 35 ************************************************************* 36 * 37 * Moving windows on/off the AutoRaise/AutoLower lists 38 * 39 ************************************************************* 40 */ 41DFHANDLER(autoraise) 42{ 43 tmp_win->auto_raise = !tmp_win->auto_raise; 44 if(tmp_win->auto_raise) { 45 ++(Scr->NumAutoRaises); 46 } 47 else { 48 --(Scr->NumAutoRaises); 49 } 50} 51 52DFHANDLER(autolower) 53{ 54 tmp_win->auto_lower = !tmp_win->auto_lower; 55 if(tmp_win->auto_lower) { 56 ++(Scr->NumAutoLowers); 57 } 58 else { 59 --(Scr->NumAutoLowers); 60 } 61} 62 63 64 65 66/* 67 ************************************************************* 68 * 69 * Raising and lowering 70 * 71 ************************************************************* 72 */ 73 74/* Separate function because raise and raiseorsqueeze can both need it */ 75static void 76raise_handler(EF_FULLPROTO) 77{ 78 /* check to make sure raise is not from the WindowFunction */ 79 if(tmp_win->icon && (w == tmp_win->icon->w) && Context != C_ROOT) { 80 OtpRaise(tmp_win, IconWin); 81 } 82 else { 83 OtpRaise(tmp_win, WinWin); 84 WMapRaise(tmp_win); 85 } 86} 87 88DFHANDLER(raise) 89{ 90 raise_handler(EF_ARGS); 91} 92 93DFHANDLER(raiseorsqueeze) 94{ 95 /* FIXME using the same double-click ConstrainedMoveTime here */ 96 if((eventp->xbutton.time - last_time) < ConstrainedMoveTime) { 97 Squeeze(tmp_win); 98 return; 99 } 100 last_time = eventp->xbutton.time; 101 102 /* intentional fall-thru into F_RAISE */ 103 raise_handler(EF_ARGS); 104} 105 106DFHANDLER(lower) 107{ 108 if(tmp_win->icon && (w == tmp_win->icon->w)) { 109 OtpLower(tmp_win, IconWin); 110 } 111 else { 112 OtpLower(tmp_win, WinWin); 113 WMapLower(tmp_win); 114 } 115} 116 117DFHANDLER(raiselower) 118{ 119 if(!WindowMoved) { 120 if(tmp_win->icon && w == tmp_win->icon->w) { 121 OtpRaiseLower(tmp_win, IconWin); 122 } 123 else { 124 OtpRaiseLower(tmp_win, WinWin); 125 WMapRaiseLower(tmp_win); 126 } 127 } 128} 129 130 131/* 132 * Smaller raise/lower 133 */ 134DFHANDLER(tinyraise) 135{ 136 /* check to make sure raise is not from the WindowFunction */ 137 if(tmp_win->icon && (w == tmp_win->icon->w) && Context != C_ROOT) { 138 OtpTinyRaise(tmp_win, IconWin); 139 } 140 else { 141 OtpTinyRaise(tmp_win, WinWin); 142 WMapRaise(tmp_win); 143 } 144} 145 146DFHANDLER(tinylower) 147{ 148 /* check to make sure raise is not from the WindowFunction */ 149 if(tmp_win->icon && (w == tmp_win->icon->w) && Context != C_ROOT) { 150 OtpTinyLower(tmp_win, IconWin); 151 } 152 else { 153 OtpTinyLower(tmp_win, WinWin); 154 WMapLower(tmp_win); 155 } 156} 157 158 159/* 160 * Raising/lowering a non-targetted window 161 */ 162DFHANDLER(circleup) 163{ 164 OtpCirculateSubwindows(Scr->currentvs, RaiseLowest); 165} 166 167DFHANDLER(circledown) 168{ 169 OtpCirculateSubwindows(Scr->currentvs, LowerHighest); 170} 171 172 173 174 175/* 176 ************************************************************* 177 * 178 * Iconification and its inverse. 179 * 180 ************************************************************* 181 */ 182static void 183iconify_handler(EF_FULLPROTO) 184{ 185 if(tmp_win->isicon) { 186 DeIconify(tmp_win); 187 } 188 else if(func == F_ICONIFY) { 189 Iconify(tmp_win, eventp->xbutton.x_root - 5, 190 eventp->xbutton.y_root - 5); 191 } 192} 193 194DFHANDLER(deiconify) 195{ 196 iconify_handler(EF_ARGS); 197} 198DFHANDLER(iconify) 199{ 200 iconify_handler(EF_ARGS); 201} 202 203 204/* 205 * This is a synthetic function; it only exists as an action in some 206 * magic menus like TwmWindows (x-ref f.winwarp as well). It acts as a 207 * sort of deiconify, so I've stuck it here. 208 */ 209DFHANDLER(popup) 210{ 211 /* 212 * This is a synthetic function; it exists only to be called 213 * internally from the various magic menus like TwmWindows 214 * etc. 215 */ 216 tmp_win = (TwmWindow *)action; 217 if(! tmp_win) { 218 return; 219 } 220 if(Scr->WindowFunction.func != 0) { 221 ExecuteFunction(Scr->WindowFunction.func, 222 Scr->WindowFunction.item->action, 223 w, tmp_win, eventp, C_FRAME, false); 224 } 225 else { 226 DeIconify(tmp_win); 227 OtpRaise(tmp_win, WinWin); 228 } 229} 230 231 232 233 234/* 235 ************************************************************* 236 * 237 * Focus locking 238 * 239 ************************************************************* 240 */ 241DFHANDLER(focus) 242{ 243 if(!tmp_win->isicon) { 244 if(!Scr->FocusRoot && Scr->Focus == tmp_win) { 245 FocusOnRoot(); 246 } 247 else { 248 InstallWindowColormaps(0, tmp_win); 249 SetFocus(tmp_win, eventp->xbutton.time); 250 Scr->FocusRoot = false; 251 } 252 } 253} 254 255DFHANDLER(unfocus) 256{ 257 FocusOnRoot(); 258} 259 260 261 262 263/* 264 ************************************************************* 265 * 266 * Window destruction 267 * 268 ************************************************************* 269 */ 270static void 271SendDeleteWindowMessage(TwmWindow *tmp, Time timestamp) 272{ 273 send_clientmessage(tmp->w, XA_WM_DELETE_WINDOW, timestamp); 274} 275 276DFHANDLER(delete) 277{ 278 if(tmp_win->isiconmgr) { /* don't send ourself a message */ 279 HideIconManager(); 280 return; 281 } 282 if(tmp_win->iswspmgr 283#ifdef WINBOX 284 || tmp_win->iswinbox 285#endif 286 || (Scr->workSpaceMgr.occupyWindow 287 && tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win)) { 288 XBell(dpy, 0); 289 return; 290 } 291 if(tmp_win->protocols & DoesWmDeleteWindow) { 292 SendDeleteWindowMessage(tmp_win, EventTime); 293 if(ButtonPressed != -1) { 294 XEvent kev; 295 296 XMaskEvent(dpy, ButtonReleaseMask, &kev); 297 if(kev.xbutton.window == tmp_win->w) { 298 kev.xbutton.window = Scr->Root; 299 } 300 XPutBackEvent(dpy, &kev); 301 } 302 return; 303 } 304 XBell(dpy, 0); 305} 306 307DFHANDLER(destroy) 308{ 309 if(tmp_win->isiconmgr || tmp_win->iswspmgr 310#ifdef WINBOX 311 || tmp_win->iswinbox 312#endif 313 || (Scr->workSpaceMgr.occupyWindow 314 && tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win)) { 315 XBell(dpy, 0); 316 return; 317 } 318 XKillClient(dpy, tmp_win->w); 319 if(ButtonPressed != -1) { 320 XEvent kev; 321 322 XMaskEvent(dpy, ButtonReleaseMask, &kev); 323 if(kev.xbutton.window == tmp_win->w) { 324 kev.xbutton.window = Scr->Root; 325 } 326 XPutBackEvent(dpy, &kev); 327 } 328} 329 330DFHANDLER(deleteordestroy) 331{ 332 if(tmp_win->isiconmgr) { 333 HideIconManager(); 334 return; 335 } 336 if(tmp_win->iswspmgr 337#ifdef WINBOX 338 || tmp_win->iswinbox 339#endif 340 || (Scr->workSpaceMgr.occupyWindow 341 && tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win)) { 342 XBell(dpy, 0); 343 return; 344 } 345 if(tmp_win->protocols & DoesWmDeleteWindow) { 346 SendDeleteWindowMessage(tmp_win, EventTime); 347 } 348 else { 349 XKillClient(dpy, tmp_win->w); 350 } 351 if(ButtonPressed != -1) { 352 XEvent kev; 353 354 XMaskEvent(dpy, ButtonReleaseMask, &kev); 355 if(kev.xbutton.window == tmp_win->w) { 356 kev.xbutton.window = Scr->Root; 357 } 358 XPutBackEvent(dpy, &kev); 359 } 360} 361 362 363 364 365/* 366 ************************************************************* 367 * 368 * Messing with OnTopPriority bits 369 * 370 ************************************************************* 371 */ 372static void 373otp_priority_handler(EF_FULLPROTO) 374{ 375 WinType wintype; 376 int pri; 377 char *endp; 378 379 if(tmp_win->icon && w == tmp_win->icon->w) { 380 wintype = IconWin; 381 } 382 else { 383 wintype = WinWin; 384 } 385 switch(func) { 386 case F_PRIORITYSWITCHING: 387 OtpToggleSwitching(tmp_win, wintype); 388 break; 389 case F_SETPRIORITY: 390 pri = (int)strtol(action, &endp, 10); 391 OtpSetPriority(tmp_win, wintype, pri, 392 (*endp == '<' || *endp == 'b') ? Below : Above); 393 break; 394 case F_CHANGEPRIORITY: 395 OtpChangePriority(tmp_win, wintype, atoi(action)); 396 break; 397 case F_SWITCHPRIORITY: 398 OtpSwitchPriority(tmp_win, wintype); 399 break; 400 } 401 402 /* 403 * Stash up our current flags if there aren't any set yet. This is 404 * necessary because otherwise the EWMH prop we [may] stash below 405 * would be taken as gospel on restart, when it shouldn't be. 406 */ 407 OtpStashAflagsFirstTime(tmp_win); 408 409#ifdef EWMH 410 /* 411 * We changed the priority somehow, so we may have changed where it 412 * sits relative to the middle. So trigger rechecking/setting of the 413 * _STATE_{ABOVE,BELOW}. (_ABOVE in changes arg covers both) 414 */ 415 EwmhSet_NET_WM_STATE(tmp_win, EWMH_STATE_ABOVE); 416#endif /* EWMH */ 417} 418DFHANDLER(priorityswitching) 419{ 420 otp_priority_handler(EF_ARGS); 421} 422DFHANDLER(switchpriority) 423{ 424 otp_priority_handler(EF_ARGS); 425} 426DFHANDLER(setpriority) 427{ 428 otp_priority_handler(EF_ARGS); 429} 430DFHANDLER(changepriority) 431{ 432 otp_priority_handler(EF_ARGS); 433} 434 435 436 437 438/* 439 ************************************************************* 440 * 441 * Some misc utilities 442 * 443 ************************************************************* 444 */ 445DFHANDLER(saveyourself) 446{ 447 if(tmp_win->protocols & DoesWmSaveYourself) { 448 send_clientmessage(tmp_win->w, XA_WM_SAVE_YOURSELF, EventTime); 449 } 450 else { 451 XBell(dpy, 0); 452 } 453} 454 455DFHANDLER(colormap) 456{ 457 /* XXX Window targetting; should this be on the Defer list? */ 458 if(strcmp(action, COLORMAP_NEXT) == 0) { 459 BumpWindowColormap(tmp_win, 1); 460 } 461 else if(strcmp(action, COLORMAP_PREV) == 0) { 462 BumpWindowColormap(tmp_win, -1); 463 } 464 else { 465 BumpWindowColormap(tmp_win, 0); 466 } 467} 468 469DFHANDLER(refresh) 470{ 471 XSetWindowAttributes attributes; 472 unsigned long valuemask; 473 474 valuemask = CWBackPixel; 475 attributes.background_pixel = Scr->Black; 476 w = XCreateWindow(dpy, Scr->Root, 0, 0, 477 Scr->rootw, 478 Scr->rooth, 479 0, 480 CopyFromParent, CopyFromParent, 481 CopyFromParent, valuemask, 482 &attributes); 483 XMapWindow(dpy, w); 484 XDestroyWindow(dpy, w); 485 XFlush(dpy); 486 487} 488 489DFHANDLER(winrefresh) 490{ 491 if(context == C_ICON && tmp_win->icon && tmp_win->icon->w) 492 w = XCreateSimpleWindow(dpy, tmp_win->icon->w, 493 0, 0, 9999, 9999, 0, Scr->Black, Scr->Black); 494 else 495 w = XCreateSimpleWindow(dpy, tmp_win->frame, 496 0, 0, 9999, 9999, 0, Scr->Black, Scr->Black); 497 498 XMapWindow(dpy, w); 499 XDestroyWindow(dpy, w); 500 XFlush(dpy); 501} 502 503 504 505 506/* 507 ************************************************************* 508 * 509 * Window squeezing related bits 510 * 511 ************************************************************* 512 */ 513DFHANDLER(squeeze) 514{ 515 Squeeze(tmp_win); 516} 517 518DFHANDLER(unsqueeze) 519{ 520 if(tmp_win->squeezed) { 521 Squeeze(tmp_win); 522 } 523} 524 525 526DFHANDLER(movetitlebar) 527{ 528 Window grabwin; 529 Window rootw; 530 int deltax = 0, newx = 0; 531 int origX; 532 int origNum; 533 SqueezeInfo *si; 534 535 PopDownMenu(); 536 if(tmp_win->squeezed || 537 !tmp_win->squeeze_info || 538 !tmp_win->title_w || 539 context == C_ICON) { 540 XBell(dpy, 0); 541 return; 542 } 543 544 /* If the SqueezeInfo isn't copied yet, do it now */ 545 if(!tmp_win->squeeze_info_copied) { 546 SqueezeInfo *s = malloc(sizeof(SqueezeInfo)); 547 if(!s) { 548 return; 549 } 550 *s = *tmp_win->squeeze_info; 551 tmp_win->squeeze_info = s; 552 tmp_win->squeeze_info_copied = true; 553 } 554 si = tmp_win->squeeze_info; 555 556 if(si->denom != 0) { 557 int target_denom = tmp_win->frame_width; 558 /* 559 * If not pixel based, scale the denominator to equal the 560 * window width, so the numerator equals pixels. 561 * That way we can just modify it by pixel units, just 562 * like the other case. 563 */ 564 565 if(si->denom != target_denom) { 566 float scale = (float)target_denom / si->denom; 567 si->num *= scale; 568 si->denom = target_denom; /* s->denom *= scale; */ 569 } 570 } 571 572 /* now move the mouse */ 573#ifdef WINBOX 574 if(tmp_win->winbox) { 575 XTranslateCoordinates(dpy, Scr->Root, tmp_win->winbox->window, 576 eventp->xbutton.x_root, eventp->xbutton.y_root, 577 &eventp->xbutton.x_root, &eventp->xbutton.y_root, &JunkChild); 578 } 579#endif 580 /* 581 * the event is always a button event, since key events 582 * are "weeded out" - although incompletely only 583 * F_MOVE and F_RESIZE - in HandleKeyPress(). 584 */ 585 586 /* 587 * XXX This var may be actually unnecessary; it's used only 588 * once as an arg to a later X call, but during that time I 589 * don't believe anything can mutate eventp or anything near 590 * the root. However, due to the union nature of XEvent, 591 * it's hard to be sure without more investigation, so I 592 * leave the intermediate var for now. 593 * 594 * Note that we're looking inside the XButtonEvent member 595 * here, but other bits of this code later look at the 596 * XMotionEvent piece. This should be further investigated 597 * and resolved; they can't both be right (though the 598 * structure of the structs are such that almost all the 599 * similar elements are in the same place, at least in 600 * theory). 601 */ 602 rootw = eventp->xbutton.root; 603 604 EventHandler[EnterNotify] = HandleUnknown; 605 EventHandler[LeaveNotify] = HandleUnknown; 606 607 if(!Scr->NoGrabServer) { 608 XGrabServer(dpy); 609 } 610 611 grabwin = Scr->Root; 612#ifdef WINBOX 613 if(tmp_win->winbox) { 614 grabwin = tmp_win->winbox->window; 615 } 616#endif 617 XGrabPointer(dpy, grabwin, True, 618 ButtonPressMask | ButtonReleaseMask | 619 ButtonMotionMask | PointerMotionMask, /* PointerMotionHintMask */ 620 GrabModeAsync, GrabModeAsync, grabwin, Scr->MoveCursor, CurrentTime); 621 622#if 0 /* what's this for ? */ 623 if(! tmp_win->icon || w != tmp_win->icon->w) { 624 XTranslateCoordinates(dpy, w, tmp_win->frame, 625 eventp->xbutton.x, 626 eventp->xbutton.y, 627 &DragX, &DragY, &JunkChild); 628 629 w = tmp_win->frame; 630 } 631#endif 632 633 DragWindow = None; 634 635 XGetGeometry(dpy, tmp_win->title_w, &JunkRoot, &origDragX, &origDragY, 636 &DragWidth, &DragHeight, &DragBW, 637 &JunkDepth); 638 639 origX = eventp->xbutton.x_root; 640 origNum = si->num; 641 642 if(menuFromFrameOrWindowOrTitlebar) { 643 /* warp the pointer to the middle of the window */ 644 XWarpPointer(dpy, None, Scr->Root, 0, 0, 0, 0, 645 origDragX + DragWidth / 2, 646 origDragY + DragHeight / 2); 647 XFlush(dpy); 648 } 649 650 while(1) { 651 long releaseEvent = menuFromFrameOrWindowOrTitlebar ? 652 ButtonPress : ButtonRelease; 653 long movementMask = menuFromFrameOrWindowOrTitlebar ? 654 PointerMotionMask : ButtonMotionMask; 655 656 /* block until there is an interesting event */ 657 XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | 658 EnterWindowMask | LeaveWindowMask | 659 ExposureMask | movementMask | 660 VisibilityChangeMask, &Event); 661 662 /* throw away enter and leave events until release */ 663 if(Event.xany.type == EnterNotify || 664 Event.xany.type == LeaveNotify) { 665 continue; 666 } 667 668 if(Event.type == MotionNotify) { 669 /* discard any extra motion events before a logical release */ 670 while(XCheckMaskEvent(dpy, 671 movementMask | releaseEvent, &Event)) { 672 if(Event.type == releaseEvent) { 673 break; 674 } 675 } 676 } 677 678 if(!DispatchEvent2()) { 679 continue; 680 } 681 682 if(Event.type == releaseEvent) { 683 break; 684 } 685 686 /* something left to do only if the pointer moved */ 687 if(Event.type != MotionNotify) { 688 continue; 689 } 690 691 /* get current pointer pos, useful when there is lag */ 692 XQueryPointer(dpy, rootw, &eventp->xmotion.root, &JunkChild, 693 &eventp->xmotion.x_root, &eventp->xmotion.y_root, 694 &JunkX, &JunkY, &JunkMask); 695 696 FixRootEvent(eventp); 697#ifdef WINBOX 698 if(tmp_win->winbox) { 699 XTranslateCoordinates(dpy, Scr->Root, tmp_win->winbox->window, 700 eventp->xmotion.x_root, eventp->xmotion.y_root, 701 &eventp->xmotion.x_root, &eventp->xmotion.y_root, &JunkChild); 702 } 703#endif 704 705 if(!Scr->NoRaiseMove && Scr->OpaqueMove && !WindowMoved) { 706 OtpRaise(tmp_win, WinWin); 707 } 708 709 deltax = eventp->xmotion.x_root - origX; 710 newx = origNum + deltax; 711 712 /* 713 * Clamp to left and right. 714 * If we're in pixel size, keep within [ 0, frame_width >. 715 * If we're proportional, don't cross the 0. 716 * Also don't let the nominator get bigger than the denominator. 717 * Keep within [ -denom, -1] or [ 0, denom >. 718 */ 719 { 720 int wtmp = tmp_win->frame_width; /* or si->denom; if it were != 0 */ 721 if(origNum < 0) { 722 if(newx >= 0) { 723 newx = -1; 724 } 725 else if(newx < -wtmp) { 726 newx = -wtmp; 727 } 728 } 729 else if(origNum >= 0) { 730 if(newx < 0) { 731 newx = 0; 732 } 733 else if(newx >= wtmp) { 734 newx = wtmp - 1; 735 } 736 } 737 } 738 739 si->num = newx; 740 /* This, finally, actually moves the title bar */ 741 /* XXX pressing a second button should cancel and undo this */ 742 SetFrameShape(tmp_win); 743 } 744 745 /* 746 * The ButtonRelease handler will have taken care of 747 * ungrabbing our pointer. 748 */ 749 return; 750} 751