tray.c (10397B)
1 /* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail> 2 * See LICENSE file for license details. 3 */ 4 #include "dat.h" 5 #include <string.h> 6 #include <strings.h> 7 #include "fns.h" 8 9 static Handlers handlers; 10 static Handlers root_handlers; 11 12 void 13 restrut(Window *w, int orientation) { 14 enum { Left, Right, Top, Bottom }; 15 Rectangle strut[4]; 16 Rectangle r; 17 18 r = w->r; 19 memset(strut, 0, sizeof strut); 20 if(Dx(r) < Dx(scr.rect)/2 && orientation != OHorizontal) { 21 if(r.min.x <= scr.rect.min.x) { 22 strut[Left] = r; 23 strut[Left].min.x = 0; 24 strut[Left].max.x -= scr.rect.min.x; 25 } 26 if(r.max.x >= scr.rect.max.x) { 27 strut[Right] = r; 28 strut[Right].min.x -= scr.rect.max.x; 29 strut[Right].max.x = 0; 30 } 31 } 32 if(Dy(r) < Dy(scr.rect)/2 && orientation != OVertical) { 33 if(r.min.y <= scr.rect.min.y) { 34 strut[Top] = r; 35 strut[Top].min.y = 0; 36 strut[Top].max.y -= scr.rect.min.y; 37 } 38 if(r.max.y >= scr.rect.max.y) { 39 strut[Bottom] = r; 40 strut[Bottom].min.y -= scr.rect.max.y; 41 strut[Bottom].max.y = 0; 42 } 43 } 44 45 #if 0 46 #define pstrut(name) \ 47 if(!eqrect(strut[name], ZR)) \ 48 fprint(2, "strut["#name"] = %R\n", strut[name]) 49 pstrut(Left); 50 pstrut(Right); 51 pstrut(Top); 52 pstrut(Bottom); 53 #endif 54 55 ewmh_setstrut(w, strut); 56 } 57 58 void 59 tray_init(void) { 60 WinAttr wa; 61 XWMHints hints = { 0, }; 62 63 wa.background_pixmap = None; 64 wa.bit_gravity = NorthEastGravity; 65 wa.border_pixel = 0; 66 wa.event_mask = ExposureMask 67 | ButtonPressMask 68 | ButtonReleaseMask 69 | StructureNotifyMask 70 | SubstructureNotifyMask 71 /* Disallow clients reconfiguring themselves. */ 72 | SubstructureRedirectMask; 73 tray.win = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth, InputOutput, 74 &wa, CWBackPixmap 75 | CWBitGravity 76 | CWEventMask); 77 78 sethandler(tray.win, &handlers); 79 pushhandler(&scr.root, &root_handlers, nil); 80 selectinput(&scr.root, scr.root.eventmask | PropertyChangeMask); 81 82 83 changeprop_string(tray.win, "_WMII_TAGS", tray.tags); 84 85 changeprop_ulong(tray.win, "XdndAware", "ATOM", (ulong[1]){ 5 }, 1); 86 87 changeprop_ulong(tray.selection->owner, Net("SYSTEM_TRAY_VISUAL"), "VISUALID", 88 &scr.visual->visualid, 1); 89 changeprop_long(tray.win, Net("WM_WINDOW_TYPE"), "ATOM", 90 (long[1]){ TYPE("DOCK") }, 1); 91 92 changeprop_string(tray.win, Net("WM_NAME"), "witray"); 93 changeprop_textlist(tray.win, "WM_NAME", "STRING", 94 (char*[2]){ "witray", nil }); 95 changeprop_textlist(tray.win, "WM_CLASS", "STRING", 96 (char*[3]){ "witray", "witray", nil }); 97 changeprop_textlist(tray.win, "WM_COMMAND", "STRING", program_args); 98 99 hints.flags = InputHint; 100 hints.input = false; 101 XSetWMHints(display, tray.win->xid, &hints); 102 tray_resize(tray.win->r); 103 } 104 105 static void 106 tray_unmap(void) { 107 unmapwin(tray.win); 108 sendevent(&scr.root, false, SubstructureNotifyMask, 109 &(XUnmapEvent){ 110 .type = UnmapNotify, 111 .event = scr.root.xid, 112 .window = tray.win->xid 113 }); 114 } 115 116 static void 117 tray_draw(Rectangle r) { 118 int borderwidth; 119 120 if(!tray.pixmap) 121 return; 122 123 borderwidth = 1; 124 125 r = rectsetorigin(r, ZP); 126 border(tray.pixmap, r, borderwidth, &tray.selcolors.border); 127 r = insetrect(r, borderwidth); 128 fill(tray.pixmap, r, &tray.selcolors.bg); 129 XClearWindow(display, tray.win->xid); 130 } 131 132 void 133 tray_resize(Rectangle r) { 134 WinHints hints; 135 Image *oldimage; 136 WinAttr wa; 137 138 hints = ZWinHints; 139 hints.position = true; 140 hints.min = hints.max = Pt(Dx(r), Dy(r)); 141 hints.grav = Pt(tray.edge & East ? 0 : 142 tray.edge & West ? 2 : 1, 143 tray.edge & North ? 0 : 144 tray.edge & South ? 2 : 1); 145 /* Not necessary, since we specify fixed size, but... */ 146 // hints.base = Pt(2, 2); 147 // hints.inc = Pt(tray.iconsize, tray.iconsize); 148 sethints(tray.win, &hints); 149 150 if(!eqrect(tray.win->r, r)) { 151 oldimage = tray.pixmap; 152 153 tray.pixmap = allocimage(Dx(r), Dy(r), tray.win->depth); 154 tray_draw(r); 155 wa.background_pixmap = tray.pixmap->xid; 156 setwinattr(tray.win, &wa, CWBackPixmap); 157 158 freeimage(oldimage); 159 } 160 161 tray.r = r; 162 tray.win->r = ZR; /* Force the configure event. */ 163 reshapewin(tray.win, r); 164 restrut(tray.win, tray.orientation); 165 } 166 167 void 168 tray_update(void) { 169 Rectangle r; 170 Point p, offset, padding; 171 Client *c; 172 173 r = Rect(0, 0, tray.iconsize, tray.iconsize); 174 padding = Pt(tray.padding, tray.padding); 175 offset = padding; 176 Dprint("tray_update()\n"); 177 for(c=tray.clients; c; c=c->next) { 178 if(c->w.mapped) { 179 reshapewin(&c->w, rectaddpt(r, offset)); 180 /* This seems, sadly, to be necessary. */ 181 sendevent(&c->w, false, StructureNotifyMask, &(XEvent){ 182 .xconfigure = { 183 .type = ConfigureNotify, 184 .event = c->w.xid, 185 .window = c->w.xid, 186 .above = None, 187 .x = c->w.r.min.x, 188 .y = c->w.r.min.y, 189 .width = Dx(c->w.r), 190 .height = Dy(c->w.r), 191 .border_width = 0, 192 } 193 }); 194 195 movewin(c->indicator, addpt(offset, Pt(2, 2))); 196 if(tray.orientation == OHorizontal) 197 offset.x += tray.iconsize + tray.padding; 198 else 199 offset.y += tray.iconsize + tray.padding; 200 } 201 if(c->w.mapped && client_hasmessage(c)) 202 mapwin(c->indicator); 203 else 204 unmapwin(c->indicator); 205 } 206 207 if(eqpt(offset, padding)) 208 tray_unmap(); 209 else { 210 if(tray.orientation == OHorizontal) 211 offset.y += tray.iconsize + tray.padding; 212 else 213 offset.x += tray.iconsize + tray.padding; 214 215 r = Rpt(ZP, offset); 216 p = subpt(scr.rect.max, r.max); 217 if(tray.edge & East) 218 p.x = 0; 219 if(tray.edge & North) 220 p.y = 0; 221 tray_resize(rectaddpt(r, p)); 222 mapwin(tray.win); 223 } 224 225 tray_draw(tray.win->r); 226 } 227 228 static bool 229 config_event(Window *w, void *aux, XConfigureEvent *ev) { 230 231 USED(aux); 232 if(ev->send_event) { 233 /* 234 * Per ICCCM §4.2.3, the window manager sends 235 * synthetic configure events in the root coordinate 236 * space when it changes the window configuration. 237 * This code assumes wmii's generous behavior of 238 * supplying all relevant members in every configure 239 * notify event. 240 */ 241 w->r = rectaddpt(rectsetorigin(Rect(0, 0, ev->width, ev->height), 242 Pt(ev->x, ev->y)), 243 Pt(ev->border_width, ev->border_width)); 244 restrut(w, tray.orientation); 245 } 246 return false; 247 } 248 249 static bool 250 expose_event(Window *w, void *aux, XExposeEvent *ev) { 251 252 USED(w, aux, ev); 253 tray_draw(tray.win->r); 254 return false; 255 } 256 257 typedef struct Dnd Dnd; 258 259 struct Dnd { 260 ulong source; 261 ulong dest; 262 long data[4]; 263 Point p; 264 bool have_actions; 265 }; 266 267 static Dnd dnd; 268 269 #define Point(l) Pt((ulong)(l) >> 16, (ulong)(l) & 0xffff) 270 #define Long(p) ((long)(((ulong)(p).x << 16) | (ulong)(p).y)) 271 #define sendmessage(...) BLOCK( \ 272 Dprint("(%W) %s 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx\n", __VA_ARGS__); \ 273 sendmessage(__VA_ARGS__); \ 274 ) 275 276 static void 277 dnd_updatestatus(ulong dest) { 278 if(dest == dnd.dest) 279 return; 280 281 if(dnd.dest && dnd.dest != ~0UL) 282 sendmessage(window(dnd.dest), "XdndLeave", tray.win->xid, 0, 0, 0, 0); 283 dnd.dest = dest; 284 if(dest) 285 sendmessage(window(dest), "XdndEnter", tray.win->xid, 286 dnd.data[0], dnd.data[1], dnd.data[2], dnd.data[3]); 287 else 288 sendmessage(window(dnd.source), "XdndStatus", tray.win->xid, (1<<1), 289 Long(tray.win->r.min), (Dx(tray.win->r)<<16) | Dy(tray.win->r), 0UL); 290 } 291 292 static void 293 copyprop_long(Window *src, Window *dst, char *atom, char *type, long max) { 294 long *data; 295 long n; 296 297 /* Round trip. Really need to switch to XCB. */ 298 if((n = getprop_long(src, atom, type, 0, &data, max))) 299 changeprop_long(dst, atom, type, data, n); 300 free(data); 301 } 302 303 static void 304 copyprop_char(Window *src, Window *dst, char *atom, char *type, long max) { 305 uchar *data; 306 ulong actual, n; 307 int format; 308 309 n = getprop(src, atom, type, &actual, &format, 0, &data, max); 310 if(n > 0 && format == 8 && xatom(type) == actual) 311 changeprop_char(dst, atom, type, (char*)data, n); 312 free(data); 313 } 314 315 static bool 316 message_event(Window *w, void *aux, XClientMessageEvent *e) { 317 Client *c; 318 long *l; 319 Rectangle r; 320 Point p; 321 ulong msg; 322 323 msg = e->message_type; 324 l = e->data.l; 325 Dprint("ClientMessage: %A\n", msg); 326 if(e->format == 32) 327 Dprint("\t0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx\n", 328 l[0], l[1], l[2], l[3], l[4]); 329 330 if(msg == xatom("XdndEnter")) { 331 if(e->format != 32) 332 return false; 333 dnd = (Dnd){0}; 334 dnd.dest = ~0UL; 335 dnd.source = l[0]; 336 bcopy(&l[1], dnd.data, sizeof dnd.data); 337 338 copyprop_long(window(dnd.source), tray.win, "XdndSelection", "ATOM", 128); 339 if(l[1] & 0x01) 340 copyprop_long(window(dnd.source), tray.win, "XdndTypeList", "ATOM", 128); 341 return false; 342 }else 343 if(msg == xatom("XdndLeave")) { 344 if(e->format != 32) 345 return false; 346 dnd.source = 0UL; 347 if(dnd.dest) 348 sendmessage(window(dnd.dest), "XdndLeave", tray.win->xid, l[1], 0, 0, 0); 349 return false; 350 }else 351 if(msg == xatom("XdndPosition")) { 352 if(e->format != 32) 353 return false; 354 355 if(!dnd.have_actions && l[4] == xatom("XdndActionAsk")) { 356 dnd.have_actions = true; 357 copyprop_long(window(dnd.source), tray.win, "XdndActionList", "ATOM", 16); 358 copyprop_char(window(dnd.source), tray.win, "XdndActionDescription", "ATOM", 16 * 32); 359 } 360 361 dnd.p = subpt(Point(l[2]), tray.win->r.min); 362 for(c=tray.clients; c; c=c->next) 363 if(rect_haspoint_p(c->w.r, dnd.p)) { 364 dnd_updatestatus(c->w.xid); 365 sendmessage(&c->w, "XdndPosition", tray.win->xid, l[1], l[2], l[3], l[4]); 366 return false; 367 } 368 dnd_updatestatus(0UL); 369 return false; 370 }else 371 if(msg == xatom("XdndStatus")) { 372 if(e->format != 32) 373 return false; 374 if(l[0] != dnd.dest) 375 return false; 376 377 for(c=tray.clients; c; c=c->next) 378 if(c->w.xid == dnd.dest) { 379 p = Point(l[2]); 380 r = Rpt(p, addpt(p, Point(l[3]))); 381 r = rect_intersection(r, rectaddpt(c->w.r, tray.win->r.min)); 382 383 sendmessage(window(dnd.source), "XdndStatus", tray.win->xid, l[1], 384 Long(r.min), (Dx(r)<<16) | Dy(r), l[4]); 385 break; 386 } 387 return false; 388 }else 389 if(msg == xatom("XdndDrop") || msg == xatom("XdndFinished")) { 390 if(e->format != 32) 391 return false; 392 393 for(c=tray.clients; c; c=c->next) 394 if(c->w.xid == dnd.dest) { 395 sendmessage(&c->w, atomname(msg), 396 tray.win->xid, l[1], l[2], 0L, 0L); 397 break; 398 } 399 return false; 400 } 401 402 return true; 403 } 404 405 static Handlers handlers = { 406 .message = message_event, 407 .config = config_event, 408 .expose = expose_event, 409 }; 410 411 static bool 412 property_event(Window *w, void *aux, XPropertyEvent *ev) { 413 if(ev->atom == NET("CURRENT_DESKTOP")) 414 tray_resize(tray.r); 415 Debug if(ev->atom == NET("CURRENT_DESKTOP")) 416 print("property_event(_NET_CURRENT_DESKTOP)\n"); 417 return false; 418 } 419 420 static Handlers root_handlers = { 421 .property = property_event, 422 }; 423