1 /**************************************************************************
4 * Copyright (C) 2009 thierry lorthiois (lorthiois@bbsoft.fr)
5 * based on 'docker-1.5' from Ben Jansens.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License version 2
9 * as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **************************************************************************/
21 #include <X11/Xutil.h>
22 #include <X11/Xatom.h>
28 #include <X11/extensions/Xdamage.h>
29 #include <X11/extensions/Xcomposite.h>
31 #include "systraybar.h"
37 /* defined in the systray spec */
38 #define SYSTEM_TRAY_REQUEST_DOCK 0
39 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
40 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
43 Window net_sel_win
= None
;
45 // freedesktop specification doesn't allow multi systray
49 int systray_max_icon_size
= 0;
51 // background pixmap if we render ourselves the icons
52 static Pixmap render_background
= 0;
62 systray
.area
._draw_foreground
= draw_systray
;
63 systray
.area
._resize
= resize_systray
;
64 systray
.area
.resize
= 1;
65 systray
.area
.redraw
= 1;
66 systray
.area
.on_screen
= 1;
71 void init_systray_panel(void *p
)
73 Panel
*panel
=(Panel
*)p
;
75 if (panel_horizontal
) {
76 systray
.area
.posy
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
;
77 systray
.area
.height
= panel
->area
.height
- (2 * systray
.area
.posy
);
80 systray
.area
.posx
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
;
81 systray
.area
.width
= panel
->area
.width
- (2 * panel
->area
.bg
->border
.width
) - (2 * panel
->area
.paddingy
);
83 systray
.area
.parent
= p
;
84 systray
.area
.panel
= p
;
88 void cleanup_systray()
91 systray
.area
.on_screen
= 0;
92 free_area(&systray
.area
);
93 if (render_background
) XFreePixmap(server
.dsp
, render_background
);
97 void draw_systray(void *obj
, cairo_t
*c
)
99 if (real_transparency
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0) {
100 if (render_background
) XFreePixmap(server
.dsp
, render_background
);
101 render_background
= XCreatePixmap(server
.dsp
, server
.root_win
, systray
.area
.width
, systray
.area
.height
, server
.depth
);
102 XCopyArea(server
.dsp
, systray
.area
.pix
, render_background
, server
.gc
, 0, 0, systray
.area
.width
, systray
.area
.height
, 0, 0);
109 void resize_systray(void *obj
)
111 Systraybar
*sysbar
= obj
;
112 Panel
*panel
= sysbar
->area
.panel
;
115 int count
, posx
, posy
;
118 if (panel_horizontal
)
119 icon_size
= sysbar
->area
.height
;
121 icon_size
= sysbar
->area
.width
;
122 icon_size
= icon_size
- (2 * sysbar
->area
.bg
->border
.width
) - (2 * sysbar
->area
.paddingy
);
123 if (systray_max_icon_size
> 0 && icon_size
> systray_max_icon_size
)
124 icon_size
= systray_max_icon_size
;
126 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
127 if (!((TrayWindow
*)l
->data
)->hide
)
130 //printf("count %d\n", count);
132 if (panel_horizontal
) {
133 if (!count
) systray
.area
.width
= 0;
135 int icons_per_column
= (sysbar
->area
.height
- 2*sysbar
->area
.bg
->border
.width
- 2*sysbar
->area
.paddingy
+ sysbar
->area
.paddingx
) / (icon_size
+sysbar
->area
.paddingx
);
136 int row_count
= count
/ icons_per_column
+ (count%icons_per_column
!= 0);
137 systray
.area
.width
= (2 * systray
.area
.bg
->border
.width
) + (2 * systray
.area
.paddingxlr
) + (icon_size
* row_count
) + ((row_count
-1) * systray
.area
.paddingx
);
140 systray
.area
.posx
= panel
->area
.width
- panel
->area
.bg
->border
.width
- panel
->area
.paddingxlr
- systray
.area
.width
;
141 if (panel
->clock
.area
.on_screen
)
142 systray
.area
.posx
-= (panel
->clock
.area
.width
+ panel
->area
.paddingx
);
143 #ifdef ENABLE_BATTERY
144 if (panel
->battery
.area
.on_screen
)
145 systray
.area
.posx
-= (panel
->battery
.area
.width
+ panel
->area
.paddingx
);
149 if (!count
) systray
.area
.height
= 0;
151 int icons_per_row
= (sysbar
->area
.width
- 2*sysbar
->area
.bg
->border
.width
- 2*sysbar
->area
.paddingy
+ sysbar
->area
.paddingx
) / (icon_size
+sysbar
->area
.paddingx
);
152 int column_count
= count
/ icons_per_row
+ (count%icons_per_row
!= 0);
153 systray
.area
.height
= (2 * systray
.area
.bg
->border
.width
) + (2 * systray
.area
.paddingxlr
) + (icon_size
* column_count
) + ((column_count
-1) * systray
.area
.paddingx
);
156 systray
.area
.posy
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingxlr
;
157 if (panel
->clock
.area
.on_screen
)
158 systray
.area
.posy
+= (panel
->clock
.area
.height
+ panel
->area
.paddingx
);
159 #ifdef ENABLE_BATTERY
160 if (panel
->battery
.area
.on_screen
)
161 systray
.area
.posy
+= (panel
->battery
.area
.height
+ panel
->area
.paddingx
);
166 if (panel_horizontal
) {
167 max_line_pos
= sysbar
->area
.posy
+sysbar
->area
.height
- sysbar
->area
.bg
->border
.width
- sysbar
->area
.paddingy
- icon_size
;
168 posy
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingy
;
169 posx
= systray
.area
.posx
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingxlr
;
172 max_line_pos
= sysbar
->area
.posx
+sysbar
->area
.width
- sysbar
->area
.bg
->border
.width
- sysbar
->area
.paddingy
- icon_size
;
173 posx
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingy
;
174 posy
= systray
.area
.posy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingxlr
;
177 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
178 traywin
= (TrayWindow
*)l
->data
;
179 if (traywin
->hide
) continue;
183 traywin
->width
= icon_size
;
184 traywin
->height
= icon_size
;
185 if (panel_horizontal
) {
186 if (posy
+ icon_size
+ sysbar
->area
.paddingxlr
< max_line_pos
)
187 posy
+= icon_size
+ sysbar
->area
.paddingx
;
189 posx
+= (icon_size
+ systray
.area
.paddingx
);
190 posy
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingy
;
194 if (posx
+ icon_size
+ sysbar
->area
.paddingxlr
< max_line_pos
)
195 posx
+= icon_size
+ systray
.area
.paddingx
;
197 posy
+= (icon_size
+ systray
.area
.paddingx
);
198 posx
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingy
;
202 // position and size the icon window
203 XMoveResizeWindow(server
.dsp
, traywin
->id
, traywin
->x
, traywin
->y
, icon_size
, icon_size
);
204 XResizeWindow(server
.dsp
, traywin
->tray_id
, icon_size
, icon_size
);
209 // ***********************************************
215 // protocol already started
216 if (!systray_enabled
)
221 if (!systray_enabled
)
224 Window win
= XGetSelectionOwner(server
.dsp
, server
.atom
._NET_SYSTEM_TRAY_SCREEN
);
226 // freedesktop systray specification
229 Atom _NET_WM_PID
, actual_type
;
231 unsigned long nitems
;
232 unsigned long bytes_after
;
233 unsigned char *prop
= 0;
236 _NET_WM_PID
= XInternAtom(server
.dsp
, "_NET_WM_PID", True
);
237 int ret
= XGetWindowProperty(server
.dsp
, win
, _NET_WM_PID
, 0, 1024, False
, AnyPropertyType
, &actual_type
, &actual_format
, &nitems
, &bytes_after
, &prop
);
239 fprintf(stderr
, "tint2 : another systray is running");
240 if (ret
== Success
&& prop
) {
243 fprintf(stderr
, " pid=%d", pid
);
245 fprintf(stderr
, "\n");
249 // init systray protocol
250 net_sel_win
= XCreateSimpleWindow(server
.dsp
, server
.root_win
, -1, -1, 1, 1, 0, 0, 0);
252 // v0.3 trayer specification. tint2 always horizontal.
253 // Vertical panel will draw the systray horizontal.
255 XChangeProperty(server
.dsp
, net_sel_win
, server
.atom
._NET_SYSTEM_TRAY_ORIENTATION
, XA_CARDINAL
, 32, PropModeReplace
, (unsigned char *) &orient
, 1);
256 VisualID vid
= XVisualIDFromVisual(server
.visual
);
257 XChangeProperty(server
.dsp
, net_sel_win
, XInternAtom(server
.dsp
, "_NET_SYSTEM_TRAY_VISUAL", False
), XA_VISUALID
, 32, PropModeReplace
, (unsigned char*)&vid
, 1);
259 XSetSelectionOwner(server
.dsp
, server
.atom
._NET_SYSTEM_TRAY_SCREEN
, net_sel_win
, CurrentTime
);
260 if (XGetSelectionOwner(server
.dsp
, server
.atom
._NET_SYSTEM_TRAY_SCREEN
) != net_sel_win
) {
262 fprintf(stderr
, "tint2 : can't get systray manager\n");
266 //fprintf(stderr, "tint2 : systray started\n");
267 XClientMessageEvent ev
;
268 ev
.type
= ClientMessage
;
269 ev
.window
= server
.root_win
;
270 ev
.message_type
= server
.atom
.MANAGER
;
272 ev
.data
.l
[0] = CurrentTime
;
273 ev
.data
.l
[1] = server
.atom
._NET_SYSTEM_TRAY_SCREEN
;
274 ev
.data
.l
[2] = net_sel_win
;
277 XSendEvent(server
.dsp
, server
.root_win
, False
, StructureNotifyMask
, (XEvent
*)&ev
);
283 //fprintf(stderr, "tint2 : systray stopped\n");
284 if (systray
.list_icons
) {
285 // remove_icon change systray.list_icons
286 while(systray
.list_icons
)
287 remove_icon((TrayWindow
*)systray
.list_icons
->data
);
289 g_slist_free(systray
.list_icons
);
290 systray
.list_icons
= 0;
293 if (net_sel_win
!= None
) {
294 XDestroyWindow(server
.dsp
, net_sel_win
);
301 int window_error_handler(Display
*d
, XErrorEvent
*e
)
305 if (e
->error_code
!= BadWindow
) {
306 printf("error_handler %d\n", e
->error_code
);
312 static gint
compare_traywindows(gconstpointer a
, gconstpointer b
)
314 const TrayWindow
* traywin_a
= (TrayWindow
*)a
;
315 const TrayWindow
* traywin_b
= (TrayWindow
*)b
;
316 XTextProperty name_a
, name_b
;
318 if(XGetWMName(server
.dsp
, traywin_a
->tray_id
, &name_a
) == 0) {
321 else if(XGetWMName(server
.dsp
, traywin_b
->tray_id
, &name_b
) == 0) {
326 gint retval
= g_ascii_strncasecmp((char*)name_a
.value
, (char*)name_b
.value
, -1) * systray
.sort
;
334 gboolean
add_icon(Window id
)
338 Panel
*panel
= systray
.area
.panel
;
342 XWindowAttributes attr
;
343 XGetWindowAttributes(server
.dsp
, id
, &attr
);
344 unsigned long mask
= 0;
345 XSetWindowAttributes set_attr
;
346 if (attr
.depth
!= server
.depth
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0 ) {
347 set_attr
.colormap
= attr
.colormap
;
348 set_attr
.background_pixel
= 0;
349 set_attr
.border_pixel
= 0;
350 mask
= CWColormap
|CWBackPixel
|CWBorderPixel
;
353 set_attr
.background_pixmap
= ParentRelative
;
356 Window parent_window
;
357 parent_window
= XCreateWindow(server
.dsp
, panel
->main_win
, 0, 0, 30, 30, 0, attr
.depth
, InputOutput
, attr
.visual
, mask
, &set_attr
);
358 old
= XSetErrorHandler(window_error_handler
);
359 XReparentWindow(server
.dsp
, id
, parent_window
, 0, 0);
360 XSync(server
.dsp
, False
);
361 XSetErrorHandler(old
);
362 if (error
!= FALSE
) {
363 fprintf(stderr
, "tint2 : not icon_swallow\n");
370 unsigned long nbitem
, bytes
;
371 unsigned char *data
= 0;
374 ret
= XGetWindowProperty(server
.dsp
, id
, server
.atom
._XEMBED_INFO
, 0, 2, False
, server
.atom
._XEMBED_INFO
, &acttype
, &actfmt
, &nbitem
, &bytes
, &data
);
375 if (ret
== Success
) {
378 //hide = ((data[1] & XEMBED_MAPPED) == 0);
379 //printf("hide %d\n", hide);
385 fprintf(stderr
, "tint2 : xembed error\n");
391 e
.xclient
.type
= ClientMessage
;
392 e
.xclient
.serial
= 0;
393 e
.xclient
.send_event
= True
;
394 e
.xclient
.message_type
= server
.atom
._XEMBED
;
395 e
.xclient
.window
= id
;
396 e
.xclient
.format
= 32;
397 e
.xclient
.data
.l
[0] = CurrentTime
;
398 e
.xclient
.data
.l
[1] = XEMBED_EMBEDDED_NOTIFY
;
399 e
.xclient
.data
.l
[2] = 0;
400 e
.xclient
.data
.l
[3] = parent_window
;
401 e
.xclient
.data
.l
[4] = 0;
402 XSendEvent(server
.dsp
, id
, False
, 0xFFFFFF, &e
);
405 traywin
= g_new0(TrayWindow
, 1);
406 traywin
->id
= parent_window
;
407 traywin
->tray_id
= id
;
408 traywin
->hide
= hide
;
409 traywin
->depth
= attr
.depth
;
412 if (systray
.sort
== 3)
413 systray
.list_icons
= g_slist_prepend(systray
.list_icons
, traywin
);
414 else if (systray
.sort
== 2)
415 systray
.list_icons
= g_slist_append(systray
.list_icons
, traywin
);
417 systray
.list_icons
= g_slist_insert_sorted(systray
.list_icons
, traywin
, compare_traywindows
);
418 systray
.area
.resize
= 1;
419 systray
.area
.redraw
= 1;
420 //printf("add_icon id %lx, %d\n", id, g_slist_length(systray.list_icons));
422 // watch for the icon trying to resize itself!
423 XSelectInput(server
.dsp
, traywin
->tray_id
, StructureNotifyMask
);
424 if (real_transparency
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0) {
425 traywin
->damage
= XDamageCreate(server
.dsp
, traywin
->id
, XDamageReportNonEmpty
);
426 XCompositeRedirectWindow(server
.dsp
, traywin
->id
, CompositeRedirectManual
);
430 if (!traywin
->hide
) {
431 XMapRaised(server
.dsp
, traywin
->tray_id
);
432 XMapRaised(server
.dsp
, traywin
->id
);
435 // changed in systray force resize on panel
436 panel
->area
.resize
= 1;
442 void remove_icon(TrayWindow
*traywin
)
446 // remove from our list
447 systray
.list_icons
= g_slist_remove(systray
.list_icons
, traywin
);
448 systray
.area
.resize
= 1;
449 systray
.area
.redraw
= 1;
450 //printf("remove_icon id %lx, %d\n", traywin->id);
452 XSelectInput(server
.dsp
, traywin
->tray_id
, NoEventMask
);
454 XDamageDestroy(server
.dsp
, traywin
->damage
);
458 old
= XSetErrorHandler(window_error_handler
);
460 XUnmapWindow(server
.dsp
, traywin
->id
);
461 XReparentWindow(server
.dsp
, traywin
->tray_id
, server
.root_win
, 0, 0);
462 XDestroyWindow(server
.dsp
, traywin
->id
);
463 XSync(server
.dsp
, False
);
464 XSetErrorHandler(old
);
467 // changed in systray force resize on panel
468 Panel
*panel
= systray
.area
.panel
;
469 panel
->area
.resize
= 1;
474 void net_message(XClientMessageEvent
*e
)
476 unsigned long opcode
;
479 opcode
= e
->data
.l
[1];
481 case SYSTEM_TRAY_REQUEST_DOCK
:
483 if (id
) add_icon(id
);
486 case SYSTEM_TRAY_BEGIN_MESSAGE
:
487 case SYSTEM_TRAY_CANCEL_MESSAGE
:
488 // we don't show baloons messages.
492 if (opcode
== server
.atom
._NET_SYSTEM_TRAY_MESSAGE_DATA
)
493 printf("message from dockapp: %s\n", e
->data
.b
);
495 fprintf(stderr
, "SYSTEM_TRAY : unknown message type\n");
500 void systray_render_icon_now(void* t
)
502 TrayWindow
* traywin
= t
;
503 traywin
->render_timeout
= 0;
505 // good systray icons support 32 bit depth, but some icons are still 24 bit.
506 // We create a heuristic mask for these icons, i.e. we get the rgb value in the top left corner, and
507 // mask out all pixel with the same rgb value
508 Panel
* panel
= systray
.area
.panel
;
509 imlib_context_set_drawable(traywin
->id
);
510 Imlib_Image image
= imlib_create_image_from_drawable(0, 0, 0, traywin
->width
, traywin
->height
, 0);
514 imlib_context_set_image(image
);
515 imlib_image_set_has_alpha(1);
516 DATA32
* data
= imlib_image_get_data();
517 if (traywin
->depth
== 24) {
518 createHeuristicMask(data
, traywin
->width
, traywin
->height
);
520 if (systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0)
521 adjust_asb(data
, traywin
->width
, traywin
->height
, systray
.alpha
, (float)systray
.saturation
/100, (float)systray
.brightness
/100);
522 imlib_image_put_back_data(data
);
523 XCopyArea(server
.dsp
, render_background
, systray
.area
.pix
, server
.gc
, traywin
->x
-systray
.area
.posx
, traywin
->y
-systray
.area
.posy
, traywin
->width
, traywin
->height
, traywin
->x
-systray
.area
.posx
, traywin
->y
-systray
.area
.posy
);
524 if ( !real_transparency
) {
525 imlib_context_set_drawable(systray
.area
.pix
);
526 imlib_render_image_on_drawable(traywin
->x
-systray
.area
.posx
, traywin
->y
-systray
.area
.posy
);
529 render_image(systray
.area
.pix
, traywin
->x
-systray
.area
.posx
, traywin
->y
-systray
.area
.posy
, traywin
->width
, traywin
->height
);
531 XCopyArea(server
.dsp
, systray
.area
.pix
, panel
->main_win
, server
.gc
, traywin
->x
-systray
.area
.posx
, traywin
->y
-systray
.area
.posy
, traywin
->width
, traywin
->height
, traywin
->x
, traywin
->y
);
532 imlib_free_image_and_decache();
534 XDamageSubtract(server
.dsp
, traywin
->damage
, None
, None
);
539 void systray_render_icon(TrayWindow
* traywin
)
541 // wine tray icons update whenever mouse is over them, so we limit the updates to 50 ms
542 if (traywin
->render_timeout
== 0)
543 traywin
->render_timeout
= add_timeout(50, 0, systray_render_icon_now
, traywin
);
547 void refresh_systray_icon()
551 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
552 traywin
= (TrayWindow
*)l
->data
;
553 if (traywin
->hide
) continue;
554 if (real_transparency
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0)
555 systray_render_icon(traywin
);
557 XClearArea(server
.dsp
, traywin
->tray_id
, 0, 0, traywin
->width
, traywin
->height
, True
);