Fix a lot of memory leaks.
[lxde/lxpanel.git] / src / plugins / icons.c
CommitLineData
a52c2257
HJYP
1#include <stdlib.h>
2#include <stdio.h>
3#include <string.h>
4#include <stdlib.h>
5
6#include <X11/Xlib.h>
7#include <X11/Xutil.h>
8//#include <X11/xpm.h>
9
10#include <gdk-pixbuf/gdk-pixbuf.h>
11#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
12#include <gdk/gdk.h>
e7cb732b 13#include <glib/gi18n.h>
a52c2257
HJYP
14
15#include "panel.h"
16#include "misc.h"
17#include "plugin.h"
18#include "gtkbar.h"
19
20
21//#define DEBUG
22#include "dbg.h"
23/*
24 * TODO : icon_copied
25 * 21/03/04 aanatoly
26 * v implement wm icon scaling ??
27 * implement net_wm_icon
28 */
29
30typedef struct wmpix_t {
31 struct wmpix_t *next;
32 gulong *data;
33 int size;
34 XClassHint ch;
35} wmpix_t;
36
37struct _icons;
38typedef struct _task{
39 struct _icons *ics;
40 struct task *next;
41 Window win;
42 int refcount;
43 XClassHint ch;
44} task;
45
46
47
48typedef struct _icons{
49 plugin *plug;
50 Window *wins;
51 int win_num;
52 GHashTable *task_list;
53 int num_tasks;
54 wmpix_t *wmpix;
55 int wmpixno;
56 wmpix_t *dicon;
57} icons;
58
59
60
61static void ics_propertynotify(icons *ics, XEvent *ev);
62static GdkFilterReturn ics_event_filter( XEvent *, GdkEvent *, icons *);
63static void icons_destructor(plugin *p);
64
65
66static void
67get_wmclass(task *tk)
68{
69 ENTER;
70 if (tk->ch.res_name)
71 XFree(tk->ch.res_name);
72 if (tk->ch.res_class)
73 XFree(tk->ch.res_class);
0dcb6bf5
HJYP
74 tk->ch.res_class = tk->ch.res_name = NULL;
75 if (!XGetClassHint (gdk_display, tk->win, &tk->ch))
76 {
77 if( G_UNLIKELY(tk->ch.res_class))
78 XFree(tk->ch.res_class);
79 if( G_UNLIKELY(tk->ch.res_name))
80 XFree(tk->ch.res_name);
a52c2257 81 tk->ch.res_class = tk->ch.res_name = NULL;
0dcb6bf5 82 }
a52c2257
HJYP
83 RET();
84}
85
86
87
a52c2257
HJYP
88static inline task *
89find_task (icons * ics, Window win)
90{
91 ENTER;
92 RET(g_hash_table_lookup(ics->task_list, &win));
93}
94
95
96static void
97del_task (icons * ics, task *tk, int hdel)
98{
99 ENTER;
100 ics->num_tasks--;
101 if (hdel)
102 g_hash_table_remove(ics->task_list, &tk->win);
0dcb6bf5
HJYP
103 if (tk->ch.res_name)
104 XFree(tk->ch.res_name);
105 if (tk->ch.res_class)
106 XFree(tk->ch.res_class);
a52c2257
HJYP
107 g_free(tk);
108 RET();
109}
110
111static wmpix_t *
112get_dicon_maybe(icons *ics, task *tk)
113{
114 XWMHints *hints;
115 gulong *data;
116 int n;
117
118 ENTER;
119 data = get_xaproperty(tk->win, a_NET_WM_ICON, XA_CARDINAL, &n);
120 if (data) {
121 XFree(data);
122 RET(NULL);
123 }
124
125 hints = (XWMHints *) get_xaproperty (tk->win, XA_WM_HINTS, XA_WM_HINTS, 0);
126 if (hints) {
127 if ((hints->flags & IconPixmapHint) || (hints->flags & IconMaskHint)) {
128 XFree (hints);
129 RET(NULL);
130 }
131 XFree (hints);
132 }
133 RET(ics->dicon);
134}
135
136
137static wmpix_t *
138get_user_icon(icons *ics, task *tk)
139{
140 wmpix_t *tmp;
141
142 ENTER;
143 if (tk->ch.res_class) {
144 for (tmp = ics->wmpix; tmp; tmp = tmp->next) {
145 if ((!tmp->ch.res_name || !strcmp(tmp->ch.res_name, tk->ch.res_name))
146 && (!tmp->ch.res_class || !strcmp(tmp->ch.res_class, tk->ch.res_class))) {
147
148 RET(tmp);
149 }
150 }
151 }
152 RET(NULL);
153 //RET (ics->dicon);
154}
155
156
157
158gulong *
159pixbuf2argb (GdkPixbuf *pixbuf, int *size)
160{
161 gulong *data;
162 guchar *pixels;
163 gulong *p;
164 gint width, height, stride;
165 gint x, y;
166 gint n_channels;
167
168 ENTER;
169 *size = 0;
170 width = gdk_pixbuf_get_width (pixbuf);
171 height = gdk_pixbuf_get_height (pixbuf);
172 stride = gdk_pixbuf_get_rowstride (pixbuf);
173 n_channels = gdk_pixbuf_get_n_channels (pixbuf);
174
175 *size += 2 + width * height;
176 p = data = g_malloc (*size * sizeof (gulong));
177 *p++ = width;
178 *p++ = height;
179
180 pixels = gdk_pixbuf_get_pixels (pixbuf);
181
182 for (y = 0; y < height; y++) {
183 for (x = 0; x < width; x++) {
184 guchar r, g, b, a;
185
186 r = pixels[y*stride + x*n_channels + 0];
187 g = pixels[y*stride + x*n_channels + 1];
188 b = pixels[y*stride + x*n_channels + 2];
189 if (n_channels >= 4)
190 a = pixels[y*stride + x*n_channels + 3];
191 else
192 a = 255;
193
194 *p++ = a << 24 | r << 16 | g << 8 | b ;
195 }
196 }
197 RET(data);
198}
199
200
201
202static void
203set_icon_maybe (icons *ics, task *tk)
204{
205 wmpix_t *pix;
206
207 ENTER;
208 g_assert ((ics != NULL) && (tk != NULL));
209 g_return_if_fail(tk != NULL);
210
211
212 pix = get_user_icon(ics, tk);
213 if (!pix)
214 pix = get_dicon_maybe(ics, tk);
215
216 if (!pix)
217 RET();
218
219 DBG("%s size=%d\n", pix->ch.res_name, pix->size);
220 XChangeProperty (GDK_DISPLAY(), tk->win,
221 a_NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (guchar*) pix->data, pix->size);
222
223 RET();
224}
225
226
227
228
229/* tell to remove element with zero refcount */
230static gboolean
231remove_stale_tasks(Window *win, task *tk, gpointer data)
232{
233 ENTER;
0dcb6bf5 234 if ( tk->refcount-- == 0) {
a52c2257
HJYP
235 del_task(tk->ics, tk, 0);
236 RET(TRUE);
237 }
238 RET(FALSE);
239}
240
241/*****************************************************
242 * handlers for NET actions *
243 *****************************************************/
244
245static GdkFilterReturn
246ics_event_filter( XEvent *xev, GdkEvent *event, icons *ics)
247{
248
249 ENTER;
250 g_assert(ics != NULL);
251 if (xev->type == PropertyNotify )
252 ics_propertynotify(ics, xev);
253 RET(GDK_FILTER_CONTINUE);
254}
255
256
257static void
258do_net_client_list(GtkWidget *widget, icons *ics)
259{
260 int i;
261 task *tk;
0dcb6bf5 262
a52c2257
HJYP
263 ENTER;
264 if (ics->wins)
265 XFree(ics->wins);
0dcb6bf5 266
a52c2257 267 ics->wins = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST, XA_WINDOW, &ics->win_num);
0dcb6bf5
HJYP
268 if (!ics->wins)
269 RET();
a52c2257
HJYP
270
271 for (i = 0; i < ics->win_num; i++) {
272 if ((tk = g_hash_table_lookup(ics->task_list, &ics->wins[i]))) {
273 tk->refcount++;
274 } else {
275 tk = g_new0(task, 1);
276 tk->refcount++;
277 ics->num_tasks++;
278 tk->win = ics->wins[i];
279 tk->ics = ics;
0dcb6bf5 280
a52c2257
HJYP
281 if (!FBPANEL_WIN(tk->win))
282 XSelectInput (GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
283 get_wmclass(tk);
284 set_icon_maybe(ics, tk);
285 g_hash_table_insert(ics->task_list, &tk->win, tk);
286 }
287 }
0dcb6bf5 288
a52c2257
HJYP
289 /* remove windows that arn't in the NET_CLIENT_LIST anymore */
290 g_hash_table_foreach_remove(ics->task_list, (GHRFunc) remove_stale_tasks, NULL);
291 RET();
292}
293
294static void
295ics_propertynotify(icons *ics, XEvent *ev)
296{
297 Atom at;
298 Window win;
299
300
301 ENTER;
302 win = ev->xproperty.window;
303 at = ev->xproperty.atom;
304 DBG("win=%x at=%d\n", win, at);
305 if (win != GDK_ROOT_WINDOW()) {
306 task *tk = find_task(ics, win);
307
308 if (!tk) RET();
309 if (at == XA_WM_CLASS) {
310 get_wmclass(tk);
311 set_icon_maybe(ics, tk);
312 } else if (at == XA_WM_HINTS) {
313 set_icon_maybe(ics, tk);
314 }
315 }
316 RET();
317}
318
319static void
320icons_build_gui(plugin *p)
321{
322 icons *ics = (icons *)p->priv;
323
324 ENTER;
325 g_signal_connect (G_OBJECT (fbev), "client_list",
326 G_CALLBACK (do_net_client_list), (gpointer) ics);
327 gdk_window_add_filter(NULL, (GdkFilterFunc)ics_event_filter, ics );
328 RET();
329}
330
331static int
332read_application(plugin *p)
333{
334 icons *ics = (icons *)p->priv;
335 GdkPixbuf *gp = NULL;
336 line s;
337 gchar *fname, *appname, *classname;
338 wmpix_t *wp = NULL;
339 gulong *data;
340 int size;
341
342 ENTER;
343 s.len = 256;
344 fname = appname = classname = NULL;
345 while (get_line(p->fp, &s) != LINE_BLOCK_END) {
346 if (s.type == LINE_NONE) {
347 ERR( "icons: illegal token %s\n", s.str);
348 goto error;
349 }
350 if (s.type == LINE_VAR) {
351 if (!g_ascii_strcasecmp(s.t[0], "image"))
352 fname = expand_tilda(s.t[1]);
353 else if (!g_ascii_strcasecmp(s.t[0], "appname"))
354 appname = g_strdup(s.t[1]);
355 else if (!g_ascii_strcasecmp(s.t[0], "classname"))
356 classname = g_strdup(s.t[1]);
357 else {
358 ERR( "icons: unknown var %s\n", s.t[0]);
359 goto error;
360 }
361 } else {
362 ERR( "icons: illegal in this context %s\n", s.str);
363 goto error;
364 }
365 }
366 if (!fname)
367 RET(0);
368 gp = gdk_pixbuf_new_from_file(fname, NULL);
369 if (gp) {
370 if ((data = pixbuf2argb(gp, &size))) {
371 wp = g_new0 (wmpix_t, 1);
372 wp->next = ics->wmpix;
373 wp->data = data;
374 wp->size = size;
375 wp->ch.res_name = appname;
376 wp->ch.res_class = classname;
377 ics->wmpix = wp;
378 ics->wmpixno++;
379 }
380 g_object_unref(gp);
381 }
382 g_free(fname);
383 RET(1);
384
385 error:
386 g_free(fname);
387 g_free(appname);
388 g_free(classname);
389 RET(0);
390}
391
392static int
393read_dicon(icons *ics, gchar *name)
394{
395 gchar *fname;
396 GdkPixbuf *gp;
397 int size;
398 gulong *data;
399
400 ENTER;
401 fname = expand_tilda(name);
402 if (!fname)
403 RET(0);
404 gp = gdk_pixbuf_new_from_file(fname, NULL);
405 if (gp) {
406 if ((data = pixbuf2argb(gp, &size))) {
407 ics->dicon = g_new0 (wmpix_t, 1);
408 ics->dicon->data = data;
409 ics->dicon->size = size;
410 }
411 g_object_unref(gp);
412 }
413 g_free(fname);
414 RET(1);
415}
416
417
418static int
419icons_constructor(plugin *p)
420{
421 icons *ics;
422 line s;
423
424 ENTER;
425 ics = g_new0(icons, 1);
426 ics->plug = p;
427 p->priv = ics;
428
429 ics->wmpixno = 0;
430 ics->task_list = g_hash_table_new(g_int_hash, g_int_equal);
431 s.len = 256;
432 while (get_line(p->fp, &s) != LINE_BLOCK_END) {
433 if (s.type == LINE_NONE) {
434 ERR( "icons: illegal token %s\n", s.str);
435 goto error;
436 }
437 if (s.type == LINE_VAR) {
438 if (!g_ascii_strcasecmp(s.t[0], "DefaultIcon")) {
439 if (!read_dicon(ics, s.t[1])) {
440 goto error;
441 }
442 } else {
443 ERR( "icons: unknown var %s\n", s.t[0]);
444 goto error;
445 }
446 } else if (s.type == LINE_BLOCK_START) {
447 if (!g_ascii_strcasecmp(s.t[0], "application")) {
448 if (!read_application(p)) {
449 goto error;
450 }
451 } else {
452 ERR( "icons: unknown var %s\n", s.t[0]);
453 goto error;
454 }
455 } else {
456 ERR( "icons: illegal in this context %s\n", s.str);
457 goto error;
458 }
459 }
460
461 icons_build_gui(p);
462 do_net_client_list(NULL, ics);
463 RET(1);
464
465 error:
466 icons_destructor(p);
467 RET(0);
468}
469
470
471static void
472icons_destructor(plugin *p)
473{
474 icons *ics = (icons *)p->priv;
475 wmpix_t *wp;
476
477 ENTER;
478 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_client_list, ics);
479 gdk_window_remove_filter(NULL, (GdkFilterFunc)ics_event_filter, ics );
480 while (ics->wmpix) {
481 wp = ics->wmpix;
482 ics->wmpix = ics->wmpix->next;
483 g_free(wp->ch.res_name);
484 g_free(wp->ch.res_class);
485 g_free(wp->data);
486 g_free(wp);
487 }
488 RET();
489}
490
491plugin_class icons_plugin_class = {
492 fname: NULL,
493 count: 0,
494
495 type : "icons",
496 name : "icons",
497 version: "1.0",
e7cb732b 498 description : N_("Change window icons"),
a52c2257
HJYP
499 invisible : 1,
500
501 constructor : icons_constructor,
502 destructor : icons_destructor,
503};