1/* 2 * Copyright 2014 Olaf Seibert 3 */ 4 5/* 6 * Implements some of the Extended Window Manager Hints, as (extremely 7 * poorly) documented at 8 * http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html . 9 * In fact, the wiki page that refers to that as being the current version 10 * (http://www.freedesktop.org/wiki/Specifications/wm-spec/) 11 * neglects to tell us there are newer versions 1.4 and 1.5 at 12 * http://standards.freedesktop.org/wm-spec/wm-spec-1.5.html 13 * (which has a listable directory that I discovered by accident). 14 * The same wiki page also has lots of dead links to a CVS repository. 15 * Nevertheless, it is where one ends up if one starts at 16 * http://www.freedesktop.org/wiki/Specifications/ . 17 * 18 * EWMH is an extension to the ICCCM (Inter-Client Communication 19 * Conventions Manual). 20 * http://tronche.com/gui/x/icccm/ 21 * 22 * To fill in lots of details, the source code of other window managers 23 * has been consulted. 24 */ 25 26#include "ctwm.h" 27 28#include <stdio.h> 29#include <stdlib.h> 30#include <time.h> 31#include <stdint.h> 32#include <assert.h> 33 34#include <X11/Xatom.h> 35#include <X11/extensions/shape.h> 36 37#include "ctwm_atoms.h" 38#include "ctwm_shutdown.h" 39#include "ewmh_atoms.h" 40#include "screen.h" 41#include "events.h" 42#include "event_handlers.h" 43#include "functions_defs.h" 44#include "icons.h" 45#include "otp.h" 46#include "image.h" 47#include "list.h" 48#include "functions.h" 49#include "occupation.h" 50#include "r_layout.h" 51#include "util.h" 52#include "vscreen.h" 53#include "win_iconify.h" 54#include "win_ops.h" 55#include "win_resize.h" 56#include "win_utils.h" 57#include "workspace_utils.h" 58 59/* #define DEBUG_EWMH */ 60 61Atom XEWMHAtom[NUM_EWMH_XATOMS]; 62 63#define NET_WM_STATE_REMOVE 0 /* remove/unset property */ 64#define NET_WM_STATE_ADD 1 /* add/set property */ 65#define NET_WM_STATE_TOGGLE 2 /* toggle property */ 66 67#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 68#define _NET_WM_MOVERESIZE_SIZE_TOP 1 69#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 70#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 71#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 72#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 73#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 74#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 75#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ 76#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ 77#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ 78#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ 79 80static Image *ExtractIcon(ScreenInfo *scr, unsigned long *prop, int width, 81 int height); 82static void EwmhClientMessage_NET_WM_DESKTOP(XClientMessageEvent *msg); 83static void EwmhClientMessage_NET_WM_STATE(XClientMessageEvent *msg); 84static void EwmhClientMessage_NET_ACTIVE_WINDOW(XClientMessageEvent *msg); 85static void EwmhClientMessage_NET_WM_MOVERESIZE(XClientMessageEvent *msg); 86static void EwmhClientMessage_NET_CLOSE_WINDOW(XClientMessageEvent *msg); 87static XEvent synth_btnevent_for_moveresize(TwmWindow *twm_win); 88static unsigned long EwmhGetWindowProperty(Window w, Atom name, Atom type); 89static void EwmhGetStrut(TwmWindow *twm_win, bool update); 90static void EwmhRemoveStrut(TwmWindow *twm_win); 91static void EwmhSet_NET_WORKAREA(ScreenInfo *scr); 92static int EwmhGet_NET_WM_STATE(TwmWindow *twm_win); 93static void EwmhClientMessage_NET_WM_STATEchange(TwmWindow *twm_win, int change, 94 int newVal); 95 96#define ALL_WORKSPACES 0xFFFFFFFFU 97 98static void SendPropertyMessage(Window to, Window about, 99 Atom messagetype, 100 long l0, long l1, long l2, long l3, long l4, 101 long mask) 102{ 103 XEvent e; 104 105 e.xclient.type = ClientMessage; 106 e.xclient.message_type = messagetype; 107 e.xclient.display = dpy; 108 e.xclient.window = about; 109 e.xclient.format = 32; 110 e.xclient.data.l[0] = l0; 111 e.xclient.data.l[1] = l1; 112 e.xclient.data.l[2] = l2; 113 e.xclient.data.l[3] = l3; 114 e.xclient.data.l[4] = l4; 115 116 XSendEvent(dpy, to, False, mask, &e); 117} 118 119static void EwmhInitAtoms(void) 120{ 121 XInternAtoms(dpy, XEWMHAtomNames, NUM_EWMH_XATOMS, False, XEWMHAtom); 122} 123 124static bool caughtError; 125 126static int CatchError(Display *display, XErrorEvent *event) 127{ 128 caughtError = true; 129 return 0; 130} 131 132void EwmhInit(void) 133{ 134 EwmhInitAtoms(); 135} 136 137/* 138 * Force-generate some event, so that we know the current time. 139 * 140 * Suggested in the ICCCM: 141 * http://tronche.com/gui/x/icccm/sec-2.html#s-2.1 142 */ 143 144static void GenerateTimestamp(ScreenInfo *scr) 145{ 146 XEvent event; 147 struct timespec tosleep; 148 int timeout = 200; /* 0.2 seconds in ms */ 149 int found; 150 151 /* Sleep in 10ms chunks */ 152 tosleep.tv_sec = 0; 153 tosleep.tv_nsec = (10 * 1000 * 1000); 154 155 if(EventTime > 0) { 156 return; 157 } 158 159 XChangeProperty(dpy, scr->icccm_Window, 160 XA_WM_CLASS, XA_STRING, 161 8, PropModeAppend, NULL, 0); 162 163 while(timeout > 0) { 164 found = XCheckTypedWindowEvent(dpy, scr->icccm_Window, PropertyNotify, &event); 165 if(found) { 166 break; 167 } 168 nanosleep(&tosleep, NULL); 169 timeout -= 10; 170 } 171 172 if(found) { 173#ifdef DEBUG_EWMH 174 fprintf(stderr, "GenerateTimestamp: time = %ld, timeout left = %d\n", 175 event.xproperty.time, timeout); 176#endif /* DEBUG_EWMH */ 177 if(EventTime < event.xproperty.time) { 178 EventTime = event.xproperty.time; 179 } 180 } 181} 182 183/* 184 * Perform the "replace the window manager" protocol, as vaguely hinted 185 * at by the ICCCM section 4.3. 186 * http://tronche.com/gui/x/icccm/sec-4.html#s-4.3 187 * 188 * We do want to run through this even if we're not running with 189 * --replace ourselves, because it also sets up the bits for other 190 * invocations to --replace us. 191 * 192 * TODO: convert the selection to atom VERSION. 193 */ 194static bool EwmhReplaceWM(ScreenInfo *scr) 195{ 196 char atomname[32]; 197 Atom wmAtom; 198 Window selectionOwner; 199 200 snprintf(atomname, sizeof(atomname), "WM_S%d", scr->screen); 201 wmAtom = XInternAtom(dpy, atomname, False); 202 203 selectionOwner = XGetSelectionOwner(dpy, wmAtom); 204 if(selectionOwner == scr->icccm_Window) { 205 selectionOwner = None; 206 } 207 208 if(selectionOwner != None) { 209 XErrorHandler oldHandler; 210 211 /* 212 * Check if that owner still exists, and if it does, we want 213 * StructureNotify-kind events from it. 214 */ 215 caughtError = false; 216 oldHandler = XSetErrorHandler(CatchError); 217 218 XSelectInput(dpy, selectionOwner, StructureNotifyMask); 219 XSync(dpy, False); 220 221 XSetErrorHandler(oldHandler); 222 223 if(caughtError) { 224 selectionOwner = None; 225 } 226 } 227 228 /* 229 * If something else is owning things and we're not running 230 * --replace, give up now and return failure. 231 */ 232 if(selectionOwner != None && !CLarg.ewmh_replace) { 233#ifdef DEBUG_EWMH 234 fprintf(stderr, "A window manager is already running on screen %d\n", 235 scr->screen); 236#endif 237 return false; 238 } 239 240 /* We're asked to --replace, so try to do so */ 241 XSetSelectionOwner(dpy, wmAtom, scr->icccm_Window, CurrentTime); 242 243 if(XGetSelectionOwner(dpy, wmAtom) != scr->icccm_Window) { 244 fprintf(stderr, "Did not get window manager selection on screen %d\n", 245 scr->screen); 246 return false; 247 } 248 249 /* 250 * If there was a previous selection owner, wait for it 251 * to go away. 252 */ 253 254 if(selectionOwner != None) { 255 int timeout = 10 * 1000; /* 10 seconds in ms */ 256 XEvent event; 257 struct timespec tosleep; 258 259 /* Sleep in 100ms chunks */ 260 tosleep.tv_sec = 0; 261 tosleep.tv_nsec = (100 * 1000 * 1000); 262 263 while(timeout > 0) { 264 265 int found = XCheckTypedWindowEvent(dpy, selectionOwner, DestroyNotify, &event); 266 if(found) { 267 break; 268 } 269 nanosleep(&tosleep, NULL); 270 timeout -= 100; 271 } 272 273 if(timeout <= 0) { 274 fprintf(stderr, "Timed out waiting for other window manager " 275 "on screen %d to quit\n", 276 scr->screen); 277 return false; 278 } 279 } 280 281 /* 282 * Send a message to confirm we're now managing the screen. 283 * ICCCM, end of chapter 2, section "Manager Selections". 284 * http://tronche.com/gui/x/icccm/sec-2.html#s-2.8 285 * 286 * ICCCM says StructureNotifyMask, 287 * OpenBox says SubstructureNotifyMask. 288 */ 289 290 GenerateTimestamp(scr); 291 292 SendPropertyMessage(scr->XineramaRoot, scr->XineramaRoot, 293 XA_MANAGER, EventTime, wmAtom, scr->icccm_Window, 0, 0, 294 StructureNotifyMask); 295 296 return true; 297} 298 299/* 300 * This function is called very early in initialisation. 301 * 302 * Only scr->screen and scr->XineramaRoot are valid: we want to know if 303 * it makes sense to continue with the full initialisation. 304 * 305 * Create the ICCCM window that owns the WM_Sn selection. 306 */ 307bool EwmhInitScreenEarly(ScreenInfo *scr) 308{ 309 XSetWindowAttributes attrib; 310 311#ifdef DEBUG_EWMH 312 fprintf(stderr, "EwmhInitScreenEarly: XCreateWindow\n"); 313#endif 314 attrib.event_mask = PropertyChangeMask; 315 attrib.override_redirect = True; 316 scr->icccm_Window = XCreateWindow(dpy, scr->XineramaRoot, 317 -100, -100, 1, 1, 0, 318 CopyFromParent, InputOutput, 319 CopyFromParent, 320 CWEventMask | CWOverrideRedirect, 321 &attrib); 322 323 XMapWindow(dpy, scr->icccm_Window); 324 XLowerWindow(dpy, scr->icccm_Window); 325 326#ifdef DEBUG_EWMH 327 fprintf(stderr, "EwmhInitScreenEarly: call EwmhReplaceWM\n"); 328#endif 329 if(!EwmhReplaceWM(scr)) { 330 XDestroyWindow(dpy, scr->icccm_Window); 331 scr->icccm_Window = None; 332 333#ifdef DEBUG_EWMH 334 fprintf(stderr, "EwmhInitScreenEarly: return false\n"); 335#endif 336 return false; 337 } 338 339#ifdef DEBUG_EWMH 340 fprintf(stderr, "EwmhInitScreenEarly: return true\n"); 341#endif 342 return true; 343} 344 345/* 346 * This initialisation is called late, when scr has been set up 347 * completely. 348 */ 349void EwmhInitScreenLate(ScreenInfo *scr) 350{ 351 long data[2]; 352 353 /* Set _NET_SUPPORTING_WM_CHECK on root window */ 354 data[0] = scr->icccm_Window; 355 XChangeProperty(dpy, scr->XineramaRoot, 356 XA__NET_SUPPORTING_WM_CHECK, XA_WINDOW, 357 32, PropModeReplace, 358 (unsigned char *)data, 1); 359 360 /* 361 * Set properties on the window; 362 * this also belongs with _NET_SUPPORTING_WM_CHECK 363 */ 364 XChangeProperty(dpy, scr->icccm_Window, 365 XA__NET_WM_NAME, XA_UTF8_STRING, 366 8, PropModeReplace, 367 (unsigned char *)"ctwm", 4); 368 369 data[0] = scr->icccm_Window; 370 XChangeProperty(dpy, scr->icccm_Window, 371 XA__NET_SUPPORTING_WM_CHECK, XA_WINDOW, 372 32, PropModeReplace, 373 (unsigned char *)data, 1); 374 375 /* 376 * Add supported properties to the root window. 377 */ 378 data[0] = 0; 379 data[1] = 0; 380 XChangeProperty(dpy, scr->XineramaRoot, 381 XA__NET_DESKTOP_VIEWPORT, XA_CARDINAL, 382 32, PropModeReplace, 383 (unsigned char *)data, 2); 384 385 data[0] = scr->rootw; 386 data[1] = scr->rooth; 387 XChangeProperty(dpy, scr->XineramaRoot, 388 XA__NET_DESKTOP_GEOMETRY, XA_CARDINAL, 389 32, PropModeReplace, 390 (unsigned char *)data, 2); 391 392 EwmhSet_NET_WORKAREA(scr); 393 394 if(scr->workSpaceManagerActive) { 395 data[0] = scr->workSpaceMgr.count; 396 } 397 else { 398 data[0] = 1; 399 } 400 401 XChangeProperty(dpy, scr->XineramaRoot, 402 XA__NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 403 32, PropModeReplace, 404 (unsigned char *)data, 1); 405 406 if(scr->workSpaceManagerActive) { 407 /* TODO: this is for the first Virtual Screen only... */ 408 /*data[0] = scr->workSpaceMgr.workSpaceWindowList->currentwspc->number; */ 409 data[0] = 0; 410 } 411 else { 412 data[0] = 0; 413 } 414 XChangeProperty(dpy, scr->XineramaRoot, 415 XA__NET_CURRENT_DESKTOP, XA_CARDINAL, 416 32, PropModeReplace, 417 (unsigned char *)data, 1); 418 419 EwmhSet_NET_SHOWING_DESKTOP(0); 420 421 long supported[30]; 422 int i = 0; 423 424 supported[i++] = XA__NET_SUPPORTING_WM_CHECK; 425 supported[i++] = XA__NET_DESKTOP_VIEWPORT; 426 supported[i++] = XA__NET_NUMBER_OF_DESKTOPS; 427 supported[i++] = XA__NET_CURRENT_DESKTOP; 428 supported[i++] = XA__NET_DESKTOP_GEOMETRY; 429 supported[i++] = XA__NET_WM_ICON; 430 supported[i++] = XA__NET_WM_DESKTOP; 431 supported[i++] = XA__NET_CLIENT_LIST; 432 supported[i++] = XA__NET_CLIENT_LIST_STACKING; 433 supported[i++] = XA__NET_WM_WINDOW_TYPE; 434 supported[i++] = XA__NET_WM_WINDOW_TYPE_NORMAL; 435 supported[i++] = XA__NET_WM_WINDOW_TYPE_DESKTOP; 436 supported[i++] = XA__NET_WM_WINDOW_TYPE_DOCK; 437 supported[i++] = XA__NET_WM_STRUT; 438 supported[i++] = XA__NET_WM_STRUT_PARTIAL; 439 supported[i++] = XA__NET_SHOWING_DESKTOP; 440 supported[i++] = XA__NET_WM_STATE; 441 supported[i++] = XA__NET_WM_STATE_MAXIMIZED_VERT; 442 supported[i++] = XA__NET_WM_STATE_MAXIMIZED_HORZ; 443 supported[i++] = XA__NET_WM_STATE_FULLSCREEN; 444 supported[i++] = XA__NET_ACTIVE_WINDOW; 445 supported[i++] = XA__NET_WORKAREA; 446 supported[i++] = XA__NET_WM_MOVERESIZE; 447 supported[i++] = XA__NET_WM_STATE_SHADED; 448 supported[i++] = XA__NET_WM_STATE_ABOVE; 449 supported[i++] = XA__NET_WM_STATE_BELOW; 450 supported[i++] = XA__NET_CLOSE_WINDOW; 451 452 XChangeProperty(dpy, scr->XineramaRoot, 453 XA__NET_SUPPORTED, XA_ATOM, 454 32, PropModeReplace, 455 (unsigned char *)supported, i); 456} 457 458 459#ifdef VSCREEN 460/* 461 * Set up the _NET_VIRTUAL_ROOTS property, which indicates that we're 462 * using virtual root windows. 463 * This applies only if we have multiple virtual screens. 464 * 465 * Also record this as a supported atom in _NET_SUPPORTED. 466 * 467 * Really, our virtual screens (with their virtual root windows) don't quite 468 * fit in the EWMH idiom. Several root window properties (such as 469 * _NET_CURRENT_DESKTOP) are more appropriate on the virtual root windows. But 470 * that is not where other clients would look for them. 471 * 472 * The idea seems to be that the virtual roots as used for workspaces (desktops 473 * in EWMH terminology) are only mapped one at a time. 474 */ 475void EwmhInitVirtualRoots(ScreenInfo *scr) 476{ 477 int numVscreens = scr->numVscreens; 478 479 if(numVscreens > 1) { 480 long *data; 481 long d0; 482 VirtualScreen *vs; 483 int i; 484 485 data = calloc(numVscreens, sizeof(long)); 486 487 for(vs = scr->vScreenList, i = 0; 488 vs != NULL && i < numVscreens; 489 vs = vs->next, i++) { 490 data[i] = vs->window; 491 } 492 493 XChangeProperty(dpy, scr->XineramaRoot, 494 XA__NET_VIRTUAL_ROOTS, XA_WINDOW, 495 32, PropModeReplace, 496 (unsigned char *)data, numVscreens); 497 498 /* This might fail, but what can we do about it? */ 499 500 free(data); 501 502 d0 = XA__NET_VIRTUAL_ROOTS; 503 XChangeProperty(dpy, scr->XineramaRoot, 504 XA__NET_SUPPORTED, XA_ATOM, 505 32, PropModeAppend, 506 (unsigned char *)&d0, 1); 507 } 508} 509#endif 510 511 512static void EwmhTerminateScreen(ScreenInfo *scr) 513{ 514 XDeleteProperty(dpy, scr->XineramaRoot, XA__NET_SUPPORTED); 515 516 /* 517 * Don't delete scr->icccm_Window; let it be deleted automatically 518 * when we terminate the X server connection. A replacement window 519 * manager may want to start working immediately after it has 520 * disappeared. 521 */ 522} 523 524/* 525 * Clear everything that needs to be cleared before we exit. 526 */ 527 528void EwmhTerminate(void) 529{ 530 int scrnum; 531 ScreenInfo *scr; 532 533 for(scrnum = 0; scrnum < NumScreens; scrnum++) { 534 if((scr = ScreenList[scrnum]) == NULL) { 535 continue; 536 } 537 EwmhTerminateScreen(scr); 538 } 539} 540 541/* 542 * Event handler: lost the WM_Sn selection 543 * (that's the only selection we have). 544 */ 545 546void EwmhSelectionClear(XSelectionClearEvent *sev) 547{ 548#ifdef DEBUG_EWMH 549 fprintf(stderr, "sev->window = %x\n", (unsigned)sev->window); 550#endif 551 DoShutdown(); 552} 553 554/* 555 * When accepting client messages to the root window, 556 * be accepting and accept both the real root window and the 557 * current virtual screen. 558 * 559 * Should perhaps also accept any other virtual screen. 560 */ 561bool EwmhClientMessage(XClientMessageEvent *msg) 562{ 563 if(msg->format != 32) { 564 return false; 565 } 566 567 /* Messages regarding any window */ 568 if(msg->message_type == XA__NET_WM_DESKTOP) { 569 EwmhClientMessage_NET_WM_DESKTOP(msg); 570 return true; 571 } 572 else if(msg->message_type == XA__NET_WM_STATE) { 573 EwmhClientMessage_NET_WM_STATE(msg); 574 return true; 575 } 576 else if(msg->message_type == XA__NET_ACTIVE_WINDOW) { 577 EwmhClientMessage_NET_ACTIVE_WINDOW(msg); 578 return true; 579 } 580 else if(msg->message_type == XA__NET_WM_MOVERESIZE) { 581 EwmhClientMessage_NET_WM_MOVERESIZE(msg); 582 return true; 583 } 584 else if(msg->message_type == XA__NET_CLOSE_WINDOW) { 585 EwmhClientMessage_NET_CLOSE_WINDOW(msg); 586 return true; 587 } 588 589 /* Messages regarding the root window */ 590 if(msg->window != Scr->XineramaRoot && 591 msg->window != Scr->Root) { 592#ifdef DEBUG_EWMH 593 fprintf(stderr, "Received unrecognized client message: %s\n", 594 XGetAtomName(dpy, msg->message_type)); 595#endif 596 return false; 597 } 598 599 if(msg->message_type == XA__NET_CURRENT_DESKTOP) { 600 GotoWorkSpaceByNumber(Scr->currentvs, msg->data.l[0]); 601 return true; 602 } 603 else if(msg->message_type == XA__NET_SHOWING_DESKTOP) { 604 ShowBackground(Scr->currentvs, msg->data.l[0] ? 1 : 0); 605 return true; 606 } 607 else { 608#ifdef DEBUG_EWMH 609 fprintf(stderr, "Received unrecognized client message about root window: %s\n", 610 XGetAtomName(dpy, msg->message_type)); 611#endif 612 } 613 614 return false; 615} 616 617/* 618 * The format of the _NET_WM_ICON property is 619 * 620 * [0] width 621 * [1] height 622 * height repetitions of 623 * row, which is 624 * width repetitions of 625 * pixel: ARGB 626 * repeat for next size. 627 * 628 * Some icons can be 256x256 CARDINALs which is 65536 CARDINALS! 629 * Therefore we fetch in pieces and skip the pixels of large icons 630 * until needed. 631 * 632 * First scan all sizes. Keep a record of the closest smaller and larger 633 * size. At the end, choose from one of those. 634 * Finally, go and fetch the pixel data. 635 */ 636 637Image *EwmhGetIcon(ScreenInfo *scr, TwmWindow *twm_win) 638{ 639 int fetch_offset; 640 Atom actual_type; 641 int actual_format; 642 unsigned long nitems, bytes_after; 643 unsigned long *prop; 644 645 int wanted_area; 646 int smaller, larger; 647 int offset; 648 int smaller_offset, larger_offset; 649 int i; 650 651 int width, height; 652 653 fetch_offset = 0; 654 if(XGetWindowProperty(dpy, twm_win->w, XA__NET_WM_ICON, 655 fetch_offset, 8 * 1024, False, XA_CARDINAL, 656 &actual_type, &actual_format, &nitems, 657 &bytes_after, (unsigned char **)&prop) != Success || nitems == 0) { 658 return NULL; 659 } 660 661 if(actual_format != 32) { 662 XFree(prop); 663 return NULL; 664 } 665 666#ifdef DEBUG_EWMH 667 fprintf(stderr, "_NET_WM_ICON data fetched\n"); 668#endif 669 /* 670 * Usually the icons are square, but that is not a rule. 671 * So we measure the area instead. 672 * 673 * Approach wanted size from both directions and at the end, 674 * choose the "nearest". 675 */ 676 wanted_area = Scr->PreferredIconWidth * Scr->PreferredIconHeight; 677 smaller = 0; 678 larger = 999999; 679 smaller_offset = -1; 680 larger_offset = -1; 681 i = 0; 682 683 for(;;) { 684 offset = i; 685 686 int w = prop[i++]; 687 int h = prop[i++]; 688 int size = w * h; 689 690 const int area = w * h; 691 692#ifdef DEBUG_EWMH 693 fprintf(stderr, "[%d+%d] w=%d h=%d\n", fetch_offset, offset, w, h); 694#endif 695 696 697 if(area == wanted_area) { 698#ifdef DEBUG_EWMH 699 fprintf(stderr, "exact match [%d+%d=%d] w=%d h=%d\n", fetch_offset, offset, 700 fetch_offset + offset, w, h); 701#endif /* DEBUG_EWMH */ 702 smaller_offset = fetch_offset + offset; 703 smaller = area; 704 larger_offset = -1; 705 break; 706 } 707 else if(area < wanted_area) { 708 if(area > smaller) { 709#ifdef DEBUG_EWMH 710 fprintf(stderr, "increase smaller, was [%d]\n", smaller_offset); 711#endif /* DEBUG_EWMH */ 712 smaller = area; 713 smaller_offset = fetch_offset + offset; 714 } 715 } 716 else { /* area > wanted_area */ 717 if(area < larger) { 718#ifdef DEBUG_EWMH 719 fprintf(stderr, "decrease larger, was [%d]\n", larger_offset); 720#endif /* DEBUG_EWMH */ 721 larger = area; 722 larger_offset = fetch_offset + offset; 723 } 724 } 725 726 if(i + size + 2 > nitems) { 727#ifdef DEBUG_EWMH 728 fprintf(stderr, "not enough data: %d + %d > %ld \n", i, size, nitems); 729#endif /* DEBUG_EWMH */ 730 731 if(i + size + 2 <= nitems + bytes_after / 4) { 732 /* we can fetch some more... */ 733 XFree(prop); 734 fetch_offset += i + size; 735 if(XGetWindowProperty(dpy, twm_win->w, XA__NET_WM_ICON, 736 fetch_offset, 8 * 1024, False, XA_CARDINAL, 737 &actual_type, &actual_format, &nitems, 738 &bytes_after, (unsigned char **)&prop) != Success) { 739 continue; 740 } 741 i = 0; 742 continue; 743 } 744 break; 745 } 746 i += size; 747 } 748 749 /* 750 * Choose which icon approximates our desired size best. 751 */ 752 int area = 0; 753 ALLOW_DEAD_STORE(area); // all branches below init it 754 755 if(smaller_offset >= 0) { 756 if(larger_offset >= 0) { 757 /* choose the nearest */ 758#ifdef DEBUG_EWMH 759 fprintf(stderr, "choose nearest %d %d\n", smaller, larger); 760#endif /* DEBUG_EWMH */ 761 if((double)larger / wanted_area > (double)wanted_area / smaller) { 762 offset = smaller_offset; 763 area = smaller; 764 } 765 else { 766 offset = larger_offset; 767 area = larger; 768 } 769 } 770 else { 771 /* choose smaller */ 772#ifdef DEBUG_EWMH 773 fprintf(stderr, "choose smaller (only) %d\n", smaller); 774#endif /* DEBUG_EWMH */ 775 offset = smaller_offset; 776 area = smaller; 777 } 778 } 779 else if(larger_offset >= 0) { 780 /* choose larger */ 781#ifdef DEBUG_EWMH 782 fprintf(stderr, "choose larger (only) %d\n", larger); 783#endif /* DEBUG_EWMH */ 784 offset = larger_offset; 785 area = larger; 786 } 787 else { 788 /* no icons found at all? */ 789#ifdef DEBUG_EWMH 790 fprintf(stderr, "nothing to choose from\n"); 791#endif /* DEBUG_EWMH */ 792 XFree(prop); 793 return NULL; 794 } 795 796 /* 797 * Now fetch the pixels. 798 */ 799 800#ifdef DEBUG_EWMH 801 fprintf(stderr, "offset = %d fetch_offset = %d\n", offset, fetch_offset); 802 fprintf(stderr, "offset + 2 + area = %d fetch_offset + nitems = %ld\n", 803 offset + 2 + area, fetch_offset + nitems); 804#endif /* DEBUG_EWMH */ 805 if(offset < fetch_offset || 806 offset + 2 + area > fetch_offset + nitems) { 807 XFree(prop); 808 fetch_offset = offset; 809#ifdef DEBUG_EWMH 810 fprintf(stderr, "refetching from %d\n", fetch_offset); 811#endif /* DEBUG_EWMH */ 812 if(XGetWindowProperty(dpy, twm_win->w, XA__NET_WM_ICON, 813 fetch_offset, 2 + area, False, XA_CARDINAL, 814 &actual_type, &actual_format, &nitems, 815 &bytes_after, (unsigned char **)&prop) != Success) { 816 return NULL; 817 } 818 } 819 820 i = offset - fetch_offset; 821 width = prop[i++]; 822 height = prop[i++]; 823#ifdef DEBUG_EWMH 824 fprintf(stderr, "Chosen [%d] w=%d h=%d area=%d\n", offset, width, height, area); 825#endif /* DEBUG_EWMH */ 826 assert(width * height == area); 827 828 Image *image = ExtractIcon(scr, &prop[i], width, height); 829 830 XFree(prop); 831 832 return image; 833} 834 835static uint16_t *buffer_16bpp; 836static uint32_t *buffer_32bpp; 837 838static void convert_for_16(int w, int x, int y, int argb) 839{ 840 int r = (argb >> 16) & 0xFF; 841 int g = (argb >> 8) & 0xFF; 842 int b = (argb >> 0) & 0xFF; 843 buffer_16bpp [y * w + x] = ((r >> 3) << 11) + ((g >> 2) << 5) + (b >> 3); 844} 845 846static void convert_for_32(int w, int x, int y, int argb) 847{ 848 buffer_32bpp [y * w + x] = argb & 0x00FFFFFF; 849} 850 851static Image *ExtractIcon(ScreenInfo *scr, unsigned long *prop, int width, 852 int height) 853{ 854 XImage *ximage; 855 void (*store_data)(int w, int x, int y, int argb); 856 int x, y, transparency; 857 int rowbytes; 858 unsigned char *maskbits; 859 860 GC gc; 861 Pixmap pixret; 862 Pixmap mask; 863 Image *image; 864 int i; 865 866 ximage = NULL; 867 868 /** XXX sort of duplicated from util.c:LoadJpegImage() */ 869 if(scr->d_depth == 16) { 870 store_data = convert_for_16; 871 buffer_16bpp = malloc(width * height * sizeof(buffer_16bpp[0])); 872 buffer_32bpp = NULL; 873 ximage = XCreateImage(dpy, CopyFromParent, scr->d_depth, ZPixmap, 0, 874 (char *) buffer_16bpp, width, height, 16, width * 2); 875 } 876 else if(scr->d_depth == 24 || scr->d_depth == 32) { 877 store_data = convert_for_32; 878 buffer_32bpp = malloc(width * height * sizeof(buffer_32bpp[0])); 879 buffer_16bpp = NULL; 880 ximage = XCreateImage(dpy, CopyFromParent, scr->d_depth, ZPixmap, 0, 881 (char *) buffer_32bpp, width, height, 32, width * 4); 882 } 883 else { 884#ifdef DEBUG_EWMH 885 fprintf(stderr, "Screen unsupported depth for 32-bit icon: %d\n", scr->d_depth); 886#endif /* DEBUG_EWMH */ 887 XFree(prop); 888 return NULL; 889 } 890 if(ximage == NULL) { 891#ifdef DEBUG_EWMH 892 fprintf(stderr, "cannot create image for icon\n"); 893#endif /* DEBUG_EWMH */ 894 XFree(prop); 895 return NULL; 896 } 897 898 transparency = 0; 899 rowbytes = (width + 7) / 8; 900 maskbits = calloc(height, rowbytes); 901 902 /* 903 * Copy all ARGB pixels to the pixmap (the RGB part), and the bitmap (the 904 * Alpha, or opaqueness part). If any pixels are transparent, we're going 905 * to need a shape. 906 */ 907 i = 0; 908 for(y = 0; y < height; y++) { 909 for(x = 0; x < width; x++) { 910 unsigned long argb = prop[i++]; 911 store_data(width, x, y, argb); 912 int opaque = ((argb >> 24) & 0xFF) >= 0x80; /* arbitrary cutoff */ 913 if(opaque) { 914 maskbits [rowbytes * y + (x / 8)] |= 0x01 << (x % 8); 915 } 916 else { 917 transparency = 1; 918 } 919 } 920 } 921 922 gc = DefaultGC(dpy, scr->screen); 923 pixret = XCreatePixmap(dpy, scr->Root, width, height, scr->d_depth); 924 XPutImage(dpy, pixret, gc, ximage, 0, 0, 0, 0, width, height); 925 XDestroyImage(ximage); /* also frees buffer_{16,32}bpp */ 926 ximage = NULL; 927 928 mask = None; 929 if(transparency) { 930 mask = XCreatePixmapFromBitmapData(dpy, scr->Root, (char *)maskbits, 931 width, height, 1, 0, 1); 932 } 933 free(maskbits); 934 935 image = AllocImage(); 936 image->width = width; 937 image->height = height; 938 image->pixmap = pixret; 939 image->mask = mask; 940 941 return image; 942} 943 944/* 945 * Handle a PropertyNotify on _NET_WM_ICON. 946 */ 947static void EwmhHandle_NET_WM_ICONNotify(XPropertyEvent *event, 948 TwmWindow *twm_win) 949{ 950 unsigned long valuemask; /* mask for create windows */ 951 XSetWindowAttributes attributes; /* attributes for create windows */ 952 Icon *icon = twm_win->icon; 953 int x; 954 955#ifdef DEBUG_EWMH 956 fprintf(stderr, "EwmhHandlePropertyNotify: NET_WM_ICON\n"); 957#endif /* DEBUG_EWMH */ 958 /* 959 * If there is no icon yet, we'll look at this property 960 * later, if and when we do create an icon. 961 */ 962 if(!icon || icon->match != match_net_wm_icon) { 963#ifdef DEBUG_EWMH 964 fprintf(stderr, "no icon, or not match_net_wm_icon\n"); 965#endif /* DEBUG_EWMH */ 966 return; 967 } 968 969 Image *image = EwmhGetIcon(Scr, twm_win); 970 971 /* TODO: de-duplicate with handling of XA_WM_HINTS */ 972 { 973 Image *old_image = icon->image; 974 icon->image = image; 975 FreeImage(old_image); 976 } 977 978 979 if(twm_win->icon->bm_w) { 980 XDestroyWindow(dpy, twm_win->icon->bm_w); 981 } 982 983 valuemask = CWBackPixmap; 984 attributes.background_pixmap = image->pixmap; 985 986 x = GetIconOffset(twm_win->icon); 987 twm_win->icon->bm_w = 988 XCreateWindow(dpy, twm_win->icon->w, x, 0, 989 twm_win->icon->width, 990 twm_win->icon->height, 991 0, Scr->d_depth, 992 CopyFromParent, Scr->d_visual, 993 valuemask, &attributes); 994 995 if(image->mask) { 996 XShapeCombineMask(dpy, twm_win->icon->bm_w, ShapeBounding, 0, 0, image->mask, 997 ShapeSet); 998 XShapeCombineMask(dpy, twm_win->icon->w, ShapeBounding, x, 0, image->mask, 999 ShapeSet); 1000 } 1001 else { 1002 XRectangle rect; 1003 1004 rect.x = x; 1005 rect.y = 0; 1006 rect.width = twm_win->icon->width; 1007 rect.height = twm_win->icon->height; 1008 XShapeCombineRectangles(dpy, twm_win->icon->w, ShapeBounding, 0, 1009 0, &rect, 1, ShapeUnion, 0); 1010 } 1011 XMapSubwindows(dpy, twm_win->icon->w); 1012 RedoIconName(twm_win); 1013} 1014 1015/* 1016 * Handle a PropertyNotify on _NET_WM_STRUT(PARTIAL). 1017 */ 1018static void EwmhHandle_NET_WM_STRUTNotify(XPropertyEvent *event, 1019 TwmWindow *twm_win) 1020{ 1021 EwmhGetStrut(twm_win, true); 1022} 1023 1024/* 1025 * Handle a _NET_WM_STATE ClientMessage. 1026 */ 1027static int atomToFlag(Atom a) 1028{ 1029#ifdef DEBUG_EWMH 1030# define CRWARN(x) fprintf(stderr, "atomToFlag: ignoring " #x "\n") 1031#else 1032# define CRWARN(x) (void)0 1033#endif 1034#define CHKNRET(st) \ 1035 if(a == XA__NET_WM_##st) { \ 1036 if(LookInNameList(Scr->EWMHIgnore, #st)) { \ 1037 CRWARN(st); \ 1038 return 0; \ 1039 } \ 1040 return EWMH_##st; \ 1041 } 1042 1043 /* Check (potentially ignoring) various flags we know */ 1044 CHKNRET(STATE_MAXIMIZED_VERT); 1045 CHKNRET(STATE_MAXIMIZED_HORZ); 1046 CHKNRET(STATE_FULLSCREEN); 1047 CHKNRET(STATE_SHADED); 1048 CHKNRET(STATE_ABOVE); 1049 CHKNRET(STATE_BELOW); 1050 1051#undef CHKNRET 1052#undef CRWARN 1053 1054 /* Else we don't recognize it */ 1055 return 0; 1056} 1057 1058 1059/* 1060 * Handle the NET_WM_STATE client message. 1061 * It can change 1 or 2 values represented in NET_WM_STATE. 1062 * The second change is allowed 1063 * specifically for (re)setting horizontal and vertical maximalisation in 1064 * one go. Treat that as a special case. 1065 */ 1066static void EwmhClientMessage_NET_WM_STATE(XClientMessageEvent *msg) 1067{ 1068 Window w = msg->window; 1069 TwmWindow *twm_win; 1070 int change, change1, change2, newValue; 1071 1072 twm_win = GetTwmWindow(w); 1073 1074 if(twm_win == NULL) { 1075 return; 1076 } 1077 1078 /* 1079 * Due to EWMHIgnore, it's possible to wind up with change1=0 and 1080 * change2=something, so swap 'em if that happens. 1081 */ 1082 change1 = atomToFlag(msg->data.l[1]); 1083 change2 = atomToFlag(msg->data.l[2]); 1084 if(change1 == 0 && change2 != 0) { 1085 change1 = change2; 1086 change2 = 0; 1087 } 1088 change = change1 | change2; 1089 1090 switch(msg->data.l[0]) { 1091 case NET_WM_STATE_REMOVE: 1092#ifdef DEBUG_EWMH 1093 printf("NET_WM_STATE_REMOVE: "); 1094#endif 1095 newValue = 0; 1096 break; 1097 case NET_WM_STATE_ADD: 1098#ifdef DEBUG_EWMH 1099 printf("NET_WM_STATE_ADD: "); 1100#endif 1101 newValue = change; 1102 break; 1103 case NET_WM_STATE_TOGGLE: 1104#ifdef DEBUG_EWMH 1105 printf("NET_WM_STATE_TOGGLE: "); 1106#endif 1107 newValue = ~twm_win->ewmhFlags & change; 1108 break; 1109 default: 1110#ifdef DEBUG_EWMH 1111 printf("invalid operation in NET_WM_STATE: %ld\n", msg->data.l[0]); 1112#endif 1113 return; 1114 } 1115#ifdef DEBUG_EWMH 1116 printf("%s and %s\n", XGetAtomName(dpy, msg->data.l[1]), 1117 XGetAtomName(dpy, msg->data.l[2])); 1118#endif 1119 1120 /* 1121 * Special-case the horizontal and vertical zoom. 1122 * You can turn them both on or both off, but no other combinations 1123 * are done as a unit. 1124 */ 1125 if(change == (EWMH_STATE_MAXIMIZED_VERT | EWMH_STATE_MAXIMIZED_HORZ) && 1126 (newValue == 0 || newValue == change)) { 1127 EwmhClientMessage_NET_WM_STATEchange(twm_win, change, newValue); 1128 } 1129 else { 1130 EwmhClientMessage_NET_WM_STATEchange(twm_win, change1, newValue & change1); 1131 if(change2 != 0) { 1132 EwmhClientMessage_NET_WM_STATEchange(twm_win, change2, newValue & change2); 1133 } 1134 } 1135} 1136 1137/* 1138 * change - bitmask of settings that possibly change. Only one bit is 1139 * set in this, with the possible exception of 1140 * *MAXIMIZED_{VERT,HORIZ} which can be set together. 1141 * newValue - bits with the new values; only valid bits are the ones 1142 * in change. 1143 */ 1144static void EwmhClientMessage_NET_WM_STATEchange(TwmWindow *twm_win, int change, 1145 int newValue) 1146{ 1147 /* Now check what we need to change */ 1148 1149 if(change & (EWMH_STATE_MAXIMIZED_VERT | EWMH_STATE_MAXIMIZED_HORZ | 1150 EWMH_STATE_FULLSCREEN)) { 1151 int newZoom = ZOOM_NONE; 1152 1153 switch(newValue) { 1154 case 0: 1155 newZoom = twm_win->zoomed; /* turn off whatever zoom */ 1156 break; 1157 case EWMH_STATE_MAXIMIZED_VERT: 1158 newZoom = F_ZOOM; 1159 break; 1160 case EWMH_STATE_MAXIMIZED_HORZ: 1161 newZoom = F_HORIZOOM; 1162 break; 1163 case EWMH_STATE_MAXIMIZED_HORZ | EWMH_STATE_MAXIMIZED_VERT: 1164 newZoom = F_FULLZOOM; 1165 break; 1166 case EWMH_STATE_FULLSCREEN: 1167 newZoom = F_FULLSCREENZOOM; 1168 break; 1169 } 1170 fullzoom(twm_win, newZoom); 1171 } 1172 else if(change & EWMH_STATE_SHADED) { 1173#ifdef DEBUG_EWMH 1174 printf("EWMH_STATE_SHADED: newValue: %d old: %d\n", newValue, 1175 twm_win->ewmhFlags & EWMH_STATE_SHADED); 1176#endif 1177 if((twm_win->ewmhFlags & EWMH_STATE_SHADED) ^ newValue) { 1178 /* Toggle the shade/squeeze state */ 1179#ifdef DEBUG_EWMH 1180 printf("EWMH_STATE_SHADED: change it\n"); 1181#endif 1182 Squeeze(twm_win); 1183 } 1184 } 1185 else if(change & (EWMH_STATE_ABOVE | EWMH_STATE_BELOW)) { 1186 /* 1187 * Other changes call into ctwm code, which in turn calls back to 1188 * EWMH code to update the ewmhFlags and the property. This one 1189 * we handle completely internally. 1190 */ 1191 unsigned omask = 0, oval = 0; 1192 const int prepri = OtpEffectivePriority(twm_win); 1193 1194 /* Which bits are we changing and what to? */ 1195#define DOBIT(fld) do { \ 1196 if(change & EWMH_STATE_##fld) { omask |= OTP_AFLAG_##fld; } \ 1197 if(newValue & EWMH_STATE_##fld) { oval |= OTP_AFLAG_##fld; } \ 1198 } while(0) 1199 1200 DOBIT(ABOVE); 1201 DOBIT(BELOW); 1202 1203#undef DOBIT 1204 1205 /* Update OTP as necessary */ 1206 OtpSetAflagMask(twm_win, omask, oval); 1207 if(OtpEffectivePriority(twm_win) != prepri) { 1208 OtpRestackWindow(twm_win); 1209 } 1210 1211 /* Set the EWMH property back on the window */ 1212 EwmhSet_NET_WM_STATE(twm_win, change); 1213 } 1214} 1215 1216/* 1217 * Handle the _NET_ACTIVE_WINDOW client message. 1218 * Pagers would send such a message to "activate" a window. 1219 * 1220 * What does "activate" really mean? It isn't properly described. 1221 * 1222 * Let's presume that it means that the window is de-iconified and gets 1223 * focus. The mouse may be moved to it (but not all window button apps 1224 * do that). But is it always raised or should that depend on the 1225 * RaiseOnWarp option? 1226 */ 1227static void EwmhClientMessage_NET_ACTIVE_WINDOW(XClientMessageEvent *msg) 1228{ 1229 Window w = msg->window; 1230 TwmWindow *twm_win; 1231 1232 twm_win = GetTwmWindow(w); 1233 1234 if(twm_win == NULL) { 1235 return; 1236 } 1237 1238 if(!twm_win->mapped) { 1239 DeIconify(twm_win); 1240 } 1241#if 0 1242 WarpToWindow(twm_win, Scr->RaiseOnWarp /* True ? */); 1243#else 1244 /* 1245 * Keep the mouse pointer where it is (typically the dock). 1246 * WarpToWindow() would change the current workspace if needed to go 1247 * to the window. But pagers would only send this message for 1248 * windows in the current workspace, I expect. 1249 */ 1250 if(Scr->RaiseOnWarp) { 1251 AutoRaiseWindow(twm_win); 1252 } 1253 SetFocus(twm_win, msg->data.l[1]); 1254#endif 1255} 1256 1257/* 1258 * Ugly implementation of _NET_WM_MOVERESIZE. 1259 * 1260 * window = window to be moved or resized 1261 * message_type = _NET_WM_MOVERESIZE 1262 * format = 32 1263 * data.l[0] = x_root 1264 * data.l[1] = y_root 1265 * data.l[2] = direction 1266 * data.l[3] = button 1267 * data.l[4] = source indication 1268 */ 1269static void EwmhClientMessage_NET_WM_MOVERESIZE(XClientMessageEvent *msg) 1270{ 1271 TwmWindow *twm_win; 1272 1273 twm_win = GetTwmWindow(msg->window); 1274 1275 if(twm_win == NULL) { 1276 return; 1277 } 1278 1279 if(!twm_win->mapped) { 1280 DeIconify(twm_win); 1281 } 1282 1283 switch(msg->data.l[2]) { 1284 case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: 1285 case _NET_WM_MOVERESIZE_SIZE_TOP: 1286 case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: 1287 case _NET_WM_MOVERESIZE_SIZE_RIGHT: 1288 case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: 1289 case _NET_WM_MOVERESIZE_SIZE_BOTTOM: 1290 case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: 1291 case _NET_WM_MOVERESIZE_SIZE_LEFT: 1292 case _NET_WM_MOVERESIZE_SIZE_KEYBOARD: { 1293 /* Fake up as if we triggered f.resize on it */ 1294 XEvent xevent = synth_btnevent_for_moveresize(twm_win); 1295 1296 /* 1297 * The resize won't actually start until we cross the cursor 1298 * over a border. Perhaps we should find the nearest corner, 1299 * and pre-warp the cursor there? That may be less friendly 1300 * for the user, since it might not be as predictable, and 1301 * having the cursor zoom off without warning is probably a 1302 * little surprising... 1303 */ 1304 ExecuteFunction(F_RESIZE, "", twm_win->frame, twm_win, 1305 &xevent, C_WINDOW, false); 1306 1307 /* 1308 * It's not guaranteed that this actually began as a button 1309 * press, but our implementation is expecting the button to 1310 * be held down all through the move, so that 1311 * ExecuteFunction() won't actually end until there's a 1312 * button release of some kind, which will trigger 1313 * HandleButtonRelease() properly. So we don't need to call 1314 * it ourselves. 1315 */ 1316 1317 break; 1318 } 1319 case _NET_WM_MOVERESIZE_MOVE: 1320 case _NET_WM_MOVERESIZE_MOVE_KEYBOARD: { 1321 /* Fake up as if we triggered f.move on it */ 1322 XEvent xevent = synth_btnevent_for_moveresize(twm_win); 1323 ExecuteFunction(F_MOVE, "", twm_win->frame, twm_win, 1324 &xevent, C_WINDOW, false); 1325 1326 /* X-ref HandleButtonRelease() discussion above */ 1327 break; 1328 } 1329 case _NET_WM_MOVERESIZE_CANCEL: 1330 /* 1331 * TODO: check if the twm_win is the same. 1332 * TODO: check how to make this actually work. 1333 * 1334 * As currently implemented, I don't believe we ever need to 1335 * do anything here. All the needed cleanup should happen in 1336 * our ButtonRelease handler. 1337 */ 1338 break; 1339 } 1340} 1341 1342static XEvent 1343synth_btnevent_for_moveresize(TwmWindow *twm_win) 1344{ 1345 XEvent xevent; 1346 Window root = twm_win->parent_vs->window; 1347 Window child = twm_win->w; 1348 int x_root = twm_win->frame_x; 1349 int y_root = twm_win->frame_y; 1350 int x_win = 0; 1351 int y_win = 0; 1352 unsigned int dummy_mask; 1353 1354 /* Find the pointer */ 1355 XQueryPointer(dpy, twm_win->frame, &root, &child, &x_root, &y_root, 1356 &x_win, &y_win, &dummy_mask); 1357 1358 /* Synthesize a button event */ 1359 xevent.type = ButtonPress; 1360 xevent.xbutton.root = root; 1361 xevent.xbutton.window = child; 1362 xevent.xbutton.x_root = x_root; 1363 xevent.xbutton.y_root = y_root; 1364 xevent.xbutton.x = x_win; 1365 xevent.xbutton.y = y_win; 1366 xevent.xbutton.time = EventTime; 1367 1368 return xevent; 1369} 1370 1371 1372/* 1373 * Implementation of _NET_CLOSE_WINDOW 1374 * 1375 * window = window to be closed 1376 * message_type = _NET_CLOSE_WINDOW 1377 * format = 32 1378 * data.l[0] = timestamp 1379 * data.l[1] = source indication: 1380 * 0 Clients that support only older version of this spec 1381 * 1 for normal applications, and 1382 * 2 for pagers and other Clients that represent direct user actions 1383 * data.l[2] = 0 1384 * data.l[3] = 0 1385 * data.l[4] = 0 1386 */ 1387static void EwmhClientMessage_NET_CLOSE_WINDOW(XClientMessageEvent *msg) 1388{ 1389 TwmWindow *tmp_win; 1390 1391 tmp_win = GetTwmWindow(msg->window); 1392 1393 if(tmp_win != NULL) { 1394 ButtonPressed = -1; 1395 ExecuteFunction(F_DELETE, NULL, msg->window, tmp_win, 1396 (XEvent *)msg, C_NO_CONTEXT, 0); 1397 } 1398} 1399 1400/* 1401 * Handle any PropertyNotify. 1402 */ 1403int EwmhHandlePropertyNotify(XPropertyEvent *event, TwmWindow *twm_win) 1404{ 1405 if(event->atom == XA__NET_WM_ICON) { 1406 EwmhHandle_NET_WM_ICONNotify(event, twm_win); 1407 return 1; 1408 } 1409 else if(event->atom == XA__NET_WM_STRUT_PARTIAL || 1410 event->atom == XA__NET_WM_STRUT) { 1411 EwmhHandle_NET_WM_STRUTNotify(event, twm_win); 1412 return 1; 1413 } 1414 else if(event->atom == XA__NET_WM_NAME) { 1415 char *prop = GetWMPropertyString(twm_win->w, XA__NET_WM_NAME); 1416 if(prop == NULL) { 1417 FreeWMPropertyString(twm_win->names.net_wm_name); 1418 twm_win->names.net_wm_name = NULL; 1419 apply_window_name(twm_win); 1420 return 1; 1421 } 1422 1423 if(twm_win->names.net_wm_name != NULL 1424 && strcmp(twm_win->names.net_wm_name, prop) == 0) { 1425 /* No change, just free and skip out */ 1426 free(prop); 1427 return 1; 1428 } 1429 1430 /* It's changing, free the old and bring in the new */ 1431 FreeWMPropertyString(twm_win->names.net_wm_name); 1432 twm_win->names.net_wm_name = prop; 1433 1434 /* Kick the reset process */ 1435 apply_window_name(twm_win); 1436 return 1; 1437 } 1438 else if(event->atom == XA__NET_WM_ICON_NAME) { 1439 char *prop = GetWMPropertyString(twm_win->w, XA__NET_WM_ICON_NAME); 1440 if(prop == NULL) { 1441 FreeWMPropertyString(twm_win->names.net_wm_icon_name); 1442 twm_win->names.net_wm_icon_name = NULL; 1443 apply_window_icon_name(twm_win); 1444 return 1; 1445 } 1446 1447 if(twm_win->names.net_wm_icon_name != NULL 1448 && strcmp(twm_win->names.net_wm_icon_name, prop) == 0) { 1449 /* No change, just free and skip out */ 1450 free(prop); 1451 return 1; 1452 } 1453 1454 /* It's changing, free the old and bring in the new */ 1455 FreeWMPropertyString(twm_win->names.net_wm_icon_name); 1456 twm_win->names.net_wm_icon_name = prop; 1457 1458 /* Kick the reset process */ 1459 apply_window_icon_name(twm_win); 1460 return 1; 1461 } 1462 1463 return 0; 1464} 1465 1466/* 1467 * Set the _NET_WM_DESKTOP property for the current workspace. 1468 */ 1469void EwmhSet_NET_WM_DESKTOP(TwmWindow *twm_win) 1470{ 1471 WorkSpace *ws; 1472 1473 VirtualScreen *vs = twm_win->vs; 1474 if(vs != NULL) { 1475 ws = vs->wsw->currentwspc; 1476 } 1477 else { 1478 ws = NULL; 1479 } 1480 1481 EwmhSet_NET_WM_DESKTOP_ws(twm_win, ws); 1482} 1483 1484/* 1485 * Set the _NET_WM_DESKTOP property for the given workspace. 1486 */ 1487void EwmhSet_NET_WM_DESKTOP_ws(TwmWindow *twm_win, WorkSpace *ws) 1488{ 1489 unsigned long workspaces[MAXWORKSPACE]; 1490 int n = 0; 1491 1492 if(!Scr->workSpaceManagerActive) { 1493 workspaces[n++] = 0; 1494 } 1495 else if(twm_win->occupation == fullOccupation) { 1496 workspaces[n++] = ALL_WORKSPACES; 1497 } 1498 else { 1499 /* 1500 * Our windows can occupy multiple workspaces ("virtual desktops" in 1501 * EWMH terminology) at once. Extend the _NET_WM_DESKTOP property 1502 * by setting it to multiple CARDINALs if this occurs. 1503 * Put the currently visible workspace (if any) first, since typical 1504 * pager apps don't know about this. 1505 */ 1506 int occupation = twm_win->occupation; 1507 1508 /* 1509 * Set visible workspace number. 1510 */ 1511 if(ws != NULL) { 1512 int wsn = ws->number; 1513 1514 workspaces[n++] = wsn; 1515 occupation &= ~(1 << wsn); 1516 } 1517 1518 /* 1519 * Set any other workspace numbers. 1520 */ 1521 if(occupation != 0) { 1522 int i; 1523 int mask = 1; 1524 1525 for(i = 0; i < MAXWORKSPACE; i++) { 1526 if(occupation & mask) { 1527 workspaces[n++] = i; 1528 } 1529 mask <<= 1; 1530 } 1531 } 1532 } 1533 1534 XChangeProperty(dpy, twm_win->w, 1535 XA__NET_WM_DESKTOP, XA_CARDINAL, 1536 32, PropModeReplace, 1537 (unsigned char *)workspaces, n); 1538} 1539 1540 1541static unsigned long EwmhGetWindowProperty(Window w, Atom name, Atom type) 1542{ 1543 Atom actual_type; 1544 int actual_format; 1545 unsigned long nitems, bytes_after; 1546 unsigned long *prop; 1547 unsigned long value; 1548 1549 if(XGetWindowProperty(dpy, w, name, 1550 0, 1, False, type, 1551 &actual_type, &actual_format, &nitems, 1552 &bytes_after, (unsigned char **)&prop) != Success) { 1553 return 0; 1554 } 1555 1556 if(actual_format == 32) { 1557 if(nitems >= 1) { 1558 value = prop[0]; 1559 } 1560 else { 1561 value = 0; 1562 } 1563 } 1564 else { 1565 value = 0; 1566 } 1567 1568 XFree(prop); 1569 return value; 1570} 1571 1572/* 1573 * Simple function to get multiple properties of format 32. 1574 * If it fails, returns NULL, and *nitems_return == 0. 1575 */ 1576 1577static unsigned long *EwmhGetWindowProperties(Window w, Atom name, Atom type, 1578 unsigned long *nitems_return) 1579{ 1580 Atom actual_type; 1581 int actual_format; 1582 unsigned long bytes_after; 1583 unsigned long *prop; 1584 1585 if(XGetWindowProperty(dpy, w, name, 1586 0, 8192, False, type, 1587 &actual_type, &actual_format, nitems_return, 1588 &bytes_after, (unsigned char **)&prop) != Success) { 1589 *nitems_return = 0; 1590 return NULL; 1591 } 1592 1593 if(actual_format != 32) { 1594 XFree(prop); 1595 prop = NULL; 1596 *nitems_return = 0; 1597 } 1598 1599 return prop; 1600} 1601 1602int EwmhGetOccupation(TwmWindow *twm_win) 1603{ 1604 unsigned long nitems; 1605 unsigned long *prop; 1606 int occupation; 1607 1608 occupation = 0; 1609 1610 prop = EwmhGetWindowProperties(twm_win->w, 1611 XA__NET_WM_DESKTOP, XA_CARDINAL, &nitems); 1612 1613 if(prop) { 1614 int i; 1615 for(i = 0; i < nitems; i++) { 1616 unsigned int val = prop[i]; 1617 if(val == ALL_WORKSPACES) { 1618 occupation = fullOccupation; 1619 } 1620 else if(val < Scr->workSpaceMgr.count) { 1621 occupation |= 1 << val; 1622 } 1623 else { 1624 occupation |= 1 << (Scr->workSpaceMgr.count - 1); 1625 } 1626 } 1627 1628 occupation &= fullOccupation; 1629 1630 XFree(prop); 1631 } 1632 1633 return occupation; 1634} 1635 1636/* 1637 * The message to change the desktop of a window doesn't recognize 1638 * that it may be in more than one. 1639 * 1640 * Therefore the following heuristic is applied: 1641 * - the window is removed from the workspace where it is visible (if any); 1642 * - it is added to the given workspace; 1643 * - other occupation bits are left unchanged. 1644 * 1645 * If asked to put it on a too high numbered workspace, put it on 1646 * the highest possible. 1647 */ 1648static void EwmhClientMessage_NET_WM_DESKTOP(XClientMessageEvent *msg) 1649{ 1650 Window w = msg->window; 1651 TwmWindow *twm_win; 1652 int occupation; 1653 VirtualScreen *vs; 1654 unsigned int val; 1655 1656 twm_win = GetTwmWindow(w); 1657 1658 if(twm_win == NULL) { 1659 return; 1660 } 1661 1662 occupation = twm_win->occupation; 1663 1664 /* Remove from visible workspace */ 1665 if((vs = twm_win->vs) != NULL) { 1666 occupation &= ~(1 << vs->wsw->currentwspc->number); 1667 } 1668 1669 val = (unsigned int)msg->data.l[0]; 1670 1671 /* Add to requested workspace (or to all) */ 1672 if(val == ALL_WORKSPACES) { 1673 occupation = fullOccupation; 1674 } 1675 else if(val < Scr->workSpaceMgr.count) { 1676 occupation |= 1 << val; 1677 } 1678 else { 1679 occupation |= 1 << (Scr->workSpaceMgr.count - 1); 1680 } 1681 1682 ChangeOccupation(twm_win, occupation); 1683} 1684 1685/* 1686 * Delete all properties that should be removed from a withdrawn 1687 * window. 1688 */ 1689void EwmhUnmapNotify(TwmWindow *twm_win) 1690{ 1691 XDeleteProperty(dpy, twm_win->w, XA__NET_WM_DESKTOP); 1692} 1693 1694/* 1695 * Add a new window to _NET_CLIENT_LIST. 1696 * Newer windows are always added at the end. 1697 * 1698 * Look at new_win->iconmanagerlist as an optimization for 1699 * !LookInList(Scr->IconMgrNoShow, new_win->name, &new_win->class)). 1700 */ 1701void EwmhAddClientWindow(TwmWindow *new_win) 1702{ 1703 if(Scr->ewmh_CLIENT_LIST_size == 0) { 1704 return; 1705 } 1706 if(new_win->iconmanagerlist != NULL && 1707 !new_win->iswspmgr && 1708 !new_win->isiconmgr) { 1709 Scr->ewmh_CLIENT_LIST_used++; 1710 if(Scr->ewmh_CLIENT_LIST_used > Scr->ewmh_CLIENT_LIST_size) { 1711 long *tp; 1712 int tsz = Scr->ewmh_CLIENT_LIST_size; 1713 1714 Scr->ewmh_CLIENT_LIST_size *= 2; 1715 tp = realloc(Scr->ewmh_CLIENT_LIST, 1716 sizeof(long) * Scr->ewmh_CLIENT_LIST_size); 1717 if(tp == NULL) { 1718 Scr->ewmh_CLIENT_LIST_size = tsz; 1719 fprintf(stderr, "Unable to allocate memory for EWMH client list.\n"); 1720 return; 1721 } 1722 Scr->ewmh_CLIENT_LIST = tp; 1723 } 1724 if(Scr->ewmh_CLIENT_LIST) { 1725 Scr->ewmh_CLIENT_LIST[Scr->ewmh_CLIENT_LIST_used - 1] = new_win->w; 1726 } 1727 else { 1728 Scr->ewmh_CLIENT_LIST_size = 0; 1729 fprintf(stderr, "Unable to allocate memory for EWMH client list.\n"); 1730 return; 1731 } 1732 XChangeProperty(dpy, Scr->Root, XA__NET_CLIENT_LIST, XA_WINDOW, 32, 1733 PropModeReplace, (unsigned char *)Scr->ewmh_CLIENT_LIST, 1734 Scr->ewmh_CLIENT_LIST_used); 1735 } 1736} 1737 1738void EwmhDeleteClientWindow(TwmWindow *old_win) 1739{ 1740 int i; 1741 1742 if(old_win->ewmhFlags & EWMH_HAS_STRUT) { 1743 EwmhRemoveStrut(old_win); 1744 } 1745 1746 /* 1747 * Remove the window from _NET_CLIENT_LIST. 1748 */ 1749 if(Scr->ewmh_CLIENT_LIST_size == 0) { 1750 return; 1751 } 1752 for(i = Scr->ewmh_CLIENT_LIST_used - 1; i >= 0; i--) { 1753 if(Scr->ewmh_CLIENT_LIST[i] == old_win->w) { 1754 memmove(&Scr->ewmh_CLIENT_LIST[i], 1755 &Scr->ewmh_CLIENT_LIST[i + 1], 1756 (Scr->ewmh_CLIENT_LIST_used - 1 - i) * sizeof(Scr->ewmh_CLIENT_LIST[0])); 1757 Scr->ewmh_CLIENT_LIST_used--; 1758 if(Scr->ewmh_CLIENT_LIST_used && 1759 (Scr->ewmh_CLIENT_LIST_used * 3) < Scr->ewmh_CLIENT_LIST_size) { 1760 Scr->ewmh_CLIENT_LIST_size /= 2; 1761 Scr->ewmh_CLIENT_LIST = realloc(Scr->ewmh_CLIENT_LIST, 1762 sizeof((Scr->ewmh_CLIENT_LIST[0])) * Scr->ewmh_CLIENT_LIST_size); 1763 /* memory shrinking, shouldn't have problems */ 1764 } 1765 break; 1766 } 1767 } 1768 /* If window was not found, there is no need to update the property. */ 1769 if(i >= 0) { 1770 XChangeProperty(dpy, Scr->Root, XA__NET_CLIENT_LIST, XA_WINDOW, 32, 1771 PropModeReplace, (unsigned char *)Scr->ewmh_CLIENT_LIST, 1772 Scr->ewmh_CLIENT_LIST_used); 1773 } 1774} 1775 1776/* 1777 * Similar to EwmhAddClientWindow() and EwmhDeleteClientWindow(), 1778 * but the windows are in stacking order. 1779 * Therefore we look at the OTPs, which are by definition in the correct order. 1780 */ 1781 1782void EwmhSet_NET_CLIENT_LIST_STACKING(void) 1783{ 1784 int size; 1785 unsigned long *prop; 1786 TwmWindow *twm_win; 1787 int i; 1788 1789 /* Expect the same number of windows as in the _NET_CLIENT_LIST */ 1790 size = Scr->ewmh_CLIENT_LIST_used + 10; 1791 prop = calloc(size, sizeof(unsigned long)); 1792 if(prop == NULL) { 1793 return; 1794 } 1795 1796 i = 0; 1797 for(twm_win = OtpBottomWin(); 1798 twm_win != NULL; 1799 twm_win = OtpNextWinUp(twm_win)) { 1800 if(twm_win->iconmanagerlist != NULL && 1801 !twm_win->iswspmgr && 1802 !twm_win->isiconmgr) { 1803 prop[i] = twm_win->w; 1804 i++; 1805 if(i > size) { 1806 fprintf(stderr, "Too many stacked windows\n"); 1807 break; 1808 } 1809 } 1810 } 1811 1812 if(i != Scr->ewmh_CLIENT_LIST_used) { 1813 fprintf(stderr, "Incorrect number of stacked windows: %d (expected %d)\n", 1814 i, Scr->ewmh_CLIENT_LIST_used); 1815 } 1816 1817 XChangeProperty(dpy, Scr->Root, XA__NET_CLIENT_LIST_STACKING, XA_WINDOW, 32, 1818 PropModeReplace, (unsigned char *)prop, i); 1819 1820 free(prop); 1821} 1822 1823void EwmhSet_NET_ACTIVE_WINDOW(Window w) 1824{ 1825 unsigned long prop[1]; 1826 1827 prop[0] = w; 1828 1829 XChangeProperty(dpy, Scr->Root, XA__NET_ACTIVE_WINDOW, XA_WINDOW, 32, 1830 PropModeReplace, (unsigned char *)prop, 1); 1831} 1832 1833/* 1834 * Get window properties as relevant when the window is initially mapped. 1835 * 1836 * So far, only NET_WM_WINDOW_TYPE and _NET_WM_STRUT_PARTIAL. 1837 * In particular, most of the initial value of _NET_WM_STATE is ignored. TODO. 1838 * 1839 * Also do any generic initialisation needed to EWMH-specific fields 1840 * in a TwmWindow. 1841 */ 1842void EwmhGetProperties(TwmWindow *twm_win) 1843{ 1844 twm_win->ewmhFlags = 0; 1845 1846 Atom type = EwmhGetWindowProperty(twm_win->w, XA__NET_WM_WINDOW_TYPE, XA_ATOM); 1847 1848 if(type == XA__NET_WM_WINDOW_TYPE_DESKTOP) { 1849 twm_win->ewmhWindowType = wt_Desktop; 1850 } 1851 else if(type == XA__NET_WM_WINDOW_TYPE_DOCK) { 1852 twm_win->ewmhWindowType = wt_Dock; 1853 } 1854 else { 1855 twm_win->ewmhWindowType = wt_Normal; 1856 } 1857 EwmhGetStrut(twm_win, false); 1858 /* Only the 3 listed states are supported for now */ 1859 twm_win->ewmhFlags |= EwmhGet_NET_WM_STATE(twm_win) & 1860 (EWMH_STATE_ABOVE | EWMH_STATE_BELOW | EWMH_STATE_SHADED); 1861} 1862 1863/* Only used in initially mapping a window */ 1864int EwmhGetInitPriority(TwmWindow *twm_win) 1865{ 1866 switch(twm_win->ewmhWindowType) { 1867 case wt_Desktop: 1868 return EWMH_PRI_DESKTOP; 1869 case wt_Dock: 1870 return EWMH_PRI_DOCK; 1871 default: 1872 return 0; 1873 } 1874} 1875 1876bool EwmhHasBorder(TwmWindow *twm_win) 1877{ 1878 switch(twm_win->ewmhWindowType) { 1879 case wt_Desktop: 1880 case wt_Dock: 1881 return false; 1882 default: 1883 return true; 1884 } 1885} 1886 1887bool EwmhHasTitle(TwmWindow *twm_win) 1888{ 1889 switch(twm_win->ewmhWindowType) { 1890 case wt_Desktop: 1891 case wt_Dock: 1892 return false; 1893 default: 1894 return true; 1895 } 1896} 1897 1898bool EwmhOnWindowRing(TwmWindow *twm_win) 1899{ 1900 switch(twm_win->ewmhWindowType) { 1901 case wt_Desktop: 1902 case wt_Dock: 1903 return false; 1904 default: 1905 return true; 1906 } 1907} 1908 1909/* 1910 * Recalculate the effective border values from the remembered struts. 1911 * Interestingly it is not documented how to do that. 1912 * Usually only one dock is present on each side, so it shouldn't matter 1913 * too much, but I presume that maximizing the values is the thing to do. 1914 */ 1915static void EwmhRecalculateStrut(void) 1916{ 1917 int left = 0; 1918 int right = 0; 1919 int top = 0; 1920 int bottom = 0; 1921 EwmhStrut *strut = Scr->ewmhStruts; 1922 1923 while(strut != NULL) { 1924 left = max(left, strut->left); 1925 right = max(right, strut->right); 1926 top = max(top, strut->top); 1927 bottom = max(bottom, strut->bottom); 1928 1929 strut = strut->next; 1930 } 1931 1932 Scr->BorderLeft = left; 1933 Scr->BorderRight = right; 1934 Scr->BorderTop = top; 1935 Scr->BorderBottom = bottom; 1936 1937 // Bordered layout may have changed 1938 Scr->BorderedLayout = RLayoutCopyCropped(Scr->Layout, 1939 left, right, top, bottom); 1940 if(Scr->BorderedLayout == NULL) { 1941 Scr->BorderedLayout = Scr->Layout; // nothing to crop 1942 } 1943 1944 EwmhSet_NET_WORKAREA(Scr); 1945} 1946/* 1947 * Check _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT. 1948 * These are basically automatic settings for Border{Left,Right,Top,Bottom}. 1949 * 1950 * If any values are found, collect them in a list of strut values 1951 * belonging to Scr. When a window is added or removed that has struts, 1952 * the new effective value must be calculated. The expectation is that 1953 * at most a handful of windows will have struts. 1954 * 1955 * If update is true, this is called as an update for an existing window. 1956 */ 1957static void EwmhGetStrut(TwmWindow *twm_win, bool update) 1958{ 1959 unsigned long nitems; 1960 unsigned long *prop; 1961 EwmhStrut *strut; 1962 1963 prop = EwmhGetWindowProperties(twm_win->w, 1964 XA__NET_WM_STRUT_PARTIAL, XA_CARDINAL, 1965 &nitems); 1966 if(prop == NULL) { 1967 prop = EwmhGetWindowProperties(twm_win->w, 1968 XA__NET_WM_STRUT, XA_CARDINAL, &nitems); 1969 if(prop == NULL) { 1970 return; 1971 } 1972 } 1973 1974 1975 if(nitems < 4) { 1976#ifdef DEBUG_EWMH 1977 /* This happens a lot, despite returning Success ??? */ 1978 printf("struts: prop = %p, nitems = %ld\n", prop, nitems); 1979#endif 1980 XFree(prop); 1981 return; 1982 } 1983#ifdef DEBUG_EWMH 1984 printf("struts: left %ld, right %ld, top %ld, bottom %ld\n", 1985 prop[0], prop[1], prop[2], prop[3]); 1986#endif 1987 1988 /* 1989 * If there were no struts before, the user configured margins are set 1990 * in Border{Left,Right,Top,Bottom}. In order not to lose those values 1991 * when recalculating them, convert them to struts for a dummy window. 1992 */ 1993 1994 if(Scr->ewmhStruts == NULL && 1995 (Scr->BorderLeft | 1996 Scr->BorderRight | 1997 Scr->BorderTop | 1998 Scr->BorderBottom) != 0) { 1999 strut = calloc(1, sizeof(EwmhStrut)); 2000 if(strut == NULL) { 2001 XFree(prop); 2002 return; 2003 } 2004 2005 strut->next = NULL; 2006 strut->win = NULL; 2007 strut->left = Scr->BorderLeft; 2008 strut->right = Scr->BorderRight; 2009 strut->top = Scr->BorderTop; 2010 strut->bottom = Scr->BorderBottom; 2011 2012 Scr->ewmhStruts = strut; 2013 } 2014 2015 strut = NULL; 2016 2017 /* 2018 * Find the struts of the window that we're supposed to be updating. 2019 * If not found, there is no problem: we'll just allocate a new 2020 * record. 2021 */ 2022 2023 if(update) { 2024 strut = Scr->ewmhStruts; 2025 2026 while(strut != NULL) { 2027 if(strut->win == twm_win) { 2028 break; 2029 } 2030 strut = strut->next; 2031 } 2032 } 2033 2034 /* 2035 * If needed, allocate a new struts record and link it in. 2036 */ 2037 if(strut == NULL) { 2038 strut = calloc(1, sizeof(EwmhStrut)); 2039 if(strut == NULL) { 2040 XFree(prop); 2041 return; 2042 } 2043 2044 strut->next = Scr->ewmhStruts; 2045 Scr->ewmhStruts = strut; 2046 } 2047 2048 strut->win = twm_win; 2049 strut->left = prop[0]; 2050 strut->right = prop[1]; 2051 strut->top = prop[2]; 2052 strut->bottom = prop[3]; 2053 2054 XFree(prop); 2055 2056 /* 2057 * Mark this window as having contributed some struts. 2058 * This can be checked and undone when the window is deleted. 2059 */ 2060 twm_win->ewmhFlags |= EWMH_HAS_STRUT; 2061 2062 EwmhRecalculateStrut(); 2063} 2064 2065/* 2066 * Remove the struts associated with the given window from the 2067 * remembered list. If found, recalculate the effective borders. 2068 */ 2069static void EwmhRemoveStrut(TwmWindow *twm_win) 2070{ 2071 EwmhStrut **prev = &Scr->ewmhStruts; 2072 EwmhStrut *strut = Scr->ewmhStruts; 2073 2074 while(strut != NULL) { 2075 if(strut->win == twm_win) { 2076 twm_win->ewmhFlags &= ~EWMH_HAS_STRUT; 2077 2078 *prev = strut->next; 2079 free(strut); 2080 2081 EwmhRecalculateStrut(); 2082 2083 break; 2084 } 2085 prev = &strut->next; 2086 strut = strut->next; 2087 } 2088} 2089 2090 2091/** 2092 * Set _NET_FRAME_EXTENTS property. 2093 * This tells the client how much space is being taken up by the window 2094 * decorations. Some clients may need this information to position other 2095 * windows on top of themselves. e.g., Firefox's form autofill and 2096 * context menu will be positioned a bit wrong (high, by the height of 2097 * the titlebar) without this. 2098 */ 2099void EwmhSet_NET_FRAME_EXTENTS(TwmWindow *twm_win) 2100{ 2101 long data[4]; 2102 const long w = twm_win->frame_bw3D + twm_win->frame_bw; 2103 2104 data[0] = w; // left 2105 data[1] = w; // right 2106 data[2] = twm_win->title_height + w; // top 2107 data[3] = w; // bottom 2108 2109 XChangeProperty(dpy, twm_win->w, 2110 XA__NET_FRAME_EXTENTS, XA_CARDINAL, 2111 32, PropModeReplace, 2112 (unsigned char *)data, 4); 2113} 2114 2115 2116void EwmhSet_NET_SHOWING_DESKTOP(int state) 2117{ 2118 unsigned long prop[1]; 2119 2120 prop[0] = state; 2121 2122 XChangeProperty(dpy, Scr->XineramaRoot, XA__NET_SHOWING_DESKTOP, XA_CARDINAL, 2123 32, 2124 PropModeReplace, (unsigned char *)prop, 1); 2125} 2126 2127/* 2128 * Set _NET_WM_STATE. 2129 * 2130 * TwmWindow.ewmhFlags keeps track of the atoms that should be in 2131 * the list, so that we don't have to fetch or recalculate them all. 2132 * 2133 * XXX It's not clear that the theoretical performance gain and edge-case 2134 * bug avoidance of the 'changes' arg is worth the complexity and 2135 * edge-case bug creation it brings. Consider removing it. 2136 */ 2137void EwmhSet_NET_WM_STATE(TwmWindow *twm_win, int changes) 2138{ 2139 unsigned long prop[10]; 2140 int flags; 2141 int i; 2142 2143 if(changes & EWMH_STATE_MAXIMIZED_VERT) { 2144 int newFlags = 0; 2145 2146 switch(twm_win->zoomed) { 2147 case F_FULLZOOM: 2148 newFlags = EWMH_STATE_MAXIMIZED_VERT | 2149 EWMH_STATE_MAXIMIZED_HORZ; 2150 break; 2151 case F_ZOOM: 2152 case F_LEFTZOOM: 2153 case F_RIGHTZOOM: 2154 newFlags = EWMH_STATE_MAXIMIZED_VERT; 2155 break; 2156 case F_HORIZOOM: 2157 case F_TOPZOOM: 2158 case F_BOTTOMZOOM: 2159 newFlags = EWMH_STATE_MAXIMIZED_HORZ; 2160 break; 2161 case F_FULLSCREENZOOM: 2162 newFlags = EWMH_STATE_FULLSCREEN; 2163 break; 2164 } 2165 2166 twm_win->ewmhFlags &= ~(EWMH_STATE_MAXIMIZED_VERT | 2167 EWMH_STATE_MAXIMIZED_HORZ | 2168 EWMH_STATE_FULLSCREEN); 2169 twm_win->ewmhFlags |= newFlags; 2170 } 2171 2172 if(changes & EWMH_STATE_SHADED) { 2173 if(twm_win->squeezed) { 2174 twm_win->ewmhFlags |= EWMH_STATE_SHADED; 2175 } 2176 else { 2177 twm_win->ewmhFlags &= ~EWMH_STATE_SHADED; 2178 } 2179 } 2180 2181 if(changes & (EWMH_STATE_ABOVE | EWMH_STATE_BELOW)) { 2182 int pri = OtpEffectiveDisplayPriority(twm_win); 2183 2184 twm_win->ewmhFlags &= ~(EWMH_STATE_ABOVE | EWMH_STATE_BELOW); 2185 2186 /* 2187 * If it's a DOCK or DESKTOP, and where we expect those to be, we 2188 * consider that there's nothing to tell it. Otherwise, we tell 2189 * it ABOVE/BELOW based on where it effectively is. 2190 */ 2191 if(twm_win->ewmhWindowType == wt_Dock && pri == EWMH_PRI_DOCK) { 2192 pri = 0; 2193 } 2194 if(twm_win->ewmhWindowType == wt_Desktop && pri == EWMH_PRI_DESKTOP) { 2195 pri = 0; 2196 } 2197 2198 if(pri > 0) { 2199 twm_win->ewmhFlags |= EWMH_STATE_ABOVE; 2200 } 2201 else if(pri < 0) { 2202 twm_win->ewmhFlags |= EWMH_STATE_BELOW; 2203 } 2204 } 2205 2206 flags = twm_win->ewmhFlags; 2207 i = 0; 2208 2209 if(flags & EWMH_STATE_MAXIMIZED_VERT) { 2210 prop[i++] = XA__NET_WM_STATE_MAXIMIZED_VERT; 2211 } 2212 if(flags & EWMH_STATE_MAXIMIZED_HORZ) { 2213 prop[i++] = XA__NET_WM_STATE_MAXIMIZED_HORZ; 2214 } 2215 if(flags & EWMH_STATE_FULLSCREEN) { 2216 prop[i++] = XA__NET_WM_STATE_FULLSCREEN; 2217 } 2218 if(flags & EWMH_STATE_SHADED) { 2219 prop[i++] = XA__NET_WM_STATE_SHADED; 2220 } 2221 if(flags & EWMH_STATE_ABOVE) { 2222 prop[i++] = XA__NET_WM_STATE_ABOVE; 2223 } 2224 if(flags & EWMH_STATE_BELOW) { 2225 prop[i++] = XA__NET_WM_STATE_BELOW; 2226 } 2227 2228 XChangeProperty(dpy, twm_win->w, XA__NET_WM_STATE, XA_ATOM, 32, 2229 PropModeReplace, (unsigned char *)prop, i); 2230} 2231 2232/* 2233 * Get the initial state of _NET_WM_STATE. 2234 * 2235 * Only some of the flags are supported when initially creating a window. 2236 */ 2237static int EwmhGet_NET_WM_STATE(TwmWindow *twm_win) 2238{ 2239 int flags = 0; 2240 unsigned long *prop; 2241 unsigned long nitems; 2242 int i; 2243 2244 prop = EwmhGetWindowProperties(twm_win->w, XA__NET_WM_STATE, XA_ATOM, &nitems); 2245 2246 if(prop) { 2247 for(i = 0; i < nitems; i++) { 2248 flags |= atomToFlag(prop[i]); 2249 } 2250 2251 XFree(prop); 2252 } 2253 2254 return flags; 2255} 2256 2257/* 2258 * Set _NET_WORKAREA. 2259 */ 2260static void EwmhSet_NET_WORKAREA(ScreenInfo *scr) 2261{ 2262 unsigned long prop[4]; 2263 2264 /* x */ prop[0] = scr->BorderLeft; 2265 /* y */ prop[1] = scr->BorderTop; 2266 /* w */ prop[2] = scr->rootw - scr->BorderLeft - scr->BorderRight; 2267 /* h */ prop[3] = scr->rooth - scr->BorderTop - scr->BorderBottom; 2268 2269 XChangeProperty(dpy, Scr->XineramaRoot, XA__NET_WORKAREA, XA_CARDINAL, 32, 2270 PropModeReplace, (unsigned char *)prop, 4); 2271} 2272