ewmh.c (13111B)
1 /* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail> 2 * See LICENSE file for license details. 3 */ 4 #include "dat.h" 5 #include <limits.h> 6 #include "fns.h" 7 8 Window *ewmhwin; 9 10 static void ewmh_getwinstate(Client*); 11 static void ewmh_setstate(Client*, Atom, int); 12 static void tick(long, void*); 13 14 static Handlers client_handlers; 15 static Handlers root_handlers; 16 17 static void 18 clientprop_long(Client *c, int cache, char *prop, char *type, long *data, int l) { 19 if(l != c->proplen[cache] || memcmp(&c->propcache[cache], data, l * sizeof *data)) { 20 c->proplen[cache] = l; 21 memcpy(&c->propcache[cache], data, l * sizeof *data); 22 changeprop_long(&c->w, prop, type, data, l); 23 } 24 } 25 static void 26 clientprop_del(Client *c, int cache, char *prop) { 27 c->proplen[cache] = 0; 28 delproperty(&c->w, prop); 29 } 30 31 void 32 ewmh_init(void) { 33 char myname[] = "wmii"; 34 long win; 35 36 ewmhwin = createwindow(&scr.root, 37 Rect(0, 0, 1, 1), 0 /*depth*/, 38 InputOnly, nil, 0); 39 40 win = ewmhwin->xid; 41 changeprop_long(&scr.root, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1); 42 changeprop_long(ewmhwin, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1); 43 changeprop_string(ewmhwin, Net("WM_NAME"), myname); 44 45 changeprop_long(&scr.root, Net("DESKTOP_VIEWPORT"), "CARDINAL", 46 (long[2]){0, 0}, 2); 47 48 pushhandler(&scr.root, &root_handlers, nil); 49 50 tick(0L, nil); 51 52 long supported[] = { 53 /* Misc */ 54 NET("SUPPORTED"), 55 /* Root Properties/Messages */ 56 NET("ACTIVE_WINDOW"), 57 NET("CLOSE_WINDOW"), 58 NET("CURRENT_DESKTOP"), 59 /* Client Properties */ 60 NET("FRAME_EXTENTS"), 61 NET("WM_DESKTOP"), 62 NET("WM_FULLSCREEN_MONITORS"), 63 NET("WM_NAME"), 64 NET("WM_PID"), 65 NET("WM_STRUT"), 66 NET("WM_STRUT_PARTIAL"), 67 /* Set this so clients don't update Net("USER_TIME") */ 68 NET("USER_TIME_WINDOW"), 69 /* States */ 70 NET("WM_STATE"), 71 STATE("DEMANDS_ATTENTION"), 72 STATE("FULLSCREEN"), 73 STATE("SHADED"), 74 /* Window Types */ 75 NET("WM_WINDOW_TYPE"), 76 TYPE("DIALOG"), 77 TYPE("DOCK"), 78 TYPE("NORMAL"), 79 TYPE("MENU"), 80 TYPE("SPLASH"), 81 TYPE("TOOLBAR"), 82 /* Actions */ 83 NET("WM_ALLOWED_ACTIONS"), 84 ACTION("FULLSCREEN"), 85 /* Desktops */ 86 NET("DESKTOP_NAMES"), 87 NET("NUMBER_OF_DESKTOPS"), 88 /* Client List */ 89 NET("CLIENT_LIST"), 90 NET("CLIENT_LIST_STACKING"), 91 }; 92 changeprop_long(&scr.root, Net("SUPPORTED"), "ATOM", supported, nelem(supported)); 93 } 94 95 inline bool 96 ewmh_responsive_p(Client *c) { 97 return c->w.ewmh.ping == 0 || (ulong)(nsec() / 1000000) - c->w.ewmh.ping < PingTime; 98 } 99 100 void 101 ewmh_checkresponsive(Client *c) { 102 103 if(!ewmh_responsive_p(c)) 104 if(!c->dead) 105 frame_draw(c->sel); 106 else if(c->dead++ == 1) 107 event("Unresponsive %#C\n", c); 108 } 109 110 static void 111 tick(long id, void *v) { 112 static int count; 113 Client *c; 114 ulong time; 115 int mod, i; 116 117 time = nsec() / 1000000; 118 count++; 119 mod = count % PingPartition; 120 for(i=0, c=client; c; c=c->next, i++) 121 if(c->proto & ProtoPing) { 122 if(!ewmh_responsive_p(c)) 123 ewmh_checkresponsive(c); 124 if(i % PingPartition == mod) 125 sendmessage(&c->w, "WM_PROTOCOLS", NET("WM_PING"), time, c->w.xid, 0, 0); 126 if(i % PingPartition == mod) 127 Dprint(DEwmh, "_NET_WM_PING %#C %,uld\n", c, time); 128 } 129 130 ixp_settimer(&srv, PingPeriod / PingPartition, tick, nil); 131 } 132 133 void 134 ewmh_updateclientlist(void) { 135 Vector_long vec; 136 Client *c; 137 138 vector_linit(&vec); 139 for(c=client; c; c=c->next) 140 vector_lpush(&vec, c->w.xid); 141 changeprop_long(&scr.root, Net("CLIENT_LIST"), "WINDOW", vec.ary, vec.n); 142 free(vec.ary); 143 } 144 145 void 146 ewmh_updatestacking(void) { 147 Vector_long vec; 148 Frame *f; 149 Area *a; 150 View *v; 151 int s; 152 153 vector_linit(&vec); 154 155 for(v=view; v; v=v->next) { 156 foreach_column(v, s, a) 157 for(f=a->frame; f; f=f->anext) 158 if(f->client->sel == f) 159 vector_lpush(&vec, f->client->w.xid); 160 } 161 for(v=view; v; v=v->next) { 162 for(f=v->floating->stack; f; f=f->snext) 163 if(!f->snext) break; 164 for(; f; f=f->sprev) 165 if(f->client->sel == f) 166 vector_lpush(&vec, f->client->w.xid); 167 } 168 169 changeprop_long(&scr.root, Net("CLIENT_LIST_STACKING"), "WINDOW", vec.ary, vec.n); 170 vector_lfree(&vec); 171 } 172 173 void 174 ewmh_initclient(Client *c) { 175 long allowed[] = { 176 ACTION("FULLSCREEN"), 177 }; 178 179 changeprop_long(&c->w, Net("WM_ALLOWED_ACTIONS"), "ATOM", 180 allowed, nelem(allowed)); 181 ewmh_getwintype(c); 182 ewmh_getwinstate(c); 183 ewmh_getstrut(c); 184 ewmh_framesize(c); 185 ewmh_updateclientlist(); 186 pushhandler(&c->w, &client_handlers, c); 187 } 188 189 void 190 ewmh_destroyclient(Client *c) { 191 192 ewmh_updateclientlist(); 193 194 free(c->strut); 195 } 196 197 #ifdef notdef 198 static ulong 199 usertime(Window *w) { 200 long *l; 201 long ret; 202 203 ret = 0; 204 if(getprop_long(w, Net("WM_USER_TIME_WINDOW"), "CARDINAL", 0, &l, 1)) { 205 w = window(*l); 206 free(l); 207 } 208 if(getprop_long(w, Net("WM_USER_TIME"), "CARDINAL", 0, &l, 1)) { 209 ret = *l; 210 free(l); 211 } 212 return ret; 213 } 214 #endif 215 216 static bool 217 event_client_clientmessage(Window *w, void *aux, XClientMessageEvent *e) { 218 Client *c; 219 ulong *l; 220 ulong msg; 221 int action; 222 223 c = aux; 224 l = (ulong*)e->data.l; 225 msg = e->message_type; 226 Dprint(DEwmh, "ClientMessage: %A\n", msg); 227 228 if(msg == NET("WM_STATE")) { 229 enum { 230 StateUnset, 231 StateSet, 232 StateToggle, 233 }; 234 if(e->format != 32) 235 return false; 236 237 switch(l[0]) { 238 case StateUnset: action = Off; break; 239 case StateSet: action = On; break; 240 case StateToggle: action = Toggle; break; 241 default: return false; 242 } 243 244 Dprint(DEwmh, "\tAction: %s\n", TOGGLE(action)); 245 ewmh_setstate(c, l[1], action); 246 ewmh_setstate(c, l[2], action); 247 return false; 248 }else 249 if(msg == NET("ACTIVE_WINDOW")) { 250 if(e->format != 32) 251 return false; 252 Dprint(DEwmh, "\tsource: %uld\n", l[0]); 253 Dprint(DEwmh, "\ttimestamp: %,uld\n", l[1]); 254 Dprint(DEwmh, "\tactive: %#ulx\n", l[2]); 255 Dprint(DEwmh, "\twindow: %#ulx\n", e->window); 256 Dprint(DEwmh, "\tclient: %C\n", c); 257 258 if(l[0] == SourceClient && !(c->permission & PermActivate)) 259 return false; 260 if(l[0] == SourceClient || l[0] == SourcePager) 261 focus(c, true); 262 return false; 263 }else 264 if(msg == NET("CLOSE_WINDOW")) { 265 if(e->format != 32) 266 return false; 267 Dprint(DEwmh, "\tsource: %ld\n", l[0]); 268 Dprint(DEwmh, "\twindow: %#ulx\n", e->window); 269 client_kill(c, true); 270 return false; 271 } 272 273 return false; 274 } 275 276 static bool 277 event_client_property(Window *w, void *aux, XPropertyEvent *e) { 278 return ewmh_prop(aux, e->atom); 279 } 280 281 static Handlers client_handlers = { 282 .message = event_client_clientmessage, 283 .property = event_client_property, 284 }; 285 286 bool 287 ewmh_prop(Client *c, Atom a) { 288 if(a == NET("WM_WINDOW_TYPE")) 289 ewmh_getwintype(c); 290 else 291 if(a == NET("WM_STRUT_PARTIAL")) 292 ewmh_getstrut(c); 293 else 294 return true; 295 return false; 296 } 297 298 typedef struct Prop Prop; 299 struct Prop { 300 char* name; 301 long mask; 302 Atom atom; 303 }; 304 305 static long 306 getmask(Prop *props, ulong *vals, int n) { 307 Prop *p; 308 long ret; 309 310 if(props[0].atom == 0) 311 for(p=props; p->name; p++) 312 p->atom = xatom(p->name); 313 314 ret = 0; 315 while(n--) { 316 Dprint(DEwmh, "\tvals[%d] = \"%A\"\n", n, vals[n]); 317 for(p=props; p->name; p++) 318 if(p->atom == vals[n]) { 319 ret |= p->mask; 320 break; 321 } 322 } 323 return ret; 324 } 325 326 static long 327 getprop_mask(Window *w, char *prop, Prop *props) { 328 ulong *vals; 329 long n, mask; 330 331 n = getprop_ulong(w, prop, "ATOM", 332 0L, &vals, 16); 333 mask = getmask(props, vals, n); 334 free(vals); 335 return mask; 336 } 337 338 void 339 ewmh_getwintype(Client *c) { 340 static Prop props[] = { 341 {Type("DESKTOP"), TypeDesktop}, 342 {Type("DOCK"), TypeDock}, 343 {Type("TOOLBAR"), TypeToolbar}, 344 {Type("MENU"), TypeMenu}, 345 {Type("UTILITY"), TypeUtility}, 346 {Type("SPLASH"), TypeSplash}, 347 {Type("DIALOG"), TypeDialog}, 348 {Type("NORMAL"), TypeNormal}, 349 {0, } 350 }; 351 long mask; 352 353 mask = getprop_mask(&c->w, Net("WM_WINDOW_TYPE"), props); 354 355 c->w.ewmh.type = mask; 356 if(mask & (TypeDock|TypeMenu|TypeToolbar)) { 357 c->borderless = true; 358 c->titleless = true; 359 } 360 if(mask & (TypeSplash|TypeDock)) 361 c->nofocus = true; 362 } 363 364 static void 365 ewmh_getwinstate(Client *c) { 366 ulong *vals; 367 long n; 368 369 n = getprop_ulong(&c->w, Net("WM_STATE"), "ATOM", 370 0L, &vals, 16); 371 while(--n >= 0) 372 ewmh_setstate(c, vals[n], On); 373 free(vals); 374 } 375 376 long 377 ewmh_protocols(Window *w) { 378 static Prop props[] = { 379 {"WM_DELETE_WINDOW", ProtoDelete}, 380 {"WM_TAKE_FOCUS", ProtoTakeFocus}, 381 {Net("WM_PING"), ProtoPing}, 382 {0, } 383 }; 384 385 return getprop_mask(w, "WM_PROTOCOLS", props); 386 } 387 388 void 389 ewmh_getstrut(Client *c) { 390 enum { 391 Left, Right, Top, Bottom, 392 LeftMin, LeftMax, 393 RightMin, RightMax, 394 TopMin, TopMax, 395 BottomMin, BottomMax, 396 Last 397 }; 398 long *strut; 399 ulong n; 400 401 if(c->strut != nil) 402 free(c->strut); 403 c->strut = nil; 404 405 n = getprop_long(&c->w, Net("WM_STRUT_PARTIAL"), "CARDINAL", 406 0L, &strut, Last); 407 if(n != Last) { 408 free(strut); 409 n = getprop_long(&c->w, Net("WM_STRUT"), "CARDINAL", 410 0L, &strut, 4L); 411 if(n != 4) { 412 free(strut); 413 return; 414 } 415 Dprint(DEwmh, "ewmh_getstrut(%#C[%C]) Using WM_STRUT\n", c, c); 416 strut = erealloc(strut, Last * sizeof *strut); 417 strut[LeftMin] = strut[RightMin] = 0; 418 strut[LeftMax] = strut[RightMax] = INT_MAX; 419 strut[TopMin] = strut[BottomMin] = 0; 420 strut[TopMax] = strut[BottomMax] = INT_MAX; 421 } 422 c->strut = emalloc(sizeof *c->strut); 423 c->strut->left = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]); 424 c->strut->right = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]); 425 c->strut->top = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]); 426 c->strut->bottom = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0); 427 Dprint(DEwmh, "ewmh_getstrut(%#C[%C])\n", c, c); 428 Dprint(DEwmh, "\ttop: %R\n", c->strut->top); 429 Dprint(DEwmh, "\tleft: %R\n", c->strut->left); 430 Dprint(DEwmh, "\tright: %R\n", c->strut->right); 431 Dprint(DEwmh, "\tbottom: %R\n", c->strut->bottom); 432 free(strut); 433 view_update(selview); 434 } 435 436 static void 437 ewmh_setstate(Client *c, Atom state, int action) { 438 439 Dprint(DEwmh, "\tSTATE = %A\n", state); 440 if(state == 0) 441 return; 442 443 if(state == STATE("FULLSCREEN")) 444 fullscreen(c, action, -1); 445 else 446 if(state == STATE("DEMANDS_ATTENTION")) 447 client_seturgent(c, action, UrgClient); 448 } 449 450 static bool 451 event_root_clientmessage(Window *w, void *aux, XClientMessageEvent *e) { 452 Client *c; 453 View *v; 454 ulong *l; 455 ulong msg; 456 int i; 457 458 l = (ulong*)e->data.l; 459 msg = e->message_type; 460 Debug(DEwmh) 461 if(msg != xatom("WM_PROTOCOLS") && l[0] != NET("WM_PING")) 462 Dprint(DEwmh, "ClientMessage: %A\n", msg); 463 464 if(msg == NET("CURRENT_DESKTOP")) { 465 if(e->format != 32) 466 return false; 467 for(v=view, i=l[0]; v; v=v->next, i--) 468 if(i == 0) 469 break; 470 Dprint(DEwmh, "\t%s\n", v->name); 471 if(i == 0) 472 view_select(v->name); 473 return 1; 474 } 475 if(msg == xatom("WM_PROTOCOLS")) { 476 if(e->format != 32) 477 return false; 478 if(l[0] == NET("WM_PING")) { 479 if(e->window != scr.root.xid) 480 return false; 481 if(!(c = win2client(l[2]))) 482 return false; 483 i = ewmh_responsive_p(c); 484 c->w.ewmh.ping = nsec() / 1000000; 485 c->w.ewmh.lag = (c->w.ewmh.ping & 0xffffffff) - (l[1] & 0xffffffff); 486 if(i == false) 487 frame_draw(c->sel); 488 return false; 489 } 490 return false; 491 } 492 493 return false; 494 } 495 496 static Handlers root_handlers = { 497 .message = event_root_clientmessage, 498 }; 499 500 501 void 502 ewmh_framesize(Client *c) { 503 Rectangle rc, rf; 504 Frame *f; 505 506 if((f = c->sel)) { 507 rc = f->crect; 508 rf = f->r; 509 } 510 else { 511 rf = frame_client2rect(c, ZR, c->floating); 512 rc = rectsubpt(ZR, rf.min); 513 } 514 515 long extents[] = { 516 rc.min.x, Dx(rf) - rc.max.x, 517 rc.min.y, Dy(rf) - rc.max.y, 518 }; 519 clientprop_long(c, PExtents, Net("FRAME_EXTENTS"), "CARDINAL", 520 extents, nelem(extents)); 521 } 522 523 void 524 ewmh_updatestate(Client *c) { 525 long state[16]; 526 Frame *f; 527 int i; 528 529 f = c->sel; 530 if(f == nil || f->view != selview) 531 return; 532 533 i = 0; 534 if(f->collapsed) 535 state[i++] = STATE("SHADED"); 536 if(c->fullscreen >= 0) 537 state[i++] = STATE("FULLSCREEN"); 538 if(c->urgent) 539 state[i++] = STATE("DEMANDS_ATTENTION"); 540 541 if(i > 0) 542 clientprop_long(c, PState, Net("WM_STATE"), "ATOM", state, i); 543 else 544 clientprop_del(c, PState, Net("WM_STATE")); 545 546 if(c->fullscreen >= 0) 547 clientprop_long(c, PMonitors, Net("WM_FULLSCREEN_MONITORS"), "CARDINAL", 548 (long[]) { c->fullscreen, c->fullscreen, 549 c->fullscreen, c->fullscreen }, 550 4); 551 else 552 clientprop_del(c, PMonitors, Net("WM_FULLSCREEN_MONITORS")); 553 } 554 555 /* Views */ 556 void 557 ewmh_updateviews(void) { 558 View *v; 559 Vector_ptr tags; 560 long i; 561 562 if(starting) 563 return; 564 565 vector_pinit(&tags); 566 for(v=view, i=0; v; v=v->next, i++) 567 vector_ppush(&tags, v->name); 568 vector_ppush(&tags, nil); 569 changeprop_textlist(&scr.root, Net("DESKTOP_NAMES"), "UTF8_STRING", (char**)tags.ary); 570 changeprop_long(&scr.root, Net("NUMBER_OF_DESKTOPS"), "CARDINAL", &i, 1); 571 vector_pfree(&tags); 572 ewmh_updateview(); 573 ewmh_updateclients(); 574 } 575 576 static int 577 viewidx(View *v) { 578 View *vp; 579 int i; 580 581 for(vp=view, i=0; vp; vp=vp->next, i++) 582 if(vp == v) 583 break; 584 assert(vp); 585 return i; 586 } 587 588 void 589 ewmh_updateview(void) { 590 long i; 591 592 if(starting) 593 return; 594 595 i = viewidx(selview); 596 changeprop_long(&scr.root, Net("CURRENT_DESKTOP"), "CARDINAL", &i, 1); 597 } 598 599 void 600 ewmh_updateclient(Client *c) { 601 long i; 602 603 i = -1; 604 if(c->sel) 605 i = viewidx(c->sel->view); 606 clientprop_long(c, PDesktop, Net("WM_DESKTOP"), "CARDINAL", &i, 1); 607 } 608 609 void 610 ewmh_updateclients(void) { 611 Client *c; 612 613 if(starting) 614 return; 615 616 for(c=client; c; c=c->next) 617 ewmh_updateclient(c); 618 } 619