2 /**********************************************************
3 ** Rather Small Panel 0.8beta1 Copyright (c) 2006 **
4 ** By Mikael Magnusson **
5 ** See file COPYING for license details. **
6 **********************************************************/
12 #include <sys/types.h>
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
26 /* you can edit these */
27 #define MAX_TASK_WIDTH 500
32 # define WINWIDTH 1280
37 /* don't edit these */
39 #define GRILL_WIDTH 10
41 #include <openbox/render.h>
42 #include <openbox/theme.h>
59 RrAppearance *background;
60 RrAppearance *focused_task;
61 RrAppearance *unfocused_task;
62 RrAppearance *normal_text;
63 RrAppearance *iconified_text;
64 RrAppearance *shaded_text;
65 RrAppearance *focused_text;
67 /* we draw stuff to this guy then set him as a window
68 * background pixmap, yay no flickering! */
71 char *atom_names[] = {
77 "_NET_WM_STATE_SKIP_TASKBAR",
78 "_NET_WM_STATE_SHADED",
79 "_NET_WM_STATE_BELOW",
80 "_NET_WM_STATE_HIDDEN",
82 "_NET_WM_WINDOW_TYPE",
83 "_NET_WM_WINDOW_TYPE_DOCK",
88 "_NET_CLIENT_LIST_STACKING",
89 "_NET_NUMBER_OF_DESKTOPS",
90 "_NET_CURRENT_DESKTOP",
95 "_NET_RESTACK_WINDOW",
97 "_NET_WM_ICON_GEOMETRY"
105 _NET_WM_STATE_SKIP_TASKBAR,
106 _NET_WM_STATE_SHADED,
108 _NET_WM_STATE_HIDDEN,
111 _NET_WM_WINDOW_TYPE_DOCK,
115 _NET_CLIENT_LIST_STACKING,
116 _NET_NUMBER_OF_DESKTOPS,
117 _NET_CURRENT_DESKTOP,
124 _NET_WM_ICON_GEOMETRY,
128 Atom atoms[ATOM_COUNT];
136 static void *get_prop_data(Window win, Atom prop, Atom type, int *items)
140 unsigned long items_ret;
141 unsigned long after_ret;
142 unsigned char *prop_data;
146 XGetWindowProperty(dd, win, prop, 0, 0x7fffffff, False, type, &type_ret,
147 &format_ret, &items_ret, &after_ret, &prop_data);
154 static int generic_get_int(Window win, Atom at)
159 data = get_prop_data(win, at, XA_CARDINAL, 0);
167 static int find_desktop(Window win)
169 return generic_get_int(win, atoms[_NET_WM_DESKTOP]);
172 static int find_state(Window win, atom_t atom)
178 data = get_prop_data(win, atoms[_NET_WM_STATE], XA_ATOM, &num);
181 if ((data[num]) == atoms[atom])
189 /* this naming is very confusing :) */
190 static int is_hidden(Window win)
192 return find_state(win, _NET_WM_STATE_SKIP_TASKBAR);
195 static int is_iconified(Window win)
197 return find_state(win, _NET_WM_STATE_HIDDEN);
200 static int is_shaded(Window win)
202 return find_state(win, _NET_WM_STATE_SHADED);
205 static int get_current_desktop(void)
207 return generic_get_int(root_win, atoms[_NET_CURRENT_DESKTOP]);
211 static int get_number_of_desktops(void)
213 return generic_get_int(root_win, atoms[_NET_NUMBER_OF_DESKTOPS]);
217 static void add_task(Window win, int focus)
225 /* is this window on a different desktop? */
226 desk = find_desktop(win);
227 if ((desk != 0xffffffff && tb.my_desktop != desk) || is_hidden(win))
230 tk = calloc(1, sizeof(task));
233 tk->name = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
234 get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
235 // tk->name = get_prop_data(win, XA_WM_NAME, XA_STRING, 0);
236 //tk->locale = get_prop_data(win, XA_WM_LOCALE_NAME, XA_STRING, 0);
237 tk->iconified = is_iconified(win);
238 tk->shaded = is_shaded(win);
240 XSelectInput(dd, win, PropertyChangeMask | StructureNotifyMask);
242 /* now append it to our linked list */
259 static void set_prop(Window win, Atom at, Atom type, long val)
261 XChangeProperty(dd, win, at, type, 32,
262 PropModeReplace, (unsigned char *)&val, 1);
265 static Window gui_create_taskbar(void)
268 XSizeHints size_hints;
269 XSetWindowAttributes att;
272 att.event_mask = ButtonPressMask;
275 tb.y = scr_height - WINHEIGHT;
277 win = rspanelwin = XCreateWindow(/* display */ dd,
278 /* parent */ root_win,
281 /* XXX Maybe just use scr_width here? */
282 /* width */ WINWIDTH,
283 /* height */ WINHEIGHT,
285 /* depth */ CopyFromParent,
286 /* class */ InputOutput,
287 /* visual */ CopyFromParent,
288 /*value mask*/ CWEventMask,
291 /* reside on ALL desktops */
292 set_prop(win, atoms[_NET_WM_DESKTOP], XA_CARDINAL, 0xFFFFFFFF);
293 set_prop(win, atoms[_NET_WM_WINDOW_TYPE], XA_ATOM,
294 atoms[_NET_WM_WINDOW_TYPE_DOCK]);
295 set_prop(win, atoms[_NET_WM_STATE], XA_ATOM, atoms[_NET_WM_STATE_BELOW]);
296 XChangeProperty(dd, win, XA_WM_NAME, XA_STRING, 8, PropModeReplace,
297 (unsigned char *)"rspanel", 7);
299 /* make sure the WM obays our window position */
300 size_hints.flags = PPosition;
301 /*XSetWMNormalHints (dd, win, &size_hints); */
302 XChangeProperty(dd, win, XA_WM_NORMAL_HINTS, XA_WM_SIZE_HINTS, 32,
303 PropModeReplace, (unsigned char *)&size_hints,
304 sizeof(XSizeHints) / 4);
306 xclhints.res_name = "rspanel";
307 xclhints.res_class = "RSPanel";
308 XSetClassHint(dd, win, &xclhints);
315 static void gui_init(void)
319 gcv.graphics_exposures = False;
321 fore_gc = XCreateGC(dd, root_win, GCGraphicsExposures, &gcv);
323 inst = RrInstanceNew(dd, scr_screen);
324 background = RrAppearanceNew(inst, 0);
325 focused_task = RrAppearanceNew(inst, 0);
326 unfocused_task = RrAppearanceNew(inst, 0);
327 normal_text = RrAppearanceNew(inst, 1);
329 background->surface.grad = RR_SURFACE_DIAGONAL;
330 background->surface.primary = RrColorNew(inst, 170, 170, 190);
331 background->surface.secondary = RrColorNew(inst, 100, 100, 160);
333 unfocused_task->surface.parent = background;
334 unfocused_task->surface.grad = RR_SURFACE_PARENTREL;
335 unfocused_task->surface.relief = RR_RELIEF_SUNKEN;
337 focused_task->surface.parent = background;
338 focused_task->surface.grad = RR_SURFACE_CROSS_DIAGONAL;
339 focused_task->surface.secondary = RrColorNew(inst, 70, 80, 110);
340 focused_task->surface.primary = RrColorNew(inst, 130, 160, 250);
341 focused_task->surface.relief = RR_RELIEF_RAISED;
343 normal_text->surface.grad = RR_SURFACE_PARENTREL;
344 normal_text->texture[0].type = RR_TEXTURE_TEXT;
345 normal_text->texture[0].data.text.font = RrFontOpenDefault(inst);
346 normal_text->texture[0].data.text.justify = RR_JUSTIFY_LEFT;
348 iconified_text = RrAppearanceCopy(normal_text);
349 normal_text->texture[0].data.text.color = RrColorNew(inst, 20, 20, 40);
350 focused_text = RrAppearanceCopy(normal_text);
351 iconified_text->texture[0].data.text.shadow_offset_x = 2;
352 iconified_text->texture[0].data.text.shadow_offset_y = 2;
353 iconified_text->texture[0].data.text.shadow_alpha = 100;
354 iconified_text->texture[0].data.text.shadow_color = RrColorNew(inst, 0, 0, 0);
355 shaded_text = RrAppearanceCopy(normal_text);
356 shaded_text->texture[0].data.text.color = RrColorNew(inst, 50, 60, 90);
357 iconified_text->texture[0].data.text.color = RrColorNew(inst, 200, 200, 200);
358 focused_text->texture[0].data.text.color = RrColorNew(inst, 230, 230, 255);
362 static void gui_draw_task(task *tk, int redraw)
370 else if (tk->focused)
374 b = tk->focused ? focused_task : unfocused_task;
375 a->surface.parent = b;
376 a->surface.parentx = PADDING;
377 a->texture[0].data.text.string = tk->name;
378 b->surface.parentx = tk->pos_x;
379 RrPaintPixmap(b, tk->width, WINHEIGHT);
380 RrPaintPixmap(a, tk->width-2*PADDING, WINHEIGHT);
382 XCopyArea(dd, a->pixmap, b->pixmap, fore_gc, 0, 0, tk->width-2*PADDING, WINHEIGHT, PADDING, 0);
383 XCopyArea(dd, b->pixmap, bgpixmap, fore_gc, 0, 0, tk->width, WINHEIGHT, tk->pos_x, 0);
385 XCopyArea(dd, a->pixmap, bgpixmap, fore_gc, 0, 0, tk->width, WINHEIGHT, tk->pos_x, 0);
388 XFreePixmap(dd, a->pixmap);
389 XFreePixmap(dd, b->pixmap);
391 XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
392 XClearWindow(dd, tb.win);
396 static void netwm_action(Window win, atom_t atom, Time time, long l)
398 XClientMessageEvent xev = {
399 .type = ClientMessage,
403 .message_type = atoms[atom]
406 if (atom == _NET_ACTIVE_WINDOW) {
408 xev.data.l[1] = time;
410 } else if (atom == _NET_CURRENT_DESKTOP) {
412 xev.data.l[1] = time;
413 } else if (atom == _NET_RESTACK_WINDOW) {
416 } else if (atom == _OB_FOCUS) {
418 xev.message_type = atoms[_NET_WM_STATE];
420 xev.data.l[1] = atoms[atom];
424 XSendEvent(dd, root_win, False, SubstructureNotifyMask
425 |SubstructureRedirectMask, (XEvent *)&xev);
428 static void set_icon_geometry(task *tk)
432 coords[0] = tb.x + tk->pos_x;
434 coords[2] = MAX(tk->width, 1);
435 coords[3] = WINHEIGHT;
437 XChangeProperty(dd, tk->win,
438 atoms[_NET_WM_ICON_GEOMETRY], XA_CARDINAL,
439 32, PropModeReplace, (unsigned char*) &coords, 4);
445 static void switch_desk(int new_desk, Time time)
447 if (get_number_of_desktops() <= new_desk)
450 netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
453 /* This doesn't work with obrender yet */
454 static void pager_draw_button(int x, int num)
461 if (num == tb.my_desktop) {
462 /* current desktop */
463 draw_box(x, PAGER_BUTTON_WIDTH);
466 fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
472 col.color.alpha = 0xffff;
473 col.color.red = cols[5].red;
474 col.color.green = cols[5].green;
475 col.color.blue = cols[5].blue;
476 XftDrawString8(xftdraw, &col, xfs,
477 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
481 XDrawString(dd, tb.win, fore_gc,
482 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
487 static void pager_draw(void)
489 int desks, i, x = GRILL_WIDTH;
491 desks = get_number_of_desktops();
493 for (i = 0; i < desks; i++) {
494 pager_draw_button(x, i);
497 x += PAGER_BUTTON_WIDTH;
505 static void gui_draw_taskbar(void)
513 pager_size = TEXTPAD;
517 width = WINWIDTH - (pager_size + GRILL_WIDTH);
518 #warning only rerender if width changed!
519 if (bgpixmap) XFreePixmap(dd, bgpixmap);
520 bgpixmap = XCreatePixmap(dd, root_win, WINWIDTH, WINHEIGHT, RrDepth(inst));
522 XFreePixmap(dd, RrPaintPixmap(background, WINWIDTH, WINHEIGHT));
523 XCopyArea(dd, background->pixmap, bgpixmap, fore_gc, 0, 0, WINWIDTH, WINHEIGHT, 0, 0);
524 if (tb.num_tasks == 0)
527 taskw = width / tb.num_tasks;
528 if (taskw > MAX_TASK_WIDTH)
529 taskw = MAX_TASK_WIDTH;
534 tk->width = taskw - 1;
535 gui_draw_task(tk, FALSE);
536 set_icon_geometry(tk);
542 XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
543 XClearWindow(dd, tb.win);
546 static task *find_task(Window win)
548 task *list = tb.task_list;
550 if (list->win == win)
557 static void del_task(Window win)
559 task *next, *prev = 0, *list = tb.task_list;
563 if (list->win == win) {
564 /* unlink and free this task */
566 if (list->icon_copied) {
567 XFreePixmap(dd, list->icon);
568 if (list->mask != None)
569 XFreePixmap(dd, list->mask);
585 static void move_taskbar(void)
590 tb.x = TEXTPAD - WINWIDTH;
593 tb.y = scr_height - WINHEIGHT;
595 XMoveWindow(dd, tb.win, tb.x, tb.y);
598 static void taskbar_read_clientlist(void)
600 Window *win, focus_win = 0;
601 int num, i, desk, new_desk = 0;
603 desk = get_current_desktop();
611 XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
614 if (desk != tb.my_desktop) {
616 tb.my_desktop = desk;
619 win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
620 if (win && num > 0) {
625 win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
629 /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
632 list->focused = (focus_win == list->win);
636 for (i = num - 1; i >= 0; i--)
637 if (list->win == win[i] && !is_hidden(win[i]))
645 /* add any new windows */
646 for (i = 0; i < num; i++) {
647 if (!find_task(win[i]))
648 add_task(win[i], (win[i] == focus_win));
654 static void handle_press(int x, int y, int button, Time time)
659 if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
660 switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
663 /* clicked left grill */
666 tb.at_top = 1 - tb.at_top;
668 } else if (button == 4) {
674 /* clicked right grill */
675 else if (x + TEXTPAD > WINWIDTH) {
676 if (tb.hidden && (button == 1 || button == 5)) {
679 } else if (!tb.hidden && (button == 1 || button == 4)) {
686 /* clicked on a task button */
687 /* XXX Make this configureable */
688 if (x > tk->pos_x && x < tk->pos_x + tk->width) {
691 netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
695 netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
697 netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
700 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
703 if (is_shaded(tk->win))
704 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
706 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
709 if (is_shaded(tk->win))
710 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
712 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
716 netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
718 netwm_action(tk->win, _OB_FOCUS, 0, 0);
721 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
724 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
730 } /* clicked on the background */
733 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
736 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
739 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
754 static void handle_focusin(Window win)
761 if (tk->win != win) {
766 if (tk->win == win) {
776 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
778 Atom at = *(Atom*)arg;
779 return (e->type == PropertyNotify && e->xproperty.atom == at);
782 static void handle_propertynotify(Window win, Atom at)
786 if (win == root_win) {
787 /* XXX only redraw the task that got focused/unfocused
788 * when _NET_ACTIVE_WINDOW triggers this */
789 if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP]
790 || at == atoms[_NET_ACTIVE_WINDOW])
795 check = atoms[_NET_CLIENT_LIST];
796 while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
797 check = atoms[_NET_CURRENT_DESKTOP];
798 while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
799 check = atoms[_NET_ACTIVE_WINDOW];
800 while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
802 taskbar_read_clientlist();
808 /* XXX make this work when SKIP_TASKBAR is toggled too */
813 if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
814 /* window's title changed */
816 newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
817 get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
819 /* It didn't change */
820 if (tk->name && !strcmp(newname, tk->name)) {
828 gui_draw_task(tk, TRUE);
829 } else if (at == atoms[_NET_WM_STATE]) {
830 /* iconified state changed? */
831 if (is_iconified(tk->win) != tk->iconified) {
832 tk->iconified = !tk->iconified;
833 gui_draw_task(tk, TRUE);
835 /* shaded state changed? */
836 if (is_shaded(tk->win) != tk->shaded) {
837 tk->shaded = !tk->shaded;
838 gui_draw_task(tk, TRUE);
840 /* XXX use _NET_WM_ICON */
841 // } else if (at == XA_WM_HINTS) {
842 /* some windows set their WM_HINTS icon after mapping */
843 //if (tk->icon == generic_icon) {
844 // get_task_hinticon(tk);
845 // gui_draw_task(tk, TRUE);
847 } else if (at == atoms[_NET_WM_DESKTOP]) {
848 if (find_desktop(win) != get_current_desktop())
853 static void handle_error(Display * d, XErrorEvent * ev)
861 main(int argc, char *argv[])
868 dd = XOpenDisplay(NULL);
871 scr_screen = DefaultScreen(dd);
872 scr_depth = DefaultDepth(dd, scr_screen);
873 scr_height = DisplayHeight(dd, scr_screen);
874 scr_width = DisplayWidth(dd, scr_screen);
875 root_win = RootWindow(dd, scr_screen);
877 /* helps us catch windows closing/opening */
878 XSelectInput(dd, root_win, PropertyChangeMask);
880 XSetErrorHandler((XErrorHandler) handle_error);
882 XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
885 memset(&tb, 0, sizeof(tb));
886 tb.win = gui_create_taskbar();
887 xfd = ConnectionNumber(dd);
893 select(xfd + 1, &fd, 0, 0, 0);
895 while (XPending(dd)) {
899 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
902 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
905 printf ("unknown evt type: %d\n", ev.type); */
909 /* RrInstanceFree(inst);
910 * XCloseDisplay (dd);