1 /* ************************************
2  * vim:tabstop=4:shiftwidth=4:cindent:fen:fdm=syntax
3  * tray.c
4  * Tue, 07 Mar 2006 10:36:10 +0600
5  * ************************************
6  * tray functions
7  * ************************************/
8 
9 #include "config.h"
10 
11 #include <X11/Xmd.h>
12 #include <X11/Xlib.h>
13 #include <X11/Xutil.h>
14 #include <X11/Xatom.h>
15 
16 #ifdef XPM_SUPPORTED
17 #include <X11/xpm.h>
18 #endif
19 
20 #include <unistd.h>
21 
22 #include "common.h"
23 #include "tray.h"
24 #include "settings.h"
25 #include "xutils.h"
26 #include "wmh.h"
27 #include "embed.h"
28 #include "image.h"
29 #include "icons.h"
30 
31 #ifndef NO_NATIVE_KDE
32 #include "kde_tray.h"
33 #endif
34 
35 #include "debug.h"
36 
tray_init()37 void tray_init()
38 {
39 	tray_data.tray = None;
40 	tray_data.hint_win = None;
41 	tray_data.dpy = NULL;
42 	tray_data.terminated = False;
43 	tray_data.bg_pmap = None;
44 	tray_data.xa_xrootpmap_id = None;
45 	tray_data.xa_xsetroot_id = None;
46 	tray_data.kde_tray_old_mode = 0;
47 	scrollbars_init();
48 }
49 
50 #ifdef XPM_SUPPORTED
tray_init_pixmap_bg()51 int tray_init_pixmap_bg()
52 {
53 	XpmAttributes xpma;
54 	Pixmap mask = None;
55 	int rc;
56 	xpma.valuemask = 0;
57 	rc = XpmReadFileToPixmap(
58 			tray_data.dpy,
59 			tray_data.tray,
60 			settings.bg_pmap_path,
61 			&tray_data.bg_pmap,
62 			&mask,
63 			&xpma);
64 	if (rc != XpmSuccess) {
65 		DIE(("Could not read background pixmap from %s.\n", settings.bg_pmap_path));
66 		return FAILURE;
67 	}
68 	/* Ignore the mask */
69 	if (mask != None) XFreePixmap(tray_data.dpy, mask);
70 	tray_data.bg_pmap_dims.x = xpma.height;
71 	tray_data.bg_pmap_dims.y = xpma.width;
72 	LOG_TRACE(("created background pixmap (%dx%d)\n", xpma.width, xpma.height));
73 	return SUCCESS;
74 }
75 #endif
76 
77 /* Mostly from FVWM:fvwm/colorset.c:172 */
tray_get_root_pixmap(Atom prop)78 Pixmap tray_get_root_pixmap(Atom prop)
79 {
80 	Atom type;
81 	int format;
82 	unsigned long length, after;
83 	unsigned char *reteval = NULL;
84 	int ret;
85 	Pixmap pix = None;
86 	Window root_window = XRootWindow(tray_data.dpy, DefaultScreen(tray_data.dpy));
87 	ret = XGetWindowProperty(tray_data.dpy,
88 			root_window,
89 			prop,
90 			0L,
91 			1L,
92 			False,
93 			XA_PIXMAP,
94 			&type,
95 			&format,
96 			&length,
97 			&after,
98 			&reteval);
99 	if (x11_ok() && ret == Success && type == XA_PIXMAP && format == 32 && length == 1 && after == 0)
100 		pix = (Pixmap)(*(long *)reteval);
101 	if (reteval) XFree(reteval);
102 	return pix;
103 }
104 
105 /* Originally from FVWM:fvwm/colorset.c:195 */
tray_update_root_bg_pmap(Pixmap * pmap,int * width,int * height)106 int tray_update_root_bg_pmap(Pixmap *pmap, int *width, int *height)
107 {
108 	unsigned int w = 0, h = 0;
109 	int rc = 0;
110 	XID dummy;
111 	Pixmap pix = None;
112 	/* Retrive root image pixmap */
113 	/* Try _XROOTPMAP_ID first */
114 	if (tray_data.xa_xrootpmap_id != None)
115 		pix = tray_get_root_pixmap(tray_data.xa_xrootpmap_id);
116 	/* Else try _XSETROOT_ID */
117 	if (!pix && tray_data.xa_xsetroot_id != None)
118 		pix = tray_get_root_pixmap(tray_data.xa_xsetroot_id);
119 	/* Get root pixmap size */
120 	if (pix)
121 		rc = XGetGeometry(tray_data.dpy,
122 				pix,
123 				&dummy,
124 				(int *)&dummy,
125 				(int *)&dummy,
126 				&w,
127 				&h,
128 				(unsigned int *)&dummy,
129 				(unsigned int *)&dummy);
130 	if (!x11_ok() || !pix || !rc) {
131 		LOG_TRACE(("could not update root background pixmap\n"));
132 		return FAILURE;
133 	}
134 	*pmap = pix;
135 	if (width != NULL) *width = w;
136 	if (height != NULL) *height = h;
137 	LOG_TRACE(("got new root pixmap: 0x%lx (%ix%i)\n", pix, w, h));
138 	return SUCCESS;
139 }
140 
141 /* XXX: most of Pixmaps are not really needed. Use XImages instead.
142  * GCs, XImages and Pixmaps stay allocated during cleanup, valgrind
143  * complains. Who cares?
144  * TODO: move pixmaps/GCs into tray_data and free them during the exit. */
tray_update_bg(int update_pixmap)145 int tray_update_bg(int update_pixmap)
146 {
147 	static Pixmap root_pmap = None;
148 	static Pixmap bg_pmap = None;
149 	static Pixmap final_pmap = None;
150 	static GC root_gc = None;
151 	static GC bg_gc = None;
152 	static GC final_gc = None;
153 	static int old_width = -1, old_height = -1;
154 	struct Rect clr;		/* clipping rectangle */
155 	struct Rect clr_loc;	/* clipping rectangle in local coords */
156 	XImage *bg_img;
157 	int bg_pmap_updated = False;
158 #define make_gc(pmap,gc) do { XGCValues gcv; \
159 		if (gc != None) XFreeGC(tray_data.dpy, gc); \
160 		gcv.graphics_exposures = False; \
161 		gc = XCreateGC(tray_data.dpy, pmap, GCGraphicsExposures, &gcv); \
162 	} while(0)
163 #define make_pmap(pmap) do { \
164 		if (pmap != None) XFreePixmap(tray_data.dpy, pmap); \
165 		pmap = XCreatePixmap(tray_data.dpy, tray_data.tray, \
166 				tray_data.xsh.width, tray_data.xsh.height, \
167 				DefaultDepth(tray_data.dpy, DefaultScreen(tray_data.dpy))); \
168 	} while(0)
169 #define recreate_pixmap(pmap,gc) do { \
170 		make_pmap(pmap); \
171 		make_gc(pmap, gc); \
172 	} while(0)
173 	/* No need to update background if it is a color one */
174 	if (!settings.transparent && !settings.pixmap_bg) return SUCCESS;
175 	/* Calculate clipping rect */
176 	clr.x = cutoff(tray_data.xsh.x, 0, tray_data.root_wnd.width);
177 	clr.y = cutoff(tray_data.xsh.y, 0, tray_data.root_wnd.height);
178 	clr.w = cutoff(tray_data.xsh.x + tray_data.xsh.width, 0, tray_data.root_wnd.width) - clr.x;
179 	clr.h = cutoff(tray_data.xsh.y + tray_data.xsh.height, 0, tray_data.root_wnd.height) - clr.y;
180 	/* There's no need to update background if tray is not visible */
181 	/* TODO: visibility is better to be tracked using some events */
182 	if (clr.w == 0 || clr.h == 0) return SUCCESS;
183 	/* Calculate local clipping rect */
184 	clr_loc.x = clr.x - tray_data.xsh.x;
185 	clr_loc.y = clr.y - tray_data.xsh.y;
186 	clr_loc.w = clr.w;
187 	clr_loc.h = clr.h;
188 	if (old_width != tray_data.xsh.width || old_height != tray_data.xsh.height || final_pmap == None)
189 		recreate_pixmap(final_pmap, final_gc);
190 	/* Update root pixmap if asked and necessary */
191 	if ((root_pmap == None || update_pixmap) &&
192 			(settings.transparent || settings.fuzzy_edges))
193 	{
194 		if (!tray_update_root_bg_pmap(&root_pmap, NULL, NULL)) {
195 			/* More gracefull solution */
196 			LOG_TRACE(("still waiting for root pixmap\n"));
197 			return SUCCESS;
198 		} else
199 			make_gc(root_pmap, root_gc);
200 	}
201 	/* We don't have to update background if only window position has
202 	 * changed unless we depend on root pixmap */
203 	if (tray_data.xsh.width == old_width && tray_data.xsh.width == old_height &&
204 			!settings.transparent && !settings.fuzzy_edges)
205 	{
206 		return SUCCESS;
207 	}
208 	/* If pixmap bg is used, bg_pmap holds tinted and tiled background pixmap,
209 	 * so there's no need to update it unless window size has changed */
210 	if (settings.pixmap_bg && (bg_pmap == None ||
211 			old_width != tray_data.xsh.width || old_height != tray_data.xsh.height))
212 	{
213 		int i, j;
214 		recreate_pixmap(bg_pmap, bg_gc);
215 		for (i = 0; i < tray_data.xsh.width / tray_data.bg_pmap_dims.x + 1; i++)
216 			for (j = 0; j < tray_data.xsh.height / tray_data.bg_pmap_dims.y + 1; j++) {
217 				XCopyArea(tray_data.dpy, tray_data.bg_pmap, bg_pmap, bg_gc,
218 						0, 0, tray_data.bg_pmap_dims.x, tray_data.bg_pmap_dims.y,
219 						i * tray_data.bg_pmap_dims.x, j * tray_data.bg_pmap_dims.y);
220 			}
221 		bg_pmap_updated = True;
222 	} else
223 	/* If root transparency is enabled, it is neccessary to copy portion of
224 	 * root pixmap under the window (root_pmap) to bg_pmap */
225 	/* XXX: must correctly work around situations when bg pixmap is smaller than root window (but how?) */
226 	if (settings.transparent) {
227 		recreate_pixmap(bg_pmap, bg_gc);
228 		XCopyArea(tray_data.dpy, root_pmap, bg_pmap, bg_gc,
229 				tray_data.xsh.x, tray_data.xsh.y,
230 				tray_data.xsh.width, tray_data.xsh.height,
231 				0, 0);
232 	}
233 	/* Create an XImage from bg_pmap */
234 	bg_img = XGetImage(tray_data.dpy, bg_pmap,
235 			0, 0, tray_data.xsh.width, tray_data.xsh.height,
236 			XAllPlanes(), ZPixmap);
237 	if (bg_img == NULL) return FAILURE;
238 	/* Tint the image if necessary. If bg_pmap was not updated, tinting
239 	 * is not needed, since it has been already done */
240 	if (settings.tint_level && ((settings.pixmap_bg && bg_pmap_updated) || settings.transparent)) {
241 		image_tint(bg_img, &settings.tint_color, settings.tint_level);
242 		XPutImage(tray_data.dpy, bg_pmap, bg_gc, bg_img,
243 				0, 0,
244 				0, 0, tray_data.xsh.width, tray_data.xsh.height);
245 		bg_pmap_updated = False;
246 	}
247 	/* Apply fuzzy edges */
248 	/* XXX: THIS IS UGLY */
249 	if (settings.fuzzy_edges) {
250 		static CARD8 *alpha_mask = NULL;
251 		/* Portion of root pixmap under the tray */
252 		XImage *root_img = XGetImage(tray_data.dpy, root_pmap,
253 				clr.x, clr.y, clr.w, clr.h,
254 				XAllPlanes(), ZPixmap);
255 		/* Proxy structure to work on */
256 		XImage *tmp_img = NULL;
257 		static Pixmap tmp_pmap = None;
258 		static GC tmp_gc = None;
259 		if (root_img == NULL) {
260 			LOG_ERROR(("Failed to get image of root pixmap under tray window\n"));
261 			LOG_TRACE(("Clipping rectangle: %dx%d+%d+%d\n", clr.w, clr.h, clr.x, clr.y));
262 			return x11_ok();
263 		}
264 		/* Alpha mask needs to be updated only on size changes */
265 		if (old_width != tray_data.xsh.width || old_height != tray_data.xsh.height) {
266 			recreate_pixmap(tmp_pmap, tmp_gc);
267 			if (alpha_mask != NULL) free(alpha_mask);
268 			alpha_mask = image_create_alpha_mask(settings.fuzzy_edges, tray_data.xsh.width, tray_data.xsh.height);
269 		}
270 		XPutImage(tray_data.dpy, tmp_pmap, tmp_gc, root_img,
271 				clr_loc.x, clr_loc.y,
272 				0, 0, clr_loc.w, clr_loc.h);
273 		tmp_img = XGetImage(tray_data.dpy, tmp_pmap,
274 				0, 0, tray_data.xsh.width, tray_data.xsh.height,
275 				XAllPlanes(), ZPixmap);
276 		if (alpha_mask != NULL && tmp_img != NULL) image_compose(bg_img, tmp_img, alpha_mask);
277 		XDestroyImage(root_img);
278 		if (tmp_img != NULL) XDestroyImage(tmp_img);
279 	}
280 	XPutImage(tray_data.dpy, final_pmap, final_gc, bg_img,
281 			0, 0,
282 			0, 0, tray_data.xsh.width, tray_data.xsh.height);
283 	XSetWindowBackgroundPixmap(tray_data.dpy, tray_data.tray, final_pmap);
284 	XDestroyImage(bg_img);
285 	old_width = tray_data.xsh.width;
286 	old_height = tray_data.xsh.height;
287 	RETURN_STATUS(x11_ok());
288 }
289 
tray_refresh_window(int exposures)290 void tray_refresh_window(int exposures)
291 {
292 	LOG_TRACE(("refreshing tray window\n"));
293 	icon_list_forall(&embedder_refresh);
294 	x11_refresh_window(tray_data.dpy, tray_data.tray, tray_data.xsh.width, tray_data.xsh.height, exposures);
295 	scrollbars_refresh(exposures);
296 }
297 
tray_calc_window_size(int base_width,int base_height,int * wnd_width,int * wnd_height)298 int tray_calc_window_size(int base_width, int base_height, int *wnd_width, int *wnd_height)
299 {
300 	*wnd_width = base_width;
301 	*wnd_height = base_height;
302 	if (settings.scrollbars_mode & SB_MODE_HORZ) *wnd_width += settings.scrollbars_size * 2;
303 	if (settings.scrollbars_mode & SB_MODE_VERT) *wnd_height += settings.scrollbars_size * 2;
304 	return SUCCESS;
305 }
306 
tray_calc_tray_area_size(int wnd_width,int wnd_height,int * base_width,int * base_height)307 int tray_calc_tray_area_size(int wnd_width, int wnd_height, int *base_width, int *base_height)
308 {
309 	*base_width = wnd_width;
310 	*base_height = wnd_height;
311 	if (settings.scrollbars_mode & SB_MODE_HORZ) *base_width -= settings.scrollbars_size * 2;
312 	if (settings.scrollbars_mode & SB_MODE_VERT) *base_height -= settings.scrollbars_size * 2;
313 	return SUCCESS;
314 }
315 
tray_update_window_strut()316 int tray_update_window_strut()
317 {
318 	/* Set window strut */
319 	if (settings.wm_strut_mode != WM_STRUT_NONE) {
320 		int strut_mode;
321 		if (settings.wm_strut_mode == WM_STRUT_AUTO) {
322 			/* Do autodetection: if some edge of tray is adjacent to one
323 			 * of screen edges, we could set window strut to that */
324 			int h_strut_mode, v_strut_mode;
325 			int p_strut_mode, s_strut_mode;
326 			h_strut_mode = (tray_data.xsh.x == 0 ? WM_STRUT_LFT :
327 					(tray_data.xsh.x + tray_data.xsh.width == tray_data.root_wnd.width ? WM_STRUT_RHT :
328 					 	WM_STRUT_NONE));
329 			v_strut_mode = (tray_data.xsh.y == 0 ? WM_STRUT_TOP :
330 					(tray_data.xsh.x + tray_data.xsh.height == tray_data.root_wnd.height ? WM_STRUT_BOT :
331 					 	WM_STRUT_NONE));
332 			/* If tray is vertical, horizontal strut mode has higher priority,
333 			 * else vertical strut mode has higher priority */
334 			if (settings.vertical) {
335 				p_strut_mode = h_strut_mode;
336 				s_strut_mode = v_strut_mode;
337 			} else {
338 				p_strut_mode = v_strut_mode;
339 				s_strut_mode = h_strut_mode;
340 			}
341 			if (p_strut_mode != WM_STRUT_NONE)
342 				strut_mode = p_strut_mode;
343 			else
344 				strut_mode = s_strut_mode;
345 		} else
346 			strut_mode = settings.wm_strut_mode;
347 		LOG_TRACE(("computed final strut mode: %d\n", strut_mode));
348 		/* Update respective window hint */
349 		if (strut_mode != WM_STRUT_NONE) {
350 			wm_strut_t wm_strut;
351 			int base_idx;
352 			memset(wm_strut, 0, sizeof(wm_strut));
353 			LOG_TRACE(("current tray geometry: %dx%d+%d+%d\n",
354 						tray_data.xsh.width, tray_data.xsh.height,
355 						tray_data.xsh.x, tray_data.xsh.y));
356 			if (strut_mode == WM_STRUT_TOP || strut_mode == WM_STRUT_BOT) {
357 				if (strut_mode == WM_STRUT_TOP) {
358 					base_idx = WM_STRUT_IDX_TOP;
359 					wm_strut[WM_STRUT_IDX_TOP] = tray_data.xsh.y + tray_data.xsh.height;
360 				} else {
361 					base_idx = WM_STRUT_IDX_BOT;
362 					wm_strut[WM_STRUT_IDX_BOT] = tray_data.root_wnd.height - tray_data.xsh.y;
363 				}
364 				wm_strut[WM_STRUT_IDX_START(base_idx)] = tray_data.xsh.x;
365 				wm_strut[WM_STRUT_IDX_END(base_idx)] = tray_data.xsh.x + tray_data.xsh.width - 1;
366 			} else {
367 				if (strut_mode == WM_STRUT_LFT) {
368 					base_idx = WM_STRUT_IDX_LFT;
369 					wm_strut[WM_STRUT_IDX_LFT] = tray_data.xsh.x + tray_data.xsh.width;
370 				} else {
371 					base_idx = WM_STRUT_IDX_RHT;
372 					wm_strut[WM_STRUT_IDX_RHT] = tray_data.root_wnd.width - tray_data.xsh.x;
373 				}
374 				wm_strut[WM_STRUT_IDX_START(base_idx)] = tray_data.xsh.y;
375 				wm_strut[WM_STRUT_IDX_END(base_idx)] = tray_data.xsh.y + tray_data.xsh.height - 1;
376 			}
377 			{	int i;
378 				for (i = 0; i < _NET_WM_STRUT_PARTIAL_SZ; i++)
379 					LOG_TRACE(("computed hints [%d] = %d\n", i, wm_strut[i]));
380 			}
381 			ewmh_set_window_strut(tray_data.dpy, tray_data.tray, wm_strut);
382 		}
383 	}
384 	return SUCCESS;
385 }
386 
tray_update_window_props()387 int tray_update_window_props()
388 {
389 	XSizeHints xsh;
390 	int cur_base_width, cur_base_height;
391 	int new_width, new_height;
392 	int layout_width, layout_height;
393 	/* Phase 1: calculate new tray window dimensions.
394 	 * Algorithm summary:
395 	 * new_dims =
396 	 *      if (layout_dims > max_dims) max_dims;
397 	 * else if ((shrink_back && layout_dims > orig_dims) ||
398 	 *          (layout_dims > current_dims)) layout_dims;
399 	 * else if (shrink_back) orig_dims;
400 	 * else                  current_dims;
401 	 */
402 	x11_get_window_size(tray_data.dpy, tray_data.tray,
403 			&tray_data.xsh.width, &tray_data.xsh.height);
404 	layout_get_size(&layout_width, &layout_height);
405 	LOG_TRACE(("layout geometry: %dx%d\n", layout_width, layout_height));
406 	tray_calc_tray_area_size(tray_data.xsh.width, tray_data.xsh.height,
407 			&cur_base_width, &cur_base_height);
408 	LOG_TRACE(("base tray geometry: %dx%d\n", cur_base_width, cur_base_height));
409 	LOG_TRACE(("orig tray geometry: %dx%d\n", settings.orig_tray_dims.x, settings.orig_tray_dims.y));
410 	LOG_TRACE(("max tray geometry: %dx%d\n", settings.max_tray_dims.x, settings.max_tray_dims.y));
411 #define CALC_DIM(tgt,cur,layout,max,orig) \
412 	if (layout > max) tgt = max; \
413 	else if ((settings.shrink_back_mode && layout > orig) || layout > cur) tgt = layout; \
414 	else if (settings.shrink_back_mode) tgt = orig; \
415 	else tgt = cur;
416 	CALC_DIM(new_width, cur_base_width, layout_width,
417 			settings.max_tray_dims.x, settings.orig_tray_dims.x);
418 	CALC_DIM(new_height, cur_base_height, layout_height,
419 			settings.max_tray_dims.y, settings.orig_tray_dims.y);
420 	LOG_TRACE(("new base tray geometry: %dx%d\n", new_width, new_height));
421 	tray_calc_window_size(new_width, new_height, &new_width, &new_height);
422 	LOG_TRACE(("new tray geometry: %dx%d\n", new_width, new_height));
423 #if 1
424 	/* Not sure if this is really necessary */
425 	xsh.x = tray_data.xsh.x;
426 	xsh.y = tray_data.xsh.y;
427 	xsh.width = new_width;
428 	xsh.height = new_height;
429 #endif
430 	xsh.min_width = new_width;
431 	xsh.min_height = new_height;
432 	xsh.max_width = new_width;
433 	xsh.max_height = new_height;
434 	xsh.width_inc = settings.slot_size;
435 	xsh.height_inc = settings.slot_size;
436 	tray_calc_window_size(0, 0, &xsh.base_width, &xsh.base_height);
437 	xsh.win_gravity = NorthWestGravity;
438 	xsh.flags = tray_data.xsh.flags;
439 	XSetWMNormalHints(tray_data.dpy, tray_data.tray, &xsh);
440 	/* Phase 2: set new window size
441 	 * This check helps to avod extra (erroneous) moves of the window when
442 	 * geometry changes are not updated yet, but tray_update_window_props() was
443 	 * called once again */
444 	if (new_width != tray_data.xsh.width || new_height != tray_data.xsh.height) {
445 		/* Apparently, not every WM (hello, WindowMaker!) handles gravity the
446 		 * way I have expected (i.e. using it to calculate reference point as
447 		 * described in ICCM/WM specs). Perhaps, I was dreaming.  So, prior to
448 		 * resizing trays window, it is necessary to recalculate window
449 		 * absolute position and shift it according to grow gravity settings */
450 		x11_get_window_abs_coords(tray_data.dpy, tray_data.tray,
451 				&tray_data.xsh.x, &tray_data.xsh.y);
452 		LOG_TRACE(("old tray window geometry: %dx%d+%d+%d\n", new_width, new_height,
453 					tray_data.xsh.x, tray_data.xsh.y));
454 		if (settings.grow_gravity & GRAV_E)
455 			tray_data.xsh.x -= new_width - tray_data.xsh.width;
456 		else if (!(settings.grow_gravity & GRAV_H))
457 			tray_data.xsh.x -= (new_width - tray_data.xsh.width) / 2;
458 		if (settings.grow_gravity & GRAV_S)
459 			tray_data.xsh.y -= new_height - tray_data.xsh.height;
460 		else if (!(settings.grow_gravity & GRAV_V))
461 			tray_data.xsh.y -= (new_height - tray_data.xsh.height) / 2;
462 		tray_data.xsh.width = new_width;
463 		tray_data.xsh.height = new_height;
464 		LOG_TRACE(("new tray window geometry: %dx%d+%d+%d\n", new_width, new_height,
465 					tray_data.xsh.x, tray_data.xsh.y));
466 		XResizeWindow(tray_data.dpy, tray_data.tray, new_width, new_height);
467 		XMoveWindow(tray_data.dpy, tray_data.tray, tray_data.xsh.x, tray_data.xsh.y);
468 		if (!x11_ok()) {
469 			LOG_TRACE(("could not update tray window geometry\n"));
470 			return FAILURE;
471 		}
472 	} else {
473 		/* XXX: Why do we need this again? */
474 		XResizeWindow(tray_data.dpy, tray_data.tray,
475 						tray_data.xsh.width, tray_data.xsh.height);
476 	}
477 	tray_update_window_strut();
478 	scrollbars_update();
479 	return SUCCESS;
480 }
481 
tray_create_window(int argc,char ** argv)482 void tray_create_window(int argc, char **argv)
483 {
484 	XTextProperty wm_name;
485 	XSetWindowAttributes xswa;
486 	XClassHint xch;
487 	XWMHints xwmh;
488 	Atom net_system_tray_orientation;
489 	Atom orient;
490 	Atom protocols_atoms[3];
491 	/* Create some atoms */
492 	tray_data.xa_wm_delete_window =
493 		XInternAtom(tray_data.dpy, "WM_DELETE_WINDOW", False);
494 	tray_data.xa_net_wm_ping =
495 		XInternAtom(tray_data.dpy, "_NET_WM_PING", False);
496 	tray_data.xa_wm_take_focus =
497 		XInternAtom(tray_data.dpy, "WM_TAKE_FOCUS", False);
498 	tray_data.xa_wm_protocols =
499 		XInternAtom(tray_data.dpy, "WM_PROTOCOLS", False);
500 	tray_data.xa_kde_net_system_tray_windows =
501 		XInternAtom(tray_data.dpy, "_KDE_NET_SYSTEM_TRAY_WINDOWS", False);
502 	tray_data.xa_net_client_list =
503 		XInternAtom(tray_data.dpy, "_NET_CLIENT_LIST", False);
504 	/* Create tray window */
505 	tray_data.tray = XCreateSimpleWindow(
506 						tray_data.dpy,
507 						DefaultRootWindow(tray_data.dpy),
508 						tray_data.xsh.x, tray_data.xsh.y,
509 						tray_data.xsh.width, tray_data.xsh.height,
510 						0,
511 						settings.bg_color.pixel,
512 						settings.bg_color.pixel);
513 	LOG_TRACE(("created tray window: 0x%x\n", tray_data.tray));
514 	if (settings.dockapp_mode == DOCKAPP_WMAKER) {
515 		tray_data.hint_win = XCreateSimpleWindow(
516 							tray_data.dpy,
517 							DefaultRootWindow(tray_data.dpy),
518 							0, 0,
519 							1, 1,
520 							0,
521 							settings.bg_color.pixel,
522 							settings.bg_color.pixel);
523 		LOG_TRACE(("created hint_win window: 0x%x\n", tray_data.hint_win));
524 	}
525 	/* Set tray window properties */
526 	xswa.bit_gravity = settings.bit_gravity;
527 	xswa.win_gravity = settings.win_gravity;
528 	xswa.backing_store = settings.parent_bg ? NotUseful : WhenMapped;
529 	XChangeWindowAttributes(tray_data.dpy,
530 			tray_data.tray,
531 			CWBitGravity | CWWinGravity | CWBackingStore,
532 			&xswa);
533 	{
534 		/* XXX: use XStoreName ?*/
535 		int numtries = 0;
536 		/* First, try user-supplied value */
537 		while (1) {
538 			if (XmbTextListToTextProperty(tray_data.dpy, &settings.wnd_name, 1, XTextStyle, &wm_name) != Success)
539 				/* Retry with default value */
540 				settings.wnd_name = PROGNAME;
541 			else
542 				break;
543 			if (numtries++ > 1) DIE(("Invalid window name \"%s\"\n", settings.wnd_name));
544 		}
545 	}
546 	XSetWMName(tray_data.dpy, tray_data.tray, &wm_name);
547 	XFree(wm_name.value);
548 	/* Setup class hints */
549 	xch.res_class = PROGNAME;
550 	xch.res_name = PROGNAME;
551 	/* Setup window manager hints */
552 	xwmh.flags = StateHint | InputHint;
553 	xwmh.input = False;
554 	xwmh.initial_state = settings.dockapp_mode != DOCKAPP_NONE ? WithdrawnState: NormalState;
555 	/* Apply hints */
556 	XSetWMHints(tray_data.dpy, tray_data.tray, &xwmh);
557 	XSetClassHint(tray_data.dpy, tray_data.tray, &xch);
558 	XSetWMNormalHints(tray_data.dpy, tray_data.tray, &tray_data.xsh);
559 	XSetCommand(tray_data.dpy, tray_data.tray, argv, argc);
560 	/* Set properties of hint window if WindowMaker dockapp mode enabled */
561 	if (settings.dockapp_mode == DOCKAPP_WMAKER) {
562 		xwmh.flags |= IconWindowHint | IconPositionHint | WindowGroupHint;
563 		xwmh.initial_state = WithdrawnState;
564 		xwmh.icon_x = tray_data.xsh.x;
565 		xwmh.icon_y = tray_data.xsh.y;
566 		xwmh.icon_window = tray_data.tray;
567 		xwmh.window_group = tray_data.hint_win;
568 		XSetClassHint(tray_data.dpy, tray_data.hint_win, &xch);
569 		XSetWMHints(tray_data.dpy, tray_data.hint_win, &xwmh);
570 	}
571 	/* v0.2 tray protocol support */
572 	orient =
573 		settings.vertical ? _NET_SYSTEM_TRAY_ORIENTATION_HORZ : _NET_SYSTEM_TRAY_ORIENTATION_VERT;
574 	net_system_tray_orientation = XInternAtom(tray_data.dpy, TRAY_ORIENTATION_ATOM, False);
575 	XChangeProperty(tray_data.dpy, tray_data.tray,
576 			net_system_tray_orientation, net_system_tray_orientation, 32,
577 			PropModeReplace,
578 			(unsigned char *) &orient, 1);
579 	/* Ask X server / WM to report certain events */
580 	protocols_atoms[0] = tray_data.xa_wm_delete_window;
581 	protocols_atoms[1] = tray_data.xa_wm_take_focus;
582 	protocols_atoms[2] = tray_data.xa_net_wm_ping;
583 	XSetWMProtocols(tray_data.dpy, tray_data.tray, protocols_atoms, 3);
584 	XSelectInput(tray_data.dpy, tray_data.tray,
585 			StructureNotifyMask | FocusChangeMask | PropertyChangeMask | ExposureMask );
586 	x11_extend_root_event_mask(tray_data.dpy, PropertyChangeMask);
587 	scrollbars_create();
588 	/* Set tray window background if necessary */
589 #ifdef XPM_SUPPORTED
590 	if (settings.pixmap_bg) tray_init_pixmap_bg();
591 #endif
592 	if (settings.parent_bg)
593 		XSetWindowBackgroundPixmap(tray_data.dpy, tray_data.tray, ParentRelative);
594 	else if (settings.transparent || settings.fuzzy_edges) {
595 		tray_data.xa_xrootpmap_id = XInternAtom(tray_data.dpy, "_XROOTPMAP_ID", False);
596 		tray_data.xa_xsetroot_id = XInternAtom(tray_data.dpy, "_XSETROOT_ID", False);
597 	}
598 	tray_update_bg(True);
599 }
600 
tray_create_phony_window()601 void tray_create_phony_window()
602 {
603 	/* Create fake tray window */
604 	tray_data.tray = XCreateSimpleWindow(
605 						tray_data.dpy,
606 						DefaultRootWindow(tray_data.dpy),
607 						0, 0, 1, 1,
608 						0,
609 						0, 0);
610 	/* Select for PropertyNotify so that x11_get_server_timestamp() works */
611 	XSelectInput(tray_data.dpy, tray_data.tray, PropertyChangeMask);
612 }
613 
tray_set_wm_hints()614 int tray_set_wm_hints()
615 {
616 	int mwm_decor = 0;
617 	if (settings.deco_flags & DECO_TITLE)
618 		mwm_decor |= MWM_DECOR_TITLE | MWM_DECOR_MENU;
619 	if (settings.deco_flags & DECO_BORDER)
620 		mwm_decor |= MWM_DECOR_RESIZEH | MWM_DECOR_BORDER;
621 	mwm_set_hints(tray_data.dpy, tray_data.tray, mwm_decor, MWM_FUNC_ALL);
622 	if (settings.sticky) {
623 		ewmh_add_window_state(tray_data.dpy, tray_data.tray, _NET_WM_STATE_STICKY);
624 		ewmh_set_window_atom32(tray_data.dpy, tray_data.tray, _NET_WM_DESKTOP, 0xFFFFFFFF);
625 	}
626 	if (settings.skip_taskbar)
627 		ewmh_add_window_state(tray_data.dpy, tray_data.tray, _NET_WM_STATE_SKIP_TASKBAR);
628 	if (settings.wnd_layer != NULL)
629 		ewmh_add_window_state(tray_data.dpy, tray_data.tray, settings.wnd_layer);
630 	if (strcmp(settings.wnd_type, _NET_WM_WINDOW_TYPE_NORMAL) != 0)
631 		ewmh_add_window_type(tray_data.dpy, tray_data.tray, settings.wnd_type);
632 	/* Alwas add NORMAL window type for WM that do not support (some) special types */
633 	ewmh_add_window_type(tray_data.dpy, tray_data.tray, _NET_WM_WINDOW_TYPE_NORMAL);
634 	return SUCCESS;
635 }
636 
tray_init_selection_atoms()637 void tray_init_selection_atoms()
638 {
639 	static char *tray_sel_atom_name = NULL;
640 	/* Obtain selection atom name basing on current screen number */
641 	if (tray_sel_atom_name == NULL) {
642 		tray_sel_atom_name = (char *)malloc(strlen(TRAY_SEL_ATOM) + 10);
643 		if (tray_sel_atom_name == NULL) DIE_OOM(("could not allocate memory for selection atom name\n"));
644 		snprintf(tray_sel_atom_name,
645 				strlen(TRAY_SEL_ATOM) + 10,
646 				"%s%u",
647 				TRAY_SEL_ATOM,
648 				DefaultScreen(tray_data.dpy));
649 	}
650 	LOG_TRACE(("tray_sel_atom_name=%s\n", tray_sel_atom_name));
651 	/* Initialize atom values */
652 	tray_data.xa_tray_selection = XInternAtom(tray_data.dpy, tray_sel_atom_name, False);
653 	tray_data.xa_tray_opcode = XInternAtom(tray_data.dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
654 	tray_data.xa_tray_data = XInternAtom(tray_data.dpy, "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
655 }
656 
tray_acquire_selection()657 void tray_acquire_selection()
658 {
659 	Time timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray);
660 	tray_init_selection_atoms();
661 	/* Save old selection owner */
662 	tray_data.old_selection_owner = XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection);
663 	LOG_TRACE(("old selection owner: 0x%x\n", tray_data.old_selection_owner));
664 	/* Acquire selection */
665 	XSetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection,
666 			tray_data.tray, timestamp);
667 	/* Check if we have really got the selection */
668 	if (XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection) != tray_data.tray) {
669 		DIE(("could not set selection owner.\nMay be another (greedy) tray running?\n"));
670 	} else {
671 		tray_data.is_active = True;
672 		LOG_TRACE(("ok, got _NET_SYSTEM_TRAY selection\n"));
673 	}
674 	/* Send the message notifying about new MANAGER */
675 	x11_send_client_msg32(tray_data.dpy, DefaultRootWindow(tray_data.dpy),
676 	                      DefaultRootWindow(tray_data.dpy),
677 	                      XInternAtom(tray_data.dpy, "MANAGER", False),
678 	                      timestamp,
679 	                      tray_data.xa_tray_selection, tray_data.tray, 0, 0);
680 }
681 
tray_show_window()682 void tray_show_window()
683 {
684 	tray_set_wm_hints();
685 	tray_update_window_props();
686 	XMapRaised(tray_data.dpy, tray_data.tray);
687 	if (settings.dockapp_mode == DOCKAPP_NONE)
688 		XMoveWindow(tray_data.dpy, tray_data.tray, tray_data.xsh.x, tray_data.xsh.y);
689 	if (settings.dockapp_mode == DOCKAPP_WMAKER)
690 		XMapWindow(tray_data.dpy, tray_data.hint_win);
691 	/* XXX: I do not why, but for some WM it is
692 	 * required to set hints / window properties
693 	 * after and before window creation */
694 	/* TODO: check if this is really necessary */
695 	tray_set_wm_hints();
696 	tray_update_window_props();
697 }
698 
699