1 /**************************************************************************
4 * Copyright (C) 2009 thierry lorthiois (lorthiois@bbsoft.fr) from Omega distribution
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>
30 #include <X11/extensions/Xrender.h>
33 #include "systraybar.h"
39 /* defined in the systray spec */
40 #define SYSTEM_TRAY_REQUEST_DOCK 0
41 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
42 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
45 Window net_sel_win
= None
;
47 // freedesktop specification doesn't allow multi systray
51 int systray_max_icon_size
;
53 // background pixmap if we render ourselves the icons
54 static Pixmap render_background
;
57 void default_systray()
59 memset(&systray
, 0, sizeof(Systraybar
));
60 render_background
= 0;
63 systray
.area
._draw_foreground
= draw_systray
;
64 systray
.area
._on_change_layout
= on_change_systray
;
65 systray
.area
.size_mode
= SIZE_BY_CONTENT
;
66 systray
.area
._resize
= resize_systray
;
69 void cleanup_systray()
73 systray_max_icon_size
= 0;
74 systray
.area
.on_screen
= 0;
75 free_area(&systray
.area
);
76 if (render_background
) {
77 XFreePixmap(server
.dsp
, render_background
);
78 render_background
= 0;
89 if (!server
.visual32
&& (systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0)) {
90 printf("No 32 bit visual for your X implementation. 'systray_asb = 100 0 0' will be forced\n");
92 systray
.brightness
= systray
.saturation
= 0;
97 void init_systray_panel(void *p
)
99 systray
.area
.parent
= p
;
100 systray
.area
.panel
= p
;
104 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
105 if (!((TrayWindow
*)l
->data
)->hide
)
116 void draw_systray(void *obj
, cairo_t
*c
)
118 if (server
.real_transparency
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0) {
119 if (render_background
) XFreePixmap(server
.dsp
, render_background
);
120 render_background
= XCreatePixmap(server
.dsp
, server
.root_win
, systray
.area
.width
, systray
.area
.height
, server
.depth
);
121 XCopyArea(server
.dsp
, systray
.area
.pix
, render_background
, server
.gc
, 0, 0, systray
.area
.width
, systray
.area
.height
, 0, 0);
128 int resize_systray(void *obj
)
130 Systraybar
*sysbar
= obj
;
134 if (panel_horizontal
)
135 sysbar
->icon_size
= sysbar
->area
.height
;
137 sysbar
->icon_size
= sysbar
->area
.width
;
138 sysbar
->icon_size
= sysbar
->icon_size
- (2 * sysbar
->area
.bg
->border
.width
) - (2 * sysbar
->area
.paddingy
);
139 if (systray_max_icon_size
> 0 && sysbar
->icon_size
> systray_max_icon_size
)
140 sysbar
->icon_size
= systray_max_icon_size
;
142 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
143 if (!((TrayWindow
*)l
->data
)->hide
)
146 //printf("count %d\n", count);
148 if (panel_horizontal
) {
149 int height
= sysbar
->area
.height
- 2*sysbar
->area
.bg
->border
.width
- 2*sysbar
->area
.paddingy
;
150 // here icons_per_column always higher than 0
151 sysbar
->icons_per_column
= (height
+sysbar
->area
.paddingx
) / (sysbar
->icon_size
+sysbar
->area
.paddingx
);
152 sysbar
->marging
= height
- (sysbar
->icons_per_column
-1)*(sysbar
->icon_size
+sysbar
->area
.paddingx
) - sysbar
->icon_size
;
153 sysbar
->icons_per_row
= count
/ sysbar
->icons_per_column
+ (count%sysbar
->icons_per_column
!= 0);
154 systray
.area
.width
= (2 * systray
.area
.bg
->border
.width
) + (2 * systray
.area
.paddingxlr
) + (sysbar
->icon_size
* sysbar
->icons_per_row
) + ((sysbar
->icons_per_row
-1) * systray
.area
.paddingx
);
157 int width
= sysbar
->area
.width
- 2*sysbar
->area
.bg
->border
.width
- 2*sysbar
->area
.paddingy
;
158 // here icons_per_row always higher than 0
159 sysbar
->icons_per_row
= (width
+sysbar
->area
.paddingx
) / (sysbar
->icon_size
+sysbar
->area
.paddingx
);
160 sysbar
->marging
= width
- (sysbar
->icons_per_row
-1)*(sysbar
->icon_size
+sysbar
->area
.paddingx
) - sysbar
->icon_size
;
161 sysbar
->icons_per_column
= count
/ sysbar
->icons_per_row
+ (count%sysbar
->icons_per_row
!= 0);
162 systray
.area
.height
= (2 * systray
.area
.bg
->border
.width
) + (2 * systray
.area
.paddingxlr
) + (sysbar
->icon_size
* sysbar
->icons_per_column
) + ((sysbar
->icons_per_column
-1) * systray
.area
.paddingx
);
168 void on_change_systray (void *obj
)
170 // here, systray.area.posx/posy are defined by rendering engine. so we can calculate position of tray icon.
171 Systraybar
*sysbar
= obj
;
172 Panel
*panel
= sysbar
->area
.panel
;
174 int start
= panel
->area
.bg
->border
.width
+ panel
->area
.paddingy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingy
+ sysbar
->marging
/2;
175 if (panel_horizontal
) {
177 posx
= systray
.area
.posx
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingxlr
;
181 posy
= systray
.area
.posy
+ systray
.area
.bg
->border
.width
+ systray
.area
.paddingxlr
;
186 for (i
=1, l
= systray
.list_icons
; l
; i
++, l
= l
->next
) {
187 traywin
= (TrayWindow
*)l
->data
;
188 if (traywin
->hide
) continue;
192 //printf("systray %d : %d,%d\n", i, posx, posy);
193 traywin
->width
= sysbar
->icon_size
;
194 traywin
->height
= sysbar
->icon_size
;
195 if (panel_horizontal
) {
196 if (i
% sysbar
->icons_per_column
)
197 posy
+= sysbar
->icon_size
+ sysbar
->area
.paddingx
;
200 posx
+= (sysbar
->icon_size
+ systray
.area
.paddingx
);
204 if (i
% sysbar
->icons_per_row
)
205 posx
+= sysbar
->icon_size
+ systray
.area
.paddingx
;
208 posy
+= (sysbar
->icon_size
+ systray
.area
.paddingx
);
212 // position and size the icon window
213 XMoveResizeWindow(server
.dsp
, traywin
->id
, traywin
->x
, traywin
->y
, sysbar
->icon_size
, sysbar
->icon_size
);
214 XResizeWindow(server
.dsp
, traywin
->tray_id
, sysbar
->icon_size
, sysbar
->icon_size
);
219 // ***********************************************
225 // protocol already started
226 if (!systray_enabled
)
231 if (!systray_enabled
)
234 Window win
= XGetSelectionOwner(server
.dsp
, server
.atom
._NET_SYSTEM_TRAY_SCREEN
);
236 // freedesktop systray specification
239 Atom _NET_WM_PID
, actual_type
;
241 unsigned long nitems
;
242 unsigned long bytes_after
;
243 unsigned char *prop
= 0;
246 _NET_WM_PID
= XInternAtom(server
.dsp
, "_NET_WM_PID", True
);
247 int ret
= XGetWindowProperty(server
.dsp
, win
, _NET_WM_PID
, 0, 1024, False
, AnyPropertyType
, &actual_type
, &actual_format
, &nitems
, &bytes_after
, &prop
);
249 fprintf(stderr
, "tint2 : another systray is running");
250 if (ret
== Success
&& prop
) {
253 fprintf(stderr
, " pid=%d", pid
);
255 fprintf(stderr
, "\n");
259 // init systray protocol
260 net_sel_win
= XCreateSimpleWindow(server
.dsp
, server
.root_win
, -1, -1, 1, 1, 0, 0, 0);
262 // v0.3 trayer specification. tint2 always horizontal.
263 // Vertical panel will draw the systray horizontal.
265 XChangeProperty(server
.dsp
, net_sel_win
, server
.atom
._NET_SYSTEM_TRAY_ORIENTATION
, XA_CARDINAL
, 32, PropModeReplace
, (unsigned char *) &orient
, 1);
267 if (server
.visual32
&& (systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0))
268 vid
= XVisualIDFromVisual(server
.visual32
);
270 vid
= XVisualIDFromVisual(server
.visual
);
271 XChangeProperty(server
.dsp
, net_sel_win
, XInternAtom(server
.dsp
, "_NET_SYSTEM_TRAY_VISUAL", False
), XA_VISUALID
, 32, PropModeReplace
, (unsigned char*)&vid
, 1);
273 XSetSelectionOwner(server
.dsp
, server
.atom
._NET_SYSTEM_TRAY_SCREEN
, net_sel_win
, CurrentTime
);
274 if (XGetSelectionOwner(server
.dsp
, server
.atom
._NET_SYSTEM_TRAY_SCREEN
) != net_sel_win
) {
276 fprintf(stderr
, "tint2 : can't get systray manager\n");
280 //fprintf(stderr, "tint2 : systray started\n");
281 XClientMessageEvent ev
;
282 ev
.type
= ClientMessage
;
283 ev
.window
= server
.root_win
;
284 ev
.message_type
= server
.atom
.MANAGER
;
286 ev
.data
.l
[0] = CurrentTime
;
287 ev
.data
.l
[1] = server
.atom
._NET_SYSTEM_TRAY_SCREEN
;
288 ev
.data
.l
[2] = net_sel_win
;
291 XSendEvent(server
.dsp
, server
.root_win
, False
, StructureNotifyMask
, (XEvent
*)&ev
);
297 //fprintf(stderr, "tint2 : systray stopped\n");
298 if (systray
.list_icons
) {
299 // remove_icon change systray.list_icons
300 while(systray
.list_icons
)
301 remove_icon((TrayWindow
*)systray
.list_icons
->data
);
303 g_slist_free(systray
.list_icons
);
304 systray
.list_icons
= 0;
307 if (net_sel_win
!= None
) {
308 XDestroyWindow(server
.dsp
, net_sel_win
);
315 int window_error_handler(Display
*d
, XErrorEvent
*e
)
319 if (e
->error_code
!= BadWindow
) {
320 printf("error_handler %d\n", e
->error_code
);
326 static gint
compare_traywindows(gconstpointer a
, gconstpointer b
)
328 const TrayWindow
* traywin_a
= (TrayWindow
*)a
;
329 const TrayWindow
* traywin_b
= (TrayWindow
*)b
;
330 XTextProperty name_a
, name_b
;
332 if(XGetWMName(server
.dsp
, traywin_a
->tray_id
, &name_a
) == 0) {
335 else if(XGetWMName(server
.dsp
, traywin_b
->tray_id
, &name_b
) == 0) {
340 gint retval
= g_ascii_strncasecmp((char*)name_a
.value
, (char*)name_b
.value
, -1) * systray
.sort
;
348 gboolean
add_icon(Window id
)
352 Panel
*panel
= systray
.area
.panel
;
356 XWindowAttributes attr
;
357 if ( XGetWindowAttributes(server
.dsp
, id
, &attr
) == False
) return FALSE
;
358 unsigned long mask
= 0;
359 XSetWindowAttributes set_attr
;
360 Visual
* visual
= server
.visual
;
361 //printf("icon with depth: %d, width %d, height %d\n", attr.depth, attr.width, attr.height);
362 //printf("icon with depth: %d\n", attr.depth);
363 if (attr
.depth
!= server
.depth
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0) {
364 visual
= attr
.visual
;
365 set_attr
.colormap
= attr
.colormap
;
366 set_attr
.background_pixel
= 0;
367 set_attr
.border_pixel
= 0;
368 mask
= CWColormap
|CWBackPixel
|CWBorderPixel
;
371 set_attr
.background_pixmap
= ParentRelative
;
374 Window parent_window
;
375 parent_window
= XCreateWindow(server
.dsp
, panel
->main_win
, 0, 0, 30, 30, 0, attr
.depth
, InputOutput
, visual
, mask
, &set_attr
);
376 old
= XSetErrorHandler(window_error_handler
);
377 XReparentWindow(server
.dsp
, id
, parent_window
, 0, 0);
378 XSync(server
.dsp
, False
);
379 XSetErrorHandler(old
);
380 if (error
!= FALSE
) {
381 fprintf(stderr
, "tint2 : not icon_swallow\n");
382 XDestroyWindow(server
.dsp
, parent_window
);
389 unsigned long nbitem
, bytes
;
390 unsigned char *data
= 0;
393 ret
= XGetWindowProperty(server
.dsp
, id
, server
.atom
._XEMBED_INFO
, 0, 2, False
, server
.atom
._XEMBED_INFO
, &acttype
, &actfmt
, &nbitem
, &bytes
, &data
);
394 if (ret
== Success
) {
397 //hide = ((data[1] & XEMBED_MAPPED) == 0);
398 //printf("hide %d\n", hide);
404 fprintf(stderr
, "tint2 : xembed error\n");
405 XDestroyWindow(server
.dsp
, parent_window
);
411 e
.xclient
.type
= ClientMessage
;
412 e
.xclient
.serial
= 0;
413 e
.xclient
.send_event
= True
;
414 e
.xclient
.message_type
= server
.atom
._XEMBED
;
415 e
.xclient
.window
= id
;
416 e
.xclient
.format
= 32;
417 e
.xclient
.data
.l
[0] = CurrentTime
;
418 e
.xclient
.data
.l
[1] = XEMBED_EMBEDDED_NOTIFY
;
419 e
.xclient
.data
.l
[2] = 0;
420 e
.xclient
.data
.l
[3] = parent_window
;
421 e
.xclient
.data
.l
[4] = 0;
422 XSendEvent(server
.dsp
, id
, False
, 0xFFFFFF, &e
);
425 traywin
= g_new0(TrayWindow
, 1);
426 traywin
->id
= parent_window
;
427 traywin
->tray_id
= id
;
428 traywin
->hide
= hide
;
429 traywin
->depth
= attr
.depth
;
432 if (systray
.area
.on_screen
== 0)
435 if (systray
.sort
== 3)
436 systray
.list_icons
= g_slist_prepend(systray
.list_icons
, traywin
);
437 else if (systray
.sort
== 2)
438 systray
.list_icons
= g_slist_append(systray
.list_icons
, traywin
);
440 systray
.list_icons
= g_slist_insert_sorted(systray
.list_icons
, traywin
, compare_traywindows
);
441 //printf("add_icon id %lx, %d\n", id, g_slist_length(systray.list_icons));
443 // watch for the icon trying to resize itself!
444 XSelectInput(server
.dsp
, traywin
->tray_id
, StructureNotifyMask
);
445 if (server
.real_transparency
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0) {
446 traywin
->damage
= XDamageCreate(server
.dsp
, traywin
->id
, XDamageReportRawRectangles
);
447 XCompositeRedirectWindow(server
.dsp
, traywin
->id
, CompositeRedirectManual
);
452 XMapWindow(server
.dsp
, traywin
->tray_id
);
453 if (!traywin
->hide
&& !panel
->is_hidden
)
454 XMapRaised(server
.dsp
, traywin
->id
);
456 // changed in systray
457 systray
.area
.resize
= 1;
463 void remove_icon(TrayWindow
*traywin
)
467 // remove from our list
468 systray
.list_icons
= g_slist_remove(systray
.list_icons
, traywin
);
469 //printf("remove_icon id %lx, %d\n", traywin->id);
471 XSelectInput(server
.dsp
, traywin
->tray_id
, NoEventMask
);
473 XDamageDestroy(server
.dsp
, traywin
->damage
);
477 old
= XSetErrorHandler(window_error_handler
);
479 XUnmapWindow(server
.dsp
, traywin
->tray_id
);
480 XReparentWindow(server
.dsp
, traywin
->tray_id
, server
.root_win
, 0, 0);
481 XDestroyWindow(server
.dsp
, traywin
->id
);
482 XSync(server
.dsp
, False
);
483 XSetErrorHandler(old
);
484 if (traywin
->render_timeout
)
485 stop_timeout(traywin
->render_timeout
);
488 // check empty systray
491 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
492 if (!((TrayWindow
*)l
->data
)->hide
)
498 // changed in systray
499 systray
.area
.resize
= 1;
504 void net_message(XClientMessageEvent
*e
)
506 unsigned long opcode
;
509 opcode
= e
->data
.l
[1];
511 case SYSTEM_TRAY_REQUEST_DOCK
:
513 if (id
) add_icon(id
);
516 case SYSTEM_TRAY_BEGIN_MESSAGE
:
517 case SYSTEM_TRAY_CANCEL_MESSAGE
:
518 // we don't show baloons messages.
522 if (opcode
== server
.atom
._NET_SYSTEM_TRAY_MESSAGE_DATA
)
523 printf("message from dockapp: %s\n", e
->data
.b
);
525 fprintf(stderr
, "SYSTEM_TRAY : unknown message type\n");
530 void systray_render_icon_now(void* t
)
532 // we end up in this function only in real transparency mode or if systray_task_asb != 100 0 0
533 // we made also sure, that we always have a 32 bit visual, i.e. we can safely create 32 bit pixmaps here
534 TrayWindow
* traywin
= t
;
535 traywin
->render_timeout
= 0;
536 if ( traywin
->width
== 0 || traywin
->height
== 0 ) {
537 // reschedule rendering since the geometry information has not yet been processed (can happen on slow cpu)
538 systray_render_icon(traywin
);
542 // good systray icons support 32 bit depth, but some icons are still 24 bit.
543 // We create a heuristic mask for these icons, i.e. we get the rgb value in the top left corner, and
544 // mask out all pixel with the same rgb value
545 Panel
* panel
= systray
.area
.panel
;
547 // Very ugly hack, but somehow imlib2 is not able to get the image from the traywindow itself,
548 // so we first render the tray window onto a pixmap, and then we tell imlib2 to use this pixmap as
549 // drawable. If someone knows why it does not work with the traywindow itself, please tell me ;)
550 Pixmap tmp_pmap
= XCreatePixmap(server
.dsp
, server
.root_win
, traywin
->width
, traywin
->height
, 32);
551 XRenderPictFormat
* f
;
552 if (traywin
->depth
== 24)
553 f
= XRenderFindStandardFormat(server
.dsp
, PictStandardRGB24
);
554 else if (traywin
->depth
== 32)
555 f
= XRenderFindStandardFormat(server
.dsp
, PictStandardARGB32
);
557 printf("Strange tray icon found with depth: %d\n", traywin
->depth
);
561 //if (server.real_transparency)
562 //pict_image = XRenderCreatePicture(server.dsp, traywin->id, f, 0, 0);
563 // reverted Rev 407 because here it's breaking alls icon with systray + xcompmgr
564 pict_image
= XRenderCreatePicture(server
.dsp
, traywin
->tray_id
, f
, 0, 0);
565 Picture pict_drawable
= XRenderCreatePicture(server
.dsp
, tmp_pmap
, XRenderFindVisualFormat(server
.dsp
, server
.visual32
), 0, 0);
566 XRenderComposite(server
.dsp
, PictOpSrc
, pict_image
, None
, pict_drawable
, 0, 0, 0, 0, 0, 0, traywin
->width
, traywin
->height
);
567 XRenderFreePicture(server
.dsp
, pict_image
);
568 XRenderFreePicture(server
.dsp
, pict_drawable
);
569 // end of the ugly hack and we can continue as before
571 imlib_context_set_visual(server
.visual32
);
572 imlib_context_set_colormap(server
.colormap32
);
573 imlib_context_set_drawable(tmp_pmap
);
574 Imlib_Image image
= imlib_create_image_from_drawable(0, 0, 0, traywin
->width
, traywin
->height
, 1);
578 imlib_context_set_image(image
);
579 //if (traywin->depth == 24)
580 //imlib_save_image("/home/thil77/test.jpg");
581 imlib_image_set_has_alpha(1);
582 DATA32
* data
= imlib_image_get_data();
583 if (traywin
->depth
== 24) {
584 createHeuristicMask(data
, traywin
->width
, traywin
->height
);
586 if (systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0)
587 adjust_asb(data
, traywin
->width
, traywin
->height
, systray
.alpha
, (float)systray
.saturation
/100, (float)systray
.brightness
/100);
588 imlib_image_put_back_data(data
);
589 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
);
590 render_image(systray
.area
.pix
, traywin
->x
-systray
.area
.posx
, traywin
->y
-systray
.area
.posy
, traywin
->width
, traywin
->height
);
591 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
);
592 imlib_free_image_and_decache();
593 XFreePixmap(server
.dsp
, tmp_pmap
);
594 imlib_context_set_visual(server
.visual
);
595 imlib_context_set_colormap(server
.colormap
);
598 XDamageSubtract(server
.dsp
, traywin
->damage
, None
, None
);
603 void systray_render_icon(TrayWindow
* traywin
)
605 if (server
.real_transparency
|| systray
.alpha
!= 100 || systray
.brightness
!= 0 || systray
.saturation
!= 0) {
606 // wine tray icons update whenever mouse is over them, so we limit the updates to 50 ms
607 if (traywin
->render_timeout
== 0)
608 traywin
->render_timeout
= add_timeout(50, 0, systray_render_icon_now
, traywin
);
611 // comment by andreas: I'm still not sure, what exactly we need to do here... Somehow trayicons which do not
612 // offer the same depth as tint2 does, need to draw a background pixmap, but this cannot be done with
613 // XCopyArea... So we actually need XRenderComposite???
614 // Pixmap pix = XCreatePixmap(server.dsp, server.root_win, traywin->width, traywin->height, server.depth);
615 // XCopyArea(server.dsp, panel->temp_pmap, pix, server.gc, traywin->x, traywin->y, traywin->width, traywin->height, 0, 0);
616 // XSetWindowBackgroundPixmap(server.dsp, traywin->id, pix);
617 XClearArea(server
.dsp
, traywin
->tray_id
, 0, 0, traywin
->width
, traywin
->height
, True
);
622 void refresh_systray_icon()
626 for (l
= systray
.list_icons
; l
; l
= l
->next
) {
627 traywin
= (TrayWindow
*)l
->data
;
628 if (traywin
->hide
) continue;
629 systray_render_icon(traywin
);