Merge
[lxde/lxpanel.git] / src / misc.c
1 /**
2 * Copyright (c) 2006 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <X11/Xatom.h>
20 #include <X11/cursorfont.h>
21
22 #include <gtk/gtk.h>
23 #include <gdk/gdk.h>
24 #include <gdk/gdkx.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <stdarg.h>
30
31 #include "misc.h"
32 #include "panel.h"
33
34 #include "dbg.h"
35
36 /* data used by themed images buttons */
37 typedef struct {
38 char* fname;
39 guint theme_changed_handler;
40 GdkPixbuf* pixbuf;
41 GdkPixbuf* hilight;
42 gulong hicolor;
43 int dw, dh; /* desired size */
44 gboolean keep_ratio;
45 } ImgData;
46
47 static GQuark img_data_id = 0;
48
49 static void on_theme_changed(GtkIconTheme* theme, GtkWidget* img);
50 static void _gtk_image_set_from_file_scaled( GtkWidget* img, const gchar *file, gint width, gint height, gboolean keep_ratio);
51
52 /* X11 data types */
53 Atom a_UTF8_STRING;
54 Atom a_XROOTPMAP_ID;
55
56 /* old WM spec */
57 Atom a_WM_STATE;
58 Atom a_WM_CLASS;
59 Atom a_WM_DELETE_WINDOW;
60 Atom a_WM_PROTOCOLS;
61
62 /* new NET spec */
63 Atom a_NET_WORKAREA;
64 Atom a_NET_CLIENT_LIST;
65 Atom a_NET_CLIENT_LIST_STACKING;
66 Atom a_NET_NUMBER_OF_DESKTOPS;
67 Atom a_NET_CURRENT_DESKTOP;
68 Atom a_NET_DESKTOP_VIEWPORT;
69 Atom a_NET_DESKTOP_NAMES;
70 Atom a_NET_ACTIVE_WINDOW;
71 Atom a_NET_CLOSE_WINDOW;
72 Atom a_NET_SHOWING_DESKTOP;
73 Atom a_NET_SUPPORTED;
74 Atom a_NET_WM_STATE;
75 Atom a_NET_WM_STATE_SKIP_TASKBAR;
76 Atom a_NET_WM_STATE_SKIP_PAGER;
77 Atom a_NET_WM_STATE_STICKY;
78 Atom a_NET_WM_STATE_HIDDEN;
79 Atom a_NET_WM_STATE_SHADED;
80 Atom a_NET_WM_WINDOW_TYPE;
81 Atom a_NET_WM_WINDOW_TYPE_DESKTOP;
82 Atom a_NET_WM_WINDOW_TYPE_DOCK;
83 Atom a_NET_WM_WINDOW_TYPE_TOOLBAR;
84 Atom a_NET_WM_WINDOW_TYPE_MENU;
85 Atom a_NET_WM_WINDOW_TYPE_UTILITY;
86 Atom a_NET_WM_WINDOW_TYPE_SPLASH;
87 Atom a_NET_WM_WINDOW_TYPE_DIALOG;
88 Atom a_NET_WM_WINDOW_TYPE_NORMAL;
89 Atom a_NET_WM_DESKTOP;
90 Atom a_NET_WM_PID;
91 Atom a_NET_WM_NAME;
92 Atom a_NET_WM_VISIBLE_NAME;
93 Atom a_NET_WM_STRUT;
94 Atom a_NET_WM_STRUT_PARTIAL;
95 Atom a_NET_WM_ICON;
96 Atom a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR;
97
98 /* SYSTEM TRAY spec */
99 Atom a_NET_SYSTEM_TRAY_OPCODE;
100 Atom a_NET_SYSTEM_TRAY_MESSAGE_DATA;
101 Atom a_NET_SYSTEM_TRAY_ORIENTATION;
102 Atom a_MANAGER;
103
104 Atom a_LXPANEL_CMD; /* for private client message */
105
106 /* if current window manager is EWMH conforming. */
107 gboolean is_ewmh_supported;
108
109 enum{
110 I_UTF8_STRING,
111 I_XROOTPMAP_ID,
112 I_WM_STATE,
113 I_WM_CLASS,
114 I_WM_DELETE_WINDOW,
115 I_WM_PROTOCOLS,
116
117 I_NET_WORKAREA,
118 I_NET_CLIENT_LIST,
119 I_NET_CLIENT_LIST_STACKING,
120 I_NET_NUMBER_OF_DESKTOPS,
121 I_NET_CURRENT_DESKTOP,
122 I_NET_DESKTOP_VIEWPORT,
123 I_NET_DESKTOP_NAMES,
124 I_NET_ACTIVE_WINDOW,
125 I_NET_SHOWING_DESKTOP,
126 I_NET_SUPPORTED,
127
128 I_NET_WM_STATE,
129 I_NET_WM_STATE_SKIP_TASKBAR,
130 I_NET_WM_STATE_SKIP_PAGER,
131 I_NET_WM_STATE_STICKY,
132 I_NET_WM_STATE_HIDDEN,
133 I_NET_WM_STATE_SHADED,
134 I_NET_WM_WINDOW_TYPE,
135
136 I_NET_WM_WINDOW_TYPE_DESKTOP,
137 I_NET_WM_WINDOW_TYPE_DOCK,
138 I_NET_WM_WINDOW_TYPE_TOOLBAR,
139 I_NET_WM_WINDOW_TYPE_MENU,
140 I_NET_WM_WINDOW_TYPE_UTILITY,
141 I_NET_WM_WINDOW_TYPE_SPLASH,
142 I_NET_WM_WINDOW_TYPE_DIALOG,
143 I_NET_WM_WINDOW_TYPE_NORMAL,
144 I_NET_WM_DESKTOP,
145 I_NET_WM_PID,
146 I_NET_WM_NAME,
147 I_NET_WM_VISIBLE_NAME,
148 I_NET_WM_STRUT,
149 I_NET_WM_STRUT_PARTIAL,
150 I_NET_WM_ICON,
151 I_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR,
152
153 I_NET_SYSTEM_TRAY_OPCODE,
154 I_NET_SYSTEM_TRAY_MESSAGE_DATA,
155 I_NET_SYSTEM_TRAY_ORIENTATION,
156 I_MANAGER,
157
158 I_LXPANEL_CMD,
159 N_ATOMS
160 };
161
162 pair allign_pair[] = {
163 { ALLIGN_NONE, "none" },
164 { ALLIGN_LEFT, "left" },
165 { ALLIGN_RIGHT, "right" },
166 { ALLIGN_CENTER, "center"},
167 { 0, NULL },
168 };
169
170 pair edge_pair[] = {
171 { EDGE_NONE, "none" },
172 { EDGE_LEFT, "left" },
173 { EDGE_RIGHT, "right" },
174 { EDGE_TOP, "top" },
175 { EDGE_BOTTOM, "bottom" },
176 { 0, NULL },
177 };
178
179 pair width_pair[] = {
180 { WIDTH_NONE, "none" },
181 { WIDTH_REQUEST, "request" },
182 { WIDTH_PIXEL, "pixel" },
183 { WIDTH_PERCENT, "percent" },
184 { 0, NULL },
185 };
186
187 pair height_pair[] = {
188 { HEIGHT_NONE, "none" },
189 { HEIGHT_PIXEL, "pixel" },
190 { 0, NULL },
191 };
192
193 pair bool_pair[] = {
194 { 0, "0" },
195 { 1, "1" },
196 { 0, NULL },
197 };
198 pair pos_pair[] = {
199 { POS_NONE, "none" },
200 { POS_START, "start" },
201 { POS_END, "end" },
202 { 0, NULL},
203 };
204
205
206 int
207 str2num(pair *p, gchar *str, int defval)
208 {
209 ENTER;
210 for (;p && p->str; p++) {
211 if (!g_ascii_strcasecmp(str, p->str))
212 RET(p->num);
213 }
214 RET(defval);
215 }
216
217 gchar *
218 num2str(pair *p, int num, gchar *defval)
219 {
220 ENTER;
221 for (;p && p->str; p++) {
222 if (num == p->num)
223 RET(p->str);
224 }
225 RET(defval);
226 }
227
228 int buf_gets( char* buf, int len, char **fp )
229 {
230 char* p;
231 int i = 0;
232 if( !fp || !(p = *fp) || !**fp )
233 {
234 buf[0] = '\0';
235 return 0;
236 }
237
238 do
239 {
240 if( G_LIKELY( i < len ) )
241 {
242 buf[i] = *p;
243 ++i;
244 }
245 if( G_UNLIKELY(*p == '\n') )
246 {
247 ++p;
248 break;
249 }
250 }while( *(++p) );
251 buf[i] = '\0';
252 *fp = p;
253 return i;
254 }
255
256 extern int
257 lxpanel_put_line(FILE* fp, const char* format, ...)
258 {
259 static int indent = 0;
260 int i, ret;
261 va_list args;
262
263 if( strchr(format, '}') )
264 --indent;
265
266 for( i = 0; i < indent; ++i )
267 fputs( " ", fp );
268
269 va_start (args, format);
270 ret = vfprintf (fp, format, args);
271 va_end (args);
272
273 if( strchr(format, '{') )
274 ++indent;
275 fputc( '\n', fp ); /* add line break */
276 return (ret + 1);
277 }
278
279 extern int
280 lxpanel_get_line(char**fp, line *s)
281 {
282 gchar *tmp, *tmp2;
283
284 s->type = LINE_NONE;
285 if (!fp)
286 RET(s->type);
287 while (buf_gets(s->str, s->len, fp)) {
288
289 g_strstrip(s->str);
290
291 if (s->str[0] == '#' || s->str[0] == 0) {
292 continue;
293 }
294 if (!g_ascii_strcasecmp(s->str, "}")) {
295 s->type = LINE_BLOCK_END;
296 break;
297 }
298
299 s->t[0] = s->str;
300 for (tmp = s->str; isalnum(*tmp); tmp++);
301 for (tmp2 = tmp; isspace(*tmp2); tmp2++);
302 if (*tmp2 == '=') {
303 for (++tmp2; isspace(*tmp2); tmp2++);
304 s->t[1] = tmp2;
305 *tmp = 0;
306 s->type = LINE_VAR;
307 } else if (*tmp2 == '{') {
308 *tmp = 0;
309 s->type = LINE_BLOCK_START;
310 } else {
311 ERR( "parser: unknown token: '%c'\n", *tmp2);
312 }
313 break;
314 }
315 return s->type;
316 }
317
318 int
319 get_line_as_is(char** fp, line *s)
320 {
321 gchar *tmp, *tmp2;
322
323 ENTER;
324 if (!fp) {
325 s->type = LINE_NONE;
326 RET(s->type);
327 }
328 s->type = LINE_NONE;
329 while (buf_gets(s->str, s->len, fp)) {
330 g_strstrip(s->str);
331 if (s->str[0] == '#' || s->str[0] == 0)
332 continue;
333 DBG( ">> %s\n", s->str);
334 if (!g_ascii_strcasecmp(s->str, "}")) {
335 s->type = LINE_BLOCK_END;
336 DBG( " : line_block_end\n");
337 break;
338 }
339 for (tmp = s->str; isalnum(*tmp); tmp++);
340 for (tmp2 = tmp; isspace(*tmp2); tmp2++);
341 if (*tmp2 == '=') {
342 s->type = LINE_VAR;
343 } else if (*tmp2 == '{') {
344 s->type = LINE_BLOCK_START;
345 } else {
346 DBG( " : ? <%c>\n", *tmp2);
347 }
348 break;
349 }
350 RET(s->type);
351
352 }
353
354 void resolve_atoms()
355 {
356 static const char* atom_names[ N_ATOMS ];
357
358 atom_names[ I_UTF8_STRING ] = "UTF8_STRING";
359 atom_names[ I_XROOTPMAP_ID ] = "_XROOTPMAP_ID";
360 atom_names[ I_WM_STATE ] = "WM_STATE";
361 atom_names[ I_WM_CLASS ] = "WM_CLASS";
362 atom_names[ I_WM_DELETE_WINDOW ] = "WM_DELETE_WINDOW";
363 atom_names[ I_WM_PROTOCOLS ] = "WM_PROTOCOLS";
364 atom_names[ I_NET_WORKAREA ] = "_NET_WORKAREA";
365 atom_names[ I_NET_CLIENT_LIST ] = "_NET_CLIENT_LIST";
366 atom_names[ I_NET_CLIENT_LIST_STACKING ] = "_NET_CLIENT_LIST_STACKING";
367 atom_names[ I_NET_NUMBER_OF_DESKTOPS ] = "_NET_NUMBER_OF_DESKTOPS";
368 atom_names[ I_NET_CURRENT_DESKTOP ] = "_NET_CURRENT_DESKTOP";
369 atom_names[ I_NET_DESKTOP_VIEWPORT ] = "_NET_DESKTOP_VIEWPORT";
370 atom_names[ I_NET_DESKTOP_NAMES ] = "_NET_DESKTOP_NAMES";
371 atom_names[ I_NET_ACTIVE_WINDOW ] = "_NET_ACTIVE_WINDOW";
372 atom_names[ I_NET_SHOWING_DESKTOP ] = "_NET_SHOWING_DESKTOP";
373 atom_names[ I_NET_SUPPORTED ] = "_NET_SUPPORTED";
374 atom_names[ I_NET_WM_DESKTOP ] = "_NET_WM_DESKTOP";
375 atom_names[ I_NET_WM_STATE ] = "_NET_WM_STATE";
376 atom_names[ I_NET_WM_STATE_SKIP_TASKBAR ] = "_NET_WM_STATE_SKIP_TASKBAR";
377 atom_names[ I_NET_WM_STATE_SKIP_PAGER ] = "_NET_WM_STATE_SKIP_PAGER";
378 atom_names[ I_NET_WM_STATE_STICKY ] = "_NET_WM_STATE_STICKY";
379 atom_names[ I_NET_WM_STATE_HIDDEN ] = "_NET_WM_STATE_HIDDEN";
380 atom_names[ I_NET_WM_STATE_SHADED ] = "_NET_WM_STATE_SHADED";
381 atom_names[ I_NET_WM_WINDOW_TYPE ] = "_NET_WM_WINDOW_TYPE";
382
383 atom_names[ I_NET_WM_WINDOW_TYPE_DESKTOP ] = "_NET_WM_WINDOW_TYPE_DESKTOP";
384 atom_names[ I_NET_WM_WINDOW_TYPE_DOCK ] = "_NET_WM_WINDOW_TYPE_DOCK";
385 atom_names[ I_NET_WM_WINDOW_TYPE_TOOLBAR ] = "_NET_WM_WINDOW_TYPE_TOOLBAR";
386 atom_names[ I_NET_WM_WINDOW_TYPE_MENU ] = "_NET_WM_WINDOW_TYPE_MENU";
387 atom_names[ I_NET_WM_WINDOW_TYPE_UTILITY ] = "_NET_WM_WINDOW_TYPE_UTILITY";
388 atom_names[ I_NET_WM_WINDOW_TYPE_SPLASH ] = "_NET_WM_WINDOW_TYPE_SPLASH";
389 atom_names[ I_NET_WM_WINDOW_TYPE_DIALOG ] = "_NET_WM_WINDOW_TYPE_DIALOG";
390 atom_names[ I_NET_WM_WINDOW_TYPE_NORMAL ] = "_NET_WM_WINDOW_TYPE_NORMAL";
391 atom_names[ I_NET_WM_DESKTOP ] = "_NET_WM_DESKTOP";
392 atom_names[ I_NET_WM_PID ] = "_NET_WM_PID";
393 atom_names[ I_NET_WM_NAME ] = "_NET_WM_NAME";
394 atom_names[ I_NET_WM_VISIBLE_NAME ] = "_NET_WM_VISIBLE_NAME";
395 atom_names[ I_NET_WM_STRUT ] = "_NET_WM_STRUT";
396 atom_names[ I_NET_WM_STRUT_PARTIAL ] = "_NET_WM_STRUT_PARTIAL";
397 atom_names[ I_NET_WM_ICON ] = "_NET_WM_ICON";
398 atom_names[ I_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR ] = "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR";
399
400 atom_names[ I_NET_SYSTEM_TRAY_OPCODE ] = "_NET_SYSTEM_TRAY_OPCODE";
401 atom_names[ I_NET_SYSTEM_TRAY_MESSAGE_DATA ] = "_NET_SYSTEM_TRAY_MESSAGE_DATA";
402 atom_names[ I_NET_SYSTEM_TRAY_ORIENTATION ] = "_NET_SYSTEM_TRAY_ORIENTATION";
403 atom_names[ I_MANAGER ] = "MANAGER";
404
405 atom_names[ I_LXPANEL_CMD ] = "_LXPANEL_CMD";
406
407 Atom atoms[ N_ATOMS ];
408
409 ENTER;
410
411 if( ! XInternAtoms( GDK_DISPLAY(), (char**)atom_names,
412 N_ATOMS, False, atoms ) )
413 {
414 g_warning( "Error: unable to return Atoms" );
415 return;
416 }
417
418 a_UTF8_STRING = atoms[ I_UTF8_STRING ];
419 a_XROOTPMAP_ID = atoms[ I_XROOTPMAP_ID ];
420 a_WM_STATE = atoms[ I_WM_STATE ];
421 a_WM_CLASS = atoms[ I_WM_CLASS ];
422 a_WM_DELETE_WINDOW = atoms[ I_WM_DELETE_WINDOW ];
423 a_WM_PROTOCOLS = atoms[ I_WM_PROTOCOLS ];
424
425 a_NET_WORKAREA = atoms[ I_NET_WORKAREA ];
426 a_NET_CLIENT_LIST = atoms[ I_NET_CLIENT_LIST ];
427 a_NET_CLIENT_LIST_STACKING = atoms[ I_NET_CLIENT_LIST_STACKING ];
428 a_NET_NUMBER_OF_DESKTOPS = atoms[ I_NET_NUMBER_OF_DESKTOPS ];
429 a_NET_CURRENT_DESKTOP = atoms[ I_NET_CURRENT_DESKTOP ];
430 a_NET_DESKTOP_VIEWPORT = atoms[ I_NET_DESKTOP_VIEWPORT ];
431 a_NET_DESKTOP_NAMES = atoms[ I_NET_DESKTOP_NAMES ];
432 a_NET_ACTIVE_WINDOW = atoms[ I_NET_ACTIVE_WINDOW ];
433 a_NET_SHOWING_DESKTOP = atoms[ I_NET_SHOWING_DESKTOP ];
434 a_NET_SUPPORTED = atoms[ I_NET_SUPPORTED ];
435 a_NET_WM_STATE = atoms[ I_NET_WM_STATE ];
436 a_NET_WM_STATE_SKIP_TASKBAR = atoms[ I_NET_WM_STATE_SKIP_TASKBAR ];
437 a_NET_WM_STATE_SKIP_PAGER = atoms[ I_NET_WM_STATE_SKIP_PAGER ];
438 a_NET_WM_STATE_STICKY = atoms[ I_NET_WM_STATE_STICKY ];
439 a_NET_WM_STATE_HIDDEN = atoms[ I_NET_WM_STATE_HIDDEN ];
440 a_NET_WM_STATE_SHADED = atoms[ I_NET_WM_STATE_SHADED ];
441 a_NET_WM_WINDOW_TYPE = atoms[ I_NET_WM_WINDOW_TYPE ];
442
443 a_NET_WM_WINDOW_TYPE_DESKTOP = atoms[ I_NET_WM_WINDOW_TYPE_DESKTOP ];
444 a_NET_WM_WINDOW_TYPE_DOCK = atoms[ I_NET_WM_WINDOW_TYPE_DOCK ];
445 a_NET_WM_WINDOW_TYPE_TOOLBAR = atoms[ I_NET_WM_WINDOW_TYPE_TOOLBAR ];
446 a_NET_WM_WINDOW_TYPE_MENU = atoms[ I_NET_WM_WINDOW_TYPE_MENU ];
447 a_NET_WM_WINDOW_TYPE_UTILITY = atoms[ I_NET_WM_WINDOW_TYPE_UTILITY ];
448 a_NET_WM_WINDOW_TYPE_SPLASH = atoms[ I_NET_WM_WINDOW_TYPE_SPLASH ];
449 a_NET_WM_WINDOW_TYPE_DIALOG = atoms[ I_NET_WM_WINDOW_TYPE_DIALOG ];
450 a_NET_WM_WINDOW_TYPE_NORMAL = atoms[ I_NET_WM_WINDOW_TYPE_NORMAL ];
451 a_NET_WM_DESKTOP = atoms[ I_NET_WM_DESKTOP ];
452 a_NET_WM_PID = atoms[ I_NET_WM_PID ];
453 a_NET_WM_NAME = atoms[ I_NET_WM_NAME ];
454 a_NET_WM_VISIBLE_NAME = atoms[ I_NET_WM_VISIBLE_NAME ];
455 a_NET_WM_STRUT = atoms[ I_NET_WM_STRUT ];
456 a_NET_WM_STRUT_PARTIAL = atoms[ I_NET_WM_STRUT_PARTIAL ];
457 a_NET_WM_ICON = atoms[ I_NET_WM_ICON ];
458 a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR = atoms[ I_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR ];
459
460 a_NET_SYSTEM_TRAY_OPCODE = atoms[ I_NET_SYSTEM_TRAY_OPCODE ];
461 a_NET_SYSTEM_TRAY_MESSAGE_DATA = atoms [ I_NET_SYSTEM_TRAY_MESSAGE_DATA ];
462 a_NET_SYSTEM_TRAY_ORIENTATION = atoms[ I_NET_SYSTEM_TRAY_ORIENTATION ];
463 a_MANAGER = atoms[ I_MANAGER ];
464
465 a_LXPANEL_CMD = atoms[ I_LXPANEL_CMD ];
466
467 RET();
468 }
469
470
471 void
472 Xclimsg(Window win, Atom type, long l0, long l1, long l2, long l3, long l4)
473 {
474 XClientMessageEvent xev;
475 xev.type = ClientMessage;
476 xev.window = win;
477 xev.message_type = type;
478 xev.format = 32;
479 xev.data.l[0] = l0;
480 xev.data.l[1] = l1;
481 xev.data.l[2] = l2;
482 xev.data.l[3] = l3;
483 xev.data.l[4] = l4;
484 XSendEvent(GDK_DISPLAY(), GDK_ROOT_WINDOW(), False,
485 (SubstructureNotifyMask | SubstructureRedirectMask),
486 (XEvent *) &xev);
487 }
488
489 void
490 Xclimsgwm(Window win, Atom type, Atom arg)
491 {
492 XClientMessageEvent xev;
493
494 xev.type = ClientMessage;
495 xev.window = win;
496 xev.message_type = type;
497 xev.format = 32;
498 xev.data.l[0] = arg;
499 xev.data.l[1] = GDK_CURRENT_TIME;
500 XSendEvent(GDK_DISPLAY(), win, False, 0L, (XEvent *) &xev);
501 }
502
503
504 void *
505 get_utf8_property(Window win, Atom atom)
506 {
507 Atom type;
508 int format;
509 gulong nitems;
510 gulong bytes_after;
511 gchar *val, *retval;
512 int result;
513 guchar *tmp = NULL;
514
515 type = None;
516 retval = NULL;
517 result = XGetWindowProperty (GDK_DISPLAY(), win, atom, 0, G_MAXLONG, False,
518 a_UTF8_STRING, &type, &format, &nitems,
519 &bytes_after, &tmp);
520 if (result != Success || type == None)
521 return NULL;
522 val = (gchar *) tmp;
523 if (val) {
524 if (type == a_UTF8_STRING && format == 8 && nitems != 0)
525 retval = g_strndup (val, nitems);
526 XFree (val);
527 }
528 return retval;
529
530 }
531
532 char **
533 get_utf8_property_list(Window win, Atom atom, int *count)
534 {
535 Atom type;
536 int format, i;
537 gulong nitems;
538 gulong bytes_after;
539 gchar *s, **retval = NULL;
540 int result;
541 guchar *tmp = NULL;
542
543 *count = 0;
544 result = XGetWindowProperty(GDK_DISPLAY(), win, atom, 0, G_MAXLONG, False,
545 a_UTF8_STRING, &type, &format, &nitems,
546 &bytes_after, &tmp);
547 if (result != Success || type != a_UTF8_STRING || tmp == NULL)
548 return NULL;
549
550 if (nitems) {
551 gchar *val = (gchar *) tmp;
552 DBG("res=%d(%d) nitems=%d val=%s\n", result, Success, nitems, val);
553 for (i = 0; i < nitems; i++) {
554 if (!val[i])
555 (*count)++;
556 }
557 retval = g_new0 (char*, *count + 2);
558 for (i = 0, s = val; i < *count; i++, s = s + strlen (s) + 1) {
559 retval[i] = g_strdup(s);
560 }
561 if (val[nitems-1]) {
562 result = nitems - (s - val);
563 DBG("val does not ends by 0, moving last %d bytes\n", result);
564 g_memmove(s - 1, s, result);
565 val[nitems-1] = 0;
566 DBG("s=%s\n", s -1);
567 retval[i] = g_strdup(s - 1);
568 (*count)++;
569 }
570 }
571 XFree (tmp);
572
573 return retval;
574
575 }
576
577 void *
578 get_xaproperty (Window win, Atom prop, Atom type, int *nitems)
579 {
580 Atom type_ret;
581 int format_ret;
582 unsigned long items_ret;
583 unsigned long after_ret;
584 unsigned char *prop_data;
585
586 ENTER;
587 prop_data = NULL;
588 if (XGetWindowProperty (GDK_DISPLAY(), win, prop, 0, 0x7fffffff, False,
589 type, &type_ret, &format_ret, &items_ret,
590 &after_ret, &prop_data) != Success)
591 {
592 if( G_UNLIKELY(prop_data) )
593 XFree( prop_data );
594 if( nitems )
595 *nitems = 0;
596 RET(NULL);
597 }
598 if (nitems)
599 *nitems = items_ret;
600 RET(prop_data);
601 }
602
603 static char*
604 text_property_to_utf8 (const XTextProperty *prop)
605 {
606 char **list;
607 int count;
608 char *retval;
609
610 ENTER;
611 list = NULL;
612 count = gdk_text_property_to_utf8_list (gdk_x11_xatom_to_atom (prop->encoding),
613 prop->format,
614 prop->value,
615 prop->nitems,
616 &list);
617
618 DBG("count=%d\n", count);
619 if (count == 0)
620 return NULL;
621
622 retval = list[0];
623 list[0] = g_strdup (""); /* something to free */
624
625 g_strfreev (list);
626
627 RET(retval);
628 }
629
630 char *
631 get_textproperty(Window win, Atom atom)
632 {
633 XTextProperty text_prop;
634 char *retval;
635
636 ENTER;
637 if (XGetTextProperty(GDK_DISPLAY(), win, &text_prop, atom)) {
638 DBG("format=%d enc=%d nitems=%d value=%s \n",
639 text_prop.format,
640 text_prop.encoding,
641 text_prop.nitems,
642 text_prop.value);
643 retval = text_property_to_utf8 (&text_prop);
644 if (text_prop.nitems > 0)
645 XFree (text_prop.value);
646 RET(retval);
647
648 }
649 RET(NULL);
650 }
651
652
653 int
654 get_net_number_of_desktops()
655 {
656 int desknum;
657 guint32 *data;
658
659 ENTER;
660 data = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_NUMBER_OF_DESKTOPS,
661 XA_CARDINAL, 0);
662 if (!data)
663 RET(0);
664
665 desknum = *data;
666 XFree (data);
667 RET(desknum);
668 }
669
670
671 int
672 get_net_current_desktop ()
673 {
674 int desk;
675 guint32 *data;
676
677 ENTER;
678 data = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, XA_CARDINAL, 0);
679 if (!data)
680 RET(0);
681
682 desk = *data;
683 XFree (data);
684 RET(desk);
685 }
686
687 int
688 get_net_wm_desktop(Window win)
689 {
690 int desk = 0;
691 guint32 *data;
692
693 ENTER;
694 data = get_xaproperty (win, a_NET_WM_DESKTOP, XA_CARDINAL, 0);
695 if (data) {
696 desk = *data;
697 XFree (data);
698 }
699 RET(desk);
700 }
701
702 GPid
703 get_net_wm_pid(Window win)
704 {
705 GPid pid = 0;
706 guint32 *data;
707
708 ENTER;
709 data = get_xaproperty (win, a_NET_WM_PID, XA_CARDINAL, 0);
710 if (data) {
711 pid = *data;
712 XFree (data);
713 }
714 RET(pid);
715 }
716
717 void
718 get_net_wm_state(Window win, NetWMState *nws)
719 {
720 Atom *state;
721 int num3;
722
723
724 ENTER;
725 memset(nws, 0, sizeof(*nws));
726 if (!(state = get_xaproperty(win, a_NET_WM_STATE, XA_ATOM, &num3)))
727 RET();
728
729 DBG( "%x: netwm state = { ", (unsigned int)win);
730 while (--num3 >= 0) {
731 if (state[num3] == a_NET_WM_STATE_SKIP_PAGER) {
732 DBG("NET_WM_STATE_SKIP_PAGER ");
733 nws->skip_pager = 1;
734 } else if (state[num3] == a_NET_WM_STATE_SKIP_TASKBAR) {
735 DBG( "NET_WM_STATE_SKIP_TASKBAR ");
736 nws->skip_taskbar = 1;
737 } else if (state[num3] == a_NET_WM_STATE_STICKY) {
738 DBG( "NET_WM_STATE_STICKY ");
739 nws->sticky = 1;
740 } else if (state[num3] == a_NET_WM_STATE_HIDDEN) {
741 DBG( "NET_WM_STATE_HIDDEN ");
742 nws->hidden = 1;
743 } else if (state[num3] == a_NET_WM_STATE_SHADED) {
744 DBG( "NET_WM_STATE_SHADED ");
745 nws->shaded = 1;
746 } else {
747 DBG( "... ");
748 }
749 }
750 XFree(state);
751 DBG( "}\n");
752 RET();
753 }
754
755 void
756 get_net_wm_window_type(Window win, NetWMWindowType *nwwt)
757 {
758 Atom *state;
759 int num3;
760
761
762 ENTER;
763 memset(nwwt, 0, sizeof(*nwwt));
764 if (!(state = get_xaproperty(win, a_NET_WM_WINDOW_TYPE, XA_ATOM, &num3)))
765 RET();
766
767 DBG( "%x: netwm state = { ", (unsigned int)win);
768 while (--num3 >= 0) {
769 if (state[num3] == a_NET_WM_WINDOW_TYPE_DESKTOP) {
770 DBG("NET_WM_WINDOW_TYPE_DESKTOP ");
771 nwwt->desktop = 1;
772 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_DOCK) {
773 DBG( "NET_WM_WINDOW_TYPE_DOCK ");
774 nwwt->dock = 1;
775 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_TOOLBAR) {
776 DBG( "NET_WM_WINDOW_TYPE_TOOLBAR ");
777 nwwt->toolbar = 1;
778 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_MENU) {
779 DBG( "NET_WM_WINDOW_TYPE_MENU ");
780 nwwt->menu = 1;
781 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_UTILITY) {
782 DBG( "NET_WM_WINDOW_TYPE_UTILITY ");
783 nwwt->utility = 1;
784 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_SPLASH) {
785 DBG( "NET_WM_WINDOW_TYPE_SPLASH ");
786 nwwt->splash = 1;
787 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_DIALOG) {
788 DBG( "NET_WM_WINDOW_TYPE_DIALOG ");
789 nwwt->dialog = 1;
790 } else if (state[num3] == a_NET_WM_WINDOW_TYPE_NORMAL) {
791 DBG( "NET_WM_WINDOW_TYPE_NORMAL ");
792 nwwt->normal = 1;
793 } else {
794 DBG( "... ");
795 }
796 }
797 XFree(state);
798 DBG( "}\n");
799 RET();
800 }
801
802 int
803 get_wm_state (Window win)
804 {
805 unsigned long *data;
806 int ret = 0;
807
808 ENTER;
809 data = get_xaproperty (win, a_WM_STATE, a_WM_STATE, 0);
810 if (data) {
811 ret = data[0];
812 XFree (data);
813 }
814 RET(ret);
815 }
816
817 static void
818 calculate_width(int scrw, int wtype, int allign, int margin,
819 int *panw, int *x)
820 {
821 ENTER;
822 DBG("scrw=%d\n", scrw);
823 DBG("IN panw=%d, margin=%d\n", *panw, margin);
824 //scrw -= 2;
825 if (wtype == WIDTH_PERCENT) {
826 /* sanity check */
827 if (*panw > 100)
828 *panw = 100;
829 else if (*panw < 0)
830 *panw = 1;
831 *panw = ((gfloat) scrw * (gfloat) *panw) / 100.0;
832 }
833 if (allign != ALLIGN_CENTER) {
834 if (margin > scrw) {
835 ERR( "margin is bigger then edge size %d > %d. Ignoring margin\n",
836 margin, scrw);
837 margin = 0;
838 }
839 *panw = MIN(scrw - margin, *panw);
840 }
841 DBG("OUT panw=%d\n", *panw);
842 if (allign == ALLIGN_LEFT)
843 *x += margin;
844 else if (allign == ALLIGN_RIGHT) {
845 *x += scrw - *panw - margin;
846 if (*x < 0)
847 *x = 0;
848 } else if (allign == ALLIGN_CENTER)
849 *x += (scrw - *panw) / 2;
850 RET();
851 }
852
853
854 void
855 calculate_position(Panel *np)
856 {
857 GdkScreen *screen;
858 GdkRectangle marea;
859
860 ENTER;
861 /* FIXME: Why this doesn't work? */
862 /* if you are still going to use this, be sure to update it to take into
863 account multiple monitors */
864 if (0) {
865 // if (np->curdesk < np->wa_len/4) {
866 marea.x = np->workarea[np->curdesk*4 + 0];
867 marea.y = np->workarea[np->curdesk*4 + 1];
868 marea.width = np->workarea[np->curdesk*4 + 2];
869 marea.height = np->workarea[np->curdesk*4 + 3];
870 } else {
871 screen = gtk_widget_get_screen(np->topgwin);
872 g_assert(np->monitor >= 0 && np->monitor < gdk_screen_get_n_monitors(screen));
873 gdk_screen_get_monitor_geometry(screen,np->monitor,&marea);
874 }
875
876 if (np->edge == EDGE_TOP || np->edge == EDGE_BOTTOM) {
877 np->aw = np->width;
878 np->ax = marea.x;
879 calculate_width(marea.width, np->widthtype, np->allign, np->margin,
880 &np->aw, &np->ax);
881 np->ah = ((( ! np->autohide) || (np->visible)) ? np->height : np->height_when_hidden);
882 np->ay = marea.y + ((np->edge == EDGE_TOP) ? 0 : (marea.height - np->ah));
883
884 } else {
885 np->ah = np->width;
886 np->ay = marea.y;
887 calculate_width(marea.height, np->widthtype, np->allign, np->margin,
888 &np->ah, &np->ay);
889 np->aw = ((( ! np->autohide) || (np->visible)) ? np->height : np->height_when_hidden);
890 np->ax = marea.x + ((np->edge == EDGE_LEFT) ? 0 : (marea.width - np->aw));
891 }
892 //g_debug("%s - x=%d y=%d w=%d h=%d\n", __FUNCTION__, np->ax, np->ay, np->aw, np->ah);
893 RET();
894 }
895
896 gchar *
897 expand_tilda(gchar *file)
898 {
899 ENTER;
900 RET((file[0] == '~') ?
901 g_strdup_printf("%s%s", getenv("HOME"), file+1)
902 : g_strdup(file));
903
904 }
905
906 /*
907 * SuxPanel version 0.1
908 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
909 *
910 * This program may be distributed under the terms of GNU General
911 * Public License version 2. You should have received a copy of the
912 * license with this program; if not, please consult http://www.fsf.org/.
913 *
914 * This program comes with no warranty. Use at your own risk.
915 *
916 */
917
918 /* DestroyNotify handler for image data in _gtk_image_new_from_file_scaled. */
919 static void img_data_free(ImgData * data)
920 {
921 g_free(data->fname);
922 if (data->theme_changed_handler != 0)
923 g_signal_handler_disconnect(gtk_icon_theme_get_default(), data->theme_changed_handler);
924 if (data->pixbuf != NULL)
925 g_object_unref(data->pixbuf);
926 if (data->hilight != NULL)
927 g_object_unref(data->hilight);
928 g_free(data);
929 }
930
931 /* Handler for "changed" signal in _gtk_image_new_from_file_scaled. */
932 static void on_theme_changed(GtkIconTheme * theme, GtkWidget * img)
933 {
934 ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(img), img_data_id);
935 _gtk_image_set_from_file_scaled(img, data->fname, data->dw, data->dh, data->keep_ratio);
936 }
937
938 void fb_button_set_from_file(GtkWidget * btn, const char * img_file, gint width, gint height, gboolean keep_ratio)
939 {
940 /* Locate the image within the button. */
941 GtkWidget * child = gtk_bin_get_child(GTK_BIN(btn));
942 GtkWidget * img = NULL;
943 if (GTK_IS_IMAGE(child))
944 img = child;
945 else if (GTK_IS_BOX(child))
946 {
947 GList * children = gtk_container_get_children(GTK_CONTAINER(child));
948 img = GTK_WIDGET(GTK_IMAGE(children->data));
949 g_list_free(children);
950 }
951
952 if (img != NULL)
953 {
954 ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(img), img_data_id);
955 g_free(data->fname);
956 data->fname = g_strdup(img_file);
957 data->dw = width;
958 data->dh = height;
959 data->keep_ratio = keep_ratio;
960 _gtk_image_set_from_file_scaled(img, data->fname, data->dw, data->dh, data->keep_ratio);
961 }
962 }
963
964 static void _gtk_image_set_from_file_scaled(GtkWidget * img, const gchar * file, gint width, gint height, gboolean keep_ratio)
965 {
966 ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(img), img_data_id);
967 data->dw = width;
968 data->dh = height;
969 data->keep_ratio = keep_ratio;
970
971 if (data->pixbuf != NULL)
972 {
973 g_object_unref(data->pixbuf);
974 data->pixbuf = NULL;
975 }
976
977 /* if there is a cached hilighted version of this pixbuf, free it */
978 if (data->hilight != NULL)
979 {
980 g_object_unref(data->hilight);
981 data->hilight = NULL;
982 }
983
984 /* if they are the same string, eliminate unnecessary copy. */
985 gboolean themed = FALSE;
986 if (file != NULL)
987 {
988 if (data->fname != file)
989 {
990 g_free(data->fname);
991 data->fname = g_strdup(file);
992 }
993
994 if (g_file_test(file, G_FILE_TEST_EXISTS))
995 {
996 GdkPixbuf * pb_scaled = gdk_pixbuf_new_from_file_at_scale(file, width, height, keep_ratio, NULL);
997 if (pb_scaled != NULL)
998 data->pixbuf = pb_scaled;
999 }
1000 else
1001 {
1002 data->pixbuf = lxpanel_load_icon(file, width, height, keep_ratio);
1003 themed = TRUE;
1004 }
1005 }
1006
1007 if (data->pixbuf != NULL)
1008 {
1009 /* Set the pixbuf into the image widget. */
1010 gtk_image_set_from_pixbuf((GtkImage *)img, data->pixbuf);
1011 if (themed)
1012 {
1013 /* This image is loaded from icon theme. Update the image if the icon theme is changed. */
1014 if (data->theme_changed_handler == 0)
1015 data->theme_changed_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(on_theme_changed), img);
1016 }
1017 else
1018 {
1019 /* This is not loaded from icon theme. Disconnect the signal handler. */
1020 if (data->theme_changed_handler != 0)
1021 {
1022 g_signal_handler_disconnect(gtk_icon_theme_get_default(), data->theme_changed_handler);
1023 data->theme_changed_handler = 0;
1024 }
1025 }
1026 }
1027 else
1028 {
1029 /* No pixbuf available. Set the "missing image" icon. */
1030 gtk_image_set_from_stock(GTK_IMAGE(img), GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_BUTTON);
1031 }
1032 return;
1033 }
1034
1035 GtkWidget * _gtk_image_new_from_file_scaled(const gchar * file, gint width, gint height, gboolean keep_ratio)
1036 {
1037 GtkWidget * img = gtk_image_new();
1038 ImgData * data = g_new0(ImgData, 1);
1039 if (img_data_id == 0)
1040 img_data_id = g_quark_from_static_string("ImgData");
1041 g_object_set_qdata_full(G_OBJECT(img), img_data_id, data, (GDestroyNotify) img_data_free);
1042 _gtk_image_set_from_file_scaled(img, file, width, height, keep_ratio);
1043 return img;
1044 }
1045
1046
1047 void
1048 get_button_spacing(GtkRequisition *req, GtkContainer *parent, gchar *name)
1049 {
1050 GtkWidget *b;
1051 //gint focus_width;
1052 //gint focus_pad;
1053
1054 ENTER;
1055 b = gtk_button_new();
1056 gtk_widget_set_name(GTK_WIDGET(b), name);
1057 GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_FOCUS);
1058 GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_DEFAULT);
1059 gtk_container_set_border_width (GTK_CONTAINER (b), 0);
1060
1061 if (parent)
1062 gtk_container_add(parent, b);
1063
1064 gtk_widget_show(b);
1065 gtk_widget_size_request(b, req);
1066
1067 gtk_widget_destroy(b);
1068 RET();
1069 }
1070
1071
1072 guint32 gcolor2rgb24(GdkColor *color)
1073 {
1074 guint32 i;
1075
1076 ENTER;
1077
1078 i = (color->red * 0xFF / 0xFFFF) & 0xFF;
1079 i <<= 8;
1080 i |= (color->green * 0xFF / 0xFFFF) & 0xFF;
1081 i <<= 8;
1082 i |= (color->blue * 0xFF / 0xFFFF) & 0xFF;
1083 DBG("i=%x\n", i);
1084 RET(i);
1085 }
1086
1087 /* Handler for "enter-notify-event" signal on image that has highlighting requested. */
1088 static gboolean fb_button_enter(GtkImage * widget, GdkEventCrossing * event)
1089 {
1090 if (gtk_image_get_storage_type(widget) == GTK_IMAGE_PIXBUF)
1091 {
1092 ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(widget), img_data_id);
1093 if (data != NULL)
1094 {
1095 if (data->hilight == NULL)
1096 {
1097 GdkPixbuf * dark = data->pixbuf;
1098 int height = gdk_pixbuf_get_height(dark);
1099 int rowstride = gdk_pixbuf_get_rowstride(dark);
1100 gulong hicolor = data->hicolor;
1101
1102 GdkPixbuf * light = gdk_pixbuf_add_alpha(dark, FALSE, 0, 0, 0);
1103 if (light != NULL)
1104 {
1105 guchar extra[3];
1106 int i;
1107 for (i = 2; i >= 0; i--, hicolor >>= 8)
1108 extra[i] = hicolor & 0xFF;
1109
1110 guchar * src = gdk_pixbuf_get_pixels(light);
1111 guchar * up;
1112 for (up = src + height * rowstride; src < up; src += 4)
1113 {
1114 if (src[3] != 0)
1115 {
1116 for (i = 0; i < 3; i++)
1117 {
1118 int value = src[i] + extra[i];
1119 if (value > 255) value = 255;
1120 src[i] = value;
1121 }
1122 }
1123 }
1124 data->hilight = light;
1125 }
1126 }
1127
1128 if (data->hilight != NULL)
1129 gtk_image_set_from_pixbuf(widget, data->hilight);
1130 }
1131 }
1132 return TRUE;
1133 }
1134
1135 /* Handler for "leave-notify-event" signal on image that has highlighting requested. */
1136 static gboolean fb_button_leave(GtkImage * widget, GdkEventCrossing * event, gpointer user_data)
1137 {
1138 if (gtk_image_get_storage_type(widget) == GTK_IMAGE_PIXBUF)
1139 {
1140 ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(widget), img_data_id);
1141 if ((data != NULL) && (data->pixbuf != NULL))
1142 gtk_image_set_from_pixbuf(widget, data->pixbuf);
1143 }
1144 return TRUE;
1145 }
1146
1147
1148 GtkWidget * fb_button_new_from_file(
1149 gchar * image_file, int width, int height, gulong highlight_color, gboolean keep_ratio)
1150 {
1151 return fb_button_new_from_file_with_label(image_file, width, height, highlight_color, keep_ratio, NULL, NULL);
1152 }
1153
1154 GtkWidget * fb_button_new_from_file_with_label(
1155 gchar * image_file, int width, int height, gulong highlight_color, gboolean keep_ratio, Panel * panel, gchar * label)
1156 {
1157 GtkWidget * event_box = gtk_event_box_new();
1158 gtk_container_set_border_width(GTK_CONTAINER(event_box), 0);
1159 GTK_WIDGET_UNSET_FLAGS(event_box, GTK_CAN_FOCUS);
1160
1161 GtkWidget * image = _gtk_image_new_from_file_scaled(image_file, width, height, keep_ratio);
1162 gtk_misc_set_padding(GTK_MISC(image), 0, 0);
1163 gtk_misc_set_alignment(GTK_MISC(image), 0, 0);
1164 if (highlight_color != 0)
1165 {
1166 ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(image), img_data_id);
1167 data->hicolor = highlight_color;
1168
1169 gtk_widget_add_events(event_box, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1170 g_signal_connect_swapped(G_OBJECT(event_box), "enter-notify-event", G_CALLBACK(fb_button_enter), image);
1171 g_signal_connect_swapped(G_OBJECT(event_box), "leave-notify-event", G_CALLBACK(fb_button_leave), image);
1172 }
1173
1174 if (label == NULL)
1175 gtk_container_add(GTK_CONTAINER(event_box), image);
1176 else
1177 {
1178 GtkWidget * inner = gtk_hbox_new(FALSE, 0);
1179 gtk_container_set_border_width(GTK_CONTAINER(inner), 0);
1180 GTK_WIDGET_UNSET_FLAGS (inner, GTK_CAN_FOCUS);
1181 gtk_container_add(GTK_CONTAINER(event_box), inner);
1182
1183 gtk_box_pack_start(GTK_BOX(inner), image, FALSE, FALSE, 0);
1184
1185 GtkWidget * lbl = gtk_label_new("");
1186 panel_draw_label_text(panel, lbl, label, FALSE, 1, TRUE);
1187 gtk_misc_set_padding(GTK_MISC(lbl), 2, 0);
1188 gtk_box_pack_end(GTK_BOX(inner), lbl, FALSE, FALSE, 0);
1189 }
1190
1191 gtk_widget_show_all(event_box);
1192 return event_box;
1193 }
1194
1195 char* translate_exec_to_cmd( const char* exec, const char* icon,
1196 const char* title, const char* fpath )
1197 {
1198 GString* cmd = g_string_sized_new( 256 );
1199 if (!exec)
1200 return NULL;
1201 for( ; *exec; ++exec )
1202 {
1203 if( G_UNLIKELY(*exec == '%') )
1204 {
1205 ++exec;
1206 if( !*exec )
1207 break;
1208 switch( *exec )
1209 {
1210 case 'c':
1211 if( title )
1212 {
1213 g_string_append( cmd, title );
1214 }
1215 break;
1216 case 'i':
1217 if( icon )
1218 {
1219 g_string_append( cmd, "--icon " );
1220 g_string_append( cmd, icon );
1221 }
1222 break;
1223 case 'k':
1224 if( fpath )
1225 {
1226 char* uri = g_filename_to_uri( fpath, NULL, NULL );
1227 g_string_append( cmd, uri );
1228 g_free( uri );
1229 }
1230 break;
1231 case '%':
1232 g_string_append_c( cmd, '%' );
1233 break;
1234 }
1235 }
1236 else
1237 g_string_append_c( cmd, *exec );
1238 }
1239 return g_string_free( cmd, FALSE );
1240 }
1241
1242 /*
1243 This function is used to re-create a new box with different
1244 orientation from the old one, add all children of the old one to
1245 the new one, and then destroy the old box.
1246 It's mainly used when we need to change the orientation of the panel or
1247 any plugin with a layout box. Since GtkHBox cannot be changed to GtkVBox,
1248 recreating a new box to replace the old one is required.
1249 */
1250 GtkWidget* recreate_box( GtkBox* oldbox, GtkOrientation orientation )
1251 {
1252 GtkBox* newbox;
1253 GList *child, *children;
1254 GtkWidget* (*my_box_new) (gboolean homogeneous, gint spacing);
1255
1256 if( GTK_IS_HBOX(oldbox) ) {
1257 if( orientation == GTK_ORIENTATION_HORIZONTAL )
1258 return GTK_WIDGET(oldbox);
1259 }
1260 else {
1261 if( orientation == GTK_ORIENTATION_VERTICAL )
1262 return GTK_WIDGET(oldbox);
1263 }
1264 my_box_new = (orientation == GTK_ORIENTATION_HORIZONTAL ? gtk_hbox_new : gtk_vbox_new);
1265
1266 newbox = GTK_BOX(my_box_new( gtk_box_get_homogeneous(oldbox),
1267 gtk_box_get_spacing(oldbox) ));
1268 gtk_container_set_border_width (GTK_CONTAINER (newbox),
1269 gtk_container_get_border_width(GTK_CONTAINER(oldbox)) );
1270 children = gtk_container_get_children( GTK_CONTAINER (oldbox) );
1271 for( child = children; child; child = child->next ) {
1272 gboolean expand, fill;
1273 guint padding;
1274 GtkWidget* w = GTK_WIDGET(child->data);
1275 gtk_box_query_child_packing( oldbox, w,
1276 &expand, &fill, &padding, NULL );
1277 /* g_debug( "repack %s, expand=%d, fill=%d", gtk_widget_get_name(w), expand, fill ); */
1278 g_object_ref( w );
1279 gtk_container_remove( GTK_CONTAINER (oldbox), w );
1280 gtk_box_pack_start( newbox, w, expand, fill, padding );
1281 g_object_unref( w );
1282 }
1283 g_list_free( children );
1284 gtk_widget_show_all( GTK_WIDGET(newbox) );
1285 gtk_widget_destroy( GTK_WIDGET(oldbox) );
1286 return GTK_WIDGET(newbox);
1287 }
1288
1289 void show_error( GtkWindow* parent_win, const char* msg )
1290 {
1291 GtkWidget* dlg = gtk_message_dialog_new( parent_win,
1292 GTK_DIALOG_MODAL,
1293 GTK_MESSAGE_ERROR,
1294 GTK_BUTTONS_OK, "%s", msg );
1295 gtk_dialog_run( (GtkDialog*)dlg );
1296 gtk_widget_destroy( dlg );
1297 }
1298
1299 /* Try to load an icon from a named file via the freedesktop.org data directories path.
1300 * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html */
1301 static GdkPixbuf * load_icon_file(const char * file_name, int height, int width)
1302 {
1303 GdkPixbuf * icon = NULL;
1304 const gchar ** dirs = (const gchar **) g_get_system_data_dirs();
1305 const gchar ** dir;
1306 for (dir = dirs; ((*dir != NULL) && (icon == NULL)); dir++)
1307 {
1308 char * file_path = g_build_filename(*dir, "pixmaps", file_name, NULL);
1309 icon = gdk_pixbuf_new_from_file_at_scale(file_path, height, width, TRUE, NULL);
1310 g_free(file_path);
1311 }
1312 return icon;
1313 }
1314
1315 /* Try to load an icon from the current theme. */
1316 static GdkPixbuf * load_icon_from_theme(GtkIconTheme * theme, const char * icon_name, int width, int height)
1317 {
1318 GdkPixbuf * icon = NULL;
1319
1320 /* Look up the icon in the current theme. */
1321 GtkIconInfo * icon_info = gtk_icon_theme_lookup_icon(theme, icon_name, height, GTK_ICON_LOOKUP_USE_BUILTIN);
1322 if (icon_info != NULL)
1323 {
1324 /* If that succeeded, get the filename of the icon.
1325 * If that succeeds, load the icon from the specified file.
1326 * Otherwise, try to get the builtin icon. */
1327 const char * file = gtk_icon_info_get_filename(icon_info);
1328 if (file != NULL)
1329 icon = gdk_pixbuf_new_from_file(file, NULL);
1330 else
1331 {
1332 icon = gtk_icon_info_get_builtin_pixbuf(icon_info);
1333 g_object_ref(icon);
1334 }
1335 gtk_icon_info_free(icon_info);
1336
1337 /* If the icon is not sized properly, take a trip through the scaler.
1338 * The lookup above takes the desired size, so we get the closest result possible. */
1339 if (icon != NULL)
1340 {
1341 if ((height != gdk_pixbuf_get_height(icon)) || (width != gdk_pixbuf_get_width(icon)))
1342 {
1343 /* Handle case of unspecified width; gdk_pixbuf_scale_simple does not. */
1344 if (width < 0)
1345 {
1346 int pixbuf_width = gdk_pixbuf_get_width(icon);
1347 int pixbuf_height = gdk_pixbuf_get_height(icon);
1348 width = height * pixbuf_width / pixbuf_height;
1349 }
1350 GdkPixbuf * scaled = gdk_pixbuf_scale_simple(icon, width, height, GDK_INTERP_BILINEAR);
1351 g_object_unref(icon);
1352 icon = scaled;
1353 }
1354 }
1355 }
1356 return icon;
1357 }
1358
1359 GdkPixbuf * lxpanel_load_icon(const char * name, int width, int height, gboolean use_fallback)
1360 {
1361 GdkPixbuf * icon = NULL;
1362
1363 if (name != NULL)
1364 {
1365 if (g_path_is_absolute(name))
1366 {
1367 /* Absolute path.
1368 * To prevent the ugly icon, try to search icon from icon theme first,
1369 * instead of loading icon from pixmaps dir */
1370 GtkIconTheme * theme = gtk_icon_theme_get_default();
1371 char *sub_suffix = strrchr(name, '.');
1372 char *sub_deli = strrchr(name, '/');
1373 int sub_len = sub_suffix - sub_deli - 1;
1374 if (sub_len > 0) {
1375 char *icon_name = g_strndup(&sub_deli[1], sub_len);
1376 //LOG(LOG_ALL, "lxpanel: icon_name:%s\n", icon_name);
1377 icon = load_icon_from_theme(theme, icon_name, width, height);
1378 g_free(icon_name);
1379 }
1380
1381 if (icon == NULL) {
1382 /* Still not found... */
1383 //LOG(LOG_ALL, "lxpanel: icon from theme not found\n");
1384 icon = gdk_pixbuf_new_from_file_at_scale(name, width, height, TRUE, NULL);
1385 }
1386 }
1387 else
1388 {
1389 /* Relative path. */
1390 GtkIconTheme * theme = gtk_icon_theme_get_default();
1391 char * suffix = strrchr(name, '.');
1392 if ((suffix != NULL)
1393 && ((g_ascii_strcasecmp(&suffix[1], "png") == 0)
1394 || (g_ascii_strcasecmp(&suffix[1], "svg") == 0)
1395 || (g_ascii_strcasecmp(&suffix[1], "xpm") == 0)))
1396 {
1397 /* The file extension indicates it could be in the system pixmap directories.
1398 * But in order to keep the icons in line with the icon theme, it's better to check
1399 * the icon in the current icon theme. Remove the suffix first. */
1400 char * icon_name = g_strndup(name, suffix - name);
1401 //LOG(LOG_ALL, "lxpanel: icon_name.suffix:%s.%s\n", icon_name, suffix);
1402 icon = load_icon_from_theme(theme, icon_name, width, height);
1403 g_free(icon_name);
1404
1405 /* Check the icon can be found in icon theme */
1406 if (icon == NULL)
1407 {
1408 /* Not found in icon theme, try to load from file directly */
1409 //LOG(LOG_ALL, "lxpanel: icon from theme not found\n");
1410 icon = load_icon_file(name, width, height);
1411 }
1412 }
1413 else
1414 {
1415 /* No file extension. It could be an icon name in the icon theme. */
1416 //LOG(LOG_ALL, "lxpanel: icon has name:%s\n", name);
1417 icon = load_icon_from_theme(theme, name, width, height);
1418 }
1419 }
1420 }
1421
1422 /* Fall back to generic icons. */
1423 if ((icon == NULL) && (use_fallback))
1424 {
1425 LOG(LOG_ALL, "lxpanel: using fallback icon for '%s'\n", name);
1426 GtkIconTheme * theme = gtk_icon_theme_get_default();
1427 icon = load_icon_from_theme(theme, "application-x-executable", width, height);
1428 if (icon == NULL)
1429 icon = load_icon_from_theme(theme, "gnome-mime-application-x-executable", width, height);
1430 }
1431 return icon;
1432 }
1433
1434 /*
1435 * Taken from pcmanfm:
1436 * Parse Exec command line of app desktop file, and translate
1437 * it into a real command which can be passed to g_spawn_command_line_async().
1438 * file_list is a null-terminated file list containing full
1439 * paths of the files passed to app.
1440 * returned char* should be freed when no longer needed.
1441 */
1442 static char* translate_app_exec_to_command_line( const char* pexec,
1443 GList* file_list )
1444 {
1445 char* file;
1446 GList* l;
1447 gchar *tmp;
1448 GString* cmd = g_string_new("");
1449 gboolean add_files = FALSE;
1450
1451 for( ; *pexec; ++pexec )
1452 {
1453 if( *pexec == '%' )
1454 {
1455 ++pexec;
1456 switch( *pexec )
1457 {
1458 case 'U':
1459 for( l = file_list; l; l = l->next )
1460 {
1461 tmp = g_filename_to_uri( (char*)l->data, NULL, NULL );
1462 file = g_shell_quote( tmp );
1463 g_free( tmp );
1464 g_string_append( cmd, file );
1465 if (l->next)
1466 g_string_append_c( cmd, ' ' );
1467 g_free( file );
1468 }
1469 add_files = TRUE;
1470 break;
1471 case 'u':
1472 if( file_list && file_list->data )
1473 {
1474 file = (char*)file_list->data;
1475 tmp = g_filename_to_uri( file, NULL, NULL );
1476 file = g_shell_quote( tmp );
1477 g_free( tmp );
1478 g_string_append( cmd, file );
1479 g_free( file );
1480 add_files = TRUE;
1481 }
1482 break;
1483 case 'F':
1484 case 'N':
1485 for( l = file_list; l; l = l->next )
1486 {
1487 file = (char*)l->data;
1488 tmp = g_shell_quote( file );
1489 g_string_append( cmd, tmp );
1490 if (l->next)
1491 g_string_append_c( cmd, ' ' );
1492 g_free( tmp );
1493 }
1494 add_files = TRUE;
1495 break;
1496 case 'f':
1497 case 'n':
1498 if( file_list && file_list->data )
1499 {
1500 file = (char*)file_list->data;
1501 tmp = g_shell_quote( file );
1502 g_string_append( cmd, tmp );
1503 g_free( tmp );
1504 add_files = TRUE;
1505 }
1506 break;
1507 case 'D':
1508 for( l = file_list; l; l = l->next )
1509 {
1510 tmp = g_path_get_dirname( (char*)l->data );
1511 file = g_shell_quote( tmp );
1512 g_free( tmp );
1513 g_string_append( cmd, file );
1514 if (l->next)
1515 g_string_append_c( cmd, ' ' );
1516 g_free( file );
1517 }
1518 add_files = TRUE;
1519 break;
1520 case 'd':
1521 if( file_list && file_list->data )
1522 {
1523 tmp = g_path_get_dirname( (char*)file_list->data );
1524 file = g_shell_quote( tmp );
1525 g_free( tmp );
1526 g_string_append( cmd, file );
1527 g_free( tmp );
1528 add_files = TRUE;
1529 }
1530 break;
1531 case 'c':
1532 #if 0
1533 g_string_append( cmd, vfs_app_desktop_get_disp_name( app ) );
1534 #endif
1535 break;
1536 case 'i':
1537 /* Add icon name */
1538 #if 0
1539 if( vfs_app_desktop_get_icon_name( app ) )
1540 {
1541 g_string_append( cmd, "--icon " );
1542 g_string_append( cmd, vfs_app_desktop_get_icon_name( app ) );
1543 }
1544 #endif
1545 break;
1546 case 'k':
1547 /* Location of the desktop file */
1548 break;
1549 case 'v':
1550 /* Device name */
1551 break;
1552 case '%':
1553 g_string_append_c ( cmd, '%' );
1554 break;
1555 case '\0':
1556 goto _finish;
1557 break;
1558 }
1559 }
1560 else /* not % escaped part */
1561 {
1562 g_string_append_c ( cmd, *pexec );
1563 }
1564 }
1565 _finish:
1566 if( ! add_files )
1567 {
1568 for( l = file_list; l; l = l->next )
1569 {
1570 g_string_append_c( cmd, ' ' );
1571 file = (char*)l->data;
1572 tmp = g_shell_quote( file );
1573 g_string_append( cmd, tmp );
1574 g_free( tmp );
1575 }
1576 }
1577
1578 return g_string_free( cmd, FALSE );
1579 }
1580
1581 gboolean spawn_command_async(GtkWindow *parent_window, gchar const* workdir,
1582 gchar const* cmd)
1583 {
1584 GError* err = NULL;
1585 gchar** argv = NULL;
1586
1587 LOG(LOG_INFO, "lxpanel: spawning \"%s\"...\n", cmd);
1588
1589 g_shell_parse_argv(cmd, NULL, &argv, &err);
1590 if (!err)
1591 g_spawn_async(workdir, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &err);
1592
1593 if (err)
1594 {
1595 ERR("%s\n", err->message);
1596 show_error(parent_window, err->message);
1597 g_error_free(err);
1598 }
1599
1600 g_strfreev(argv);
1601
1602 return !err;
1603 }
1604
1605 gboolean lxpanel_launch_app(const char* exec, GList* files, gboolean in_terminal, char const* in_workdir)
1606 {
1607 GError *error = NULL;
1608 char* cmd;
1609 if( ! exec )
1610 return FALSE;
1611 cmd = translate_app_exec_to_command_line(exec, files);
1612 if( in_terminal )
1613 {
1614 char * escaped_cmd = g_shell_quote(cmd);
1615 char* term_cmd;
1616 const char* term = lxpanel_get_terminal();
1617 if( strstr(term, "%s") )
1618 term_cmd = g_strdup_printf(term, escaped_cmd);
1619 else
1620 term_cmd = g_strconcat( term, " -e ", escaped_cmd, NULL );
1621 g_free(escaped_cmd);
1622 if( cmd != exec )
1623 g_free(cmd);
1624 cmd = term_cmd;
1625 }
1626
1627 spawn_command_async(NULL, in_workdir, cmd);
1628
1629 g_free(cmd);
1630
1631 return (error == NULL);
1632 }
1633
1634 /* vim: set sw=4 et sts=4 : */