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>
20 /* you can edit these */
21 #define MAX_TASK_WIDTH 500
26 # define WINWIDTH scr_width
31 /* don't edit these */
33 #define GRILL_WIDTH 10
35 #include <openbox/render.h>
36 #include <openbox/theme.h>
53 RrAppearance *background;
54 RrAppearance *focused_task;
55 RrAppearance *iconified_task;
56 RrAppearance *unfocused_task;
57 RrAppearance *normal_text;
58 RrAppearance *iconified_text;
59 RrAppearance *shaded_text;
60 RrAppearance *focused_text;
63 /* we draw stuff to this guy then set him as a window
64 * background pixmap, yay no flickering! */
67 char *atom_names[] = {
73 "_NET_WM_STATE_SKIP_TASKBAR",
74 "_NET_WM_STATE_SHADED",
75 "_NET_WM_STATE_BELOW",
76 "_NET_WM_STATE_HIDDEN",
78 "_NET_WM_WINDOW_TYPE",
79 "_NET_WM_WINDOW_TYPE_DOCK",
84 "_NET_CLIENT_LIST_STACKING",
85 "_NET_NUMBER_OF_DESKTOPS",
86 "_NET_CURRENT_DESKTOP",
91 "_NET_RESTACK_WINDOW",
93 "_NET_WM_ICON_GEOMETRY",
102 _NET_WM_STATE_SKIP_TASKBAR,
103 _NET_WM_STATE_SHADED,
105 _NET_WM_STATE_HIDDEN,
108 _NET_WM_WINDOW_TYPE_DOCK,
112 _NET_CLIENT_LIST_STACKING,
113 _NET_NUMBER_OF_DESKTOPS,
114 _NET_CURRENT_DESKTOP,
121 _NET_WM_ICON_GEOMETRY,
126 Atom atoms[ATOM_COUNT];
134 static void *get_prop_data(Window win, Atom prop, Atom type, int *items)
138 unsigned long items_ret;
139 unsigned long after_ret;
140 unsigned char *prop_data;
144 XGetWindowProperty(dd, win, prop, 0, 0x7fffffff, False, type, &type_ret,
145 &format_ret, &items_ret, &after_ret, &prop_data);
152 static int generic_get_int(Window win, Atom at)
157 data = get_prop_data(win, at, XA_CARDINAL, 0);
165 static int find_desktop(Window win)
167 return generic_get_int(win, atoms[_NET_WM_DESKTOP]);
170 static int find_state(Window win, atom_t atom)
176 data = get_prop_data(win, atoms[_NET_WM_STATE], XA_ATOM, &num);
179 if ((data[num]) == atoms[atom])
187 /* this naming is very confusing :) */
188 static int is_hidden(Window win)
190 return find_state(win, _NET_WM_STATE_SKIP_TASKBAR);
193 static int is_iconified(Window win)
195 return find_state(win, _NET_WM_STATE_HIDDEN);
198 static int is_shaded(Window win)
200 return find_state(win, _NET_WM_STATE_SHADED);
203 static int get_current_desktop(void)
205 return generic_get_int(root_win, atoms[_NET_CURRENT_DESKTOP]);
209 static int get_number_of_desktops(void)
211 return generic_get_int(root_win, atoms[_NET_NUMBER_OF_DESKTOPS]);
215 static void free_icons(task *tk)
219 for (i = 0; i < tk->nicons; ++i)
220 free(tk->icons[i].data);
226 static void task_update_icon(task *tk)
228 /* these bits are borrowed from openbox' client.c::client_update_icons */
231 unsigned int w, h, i, j;
233 data = get_prop_data(tk->win, atoms[_NET_WM_ICON], XA_CARDINAL, &num);
235 /* figure out how many valid icons are in here */
237 while (num - i > 2) {
241 if (i > num || w*h == 0) break;
245 tk->icons = calloc(tk->nicons, sizeof(icon));
247 /* store the icons */
249 for (j = 0; j < tk->nicons; ++j) {
252 w = tk->icons[j].width = data[i++];
253 h = tk->icons[j].height = data[i++];
255 if (w*h == 0) continue;
257 tk->icons[j].data = g_new(RrPixel32, w * h);
258 for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
263 tk->icons[j].data[t] =
264 (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
265 (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
266 (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
267 (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
276 if ((hints = XGetWMHints(dd, tk->win))) {
277 if (hints->flags & IconPixmapHint) {
279 tk->icons = g_new(icon, tk->nicons);
280 // xerror_set_ignore(TRUE);
281 if (!RrPixmapToRGBA(inst,
283 (hints->flags & IconMaskHint ?
284 hints->icon_mask : None),
285 &tk->icons[tk->nicons-1].width,
286 &tk->icons[tk->nicons-1].height,
287 &tk->icons[tk->nicons-1].data)){
288 g_free(&tk->icons[tk->nicons-1]);
291 // xerror_set_ignore(FALSE);
298 static void add_task(Window win, int focus)
306 /* is this window on a different desktop? */
307 /* XXX add even if is_hidden, but don't show it later, so we can
308 * unhide it when the prop is removed */
309 desk = find_desktop(win);
310 if ((desk != 0xffffffff && tb.my_desktop != desk) || is_hidden(win))
313 XSelectInput(dd, win, PropertyChangeMask | StructureNotifyMask);
315 tk = calloc(1, sizeof(task));
318 tk->name = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
319 get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
321 //tk->locale = get_prop_data(win, XA_WM_LOCALE_NAME, XA_STRING, 0);
322 tk->iconified = is_iconified(win);
323 tk->shaded = is_shaded(win);
324 task_update_icon(tk);
326 /* now append it to our linked list */
343 static void set_prop(Window win, Atom at, Atom type, long val)
345 XChangeProperty(dd, win, at, type, 32,
346 PropModeReplace, (unsigned char *)&val, 1);
349 static Window gui_create_taskbar(void)
352 XSizeHints size_hints;
353 XSetWindowAttributes att;
356 att.event_mask = ButtonPressMask;
359 tb.y = scr_height - WINHEIGHT;
361 win = rspanelwin = XCreateWindow(/* display */ dd,
362 /* parent */ root_win,
365 /* XXX Maybe just use scr_width here? */
366 /* width */ WINWIDTH,
367 /* height */ WINHEIGHT,
369 /* depth */ CopyFromParent,
370 /* class */ InputOutput,
371 /* visual */ CopyFromParent,
372 /*value mask*/ CWEventMask,
375 /* reside on ALL desktops */
376 set_prop(win, atoms[_NET_WM_DESKTOP], XA_CARDINAL, 0xFFFFFFFF);
377 set_prop(win, atoms[_NET_WM_WINDOW_TYPE], XA_ATOM,
378 atoms[_NET_WM_WINDOW_TYPE_DOCK]);
379 set_prop(win, atoms[_NET_WM_STATE], XA_ATOM, atoms[_NET_WM_STATE_BELOW]);
380 XChangeProperty(dd, win, XA_WM_NAME, XA_STRING, 8, PropModeReplace,
381 (unsigned char *)"rspanel", 7);
383 /* make sure the WM obays our window position */
384 size_hints.flags = PPosition;
385 /*XSetWMNormalHints (dd, win, &size_hints); */
386 XChangeProperty(dd, win, XA_WM_NORMAL_HINTS, XA_WM_SIZE_HINTS, 32,
387 PropModeReplace, (unsigned char *)&size_hints,
388 sizeof(XSizeHints) / 4);
390 xclhints.res_name = "rspanel";
391 xclhints.res_class = "RSPanel";
392 XSetClassHint(dd, win, &xclhints);
399 static void gui_init(void)
403 gcv.graphics_exposures = False;
405 fore_gc = XCreateGC(dd, root_win, GCGraphicsExposures, &gcv);
407 inst = RrInstanceNew(dd, scr_screen);
408 background = RrAppearanceNew(inst, 0);
409 focused_task = RrAppearanceNew(inst, 0);
410 unfocused_task = RrAppearanceNew(inst, 0);
411 normal_text = RrAppearanceNew(inst, 1);
412 a_icon = RrAppearanceNew(inst, 1);
414 a_icon->surface.grad = RR_SURFACE_PARENTREL;
416 background->surface.grad = RR_SURFACE_DIAGONAL;
417 background->surface.primary = RrColorNew(inst, 170, 170, 190);
418 background->surface.secondary = RrColorNew(inst, 100, 100, 160);
420 unfocused_task->surface.parent = background;
421 unfocused_task->surface.grad = RR_SURFACE_PARENTREL;
423 iconified_task = RrAppearanceCopy(unfocused_task);
424 iconified_task->surface.relief = RR_RELIEF_SUNKEN;
425 iconified_task->surface.parent = background;
427 focused_task->surface.parent = background;
428 focused_task->surface.grad = RR_SURFACE_CROSS_DIAGONAL;
429 focused_task->surface.secondary = RrColorNew(inst, 70, 80, 110);
430 focused_task->surface.primary = RrColorNew(inst, 130, 160, 250);
431 focused_task->surface.relief = RR_RELIEF_RAISED;
433 normal_text->surface.grad = RR_SURFACE_PARENTREL;
434 normal_text->texture[0].type = RR_TEXTURE_TEXT;
435 normal_text->texture[0].data.text.font = RrFontOpenDefault(inst);
436 normal_text->texture[0].data.text.justify = RR_JUSTIFY_LEFT;
437 normal_text->texture[0].data.text.ellipsize = RR_ELLIPSIZE_END;
439 iconified_text = RrAppearanceCopy(normal_text);
440 normal_text->texture[0].data.text.color = RrColorNew(inst, 20, 20, 40);
441 focused_text = RrAppearanceCopy(normal_text);
442 iconified_text->texture[0].data.text.shadow_offset_x = 2;
443 iconified_text->texture[0].data.text.shadow_offset_y = 2;
444 iconified_text->texture[0].data.text.shadow_alpha = 100;
445 iconified_text->texture[0].data.text.shadow_color = RrColorNew(inst, 0, 0, 0);
446 shaded_text = RrAppearanceCopy(normal_text);
447 shaded_text->texture[0].data.text.color = RrColorNew(inst, 50, 60, 90);
448 iconified_text->texture[0].data.text.color = RrColorNew(inst, 200, 200, 200);
449 focused_text->texture[0].data.text.color = RrColorNew(inst, 230, 230, 255);
452 const icon* best_task_icon(task *tk, gint w, gint h)
456 gulong min_diff, min_i;
458 min_diff = ABS(tk->icons[0].width - w) + ABS(tk->icons[0].height - h);
461 for (i = 1; i < tk->nicons; ++i) {
464 diff = ABS(tk->icons[i].width - w) + ABS(tk->icons[i].height - h);
465 if (diff < min_diff) {
470 return &tk->icons[min_i];
474 #define ICON_SIZE WINHEIGHT-2*PADDING
475 static void gui_draw_task(task *tk, int redraw)
483 else if (tk->focused)
494 else if (tk->focused)
499 i = best_task_icon(tk, ICON_SIZE, ICON_SIZE);
502 RrTexture *c = &a_icon->texture[0];
503 RrTextureRGBA *d = &c->data.rgba;
504 c->type = RR_TEXTURE_RGBA;
505 a_icon->surface.parent = b;
506 a_icon->surface.parentx = PADDING;
507 a_icon->surface.parenty = PADDING;
509 d->height = i->height;
514 a->surface.parent = b;
515 a->surface.parentx = ICON_SIZE+2*PADDING;
516 a->texture[0].data.text.string = tk->name;
517 b->surface.parentx = tk->pos_x;
519 RrPaintPixmap(b, tk->width, WINHEIGHT);
520 RrPaintPixmap(a, tk->width-3*PADDING-ICON_SIZE, WINHEIGHT);
521 RrPaintPixmap(a_icon, ICON_SIZE, ICON_SIZE);
523 XCopyArea(dd, a_icon->pixmap, b->pixmap, fore_gc, 0, 0,
524 ICON_SIZE, ICON_SIZE, PADDING, PADDING);
525 XCopyArea(dd, a->pixmap, b->pixmap, fore_gc, 0, 0,
526 tk->width-3*PADDING-ICON_SIZE, WINHEIGHT, ICON_SIZE+2*PADDING, 0);
527 XCopyArea(dd, b->pixmap, bgpixmap, fore_gc, 0, 0,
528 tk->width, WINHEIGHT, tk->pos_x, 0);
530 a_icon->texture[1].type = RR_TEXTURE_NONE;
531 XFreePixmap(dd, a->pixmap);
532 XFreePixmap(dd, b->pixmap);
533 XFreePixmap(dd, a_icon->pixmap);
535 XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
536 XClearWindow(dd, tb.win);
540 static void netwm_action(Window win, atom_t atom, Time time, long l)
542 XClientMessageEvent xev = {
543 .type = ClientMessage,
547 .message_type = atoms[atom]
550 if (atom == _NET_ACTIVE_WINDOW) {
552 xev.data.l[1] = time;
554 } else if (atom == _NET_CURRENT_DESKTOP) {
556 xev.data.l[1] = time;
557 } else if (atom == _NET_RESTACK_WINDOW) {
560 } else if (atom == _OB_FOCUS) {
562 xev.message_type = atoms[_NET_WM_STATE];
564 xev.data.l[1] = atoms[atom];
568 XSendEvent(dd, root_win, False, SubstructureNotifyMask
569 |SubstructureRedirectMask, (XEvent *)&xev);
572 static void set_icon_geometry(task *tk)
576 coords[0] = tb.x + tk->pos_x;
578 coords[2] = MAX(tk->width, 1);
579 coords[3] = WINHEIGHT;
581 XChangeProperty(dd, tk->win,
582 atoms[_NET_WM_ICON_GEOMETRY], XA_CARDINAL,
583 32, PropModeReplace, (unsigned char*) &coords, 4);
589 static void switch_desk(int new_desk, Time time)
591 if (get_number_of_desktops() <= new_desk)
594 netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
597 /* This doesn't work with obrender yet */
598 static void pager_draw_button(int x, int num)
605 if (num == tb.my_desktop) {
606 /* current desktop */
607 draw_box(x, PAGER_BUTTON_WIDTH);
610 fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
616 col.color.alpha = 0xffff;
617 col.color.red = cols[5].red;
618 col.color.green = cols[5].green;
619 col.color.blue = cols[5].blue;
620 XftDrawString8(xftdraw, &col, xfs,
621 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
625 XDrawString(dd, tb.win, fore_gc,
626 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
631 static void pager_draw(void)
633 int desks, i, x = GRILL_WIDTH;
635 desks = get_number_of_desktops();
637 for (i = 0; i < desks; i++) {
638 pager_draw_button(x, i);
641 x += PAGER_BUTTON_WIDTH;
649 static void gui_draw_taskbar(void)
657 pager_size = TEXTPAD;
661 width = WINWIDTH - (pager_size + GRILL_WIDTH);
662 #warning only rerender if width changed!
663 if (bgpixmap) XFreePixmap(dd, bgpixmap);
664 bgpixmap = XCreatePixmap(dd, root_win, WINWIDTH, WINHEIGHT, RrDepth(inst));
666 XFreePixmap(dd, RrPaintPixmap(background, WINWIDTH, WINHEIGHT));
667 XCopyArea(dd, background->pixmap, bgpixmap, fore_gc, 0, 0, WINWIDTH, WINHEIGHT, 0, 0);
668 if (tb.num_tasks == 0)
671 taskw = width / tb.num_tasks;
672 if (taskw > MAX_TASK_WIDTH)
673 taskw = MAX_TASK_WIDTH;
678 tk->width = taskw - 1;
679 gui_draw_task(tk, FALSE);
680 set_icon_geometry(tk);
686 XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
687 XClearWindow(dd, tb.win);
690 static task *find_task(Window win)
692 task *list = tb.task_list;
694 if (list->win == win)
701 static void del_task(Window win)
703 task *next, *prev = 0, *list = tb.task_list;
707 if (list->win == win) {
708 /* unlink and free this task */
725 static void move_taskbar(void)
730 tb.x = TEXTPAD - WINWIDTH;
733 tb.y = scr_height - WINHEIGHT;
735 XMoveWindow(dd, tb.win, tb.x, tb.y);
738 static void taskbar_read_clientlist(void)
740 Window *win, focus_win = 0;
741 int num, i, desk, new_desk = 0;
743 desk = get_current_desktop();
751 XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
754 if (desk != tb.my_desktop) {
756 tb.my_desktop = desk;
759 win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
760 if (win && num > 0) {
765 win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
769 /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
772 list->focused = (focus_win == list->win);
776 for (i = num - 1; i >= 0; i--)
777 if (list->win == win[i] && !is_hidden(win[i]))
785 /* add any new windows */
786 for (i = 0; i < num; i++) {
787 if (!find_task(win[i]))
788 add_task(win[i], (win[i] == focus_win));
794 static void handle_press(int x, int y, int button, Time time)
799 if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
800 switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
803 /* clicked left grill */
806 tb.at_top = 1 - tb.at_top;
808 } else if (button == 4) {
814 /* clicked right grill */
815 else if (x + TEXTPAD > WINWIDTH) {
816 if (tb.hidden && (button == 1 || button == 5)) {
819 } else if (!tb.hidden && (button == 1 || button == 4)) {
826 /* clicked on a task button */
827 /* XXX Make this configureable */
828 if (x > tk->pos_x && x < tk->pos_x + tk->width) {
831 netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
835 netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
837 netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
840 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
843 if (is_shaded(tk->win))
844 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
846 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
849 if (is_shaded(tk->win))
850 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
852 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
856 netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
858 netwm_action(tk->win, _OB_FOCUS, 0, 0);
861 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
864 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
870 } /* clicked on the background */
873 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
876 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
879 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
894 static void handle_focusin(Window win)
901 if (tk->win != win) {
906 if (tk->win == win) {
916 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
918 Atom at = *(Atom*)arg;
919 return (e->type == PropertyNotify && e->xproperty.atom == at);
922 static void handle_propertynotify(Window win, Atom at)
926 if (win == root_win) {
927 /* XXX only redraw the task that got focused/unfocused
928 * when _NET_ACTIVE_WINDOW triggers this */
929 if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP]
930 || at == atoms[_NET_ACTIVE_WINDOW])
935 check = atoms[_NET_CLIENT_LIST];
936 while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
937 check = atoms[_NET_CURRENT_DESKTOP];
938 while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
939 check = atoms[_NET_ACTIVE_WINDOW];
940 while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
942 taskbar_read_clientlist();
948 /* XXX make this work when SKIP_TASKBAR is toggled too */
953 if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
954 /* window's title changed */
955 /* XXX make a function for this and use from here and add_task */
957 newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
958 get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
960 /* It didn't change */
961 if (tk->name && !strcmp(newname, tk->name)) {
969 gui_draw_task(tk, TRUE);
970 } else if (at == atoms[_NET_WM_ICON]) {
971 task_update_icon(tk);
972 gui_draw_task(tk, TRUE);
973 } else if (at == atoms[_NET_WM_STATE]) {
974 /* iconified state changed? */
975 if (is_iconified(tk->win) != tk->iconified) {
976 tk->iconified = !tk->iconified;
977 gui_draw_task(tk, TRUE);
979 /* shaded state changed? */
980 if (is_shaded(tk->win) != tk->shaded) {
981 tk->shaded = !tk->shaded;
982 gui_draw_task(tk, TRUE);
984 /* XXX use _NET_WM_ICON */
985 // } else if (at == XA_WM_HINTS) {
986 /* some windows set their WM_HINTS icon after mapping */
987 //if (tk->icon == generic_icon) {
988 // get_task_hinticon(tk);
989 // gui_draw_task(tk, TRUE);
991 } else if (at == atoms[_NET_WM_DESKTOP]) {
992 if (find_desktop(win) != get_current_desktop())
997 static void handle_error(Display * d, XErrorEvent * ev)
1005 main(int argc, char *argv[])
1012 dd = XOpenDisplay(NULL);
1015 scr_screen = DefaultScreen(dd);
1016 scr_depth = DefaultDepth(dd, scr_screen);
1017 scr_height = DisplayHeight(dd, scr_screen);
1018 scr_width = DisplayWidth(dd, scr_screen);
1019 root_win = RootWindow(dd, scr_screen);
1021 /* helps us catch windows closing/opening */
1022 XSelectInput(dd, root_win, PropertyChangeMask);
1024 XSetErrorHandler((XErrorHandler) handle_error);
1026 XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
1029 memset(&tb, 0, sizeof(tb));
1030 tb.win = gui_create_taskbar();
1031 xfd = ConnectionNumber(dd);
1033 taskbar_read_clientlist();
1039 select(xfd + 1, &fd, 0, 0, 0);
1041 while (XPending(dd)) {
1042 XNextEvent(dd, &ev);
1045 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
1047 case PropertyNotify:
1048 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
1051 printf ("unknown evt type: %d\n", ev.type); */
1055 /* RrInstanceFree(inst);
1056 * XCloseDisplay (dd);