/* Copyright ©2008-2010 Kris Maglione * See LICENSE file for license details. */ #include "dat.h" #include #include #include "fns.h" static Handlers handlers; static Handlers root_handlers; void restrut(Window *w, int orientation) { enum { Left, Right, Top, Bottom }; Rectangle strut[4]; Rectangle r; r = w->r; memset(strut, 0, sizeof strut); if(Dx(r) < Dx(scr.rect)/2 && orientation != OHorizontal) { if(r.min.x <= scr.rect.min.x) { strut[Left] = r; strut[Left].min.x = 0; strut[Left].max.x -= scr.rect.min.x; } if(r.max.x >= scr.rect.max.x) { strut[Right] = r; strut[Right].min.x -= scr.rect.max.x; strut[Right].max.x = 0; } } if(Dy(r) < Dy(scr.rect)/2 && orientation != OVertical) { if(r.min.y <= scr.rect.min.y) { strut[Top] = r; strut[Top].min.y = 0; strut[Top].max.y -= scr.rect.min.y; } if(r.max.y >= scr.rect.max.y) { strut[Bottom] = r; strut[Bottom].min.y -= scr.rect.max.y; strut[Bottom].max.y = 0; } } #if 0 #define pstrut(name) \ if(!eqrect(strut[name], ZR)) \ fprint(2, "strut["#name"] = %R\n", strut[name]) pstrut(Left); pstrut(Right); pstrut(Top); pstrut(Bottom); #endif ewmh_setstrut(w, strut); } void tray_init(void) { WinAttr wa; XWMHints hints = { 0, }; wa.background_pixmap = None; wa.bit_gravity = NorthEastGravity; wa.border_pixel = 0; wa.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | SubstructureNotifyMask /* Disallow clients reconfiguring themselves. */ | SubstructureRedirectMask; tray.win = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth, InputOutput, &wa, CWBackPixmap | CWBitGravity | CWEventMask); sethandler(tray.win, &handlers); pushhandler(&scr.root, &root_handlers, nil); selectinput(&scr.root, scr.root.eventmask | PropertyChangeMask); changeprop_string(tray.win, "_WMII_TAGS", tray.tags); changeprop_ulong(tray.win, "XdndAware", "ATOM", (ulong[1]){ 5 }, 1); changeprop_ulong(tray.selection->owner, Net("SYSTEM_TRAY_VISUAL"), "VISUALID", &scr.visual->visualid, 1); changeprop_long(tray.win, Net("WM_WINDOW_TYPE"), "ATOM", (long[1]){ TYPE("DOCK") }, 1); changeprop_string(tray.win, Net("WM_NAME"), "witray"); changeprop_textlist(tray.win, "WM_NAME", "STRING", (char*[2]){ "witray", nil }); changeprop_textlist(tray.win, "WM_CLASS", "STRING", (char*[3]){ "witray", "witray", nil }); changeprop_textlist(tray.win, "WM_COMMAND", "STRING", program_args); hints.flags = InputHint; hints.input = false; XSetWMHints(display, tray.win->xid, &hints); tray_resize(tray.win->r); } static void tray_draw(Rectangle r) { int borderwidth; if(!tray.pixmap) return; borderwidth = 1; r = rectsetorigin(r, ZP); border(tray.pixmap, r, borderwidth, tray.selcolors.border); r = insetrect(r, borderwidth); fill(tray.pixmap, r, tray.selcolors.bg); XClearWindow(display, tray.win->xid); } void tray_resize(Rectangle r) { WinHints hints; Image *oldimage; WinAttr wa; hints = ZWinHints; hints.position = true; hints.min = hints.max = Pt(Dx(r), Dy(r)); hints.grav = Pt(tray.edge & East ? 0 : tray.edge & West ? 2 : 1, tray.edge & North ? 0 : tray.edge & South ? 2 : 1); /* Not necessary, since we specify fixed size, but... */ // hints.base = Pt(2, 2); // hints.inc = Pt(tray.iconsize, tray.iconsize); sethints(tray.win, &hints); if(!eqrect(tray.win->r, r)) { oldimage = tray.pixmap; tray.pixmap = allocimage(Dx(r), Dy(r), tray.win->depth); tray_draw(r); wa.background_pixmap = tray.pixmap->xid; setwinattr(tray.win, &wa, CWBackPixmap); freeimage(oldimage); } tray.r = r; tray.win->r = ZR; /* Force the configure event. */ reshapewin(tray.win, r); restrut(tray.win, tray.orientation); } void tray_update(void) { Rectangle r; Point p, offset, padding; Client *c; r = Rect(0, 0, tray.iconsize, tray.iconsize); padding = Pt(tray.padding, tray.padding); offset = padding; Dprint("tray_update()\n"); for(c=tray.clients; c; c=c->next) { if(c->w.mapped) { reshapewin(&c->w, rectaddpt(r, offset)); /* This seems, sadly, to be necessary. */ sendevent(&c->w, false, StructureNotifyMask, &(XEvent){ .xconfigure = { .type = ConfigureNotify, .event = c->w.xid, .window = c->w.xid, .above = None, .x = c->w.r.min.x, .y = c->w.r.min.y, .width = Dx(c->w.r), .height = Dy(c->w.r), .border_width = 0, } }); movewin(c->indicator, addpt(offset, Pt(2, 2))); if(tray.orientation == OHorizontal) offset.x += tray.iconsize + tray.padding; else offset.y += tray.iconsize + tray.padding; } if(c->w.mapped && client_hasmessage(c)) mapwin(c->indicator); else unmapwin(c->indicator); } if(eqpt(offset, padding)) unmapwin(tray.win); else { if(tray.orientation == OHorizontal) offset.y += tray.iconsize + tray.padding; else offset.x += tray.iconsize + tray.padding; r = Rpt(ZP, offset); p = subpt(scr.rect.max, r.max); if(tray.edge & East) p.x = 0; if(tray.edge & North) p.y = 0; tray_resize(rectaddpt(r, p)); mapwin(tray.win); } tray_draw(tray.win->r); } static bool config_event(Window *w, void *aux, XConfigureEvent *ev) { USED(aux); if(ev->send_event) { /* * Per ICCCM §4.2.3, the window manager sends * synthetic configure events in the root coordinate * space when it changes the window configuration. * This code assumes wmii's generous behavior of * supplying all relevant members in every configure * notify event. */ w->r = rectaddpt(rectsetorigin(Rect(0, 0, ev->width, ev->height), Pt(ev->x, ev->y)), Pt(ev->border_width, ev->border_width)); restrut(w, tray.orientation); } return false; } static bool expose_event(Window *w, void *aux, XExposeEvent *ev) { USED(w, aux, ev); tray_draw(tray.win->r); return false; } typedef struct Dnd Dnd; struct Dnd { ulong source; ulong dest; long data[4]; Point p; bool have_actions; }; static Dnd dnd; #define Point(l) Pt((ulong)(l) >> 16, (ulong)(l) & 0xffff) #define Long(p) ((long)(((ulong)(p).x << 16) | (ulong)(p).y)) #define sendmessage(...) BLOCK( \ Dprint("(%W) %s 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx\n", __VA_ARGS__); \ sendmessage(__VA_ARGS__); \ ) static void dnd_updatestatus(ulong dest) { if(dest == dnd.dest) return; if(dnd.dest && dnd.dest != ~0UL) sendmessage(window(dnd.dest), "XdndLeave", tray.win->xid, 0, 0, 0, 0); dnd.dest = dest; if(dest) sendmessage(window(dest), "XdndEnter", tray.win->xid, dnd.data[0], dnd.data[1], dnd.data[2], dnd.data[3]); else sendmessage(window(dnd.source), "XdndStatus", tray.win->xid, (1<<1), Long(tray.win->r.min), (Dx(tray.win->r)<<16) | Dy(tray.win->r), 0UL); } static void copyprop_long(Window *src, Window *dst, char *atom, char *type, long max) { long *data; long n; /* Round trip. Really need to switch to XCB. */ if((n = getprop_long(src, atom, type, 0, &data, max))) changeprop_long(dst, atom, type, data, n); free(data); } static void copyprop_char(Window *src, Window *dst, char *atom, char *type, long max) { uchar *data; ulong actual, n; int format; n = getprop(src, atom, type, &actual, &format, 0, &data, max); if(n > 0 && format == 8 && xatom(type) == actual) changeprop_char(dst, atom, type, (char*)data, n); free(data); } static bool message_event(Window *w, void *aux, XClientMessageEvent *e) { Client *c; long *l; Rectangle r; Point p; ulong msg; msg = e->message_type; l = e->data.l; Dprint("ClientMessage: %A\n", msg); if(e->format == 32) Dprint("\t0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx\n", l[0], l[1], l[2], l[3], l[4]); if(msg == xatom("XdndEnter")) { if(e->format != 32) return false; dnd = (Dnd){0}; dnd.dest = ~0UL; dnd.source = l[0]; bcopy(&l[1], dnd.data, sizeof dnd.data); copyprop_long(window(dnd.source), tray.win, "XdndSelection", "ATOM", 128); if(l[1] & 0x01) copyprop_long(window(dnd.source), tray.win, "XdndTypeList", "ATOM", 128); return false; }else if(msg == xatom("XdndLeave")) { if(e->format != 32) return false; dnd.source = 0UL; if(dnd.dest) sendmessage(window(dnd.dest), "XdndLeave", tray.win->xid, l[1], 0, 0, 0); return false; }else if(msg == xatom("XdndPosition")) { if(e->format != 32) return false; if(!dnd.have_actions && l[4] == xatom("XdndActionAsk")) { dnd.have_actions = true; copyprop_long(window(dnd.source), tray.win, "XdndActionList", "ATOM", 16); copyprop_char(window(dnd.source), tray.win, "XdndActionDescription", "ATOM", 16 * 32); } dnd.p = subpt(Point(l[2]), tray.win->r.min); for(c=tray.clients; c; c=c->next) if(rect_haspoint_p(c->w.r, dnd.p)) { dnd_updatestatus(c->w.xid); sendmessage(&c->w, "XdndPosition", tray.win->xid, l[1], l[2], l[3], l[4]); return false; } dnd_updatestatus(0UL); return false; }else if(msg == xatom("XdndStatus")) { if(e->format != 32) return false; if(l[0] != dnd.dest) return false; for(c=tray.clients; c; c=c->next) if(c->w.xid == dnd.dest) { p = Point(l[2]); r = Rpt(p, addpt(p, Point(l[3]))); r = rect_intersection(r, rectaddpt(c->w.r, tray.win->r.min)); sendmessage(window(dnd.source), "XdndStatus", tray.win->xid, l[1], Long(r.min), (Dx(r)<<16) | Dy(r), l[4]); break; } return false; }else if(msg == xatom("XdndDrop") || msg == xatom("XdndFinished")) { if(e->format != 32) return false; for(c=tray.clients; c; c=c->next) if(c->w.xid == dnd.dest) { sendmessage(&c->w, atomname(msg), tray.win->xid, l[1], l[2], 0L, 0L); break; } return false; } return true; } static Handlers handlers = { .message = message_event, .config = config_event, .expose = expose_event, }; static bool property_event(Window *w, void *aux, XPropertyEvent *ev) { if(ev->atom == NET("CURRENT_DESKTOP")) tray_resize(tray.r); Debug if(ev->atom == NET("CURRENT_DESKTOP")) print("property_event(_NET_CURRENT_DESKTOP)\n"); return false; } static Handlers root_handlers = { .property = property_event, };