captive.c revision 0bbfda8a
1/* 2 * Captive ctwm handling bits. 3 * 4 * Captive support makes use of several X properties on various windows. 5 * 6 * The WM_CTWMSLIST property is set on the root window (of the 7 * appropriate Screen) containing a \0-separated list of the names of the 8 * captive windows inside that ctwm. So this would show up in the root 9 * window of a captive ctwm as well, if it had more captives inside it. 10 * 11 * A WM_CTWM_ROOT_<captive_name> property is set on the root window (see 12 * previous) for each of the captive ctwm's, holding the Window XID for 13 * that captive's internal root window. The combination of WM_CTWMSLIST 14 * and WM_CTWM_ROOT_<name> can be used to find the windows each of the 15 * captive ctwms inside us. 16 * 17 * A WM_CTWM_ROOT is set by the captive ctwm on its created root window, 18 * holding the XID of itself. The same property is also set by the 19 * 'outside' ctwm on the frame of that window. These are used in the 20 * f.hypermove process, to find the window ID to move stuff into. I'm 21 * not quite sure why we're setting it on both; perhaps so the border 22 * counts as part of the inner window. 23 */ 24 25#include "ctwm.h" 26 27#include <stdio.h> 28#include <string.h> 29#include <stdlib.h> 30 31#include <X11/Xatom.h> 32#include <X11/Xmu/WinUtil.h> 33 34#include "captive.h" 35#include "events.h" 36#include "screen.h" 37#include "ctwm_atoms.h" 38#include "util.h" 39#include "vscreen.h" 40 41 42static char **GetCaptivesList(int scrnum); 43static void SetCaptivesList(int scrnum, char **clist); 44static void freeCaptivesList(char **clist); 45static Window CaptiveCtwmRootWindow(Window window); 46static bool DontRedirect(Window window); 47 48static Atom XA_WM_CTWM_ROOT_our_name; 49 50/* XXX Share with occupation.c? */ 51static XrmOptionDescRec table [] = { 52 {"-xrm", NULL, XrmoptionResArg, (XPointer) NULL}, 53}; 54 55 56/* 57 * Reparent a window over to a captive ctwm, if we should. 58 */ 59bool 60RedirectToCaptive(Window window) 61{ 62 unsigned long nitems, bytesafter; 63 Atom actual_type; 64 int actual_format; 65 Bool status; 66 char *str_type; 67 XrmValue value; 68 bool ret; 69 char *atomname; 70 XrmDatabase db = NULL; 71 72 /* NOREDIRECT property set? Leave it alone. */ 73 if(DontRedirect(window)) { 74 return false; 75 } 76 77 /* Figure out what sort of -xrm stuff it might have */ 78 { 79 char **cliargv = NULL; 80 int cliargc; 81 82 /* Get its command-line */ 83 if(!XGetCommand(dpy, window, &cliargv, &cliargc)) { 84 /* Can't tell, bail */ 85 return false; 86 } 87 88 XrmParseCommand(&db, table, 1, "ctwm", &cliargc, cliargv); 89 if(cliargv) { 90 XFreeStringList(cliargv); 91 } 92 } 93 94 /* Bail if we didn't get any info */ 95 if(db == NULL) { 96 return false; 97 } 98 99 ret = false; 100 101 /* 102 * Check "-xrm ctwm.redirect" to see if that says what to do. It 103 * should contain a captive name. e.g., what ctwm was started with 104 * via --name, or an autogen'd name if no --name was given. 105 * */ 106 status = XrmGetResource(db, "ctwm.redirect", "Ctwm.Redirect", &str_type, 107 &value); 108 if((status == True) && (value.size != 0)) { 109 /* Yep, we're asked for one. Find it. */ 110 Window *prop; 111 Atom XA_WM_CTWM_ROOT_name; 112 int gpret; 113 114 asprintf(&atomname, "WM_CTWM_ROOT_%s", value.addr); 115 /* 116 * Set only_if_exists to True: the atom for the requested 117 * captive ctwm won't exist if the captive ctwm itself does not exist. 118 * There is no reason to go and create random atoms just to 119 * check. 120 */ 121 XA_WM_CTWM_ROOT_name = XInternAtom(dpy, atomname, True); 122 free(atomname); 123 124 /* 125 * Got the atom? Lookup the property it keys for, which holds a 126 * Window identifier. 127 * */ 128 gpret = !Success; 129 prop = NULL; 130 if(XA_WM_CTWM_ROOT_name != None) { 131 gpret = XGetWindowProperty(dpy, Scr->Root, XA_WM_CTWM_ROOT_name, 132 0L, 1L, False, AnyPropertyType, 133 &actual_type, &actual_format, 134 &nitems, &bytesafter, 135 (unsigned char **)&prop); 136 } 137 138 /* 139 * Got the property? Make sure it's the right type. If so, make 140 * sure the window it points at exists. 141 */ 142 if(gpret == Success 143 && actual_type == XA_WINDOW && actual_format == 32 && 144 nitems == 1 /*&& bytesafter == 0*/) { 145 Window newroot = *prop; 146 XWindowAttributes dummy_wa; 147 148 if(XGetWindowAttributes(dpy, newroot, &dummy_wa)) { 149 /* Well, it must be where we should redirect to, so do it */ 150 XReparentWindow(dpy, window, newroot, 0, 0); 151 XMapWindow(dpy, window); 152 ret = true; 153 } 154 } 155 if(prop != NULL) { 156 XFree(prop); 157 } 158 159 /* XXX Should we return here if we did the Reparent? */ 160 } 161 162 163 /* 164 * Check ctwm.rootWindow; it may contain a (hex) X window identifier, 165 * which we should parent into. 166 * */ 167 status = XrmGetResource(db, "ctwm.rootWindow", "Ctwm.RootWindow", &str_type, 168 &value); 169 if((status == True) && (value.size != 0)) { 170 char rootw [32]; 171 unsigned long int scanned; 172 173 safe_strncpy(rootw, value.addr, sizeof(rootw)); 174 if(sscanf(rootw, "%lx", &scanned) == 1) { 175 Window newroot = scanned; 176 XWindowAttributes dummy_wa; 177 178 if(XGetWindowAttributes(dpy, newroot, &dummy_wa)) { 179 XReparentWindow(dpy, window, newroot, 0, 0); 180 XMapWindow(dpy, window); 181 ret = true; 182 } 183 } 184 } 185 186 /* Cleanup xrm bits */ 187 XrmDestroyDatabase(db); 188 189 /* Whatever we found */ 190 return ret; 191} 192 193 194/* 195 * Get the list of captive ctwm's we know about on a screen. 196 * 197 * Use freeCaptivesList() to clean up the return value. 198 */ 199static char ** 200GetCaptivesList(int scrnum) 201{ 202 unsigned char *prop, *p; 203 unsigned long bytesafter; 204 unsigned long len; 205 Atom actual_type; 206 int actual_format; 207 char **ret; 208 int count; 209 int i, l; 210 Window root; 211 212 root = RootWindow(dpy, scrnum); 213 if(XGetWindowProperty(dpy, root, XA_WM_CTWMSLIST, 0L, 512, 214 False, XA_STRING, &actual_type, &actual_format, &len, 215 &bytesafter, &prop) != Success) { 216 return NULL; 217 } 218 if(len == 0) { 219 return NULL; 220 } 221 222 count = 0; 223 p = prop; 224 l = 0; 225 while(l < len) { 226 l += strlen((char *)p) + 1; 227 p += strlen((char *)p) + 1; 228 count++; 229 } 230 ret = calloc(count + 1, sizeof(char *)); 231 232 p = prop; 233 l = 0; 234 i = 0; 235 while(l < len) { 236 ret [i++] = strdup((char *) p); 237 l += strlen((char *)p) + 1; 238 p += strlen((char *)p) + 1; 239 } 240 ret [i] = NULL; 241 XFree(prop); 242 243 return ret; 244} 245 246 247/* 248 * Free GetCaptivesList() return. 249 */ 250static void 251freeCaptivesList(char **clist) 252{ 253 if(clist == NULL) { 254 return; 255 } 256 257 for(char **tmp = clist ; *tmp != NULL ; tmp++) { 258 free(*tmp); 259 } 260 261 free(clist); 262} 263 264 265/* 266 * Set the WM_CTWMSLIST property with a set of captive ctwm's, so it can 267 * be retrieved from there later (say, by a GetCaptivesList() call). 268 */ 269static void 270SetCaptivesList(int scrnum, char **clist) 271{ 272 unsigned long len; 273 char **cl; 274 char *s, *slist; 275 Window root = RootWindow(dpy, scrnum); 276 277 cl = clist; 278 len = 0; 279 while(*cl) { 280 len += strlen(*cl++) + 1; 281 } 282 if(len == 0) { 283 XDeleteProperty(dpy, root, XA_WM_CTWMSLIST); 284 return; 285 } 286 slist = calloc(len, sizeof(char)); 287 cl = clist; 288 s = slist; 289 while(*cl) { 290 strcpy(s, *cl); 291 s += strlen(*cl); 292 *s++ = '\0'; 293 cl++; 294 } 295 XChangeProperty(dpy, root, XA_WM_CTWMSLIST, XA_STRING, 8, 296 PropModeReplace, (unsigned char *) slist, len); 297 free(slist); 298} 299 300 301/* 302 * Add ourselves to the list of captive ctwms. Called during startup 303 * when --window is given. Returns the captive name, because we may not 304 * have been given one explicitly (in cptname), and so it may have been 305 * autogen'd. 306 */ 307char * 308AddToCaptiveList(const char *cptname) 309{ 310 int i, count; 311 char **clist, **cl, **newclist; 312 int busy [32]; 313 char *atomname; 314 int scrnum = Scr->screen; 315 Window croot = Scr->Root; 316 Window root; 317 char *rcname; 318 319 for(i = 0; i < 32; i++) { 320 busy [i] = 0; 321 } 322 323 /* 324 * Go through our current captives to see what's taken 325 */ 326 clist = GetCaptivesList(scrnum); 327 cl = clist; 328 count = 0; 329 while(cl && *cl) { 330 count++; 331 332 /* 333 * If we're not given a cptname, we use this loop to mark up 334 * which auto-gen'd names have been used. 335 */ 336 if(!cptname) { 337 if(!strncmp(*cl, "ctwm-", 5)) { 338 int r, n; 339 r = sscanf(*cl, "ctwm-%d", &n); 340 cl++; 341 if(r != 1) { 342 continue; 343 } 344 if((n < 0) || (n > 31)) { 345 continue; 346 } 347 busy [n] = 1; 348 } 349 else { 350 cl++; 351 } 352 continue; 353 } 354 355 /* 356 * If we do have a cptname, and a captive already has the 357 * requested name, bomb 358 */ 359 if(!strcmp(*cl, cptname)) { 360 fprintf(stderr, "A captive ctwm with name %s is already running\n", 361 cptname); 362 exit(1); 363 } 364 cl++; 365 } 366 367 368 /* 369 * If we weren't given a name, find an available autogen one. If we 370 * were, just dup it for our return value. 371 */ 372 if(!cptname) { 373 for(i = 0; i < 32; i++) { 374 if(!busy [i]) { 375 break; 376 } 377 } 378 if(i == 32) { /* no one can tell we didn't try hard */ 379 fprintf(stderr, "Cannot find a suitable name for captive ctwm\n"); 380 exit(1); 381 } 382 asprintf(&rcname, "ctwm-%d", i); 383 } 384 else { 385 rcname = strdup(cptname); 386 if(rcname == NULL) { 387 fprintf(stderr, "strdup() for rcname failed!\n"); 388 abort(); 389 } 390 } 391 392 393 /* Put together new list of captives */ 394 newclist = calloc(count + 2, sizeof(char *)); 395 for(i = 0; i < count; i++) { 396 newclist[i] = strdup(clist[i]); 397 } 398 newclist[count] = strdup(rcname); 399 newclist[count + 1] = NULL; 400 SetCaptivesList(scrnum, newclist); 401 freeCaptivesList(clist); 402 freeCaptivesList(newclist); 403 404 /* Stash property/atom of our captivename */ 405 root = RootWindow(dpy, scrnum); 406 asprintf(&atomname, "WM_CTWM_ROOT_%s", rcname); 407 XA_WM_CTWM_ROOT_our_name = XInternAtom(dpy, atomname, False); 408 free(atomname); 409 XChangeProperty(dpy, root, XA_WM_CTWM_ROOT_our_name, XA_WINDOW, 32, 410 PropModeReplace, (unsigned char *) &croot, 1); 411 412 /* 413 * Tell our caller the name we wound up with, in case they didn't 414 * give us one we could use. 415 */ 416 return rcname; 417} 418 419 420/* 421 * Take something (in practice, always ourselves) out of the list of 422 * running captives. Called during shutdown. 423 */ 424void 425RemoveFromCaptiveList(const char *cptname) 426{ 427 char **clist; 428 int scrnum = Scr->screen; 429 Window root = RootWindow(dpy, scrnum); 430 431 /* If we're not apparently captive, there's nothing to do */ 432 if(!cptname || XA_WM_CTWM_ROOT_our_name == None) { 433 return; 434 } 435 436 /* Take us out of the captives list in WM_CTWMSLIST */ 437 clist = GetCaptivesList(scrnum); 438 if(clist && *clist) { 439 char **cl = clist; 440 char **newclist; 441 int count; 442 bool found; 443 444 /* How many are there? */ 445 count = 0; 446 found = false; 447 while(*cl) { 448 if(strcmp(*cl, cptname) == 0) { 449 found = true; 450 } 451 count++; 452 cl++; 453 } 454 455 /* If we weren't there, there's nothing to do */ 456 if(!found) { 457 freeCaptivesList(clist); 458 return; 459 } 460 461 /* 462 * Make a new list without cptname in it. A list with (count) 463 * items needs (count+1) for the trailing NULL, but we know we're 464 * in it and removing ourself, so we only need ((count-1)+1). 465 * 466 * Note that we're _not_ strdup()'ing into newclist, just 467 * sticking a pointer to the existing string inside clist. Then 468 * we only have to free() newclist itself, because there's 469 * nothing new inside it. We explicitly do _NOT_ want to 470 * freeCaptivesList() it, since that would free the internals, 471 * and then when we fCL(clist) it would try to double-free them. 472 */ 473 newclist = calloc(count, sizeof(char *)); 474 cl = clist; 475 count = 0; 476 while(*cl) { 477 if(!strcmp(*cl, cptname)) { 478 cl++; 479 continue; 480 } 481 newclist [count++] = *cl; 482 cl++; 483 } 484 newclist [count] = NULL; 485 SetCaptivesList(scrnum, newclist); 486 free(newclist); 487 } 488 freeCaptivesList(clist); 489 490 /* And delete our CTWM_ROOT_x property */ 491 XDeleteProperty(dpy, root, XA_WM_CTWM_ROOT_our_name); 492} 493 494 495/* 496 * Call from AddWindow() on the 'outside'; if this new window is a 497 * captive ctwm running inside us, copy its WM_CTWM_ROOT property to the 498 * frame window we're creating around it. It's a little unclear why 499 * we're doing this; x-ref comment at top of file. 500 */ 501void 502SetPropsIfCaptiveCtwm(TwmWindow *win) 503{ 504 Window window = win->w; 505 Window frame = win->frame; 506 507 if(!CaptiveCtwmRootWindow(window)) { 508 return; 509 } 510 511 XChangeProperty(dpy, frame, XA_WM_CTWM_ROOT, XA_WINDOW, 32, 512 PropModeReplace, (unsigned char *) &window, 1); 513} 514 515 516/* 517 * Get the WM_CTWM_ROOT property of a window; that tells us whether it 518 * thinks it's a captive ctwm, and if so, what it thinks its root window 519 * is. 520 */ 521static Window 522CaptiveCtwmRootWindow(Window window) 523{ 524 Window *prop; 525 Window w; 526 unsigned long bytesafter; 527 unsigned long len; 528 Atom actual_type; 529 int actual_format; 530 531 if(XGetWindowProperty(dpy, window, XA_WM_CTWM_ROOT, 0L, 1L, 532 False, XA_WINDOW, &actual_type, &actual_format, &len, 533 &bytesafter, (unsigned char **)&prop) != Success) { 534 return ((Window)0); 535 } 536 if(len == 0) { 537 return ((Window)0); 538 } 539 w = *prop; 540 XFree(prop); 541 return w; 542} 543 544 545/* 546 * Get info about the captive CTWM instance under the cursor. Called 547 * during the f.hypermove process. 548 */ 549CaptiveCTWM 550GetCaptiveCTWMUnderPointer(void) 551{ 552 Window root; 553 Window child, croot; 554 CaptiveCTWM cctwm; 555 char *rname; 556 557 root = RootWindow(dpy, Scr->screen); 558 while(1) { 559 XQueryPointer(dpy, root, &JunkRoot, &child, 560 &JunkX, &JunkY, &JunkX, &JunkY, &JunkMask); 561 if(child && (croot = CaptiveCtwmRootWindow(child))) { 562 root = croot; 563 continue; 564 } 565 cctwm.root = root; 566 567 /* 568 * We indirect through the extra var here for probably 569 * unnecessary reasons; X resources (like that from XFetchName) 570 * are specified to be freed via XFree(), not via free(). And we 571 * don't want our callers to have to know that (or worse, know to 572 * do it SOMEtimes, since we also might create it ourselves with 573 * strdup()). So eat the extra allocation/copy and insulate 574 * callers. 575 */ 576 XFetchName(dpy, root, &rname); 577 if(rname) { 578 cctwm.name = strdup(rname); 579 XFree(rname); 580 } 581 else { 582 cctwm.name = strdup("Root"); 583 } 584 585 return (cctwm); 586 } 587} 588 589 590/* 591 * We set a NOREDIRECT property on windows in certain situations as a 592 * result of a f.hypermove. That gets checked during 593 * RedirectToCaptive(), causing it to to not mess with the window. 594 * 595 * XXX I'm not sure this actually makes any sense; RTC() only gets called 596 * at the beginning of AddWindow(), only if ctwm isn't running captive. 597 * So the upshot is that this causes AddWindow() to do nothing and return 598 * NULL, in the case that a window was hypermoved from a captive ctwm 599 * into a non-captive ctwm. 600 * 601 * That's OK I think, because all the AddWindow() stuff would have 602 * already been done for it, so there's nothing to do? But this suggests 603 * that there's leakage happening; we keep a TwmWindow struct around in 604 * the "old" ctwm when it's moved into a new one, and since AddWindow() 605 * only does the condition if we're a non-captive ctwm, it means the 606 * _captive_ ctwm recreates a new one every time it's hypermoved in? 607 */ 608void 609SetNoRedirect(Window window) 610{ 611 XChangeProperty(dpy, window, XA_WM_NOREDIRECT, XA_STRING, 8, 612 PropModeReplace, (unsigned char *) "Yes", 4); 613} 614 615static bool 616DontRedirect(Window window) 617{ 618 unsigned char *prop; 619 unsigned long bytesafter; 620 unsigned long len; 621 Atom actual_type; 622 int actual_format; 623 624 if(XGetWindowProperty(dpy, window, XA_WM_NOREDIRECT, 0L, 1L, 625 False, XA_STRING, &actual_type, &actual_format, &len, 626 &bytesafter, &prop) != Success) { 627 return false; 628 } 629 if(len == 0) { 630 return false; 631 } 632 XFree(prop); 633 return true; 634} 635 636 637/* 638 * Handling of a ConfigureNotify for a captive root window. 639 */ 640void 641ConfigureCaptiveRootWindow(XEvent *ev) 642{ 643 Window root, child; 644 int x, y; 645 unsigned int w, h, bw, d, oldw, oldh; 646 647 /* Guard */ 648 if(!CLarg.is_captive) { 649 fprintf(stderr, "BUG: %s(): Shouldn't get called unless captive.\n", 650 __func__); 651 return; 652 } 653 654 XGetGeometry(dpy, Scr->CaptiveRoot, &root, &x, &y, &w, &h, &bw, &d); 655 XTranslateCoordinates(dpy, Scr->CaptiveRoot, root, 0, 0, &Scr->crootx, 656 &Scr->crooty, &child); 657 658 oldw = Scr->crootw; 659 oldh = Scr->crooth; 660 Scr->crootw = ev->xconfigure.width; 661 Scr->crooth = ev->xconfigure.height; 662#if 0 663 fprintf(stderr, "%s(): cx = %d, cy = %d, cw = %d, ch = %d\n", 664 __func__, Scr->crootx, Scr->crooty, Scr->crootw, Scr->crooth); 665#endif 666 if(Scr->currentvs) { 667 Scr->rootx = Scr->crootx + Scr->currentvs->x; 668 Scr->rooty = Scr->crooty + Scr->currentvs->y; 669 } 670 Scr->rootw = Scr->crootw; 671 Scr->rooth = Scr->crooth; 672 673 /* 674 * XXX This is a little weird, and in my experience _always_ triggers 675 * when a captive window starts up. So what's the point? 676 */ 677 if((Scr->crootw != oldw) || (Scr->crooth != oldh)) { 678 fprintf(stderr, "%s: You cannot change root window geometry " 679 "with virtual screens active,\n" 680 "from now on, the ctwm behaviour is unpredictable.\n", 681 ProgramName); 682 } 683} 684 685 686/* 687 * Adopt a window into the calling captive ctwm. Backend for 688 * f.adoptwindow function. 689 */ 690void AdoptWindow(void) 691{ 692 unsigned long data [2]; 693 Window localroot, w; 694 unsigned char *prop; 695 unsigned long bytesafter; 696 unsigned long len; 697 Atom actual_type; 698 int actual_format; 699 XEvent event; 700 Window root, parent, child, *children; 701 unsigned int nchildren, key_buttons; 702 int root_x, root_y, win_x, win_y; 703 int ret; 704 bool savedRestartPreviousState; 705 706 localroot = w = RootWindow(dpy, Scr->screen); 707 XGrabPointer(dpy, localroot, False, 708 ButtonPressMask | ButtonReleaseMask, 709 GrabModeAsync, GrabModeAsync, 710 None, Scr->SelectCursor, CurrentTime); 711 712 XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask, &event); 713 child = event.xbutton.subwindow; 714 while(1) { 715 if(child == (Window) 0) { 716 break; 717 } 718 719 w = XmuClientWindow(dpy, child); 720 ret = XGetWindowProperty(dpy, w, XA_WM_WORKSPACESLIST, 0L, 512, 721 False, XA_STRING, &actual_type, &actual_format, &len, 722 &bytesafter, &prop); 723 XFree(prop); /* Don't ever do anything with it */ 724 if(ret != Success) { 725 break; 726 } 727 if(len == 0) { /* it is not a local root window */ 728 break; /* it is not a local root window */ 729 } 730 localroot = w; 731 XQueryPointer(dpy, localroot, &root, &child, &root_x, &root_y, 732 &win_x, &win_y, &key_buttons); 733 } 734 XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask, &event); 735 XUngrabPointer(dpy, CurrentTime); 736 737 if(localroot == Scr->Root) { 738 return; 739 } 740 if(w == localroot) { /* try to not adopt an ancestor */ 741 XQueryTree(dpy, Scr->Root, &root, &parent, &children, &nchildren); 742 while(parent != (Window) 0) { 743 XFree(children); 744 if(w == parent) { 745 return; 746 } 747 XQueryTree(dpy, parent, &root, &parent, &children, &nchildren); 748 } 749 XFree(children); 750 if(w == root) { 751 return; 752 } 753 } 754 if(localroot == RootWindow(dpy, Scr->screen)) { 755 XWithdrawWindow(dpy, w, Scr->screen); 756 } 757 else { 758 XUnmapWindow(dpy, w); 759 } 760 XReparentWindow(dpy, w, Scr->Root, 0, 0); 761 762 data [0] = (unsigned long) NormalState; 763 data [1] = (unsigned long) None; 764 765 XChangeProperty(dpy, w, XA_WM_STATE, XA_WM_STATE, 32, 766 PropModeReplace, (unsigned char *) data, 2); 767 XFlush(dpy); 768 /* 769 * We don't want to "restore" the occupation that the window had 770 * in its former environment. For one, the names of the workspaces 771 * may be different. And if not, the window will initially be 772 * shown in the current workspace, which may be at odds with that 773 * occupation (and confusion ensues). 774 * 775 * Hypermove has the same problem, but that is a "push" operation 776 * (initiated by the originating window manager) so we don't know 777 * when it happens... 778 */ 779 savedRestartPreviousState = RestartPreviousState; 780 RestartPreviousState = false; 781 SimulateMapRequest(w); 782 RestartPreviousState = savedRestartPreviousState; 783 return; 784} 785