1/* 2 * Window de/iconification handling 3 */ 4 5#include "ctwm.h" 6 7#include <stdlib.h> 8#include <sys/time.h> 9 10#include <X11/extensions/shape.h> 11 12#include "events.h" 13#include "functions.h" 14#include "iconmgr.h" 15#include "icons.h" 16#include "list.h" 17#include "otp.h" 18#include "screen.h" 19#include "util.h" 20#include "vscreen.h" 21#include "win_iconify.h" 22#include "win_ops.h" 23#include "win_utils.h" 24#include "workspace_manager.h" 25 26 27/* Animations */ 28static void MosaicFade(TwmWindow *tmp_win, Window blanket); 29static void ZoomInWindow(TwmWindow *tmp_win, Window blanket); 30static void ZoomOutWindow(TwmWindow *tmp_win, Window blanket); 31static void FadeWindow(TwmWindow *tmp_win, Window blanket); 32static void SweepWindow(TwmWindow *tmp_win, Window blanket); 33 34/* De/iconify utils */ 35static void Zoom(Window wf, Window wt); 36static void ReMapOne(TwmWindow *t, TwmWindow *leader); 37static void waitamoment(float timeout); 38 39 40 41/* 42 * The main routines for iconifying... 43 */ 44void 45Iconify(TwmWindow *tmp_win, int def_x, int def_y) 46{ 47 TwmWindow *t; 48 bool iconify; 49 long eventMask; 50 WList *wl; 51 Window leader = (Window) - 1; 52 Window blanket = (Window) - 1; 53 54 iconify = (!tmp_win->iconify_by_unmapping); 55 t = NULL; 56 if(tmp_win->istransient) { 57 leader = tmp_win->transientfor; 58 t = GetTwmWindow(leader); 59 } 60 else if((leader = tmp_win->group) != 0 && leader != tmp_win->w) { 61 t = GetTwmWindow(leader); 62 } 63 if(t && t->icon_on) { 64 iconify = false; 65 } 66 if(iconify) { 67 if(!tmp_win->icon || !tmp_win->icon->w) { 68 CreateIconWindow(tmp_win, def_x, def_y); 69 } 70 else { 71 IconUp(tmp_win); 72 } 73 if(visible(tmp_win)) { 74 OtpRaise(tmp_win, IconWin); 75 XMapWindow(dpy, tmp_win->icon->w); 76 } 77 } 78 if(tmp_win->iconmanagerlist) { 79 for(wl = tmp_win->iconmanagerlist; wl != NULL; wl = wl->nextv) { 80 XMapWindow(dpy, wl->icon); 81 } 82 } 83 84 /* Don't mask anything yet, just get the current for various uses */ 85 eventMask = mask_out_event(tmp_win->w, 0); 86 87 /* iconify transients and window group first */ 88 UnmapTransients(tmp_win, iconify, eventMask); 89 90 if(iconify) { 91 Zoom(tmp_win->frame, tmp_win->icon->w); 92 } 93 94 /* 95 * Prevent the receipt of an UnmapNotify, since that would 96 * cause a transition to the Withdrawn state. 97 */ 98 tmp_win->mapped = false; 99 100 if((Scr->IconifyStyle != ICONIFY_NORMAL) && !Scr->WindowMask) { 101 XWindowAttributes winattrs; 102 XSetWindowAttributes attr; 103 104 XGetWindowAttributes(dpy, tmp_win->frame, &winattrs); 105 blanket = XCreateWindow(dpy, Scr->Root, winattrs.x, winattrs.y, 106 winattrs.width, winattrs.height, 0, 107 CopyFromParent, CopyFromParent, 108 CopyFromParent, None, &attr); 109 XMapWindow(dpy, blanket); 110 } 111 112 mask_out_event_mask(tmp_win->w, StructureNotifyMask, eventMask); 113 XUnmapWindow(dpy, tmp_win->w); 114 XUnmapWindow(dpy, tmp_win->frame); 115 restore_mask(tmp_win->w, eventMask); 116 117 SetMapStateProp(tmp_win, IconicState); 118 119 if((Scr->IconifyStyle != ICONIFY_NORMAL) && !Scr->WindowMask) { 120 switch(Scr->IconifyStyle) { 121 case ICONIFY_MOSAIC: 122 MosaicFade(tmp_win, blanket); 123 break; 124 case ICONIFY_ZOOMIN: 125 ZoomInWindow(tmp_win, blanket); 126 break; 127 case ICONIFY_ZOOMOUT: 128 ZoomOutWindow(tmp_win, blanket); 129 break; 130 case ICONIFY_FADE: 131 FadeWindow(tmp_win, blanket); 132 break; 133 case ICONIFY_SWEEP: 134 SweepWindow(tmp_win, blanket); 135 break; 136 case ICONIFY_NORMAL: 137 /* Placate insufficiently smart clang warning */ 138 break; 139 } 140 XDestroyWindow(dpy, blanket); 141 } 142 if(tmp_win == Scr->Focus) { 143 SetFocus(NULL, EventTime); 144 if(! Scr->ClickToFocus) { 145 Scr->FocusRoot = true; 146 } 147 } 148 tmp_win->isicon = true; 149 tmp_win->icon_on = iconify; 150 WMapIconify(tmp_win); 151 if(! Scr->WindowMask && Scr->IconifyFunction.func != 0) { 152 char *action; 153 XEvent event; 154 155 action = Scr->IconifyFunction.item ? Scr->IconifyFunction.item->action : NULL; 156 ExecuteFunction(Scr->IconifyFunction.func, action, 157 (Window) 0, tmp_win, &event, C_ROOT, false); 158 } 159 XSync(dpy, 0); 160} 161 162 163/* 164 * ... and its complement 165 */ 166void 167DeIconify(TwmWindow *tmp_win) 168{ 169 TwmWindow *t = tmp_win; 170 bool isicon = false; 171 172 /* de-iconify the main window */ 173 if(Scr->WindowMask) { 174 XRaiseWindow(dpy, Scr->WindowMask); 175 } 176 if(tmp_win->isicon) { 177 isicon = true; 178 if(tmp_win->icon_on && tmp_win->icon && tmp_win->icon->w) { 179 Zoom(tmp_win->icon->w, tmp_win->frame); 180 } 181 else if(tmp_win->group != (Window) 0) { 182 TwmWindow *tmpt = GetTwmWindow(tmp_win->group); 183 if(tmpt) { 184 t = tmpt; 185 if(t->icon_on && t->icon && t->icon->w) { 186 Zoom(t->icon->w, tmp_win->frame); 187 } 188 } 189 } 190 } 191 192 ReMapOne(tmp_win, t); 193 194 if(isicon && 195 (Scr->WarpCursor || 196 LookInList(Scr->WarpCursorL, tmp_win->name, &tmp_win->class))) { 197 WarpToWindow(tmp_win, false); 198 } 199 200 /* now de-iconify any window group transients */ 201 ReMapTransients(tmp_win); 202 203 if(! Scr->WindowMask && Scr->DeIconifyFunction.func != 0) { 204 char *action; 205 XEvent event; 206 207 action = Scr->DeIconifyFunction.item ? 208 Scr->DeIconifyFunction.item->action : NULL; 209 ExecuteFunction(Scr->DeIconifyFunction.func, action, 210 (Window) 0, tmp_win, &event, C_ROOT, false); 211 } 212 XSync(dpy, 0); 213} 214 215 216 217/* 218 * Animations for popping windows around. 219 */ 220static void 221MosaicFade(TwmWindow *tmp_win, Window blanket) 222{ 223 int srect; 224 int i, j, nrects; 225 Pixmap mask; 226 GC gc; 227 XGCValues gcv; 228 XRectangle *rectangles; 229 int width = tmp_win->frame_width; 230 int height = tmp_win->frame_height; 231 232 srect = (width < height) ? (width / 20) : (height / 20); 233 mask = XCreatePixmap(dpy, blanket, width, height, 1); 234 235 gcv.foreground = 1; 236 gc = XCreateGC(dpy, mask, GCForeground, &gcv); 237 XFillRectangle(dpy, mask, gc, 0, 0, width, height); 238 gcv.function = GXclear; 239 XChangeGC(dpy, gc, GCFunction, &gcv); 240 241 nrects = ((width * height) / (srect * srect)) / 10; 242 rectangles = calloc(nrects, sizeof(XRectangle)); 243 for(j = 0; j < nrects; j++) { 244 rectangles [j].width = srect; 245 rectangles [j].height = srect; 246 } 247 for(i = 0; i < 10; i++) { 248 for(j = 0; j < nrects; j++) { 249 rectangles [j].x = ((lrand48() % width) / srect) * srect; 250 rectangles [j].y = ((lrand48() % height) / srect) * srect; 251 } 252 XFillRectangles(dpy, mask, gc, rectangles, nrects); 253 XShapeCombineMask(dpy, blanket, ShapeBounding, 0, 0, mask, ShapeSet); 254 XFlush(dpy); 255 waitamoment(0.020); 256 } 257 XFreePixmap(dpy, mask); 258 XFreeGC(dpy, gc); 259 free(rectangles); 260} 261 262 263static void 264ZoomInWindow(TwmWindow *tmp_win, Window blanket) 265{ 266 Pixmap mask; 267 GC gc, gcn; 268 XGCValues gcv; 269 270 int i, nsteps = 20; 271 int w = tmp_win->frame_width; 272 int h = tmp_win->frame_height; 273 int step = (MAX(w, h)) / (2.0 * nsteps); 274 275 mask = XCreatePixmap(dpy, blanket, w, h, 1); 276 gcv.foreground = 1; 277 gc = XCreateGC(dpy, mask, GCForeground, &gcv); 278 gcv.function = GXclear; 279 gcn = XCreateGC(dpy, mask, GCForeground | GCFunction, &gcv); 280 281 for(i = 0; i < nsteps; i++) { 282 XFillRectangle(dpy, mask, gcn, 0, 0, w, h); 283 XFillArc(dpy, mask, gc, (w / 2) - ((nsteps - i) * step), 284 (h / 2) - ((nsteps - i) * step), 285 2 * (nsteps - i) * step, 286 2 * (nsteps - i) * step, 287 0, 360 * 64); 288 XShapeCombineMask(dpy, blanket, ShapeBounding, 0, 0, mask, ShapeSet); 289 XFlush(dpy); 290 waitamoment(0.020); 291 } 292} 293 294 295static void 296ZoomOutWindow(TwmWindow *tmp_win, Window blanket) 297{ 298 Pixmap mask; 299 GC gc; 300 XGCValues gcv; 301 302 int i, nsteps = 20; 303 int w = tmp_win->frame_width; 304 int h = tmp_win->frame_height; 305 int step = (MAX(w, h)) / (2.0 * nsteps); 306 307 mask = XCreatePixmap(dpy, blanket, w, h, 1); 308 gcv.foreground = 1; 309 gc = XCreateGC(dpy, mask, GCForeground, &gcv); 310 XFillRectangle(dpy, mask, gc, 0, 0, w, h); 311 gcv.function = GXclear; 312 XChangeGC(dpy, gc, GCFunction, &gcv); 313 314 for(i = 0; i < nsteps; i++) { 315 XFillArc(dpy, mask, gc, (w / 2) - (i * step), 316 (h / 2) - (i * step), 317 2 * i * step, 318 2 * i * step, 319 0, 360 * 64); 320 XShapeCombineMask(dpy, blanket, ShapeBounding, 0, 0, mask, ShapeSet); 321 XFlush(dpy); 322 waitamoment(0.020); 323 } 324} 325 326 327void 328FadeWindow(TwmWindow *tmp_win, Window blanket) 329{ 330 Pixmap mask, stipple; 331 GC gc; 332 XGCValues gcv; 333 static unsigned char stipple_bits[] = { 0x0F, 0x0F, 334 0xF0, 0xF0, 335 0x0F, 0x0F, 336 0xF0, 0xF0, 337 0x0F, 0x0F, 338 0xF0, 0xF0, 339 0x0F, 0x0F, 340 0xF0, 0xF0, 341 }; 342 int w = tmp_win->frame_width; 343 int h = tmp_win->frame_height; 344 345 stipple = XCreateBitmapFromData(dpy, blanket, (char *)stipple_bits, 8, 8); 346 mask = XCreatePixmap(dpy, blanket, w, h, 1); 347 gcv.background = 0; 348 gcv.foreground = 1; 349 gcv.stipple = stipple; 350 gcv.fill_style = FillOpaqueStippled; 351 gc = XCreateGC(dpy, mask, GCBackground | GCForeground | GCFillStyle | GCStipple, 352 &gcv); 353 XFillRectangle(dpy, mask, gc, 0, 0, w, h); 354 355 XShapeCombineMask(dpy, blanket, ShapeBounding, 0, 0, mask, ShapeSet); 356 XFlush(dpy); 357 waitamoment(0.10); 358 XFreePixmap(dpy, stipple); 359 XFlush(dpy); 360} 361 362 363static void 364SweepWindow(TwmWindow *tmp_win, Window blanket) 365{ 366 float step = 0.0; 367 int i, nsteps = 20; 368 int dir = 0, dist = tmp_win->frame_x, dist1; 369 370 dist1 = tmp_win->frame_y; 371 if(dist1 < dist) { 372 dir = 1; 373 dist = dist1; 374 } 375 dist1 = tmp_win->vs->w - (tmp_win->frame_x + tmp_win->frame_width); 376 if(dist1 < dist) { 377 dir = 2; 378 dist = dist1; 379 } 380 dist1 = tmp_win->vs->h - (tmp_win->frame_y + tmp_win->frame_height); 381 if(dist1 < dist) { 382 dir = 3; 383 dist = dist1; 384 ALLOW_DEAD_STORE(dist); 385 } 386 387 switch(dir) { 388 case 0: 389 step = tmp_win->frame_x + tmp_win->frame_width; 390 break; 391 case 1: 392 step = tmp_win->frame_y + tmp_win->frame_height; 393 break; 394 case 2: 395 step = tmp_win->vs->w - tmp_win->frame_x; 396 break; 397 case 3: 398 step = tmp_win->vs->h - tmp_win->frame_y; 399 break; 400 } 401 step /= (float) nsteps; 402 step /= (float) nsteps; 403 for(i = 0; i < 20; i++) { 404 int x = tmp_win->frame_x; 405 int y = tmp_win->frame_y; 406 switch(dir) { 407 case 0: 408 x -= i * i * step; 409 break; 410 case 1: 411 y -= i * i * step; 412 break; 413 case 2: 414 x += i * i * step; 415 break; 416 case 3: 417 y += i * i * step; 418 break; 419 } 420 XMoveWindow(dpy, blanket, x, y); 421 XFlush(dpy); 422 waitamoment(0.020); 423 } 424} 425 426 427 428/* 429 * Utils used by various bits above 430 */ 431 432/*********************************************************************** 433 * 434 * Procedure: 435 * Zoom - zoom in or out of an icon 436 * 437 * Inputs: 438 * wf - window to zoom from 439 * wt - window to zoom to 440 * 441 *********************************************************************** 442 */ 443static void 444Zoom(Window wf, Window wt) 445{ 446 int fx, fy, tx, ty; /* from, to */ 447 unsigned int fw, fh, tw, th; /* from, to */ 448 long dx, dy, dw, dh; 449 long z; 450 int j; 451 452 if((Scr->IconifyStyle != ICONIFY_NORMAL) || !Scr->DoZoom 453 || Scr->ZoomCount < 1) { 454 return; 455 } 456 457 if(wf == None || wt == None) { 458 return; 459 } 460 461 XGetGeometry(dpy, wf, &JunkRoot, &fx, &fy, &fw, &fh, &JunkBW, &JunkDepth); 462 XGetGeometry(dpy, wt, &JunkRoot, &tx, &ty, &tw, &th, &JunkBW, &JunkDepth); 463 464 dx = (long) tx - (long) fx; /* going from -> to */ 465 dy = (long) ty - (long) fy; /* going from -> to */ 466 dw = (long) tw - (long) fw; /* going from -> to */ 467 dh = (long) th - (long) fh; /* going from -> to */ 468 z = (long)(Scr->ZoomCount + 1); 469 470 for(j = 0; j < 2; j++) { 471 long i; 472 473 XDrawRectangle(dpy, Scr->Root, Scr->DrawGC, fx, fy, fw, fh); 474 for(i = 1; i < z; i++) { 475 int x = fx + (int)((dx * i) / z); 476 int y = fy + (int)((dy * i) / z); 477 unsigned width = (unsigned)(((long) fw) + (dw * i) / z); 478 unsigned height = (unsigned)(((long) fh) + (dh * i) / z); 479 480 XDrawRectangle(dpy, Scr->Root, Scr->DrawGC, 481 x, y, width, height); 482 } 483 XDrawRectangle(dpy, Scr->Root, Scr->DrawGC, tx, ty, tw, th); 484 } 485} 486 487 488static void 489ReMapOne(TwmWindow *t, TwmWindow *leader) 490{ 491 if(t->icon_on) { 492 Zoom(t->icon->w, t->frame); 493 } 494 else if(leader->icon) { 495 Zoom(leader->icon->w, t->frame); 496 } 497 498 if(!t->squeezed) { 499 XMapWindow(dpy, t->w); 500 } 501 t->mapped = true; 502#ifdef CAPTIVE 503 if(false && Scr->Root != Scr->CaptiveRoot) { /* XXX dubious test */ 504 ReparentWindow(dpy, t, WinWin, Scr->Root, t->frame_x, t->frame_y); 505 } 506#endif 507 if(!Scr->NoRaiseDeicon) { 508 OtpRaise(t, WinWin); 509 } 510 XMapWindow(dpy, t->frame); 511 SetMapStateProp(t, NormalState); 512 513 if(t->icon && t->icon->w) { 514 XUnmapWindow(dpy, t->icon->w); 515 IconDown(t); 516 if(Scr->ShrinkIconTitles) { 517 t->icon->title_shrunk = true; 518 } 519 } 520 if(t->iconmanagerlist) { 521 WList *wl; 522 523 for(wl = t->iconmanagerlist; wl != NULL; wl = wl->nextv) { 524 XUnmapWindow(dpy, wl->icon); 525 } 526 } 527 t->isicon = false; 528 t->icon_on = false; 529 WMapDeIconify(t); 530} 531 532 533/* 534 * Mostly internal util of iconification, but squeezing code needs it 535 * too. 536 */ 537void 538ReMapTransients(TwmWindow *tmp_win) 539{ 540 TwmWindow *t; 541 542 /* find t such that it is a transient or group member window */ 543 for(t = Scr->FirstWindow; t != NULL; t = t->next) { 544 if(t != tmp_win && 545 ((t->istransient && t->transientfor == tmp_win->w) || 546 (t->group == tmp_win->w && t->isicon))) { 547 ReMapOne(t, tmp_win); 548 } 549 } 550} 551 552 553/* 554 * Ditto previous note about squeezing. 555 */ 556void 557UnmapTransients(TwmWindow *tmp_win, bool iconify, long eventMask) 558{ 559 TwmWindow *t; 560 561 for(t = Scr->FirstWindow; t != NULL; t = t->next) { 562 if(t != tmp_win && 563 ((t->istransient && t->transientfor == tmp_win->w) || 564 t->group == tmp_win->w)) { 565 if(iconify) { 566 if(t->icon_on) { 567 Zoom(t->icon->w, tmp_win->icon->w); 568 } 569 else if(tmp_win->icon) { 570 Zoom(t->frame, tmp_win->icon->w); 571 } 572 } 573 574 /* 575 * Prevent the receipt of an UnmapNotify, since that would 576 * cause a transition to the Withdrawn state. 577 */ 578 t->mapped = false; 579 580 /* 581 * Note that here, we're setting masks relative to what we 582 * were passed, which is that of the window these are 583 * transient for, rather than relative to these windows' 584 * current masks. I believe in practice it's the same thing, 585 * and it saves getting attributes on each for masking. 586 * Still, a little odd... 587 */ 588 mask_out_event_mask(t->w, StructureNotifyMask, eventMask); 589 XUnmapWindow(dpy, t->w); 590 XUnmapWindow(dpy, t->frame); 591 restore_mask(t->w, eventMask); 592 593 if(t->icon && t->icon->w) { 594 XUnmapWindow(dpy, t->icon->w); 595 } 596 SetMapStateProp(t, IconicState); 597 if(t == Scr->Focus) { 598 SetFocus(NULL, EventTime); 599 if(! Scr->ClickToFocus) { 600 Scr->FocusRoot = true; 601 } 602 } 603 if(t->iconmanagerlist) { 604 XMapWindow(dpy, t->iconmanagerlist->icon); 605 } 606 t->isicon = true; 607 t->icon_on = false; 608 WMapIconify(t); 609 } 610 } 611} 612 613 614static void 615waitamoment(float timeout) 616{ 617 struct timeval timeoutstruct; 618 int usec = timeout * 1000000; 619 timeoutstruct.tv_usec = usec % (unsigned long) 1000000; 620 timeoutstruct.tv_sec = usec / (unsigned long) 1000000; 621 select(0, NULL, NULL, NULL, &timeoutstruct); 622} 623