From 908978cca16217dc7eded60da311405c8dc08dc0 Mon Sep 17 00:00:00 2001 From: Martin Duquesnoy Date: Thu, 22 Jul 2010 20:12:27 +0200 Subject: [PATCH] !! Implementation of System tray !! --- src/event.c | 148 ++++++++++++++++++++++++++++++---- src/ewmh.c | 56 +++++++++++++ src/init.c | 1 + src/structs.h | 19 +++++ src/systray.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/wmfs.c | 7 ++ src/wmfs.h | 23 +++++- 7 files changed, 455 insertions(+), 16 deletions(-) diff --git a/src/event.c b/src/event.c index c0e413a..3b80d96 100644 --- a/src/event.c +++ b/src/event.c @@ -162,6 +162,7 @@ void clientmessageevent(XClientMessageEvent *ev) { Client *c; + Systray *sy; int s, i, mess_t = 0; Atom rt; int rf; @@ -195,8 +196,28 @@ clientmessageevent(XClientMessageEvent *ev) /* Manage _NET_ACTIVE_WINDOW */ else if(mess_t == net_active_window) + { if((c = client_gb_win(ev->window))) client_focus(c); + else if((sy = systray_find(ev->data.l[0]))) + XSetInputFocus(dpy, sy->win, RevertToNone, CurrentTime); + } + } + else if(ev->window == traywin) + { + /* Manage _NET_WM_SYSTEM_TRAY_OPCODE */ + if(mess_t == net_wm_system_tray_opcode) + { + if(ev->data.l[1] == XEMBED_EMBEDDED_NOTIFY) + { + systray_add(ev->data.l[2]); + systray_update(); + } + else if(ev->data.l[1] == XEMBED_REQUEST_FOCUS) + if((sy = systray_find(ev->data.l[2]))) + ewmh_send_message(sy->win, sy->win, "_XEMBED", + XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0, 0); + } } /* Manage _NET_WM_STATE */ @@ -214,6 +235,7 @@ clientmessageevent(XClientMessageEvent *ev) if((c = client_gb_win(ev->window))) tag_transfert(c, ev->data.l[0]); + /* Manage _WMFS_STATUSTEXT_x */ if(mess_t >= wmfs_statustext && ev->data.l[4] == True) { @@ -262,8 +284,8 @@ clientmessageevent(XClientMessageEvent *ev) return; } -/** ConfigureRequest & ConfigureNotify handle events - * \param ev XEvent pointer +/** ConfigureRequesthandle events + * \param ev XConfigureRequestEvent pointer */ void configureevent(XConfigureRequestEvent *ev) @@ -318,12 +340,19 @@ void destroynotify(XDestroyWindowEvent *ev) { Client *c; + Systray *s; if((c = client_gb_win(ev->window))) { client_unmanage(c); XSetErrorHandler(errorhandler); } + else if((s = systray_find(ev->window))) + { + setwinstate(s->win, WithdrawnState); + systray_del(s->win); + systray_update(); + } return; } @@ -335,6 +364,7 @@ void enternotify(XCrossingEvent *ev) { Client *c; + Systray *s; int n; if((ev->mode != NotifyNormal @@ -342,6 +372,9 @@ enternotify(XCrossingEvent *ev) && ev->window != ROOT) return; + if((s = systray_find(ev->window))) + XSetInputFocus(dpy, s->win, RevertToNone, CurrentTime); + if(conf.focus_fmouse) { if((c = client_gb_win(ev->window)) @@ -441,17 +474,49 @@ keypress(XKeyPressedEvent *ev) return; } -/** MapNotify handle event +/** MappingNotify handle event * \param ev XMappingEvent pointer */ void mappingnotify(XMappingEvent *ev) { + Systray *s; + XRefreshKeyboardMapping(ev); if(ev->request == MappingKeyboard) grabkeys(); + if(!(s = systray_find(ev->window))) + { + setwinstate(s->win, NormalState); + ewmh_send_message(s->win, s->win, "_XEMBED", CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); + } + + + return; +} + +/** MapNotify handle event + * \param ev XMapEvent pointer + */ +void +mapnotify(XMapEvent *ev) +{ + Client *c; + Systray *s; + + if(ev->window != ev->event && !ev->send_event) + return; + + if((c = client_gb_win(ev->window))) + setwinstate(c->win, NormalState); + else if((s = systray_find(ev->window))) + { + setwinstate(s->win, NormalState); + ewmh_send_message(s->win, s->win, "_XEMBED", CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); + } + return; } @@ -480,12 +545,27 @@ void propertynotify(XPropertyEvent *ev) { Client *c; + Systray *s; Window trans; XWMHints *h; if(ev->state == PropertyDelete) return; + if((s = systray_find(ev->window))) + { + if(ev->atom == XA_WM_NORMAL_HINTS) + { + systray_configure(s); + systray_update(); + } + else if(ev->atom == net_atom[xembedinfo]) + { + systray_state(s); + systray_update(); + } + } + if((c = client_gb_win(ev->window))) { switch(ev->atom) @@ -524,6 +604,33 @@ propertynotify(XPropertyEvent *ev) return; } +/** XReparentEvent handle event + * \param ev XReparentEvent pointer + */ +void +reparentnotify(XReparentEvent *ev) +{ + + return; +} + + +/** SelectionClearEvent handle event + * \param ev XSelectionClearEvent pointer + */ +void +selectionclearevent(XSelectionClearEvent *ev) +{ + /* Getting selection if lost it */ + if(ev->window == traywin) + systray_acquire(); + + systray_update(); + + return; +} + + /** UnmapNotify handle event * \param ev XUnmapEvent pointer */ @@ -531,6 +638,7 @@ void unmapnotify(XUnmapEvent *ev) { Client *c; + Systray *s; if((c = client_gb_win(ev->window)) && ev->send_event @@ -540,6 +648,13 @@ unmapnotify(XUnmapEvent *ev) XSetErrorHandler(errorhandler); } + if((s = systray_find(ev->window))) + { + setwinstate(s->win, WithdrawnState); + systray_del(s->win); + systray_update(); + } + return; } @@ -579,18 +694,21 @@ getevent(XEvent ev) switch(ev.type) { - case ButtonPress: buttonpress(&ev.xbutton); break; - case ClientMessage: clientmessageevent(&ev.xclient); break; - case ConfigureRequest: configureevent(&ev.xconfigurerequest); break; - case DestroyNotify: destroynotify(&ev.xdestroywindow); break; - case EnterNotify: enternotify(&ev.xcrossing); break; - case Expose: expose(&ev.xexpose); break; - case FocusIn: focusin(&ev.xfocus); break; - case KeyPress: keypress(&ev.xkey); break; - case MapRequest: maprequest(&ev.xmaprequest); break; - case MappingNotify: mappingnotify(&ev.xmapping); break; - case PropertyNotify: propertynotify(&ev.xproperty); break; - case UnmapNotify: unmapnotify(&ev.xunmap); break; + case ButtonPress: buttonpress(&ev.xbutton); break; + case ClientMessage: clientmessageevent(&ev.xclient); break; + case ConfigureRequest: configureevent(&ev.xconfigurerequest); break; + case DestroyNotify: destroynotify(&ev.xdestroywindow); break; + case EnterNotify: enternotify(&ev.xcrossing); break; + case Expose: expose(&ev.xexpose); break; + case FocusIn: focusin(&ev.xfocus); break; + case KeyPress: keypress(&ev.xkey); break; + case MapNotify: mapnotify(&ev.xmap); break; + case MapRequest: maprequest(&ev.xmaprequest); break; + case MappingNotify: mappingnotify(&ev.xmapping); break; + case PropertyNotify: propertynotify(&ev.xproperty); break; + case ReparentNotify: reparentnotify(&ev.xreparent); break; + case SelectionClear: selectionclearevent(&ev.xselectionclear); break; + case UnmapNotify: unmapnotify(&ev.xunmap); break; default: #ifdef HAVE_XRANDR diff --git a/src/ewmh.c b/src/ewmh.c index cc8e0d3..5cd2091 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -77,6 +77,11 @@ ewmh_init_hints(void) net_atom[net_wm_state_fullscreen] = ATOM("_NET_WM_STATE_FULLSCREEN"); net_atom[net_wm_state_sticky] = ATOM("_NET_WM_STATE_STICKY"); net_atom[net_wm_state_demands_attention] = ATOM("_NET_WM_STATE_DEMANDS_ATTENTION"); + net_atom[net_wm_system_tray_opcode] = ATOM("_NET_SYSTEM_TRAY_OPCODE"); + net_atom[net_system_tray_message_data] = ATOM("_NET_SYSTEM_TRAY_MESSAGE_DATA"); + net_atom[net_system_tray_s] = ATOM("_NET_SYSTEM_TRAY_S"); + net_atom[xembed] = ATOM("_XEMBED"); + net_atom[xembedinfo] = ATOM("_XEMBED_INFO"); net_atom[utf8_string] = ATOM("UTF8_STRING"); /* WMFS hints */ @@ -128,6 +133,57 @@ ewmh_init_hints(void) return; } +/** Send ewmh message + */ +void +ewmh_send_message(Window d, Window w, char *atom, long d0, long d1, long d2, long d3, long d4) +{ + + XClientMessageEvent e; + + e.type = ClientMessage; + e.message_type = ATOM(atom); + e.window = w; + e.format = 32; + e.data.l[0] = d0; + e.data.l[1] = d1; + e.data.l[2] = d2; + e.data.l[3] = d3; + e.data.l[4] = d4; + + XSendEvent(dpy, d, False, NoEventMask, (XEvent*)&e); + + return; +} + +/** Get xembed state + */ +long +ewmh_get_xembed_state(Window win) +{ + xembed_info *xi = NULL; + Atom rf; + int f; + ulong n, il; + long flags; + uchar *data = NULL; + + if(XGetWindowProperty(dpy, win, net_atom[xembedinfo], 0L, 0x7FFFFFFFL, + False, net_atom[xembedinfo], &rf, &f, &n, &il, &data) == Success && n) + xi = (xembed_info*)data; + else + { + XFree(data); + return 0; + } + + flags = xi->flags; + + XFree(xi); + + return flags; +} + /** Get the number of desktop (tag) */ void diff --git a/src/init.c b/src/init.c index a924c8d..4b81a66 100644 --- a/src/init.c +++ b/src/init.c @@ -74,6 +74,7 @@ init(void) init_status(); ewmh_update_current_tag_prop(); grabkeys(); + systray_init(); return; } diff --git a/src/structs.h b/src/structs.h index 900cd51..2bfae0b 100644 --- a/src/structs.h +++ b/src/structs.h @@ -125,6 +125,11 @@ enum net_wm_state_fullscreen, net_wm_state_sticky, net_wm_state_demands_attention, + net_wm_system_tray_opcode, + net_system_tray_message_data, + net_system_tray_s, + xembed, + xembedinfo, utf8_string, /* WMFS HINTS */ wmfs_running, @@ -252,6 +257,15 @@ typedef struct void (*func)(int screen); } Layout; +/* Systray Structure */ +typedef struct Systray Systray; +struct Systray +{ + Window win; + XRectangle geo; + Systray *next, *prev; +}; + /* Tag Structure */ typedef struct { @@ -500,4 +514,9 @@ typedef struct char *uicb; } vicmd_to_uicb; +typedef struct +{ + int flags; +} xembed_info; + #endif /* STRUCTS_H */ diff --git a/src/systray.c b/src/systray.c index 045ba31..931fa12 100644 --- a/src/systray.c +++ b/src/systray.c @@ -32,3 +32,220 @@ #include "wmfs.h" +#define TRAY_DWIDTH 18 + +Bool +systray_acquire(void) +{ + char systray_atom[32]; + + snprintf(systray_atom, sizeof(systray_atom), "_NET_SYSTEM_TRAY_S%u", SCREEN); + trayatom = XInternAtom(dpy, systray_atom, False); + + XSetSelectionOwner(dpy, ATOM(systray_atom), traywin, CurrentTime); + + if(XGetSelectionOwner(dpy, trayatom) != traywin) + return False; + + ewmh_send_message(ROOT, ROOT, "MANAGER", CurrentTime, trayatom, traywin, 0, 0); + + return True; +} + +void +systray_init(void) +{ + XSetWindowAttributes wattr; + + /* Init traywin window */ + wattr.event_mask = ButtonPressMask|ExposureMask; + wattr.override_redirect = True; + wattr.background_pixel = conf.colors.bar; + + traywin = XCreateSimpleWindow(dpy, infobar[0].bar->win, 0, 0, 1, 1, 0, 0, conf.colors.bar); + + XChangeWindowAttributes(dpy, traywin, CWEventMask | CWOverrideRedirect | CWBackPixel, &wattr); + XSelectInput(dpy, traywin, KeyPressMask | ButtonPressMask); + + XMapRaised(dpy, traywin); + + /* Select tray */ + if(!systray_acquire()) + warnx("Can't initialize system tray: owned by another process"); + + return; +} + +void +systray_kill(void) +{ + XSetSelectionOwner(dpy, trayatom, None, CurrentTime); + XUnmapWindow(dpy, traywin); + + return; +} + +void +systray_add(Window win) +{ + Systray *s = emalloc(1, sizeof(Systray)); + + s->win = win; + + s->geo.height = infobar[0].bar->geo.height; + s->geo.width = TRAY_DWIDTH; + + setwinstate(s->win, WithdrawnState); + XSelectInput(dpy, s->win, StructureNotifyMask | PropertyChangeMask| EnterWindowMask | FocusChangeMask); + XReparentWindow(dpy, s->win, traywin, 0, 0); + + ewmh_send_message(s->win, s->win, "_XEMBED", CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0, traywin, 0); + + /* Attach */ + if(trayicons) + trayicons->prev = s; + + s->next = trayicons; + trayicons = s; + + return; +} + +void +systray_del(Window win) +{ + Systray *t, **ss; + + if(!(t = systray_find(win))) + return; + + for(ss = &trayicons; *ss && *ss != t; ss = &(*ss)->next); + *ss = t->next; + + return; +} + +void +systray_configure(Systray *s) +{ + long d = 0; + XSizeHints *sh = NULL; + + if(!(sh = XAllocSizeHints())) + return; + + XGetWMNormalHints(dpy, s->win, sh, &d); + + /* TODO: Improve this.. */ + if(d > 0) + if(sh->flags & (USSize|PSize)) + s->geo.width = sh->width; + + XFree(sh); + + return; +} + +void +systray_state(Systray *s) +{ + long flags; + int code = 0; + + if(!(flags = ewmh_get_xembed_state(s->win))) + return; + + if(flags & XEMBED_MAPPED) + { + code = XEMBED_WINDOW_ACTIVATE; + XMapRaised(dpy, s->win); + setwinstate(s->win, NormalState); + } + else + { + code = XEMBED_WINDOW_DEACTIVATE; + XUnmapWindow(dpy, s->win); + setwinstate(s->win, WithdrawnState); + } + + ewmh_send_message(s->win, s->win, "_XEMBED", CurrentTime, code, 0, 0, 0); + + return; +} + +void +systray_freeicons(void) +{ + Systray *i, *next; + + for(i = trayicons; i; i = next) + { + next = i->next; + XReparentWindow(dpy, i->win, ROOT, 0, 0); + IFREE(i); + } + + XSync(dpy, 0); + + return; +} + +Systray* +systray_find(Window win) +{ + Systray *i; + + for(i = trayicons; i; i = i->next) + if(i->win == win) + return i; + + return NULL; +} + +int +systray_get_width(void) +{ + int w = 0; + Systray *i; + + for(i = trayicons; i; i = i->next) + w += i->geo.width + 2; + + return w; +} + +void +systray_update(void) +{ + Systray *i; + XWindowAttributes xa; + int x = 0; + + if(!trayicons) + { + XMoveResizeWindow(dpy, traywin, infobar[0].bar->geo.width - 1, 0, 1, 1); + return; + } + + for(i = trayicons; i; i = i->next) + { + memset(&xa, 0, sizeof(xa)); + XGetWindowAttributes(dpy, i->win, &xa); + + XMapWindow(dpy, i->win); + + if(xa.width < (i->geo.width = TRAY_DWIDTH)) + i->geo.width = xa.width; + + if(xa.height < (i->geo.height = infobar[0].bar->geo.height)) + i->geo.height = xa.height; + + XMoveResizeWindow(dpy, i->win, (i->geo.x = x), 0, i->geo.width, i->geo.height); + + x += i->geo.width + 2; + } + + XMoveResizeWindow(dpy, traywin, infobar[0].bar->geo.width - x, 0, x, infobar[0].bar->geo.height); + + return; +} diff --git a/src/wmfs.c b/src/wmfs.c index 5968ec4..43db14d 100644 --- a/src/wmfs.c +++ b/src/wmfs.c @@ -98,6 +98,9 @@ quit(void) XFreeGC(dpy, gc_stipple); infobar_destroy(); + systray_freeicons(); + systray_kill(); + IFREE(sgeo); IFREE(spgeo); IFREE(infobar); @@ -207,6 +210,7 @@ scan(void) Atom rt; int s, rf, tag = -1, screen = -1, free = -1; ulong ir, il; + long flags; uchar *ret; Client *c; @@ -218,6 +222,9 @@ scan(void) && !(wa.override_redirect || XGetTransientForHint(dpy, w[i], &usl)) && wa.map_state == IsViewable) { + if((flags = ewmh_get_xembed_state(w[i]))) + systray_add(w[i]); + if(XGetWindowProperty(dpy, w[i], ATOM("_WMFS_TAG"), 0, 32, False, XA_CARDINAL, &rt, &rf, &ir, &il, &ret) == Success && ret) { diff --git a/src/wmfs.h b/src/wmfs.h index 817391b..e7227aa 100644 --- a/src/wmfs.h +++ b/src/wmfs.h @@ -54,6 +54,8 @@ #include #include #include +#include +#include #include #include #include @@ -211,6 +213,8 @@ void uicb_client_ignore_tag(uicb_t); /* ewmh.c */ void ewmh_init_hints(void); +void ewmh_send_message(Window d, Window w, char *atom, long d0, long d1, long d2, long d3, long d4); +long ewmh_get_xembed_state(Window win); void ewmh_get_number_of_desktop(void); void ewmh_update_current_tag_prop(void); void ewmh_get_client_list(void); @@ -240,7 +244,10 @@ void focusin(XFocusChangeEvent *ev); void grabkeys(void); void keypress(XKeyPressedEvent *ev); void mappingnotify(XMappingEvent *ev); +void mapnotify(XMapEvent *ev); void maprequest(XMapRequestEvent *ev); +void reparentnotify(XReparentEvent *ev); +void selectionclearevent(XSelectionClearEvent *ev); void propertynotify(XPropertyEvent *ev); void unmapnotify(XUnmapEvent *ev); void send_client_event(long data[5], char *atom_name); @@ -341,7 +348,17 @@ void statustext_normal(int sc, char *str); void statustext_handle(int sc, char *str); /* systray.c */ - +Bool systray_acquire(void); +void systray_init(void); +void systray_kill(void); +void systray_add(Window win); +void systray_del(Window win); +void systray_configure(Systray *s); +void systray_state(Systray *s); +void systray_freeicons(void); +Systray* systray_find(Window win); +int systray_get_width(void); +void systray_update(void); /* layout.c */ void arrange(int screen, Bool update_layout); @@ -435,6 +452,7 @@ XftFont *font; /* Atoms list */ Atom *net_atom; +Atom trayatom; /* InfoBar/Tags */ InfoBar *infobar; @@ -458,6 +476,9 @@ Client *sel; func_name_list_t *func_list; extern const func_name_list_t layout_list[]; uint numlockmask; +Systray *trayicons; +Window traywin; +int tray_width; #endif /* WMFS_H */