1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17  * Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 /* gui_support.c - general (GUI) support routines */
21 
22 #include "config.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/param.h>
28 #include <stdarg.h>
29 #include <errno.h>
30 #include <time.h>
31 
32 #include <X11/Xlib.h>
33 #include <X11/Xatom.h>
34 #include <gdk/gdkx.h>
35 #include <gdk/gdk.h>
36 #include <gdk/gdkkeysyms.h>
37 
38 #include "global.h"
39 
40 #include "main.h"
41 #include "gui_support.h"
42 #include "support.h"
43 #include "pixmaps.h"
44 #include "choices.h"
45 #include "options.h"
46 #include "run.h"
47 
48 gint	screen_width, screen_height;
49 
50 gint		n_monitors;
51 GdkRectangle	*monitor_geom = NULL;
52 gint		monitor_width, monitor_height;
53 MonitorAdjacent *monitor_adjacent;
54 
55 static GdkAtom xa_cardinal;
56 GdkAtom xa__NET_WORKAREA = GDK_NONE;
57 GdkAtom xa__NET_WM_DESKTOP = GDK_NONE;
58 GdkAtom xa__NET_CURRENT_DESKTOP = GDK_NONE;
59 GdkAtom xa__NET_NUMBER_OF_DESKTOPS = GDK_NONE;
60 
61 static GtkWidget *current_dialog = NULL;
62 
63 static GtkWidget *tip_widget = NULL;
64 static time_t tip_time = 0; 	/* Time tip widget last closed */
65 static gint tip_timeout = 0;	/* When primed */
66 
67 /* Static prototypes */
68 static void run_error_info_dialog(GtkMessageType type, const char *message,
69 				  va_list args);
70 static GType simple_image_get_type(void);
71 static void gui_get_monitor_adjacent(int monitor, MonitorAdjacent *adj);
72 
gui_store_screen_geometry(GdkScreen * screen)73 void gui_store_screen_geometry(GdkScreen *screen)
74 {
75 	gint mon;
76 
77 	screen_width = gdk_screen_get_width(screen);
78 	screen_height = gdk_screen_get_height(screen);
79 
80 	if (monitor_adjacent)
81 		g_free(monitor_adjacent);
82 
83 	monitor_width = monitor_height = G_MAXINT;
84 	n_monitors = gdk_screen_get_n_monitors(screen);
85 	if (monitor_geom)
86 		g_free(monitor_geom);
87 	monitor_geom = g_new(GdkRectangle, n_monitors ? n_monitors : 1);
88 
89 	if (n_monitors)
90 	{
91 		for (mon = 0; mon < n_monitors; ++mon)
92 		{
93 			gdk_screen_get_monitor_geometry(screen, mon,
94 					&monitor_geom[mon]);
95 			if (monitor_geom[mon].width < monitor_width)
96 				monitor_width = monitor_geom[mon].width;
97 			if (monitor_geom[mon].height < monitor_height)
98 				monitor_height = monitor_geom[mon].height;
99 		}
100 		monitor_adjacent = g_new(MonitorAdjacent, n_monitors);
101 		for (mon = 0; mon < n_monitors; ++mon)
102 		{
103 			gui_get_monitor_adjacent(mon, &monitor_adjacent[mon]);
104 		}
105 	}
106 	else
107 	{
108 		n_monitors = 1;
109 		monitor_geom[0].x = monitor_geom[0].y = 0;
110 		monitor_width = monitor_geom[0].width = screen_width;
111 		monitor_height = monitor_geom[0].height = screen_height;
112 		monitor_adjacent = g_new0(MonitorAdjacent, 1);
113 	}
114 
115 }
116 
gui_support_init()117 void gui_support_init()
118 {
119 	gpointer klass;
120 
121 	xa_cardinal = gdk_atom_intern("CARDINAL", FALSE);
122         xa__NET_WORKAREA = gdk_atom_intern("_NET_WORKAREA", FALSE);
123         xa__NET_WM_DESKTOP = gdk_atom_intern("_NET_WM_DESKTOP", FALSE);
124         xa__NET_CURRENT_DESKTOP = gdk_atom_intern("_NET_CURRENT_DESKTOP",
125                                                   FALSE);
126         xa__NET_NUMBER_OF_DESKTOPS = gdk_atom_intern("_NET_NUMBER_OF_DESKTOPS",
127                                                      FALSE);
128 
129 	gui_store_screen_geometry(gdk_screen_get_default());
130 
131 	/* Work around the scrollbar placement bug */
132 	klass = g_type_class_ref(gtk_scrolled_window_get_type());
133 	((GtkScrolledWindowClass *) klass)->scrollbar_spacing = 0;
134 	/* (don't unref, ever) */
135 }
136 
137 /* Open a modal dialog box showing a message.
138  * The user can choose from a selection of buttons at the bottom.
139  * Returns -1 if the window is destroyed, or the number of the button
140  * if one is clicked (starting from zero).
141  *
142  * If a dialog is already open, returns -1 without waiting AND
143  * brings the current dialog to the front.
144  *
145  * Each button has two arguments, a GTK_STOCK icon and some text. If the
146  * text is NULL, the stock's text is used.
147  */
get_choice(const char * title,const char * message,int number_of_buttons,...)148 int get_choice(const char *title,
149 	       const char *message,
150 	       int number_of_buttons, ...)
151 {
152 	GtkWidget	*dialog;
153 	GtkWidget	*button = NULL;
154 	int		i, retval;
155 	va_list	ap;
156 
157 	if (current_dialog)
158 	{
159 		gtk_widget_hide(current_dialog);
160 		gtk_widget_show(current_dialog);
161 		return -1;
162 	}
163 
164 	current_dialog = dialog = gtk_message_dialog_new(NULL,
165 					GTK_DIALOG_MODAL,
166 					GTK_MESSAGE_QUESTION,
167 					GTK_BUTTONS_NONE,
168 					"%s", message);
169 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
170 
171 	va_start(ap, number_of_buttons);
172 
173 	for (i = 0; i < number_of_buttons; i++)
174 	{
175 		const char *stock = va_arg(ap, char *);
176 		const char *text = va_arg(ap, char *);
177 
178 		if (text)
179 			button = button_new_mixed(stock, text);
180 		else
181 			button = gtk_button_new_from_stock(stock);
182 
183 		GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
184 		gtk_widget_show(button);
185 
186 		gtk_dialog_add_action_widget(GTK_DIALOG(current_dialog),
187 						button, i);
188 	}
189 
190 	gtk_window_set_title(GTK_WINDOW(dialog), title);
191 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
192 
193 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), i - 1);
194 
195 	va_end(ap);
196 
197 	retval = gtk_dialog_run(GTK_DIALOG(dialog));
198 	if (retval == GTK_RESPONSE_NONE)
199 		retval = -1;
200 	gtk_widget_destroy(dialog);
201 
202 	current_dialog = NULL;
203 
204 	return retval;
205 }
206 
info_message(const char * message,...)207 void info_message(const char *message, ...)
208 {
209         va_list args;
210 
211 	va_start(args, message);
212 
213 	run_error_info_dialog(GTK_MESSAGE_INFO, message, args);
214 }
215 
216 /* Display a message in a window with "ROX-Filer" as title */
report_error(const char * message,...)217 void report_error(const char *message, ...)
218 {
219 	va_list args;
220 
221 	va_start(args, message);
222 
223 	run_error_info_dialog(GTK_MESSAGE_ERROR, message, args);
224 }
225 
set_cardinal_property(GdkWindow * window,GdkAtom prop,gulong value)226 void set_cardinal_property(GdkWindow *window, GdkAtom prop, gulong value)
227 {
228 	gdk_property_change(window, prop, xa_cardinal, 32,
229 				GDK_PROP_MODE_REPLACE, (gchar *) &value, 1);
230 }
231 
get_cardinal_property(GdkWindow * window,GdkAtom prop,gulong length,gulong * data,gint * actual_length)232 gboolean get_cardinal_property(GdkWindow *window, GdkAtom prop, gulong length,
233                                gulong *data, gint *actual_length)
234 {
235         GdkAtom actual_type;
236         gint actual_format, act_length;
237         guchar *d;
238         gulong *p;
239         int i;
240         gboolean ok;
241 
242         /* Cardinals are format=32 so the length in bytes is 4 * number of
243          * cardinals */
244         ok=gdk_property_get(window, prop, xa_cardinal,
245                                      0, length*4, FALSE,
246                                      &actual_type, &actual_format,
247                                      &act_length, &d);
248 
249         if(!ok)
250                 return FALSE;
251 
252         /* Check correct format */
253         if(actual_format!=32)
254         {
255                 g_free(d);
256                 return FALSE;
257         }
258 
259         /* Actual data for cardinals returned as longs, which may be 64 bit */
260         if(act_length/sizeof(gulong)>length)
261         {
262                 g_free(d);
263                 return FALSE;
264         }
265 
266         /* Copy data into return array */
267         p=(gulong *) d;
268         for(i=0; i<act_length/sizeof(gulong); i++)
269                 data[i]=p[i];
270         g_free(d);
271         *actual_length=act_length/sizeof(gulong);
272 
273         return ok;
274 }
275 
get_current_desktop(void)276 int get_current_desktop(void)
277 {
278         gint act_len;
279         gulong current;
280         Window root=GDK_ROOT_WINDOW();
281         GdkWindow *gdk_root=gdk_window_foreign_new(root);
282         int desk=0;
283 
284         if(get_cardinal_property(gdk_root, xa__NET_CURRENT_DESKTOP, 1,
285                                  &current, &act_len) && act_len==1)
286                 desk=(int) current;
287 
288         return desk;
289 }
290 
get_number_of_desktops(void)291 int get_number_of_desktops(void)
292 {
293         gint act_len;
294         gulong num;
295         Window root=GDK_ROOT_WINDOW();
296         GdkWindow *gdk_root=gdk_window_foreign_new(root);
297         int desks=1;
298 
299         if(get_cardinal_property(gdk_root, xa__NET_NUMBER_OF_DESKTOPS, 1,
300                                  &num, &act_len) && act_len==1)
301                 desks=(int) num;
302 
303         return desks;
304 }
305 
306 /* Get the working area for the desktop, excluding things like the Gnome
307  * panels. */
get_work_area(int * x,int * y,int * width,int * height)308 void get_work_area(int *x, int *y, int *width, int *height)
309 {
310         gint act_len;
311         gulong *work_area;
312         Window root=GDK_ROOT_WINDOW();
313         GdkWindow *gdk_root=gdk_window_foreign_new(root);
314         int x0, y0, w0, h0;
315         int idesk, ndesk, nval;
316 
317         idesk=get_current_desktop();
318         ndesk=get_number_of_desktops();
319         nval=4*ndesk;
320         work_area=g_new(gulong, nval);
321 
322         if(get_cardinal_property(gdk_root, xa__NET_WORKAREA, nval,
323                                          work_area, &act_len) &&
324            act_len==nval)
325         {
326                 x0 = work_area[idesk*4+0];
327                 y0 = work_area[idesk*4+1];
328                 w0 = work_area[idesk*4+2];
329                 h0 = work_area[idesk*4+3];
330         }
331         else
332         {
333                 x0 = y0 = 0;
334                 w0 = screen_width;
335                 h0 = screen_height;
336         }
337 
338         g_free(work_area);
339 
340         if(x)
341                 *x = x0;
342         if(y)
343                 *y = y0;
344         if(width)
345                 *width = w0;
346         if(height)
347                 *height = h0;
348 }
349 
350 /* NB: Also used for pinned icons.
351  * TODO: Set the level here too.
352  */
make_panel_window(GtkWidget * widget)353 void make_panel_window(GtkWidget *widget)
354 {
355 	static gboolean need_init = TRUE;
356 	static GdkAtom xa_state, xa_atom, xa_hints, xa_win_hints;
357 	GdkWindow *window = widget->window;
358 	long wm_hints_values[] = {1, False, 0, 0, 0, 0, 0, 0};
359 	GdkAtom	wm_protocols[2];
360 
361 	g_return_if_fail(window != NULL);
362 
363 	if (o_override_redirect.int_value)
364 	{
365 		gdk_window_set_override_redirect(window, TRUE);
366 		return;
367 	}
368 
369 	if (need_init)
370 	{
371 		xa_win_hints = gdk_atom_intern("_WIN_HINTS", FALSE);
372 		xa_state = gdk_atom_intern("_WIN_STATE", FALSE);
373 		xa_atom = gdk_atom_intern("ATOM", FALSE);
374 		xa_hints = gdk_atom_intern("WM_HINTS", FALSE);
375 
376 		need_init = FALSE;
377 	}
378 
379 	gdk_window_set_decorations(window, 0);
380 	gdk_window_set_functions(window, 0);
381 	gtk_window_set_resizable(GTK_WINDOW(widget), FALSE);
382 
383 	/* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
384 	 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
385 	 */
386 	set_cardinal_property(window, xa_state,
387 			WIN_STATE_STICKY |
388 			WIN_STATE_FIXED_POSITION | WIN_STATE_ARRANGE_IGNORE);
389 
390 	set_cardinal_property(window, xa_win_hints,
391 			WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST |
392 			WIN_HINTS_SKIP_TASKBAR);
393 
394 	/* Appear on all workspaces */
395 	set_cardinal_property(window, xa__NET_WM_DESKTOP, 0xffffffff);
396 
397 	gdk_property_change(window, xa_hints, xa_hints, 32,
398 			GDK_PROP_MODE_REPLACE, (guchar *) wm_hints_values,
399 			sizeof(wm_hints_values) / sizeof(long));
400 
401 	wm_protocols[0] = gdk_atom_intern("WM_DELETE_WINDOW", FALSE);
402 	wm_protocols[1] = gdk_atom_intern("_NET_WM_PING", FALSE);
403 	gdk_property_change(window,
404 			gdk_atom_intern("WM_PROTOCOLS", FALSE), xa_atom, 32,
405 			GDK_PROP_MODE_REPLACE, (guchar *) wm_protocols,
406 			sizeof(wm_protocols) / sizeof(GdkAtom));
407 
408 	gdk_window_set_skip_taskbar_hint(window, TRUE);
409 	gdk_window_set_skip_pager_hint(window, TRUE);
410 
411 	if (g_object_class_find_property(G_OBJECT_GET_CLASS(widget),
412 					"accept_focus"))
413 	{
414 		GValue vfalse = { 0, };
415 		g_value_init(&vfalse, G_TYPE_BOOLEAN);
416 		g_value_set_boolean(&vfalse, FALSE);
417 		g_object_set_property(G_OBJECT(widget),
418 					"accept_focus", &vfalse);
419 		g_value_unset(&vfalse);
420 	}
421 }
422 
error_idle_cb(gpointer data)423 static gboolean error_idle_cb(gpointer data)
424 {
425 	char	**error = (char **) data;
426 
427 	report_error("%s", *error);
428 	null_g_free(error);
429 
430 	one_less_window();
431 	return FALSE;
432 }
433 
434 /* Display an error with "ROX-Filer" as title next time we are idle.
435  * If multiple errors are reported this way before the window is opened,
436  * all are displayed in a single window.
437  * If an error is reported while the error window is open, it is discarded.
438  */
delayed_error(const char * error,...)439 void delayed_error(const char *error, ...)
440 {
441 	static char *delayed_error_data = NULL;
442 	char *old, *new;
443 	va_list args;
444 
445 	g_return_if_fail(error != NULL);
446 
447 	old = delayed_error_data;
448 
449 	va_start(args, error);
450 	new = g_strdup_vprintf(error, args);
451 	va_end(args);
452 
453 	if (old)
454 	{
455 		delayed_error_data = g_strconcat(old,
456 				_("\n---\n"),
457 				new, NULL);
458 		g_free(old);
459 		g_free(new);
460 	}
461 	else
462 	{
463 		delayed_error_data = new;
464 		g_idle_add(error_idle_cb, &delayed_error_data);
465 
466 		number_of_windows++;
467 	}
468 }
469 
470 /* Load the file into memory. Return TRUE on success.
471  * Block is zero terminated (but this is not included in the length).
472  */
load_file(const char * pathname,char ** data_out,long * length_out)473 gboolean load_file(const char *pathname, char **data_out, long *length_out)
474 {
475 	gsize len;
476 	GError *error = NULL;
477 
478 	if (!g_file_get_contents(pathname, data_out, &len, &error))
479 	{
480 		delayed_error("%s", error->message);
481 		g_error_free(error);
482 		return FALSE;
483 	}
484 
485 	if (length_out)
486 		*length_out = len;
487 	return TRUE;
488 }
489 
new_help_button(HelpFunc show_help,gpointer data)490 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
491 {
492 	GtkWidget	*b, *icon;
493 
494 	b = gtk_button_new();
495 	gtk_button_set_relief(GTK_BUTTON(b), GTK_RELIEF_NONE);
496 	icon = gtk_image_new_from_stock(GTK_STOCK_HELP,
497 					GTK_ICON_SIZE_SMALL_TOOLBAR);
498 	gtk_container_add(GTK_CONTAINER(b), icon);
499 	g_signal_connect_swapped(b, "clicked", G_CALLBACK(show_help), data);
500 
501 	GTK_WIDGET_UNSET_FLAGS(b, GTK_CAN_FOCUS);
502 
503 	return b;
504 }
505 
506 /* Read file into memory. Call parse_line(guchar *line) for each line
507  * in the file. Callback returns NULL on success, or an error message
508  * if something went wrong. Only the first error is displayed to the user.
509  */
parse_file(const char * path,ParseFunc * parse_line)510 void parse_file(const char *path, ParseFunc *parse_line)
511 {
512 	char		*data;
513 	long		length;
514 	gboolean	seen_error = FALSE;
515 
516 	if (load_file(path, &data, &length))
517 	{
518 		char *eol;
519 		const char *error;
520 		char *line = data;
521 		int  line_number = 1;
522 
523 		if (strncmp(data, "<?xml ", 6) == 0)
524 		{
525 			delayed_error(_("Attempt to read an XML file as "
526 					"a text file. File '%s' may be "
527 					"corrupted."), path);
528 			return;
529 		}
530 
531 		while (line && *line)
532 		{
533 			eol = strchr(line, '\n');
534 			if (eol)
535 				*eol = '\0';
536 
537 			error = parse_line(line);
538 
539 			if (error && !seen_error)
540 			{
541 				delayed_error(
542 		_("Error in '%s' file at line %d: "
543 		"\n\"%s\"\n"
544 		"This may be due to upgrading from a previous version of "
545 		"ROX-Filer. Open the Options window and try changing something "
546 		"and then changing it back (causing the file to be resaved).\n"
547 		"Further errors will be ignored."),
548 					path,
549 					line_number,
550 					error);
551 				seen_error = TRUE;
552 			}
553 
554 			if (!eol)
555 				break;
556 			line = eol + 1;
557 			line_number++;
558 		}
559 		g_free(data);
560 	}
561 }
562 
563 /* Returns the position of the pointer.
564  * TRUE if any modifier keys or mouse buttons are pressed.
565  */
get_pointer_xy(int * x,int * y)566 gboolean get_pointer_xy(int *x, int *y)
567 {
568 	unsigned int mask;
569 
570 	gdk_window_get_pointer(NULL, x, y, &mask);
571 
572 	return mask != 0;
573 }
574 
get_monitor_under_pointer(void)575 int get_monitor_under_pointer(void)
576 {
577 	int x, y;
578 
579 	get_pointer_xy(&x, &y);
580 	return gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x, y);
581 }
582 
583 #define DECOR_BORDER 32
584 
585 /* Centre the window at these coords */
centre_window(GdkWindow * window,int x,int y)586 void centre_window(GdkWindow *window, int x, int y)
587 {
588 	int	w, h;
589 	int m;
590 
591 	g_return_if_fail(window != NULL);
592 
593 	m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x, y);
594 
595 	gdk_drawable_get_size(window, &w, &h);
596 
597 	x -= w / 2;
598 	y -= h / 2;
599 
600 	gdk_window_move(window,
601 		CLAMP(x, DECOR_BORDER + monitor_geom[m].x,
602 			monitor_geom[m].x + monitor_geom[m].width
603 			- w - DECOR_BORDER),
604 		CLAMP(y, DECOR_BORDER + monitor_geom[m].y,
605 			monitor_geom[m].y + monitor_geom[m].height
606 			- h - DECOR_BORDER));
607 }
608 
run_error_info_dialog(GtkMessageType type,const char * message,va_list args)609 static void run_error_info_dialog(GtkMessageType type, const char *message,
610 				  va_list args)
611 {
612 	GtkWidget *dialog;
613 	gchar *s;
614 
615 	g_return_if_fail(message != NULL);
616 
617 	s = g_strdup_vprintf(message, args);
618 	va_end(args);
619 
620 	dialog = gtk_message_dialog_new(NULL,
621 					GTK_DIALOG_MODAL,
622 					type,
623 					GTK_BUTTONS_OK,
624 					"%s", s);
625 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
626 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
627 	gtk_dialog_run(GTK_DIALOG(dialog));
628 	gtk_widget_destroy(dialog);
629 
630 	g_free(s);
631 }
632 
633 static GtkWidget *current_wink_widget = NULL;
634 static gint	wink_timeout = -1;	/* Called when it's time to stop */
635 static gulong	wink_destroy;		/* Called if the widget dies first */
636 
end_wink(gpointer data)637 static gboolean end_wink(gpointer data)
638 {
639 	gtk_drag_unhighlight(current_wink_widget);
640 
641 	g_signal_handler_disconnect(current_wink_widget, wink_destroy);
642 
643 	current_wink_widget = NULL;
644 
645 	return FALSE;
646 }
647 
cancel_wink(void)648 static void cancel_wink(void)
649 {
650 	g_source_remove(wink_timeout);
651 	end_wink(NULL);
652 }
653 
wink_widget_died(gpointer data)654 static void wink_widget_died(gpointer data)
655 {
656 	current_wink_widget = NULL;
657 	g_source_remove(wink_timeout);
658 }
659 
660 /* Draw a black box around this widget, briefly.
661  * Note: uses the drag highlighting code for now.
662  */
wink_widget(GtkWidget * widget)663 void wink_widget(GtkWidget *widget)
664 {
665 	g_return_if_fail(widget != NULL);
666 
667 	if (current_wink_widget)
668 		cancel_wink();
669 
670 	current_wink_widget = widget;
671 	gtk_drag_highlight(current_wink_widget);
672 
673 	wink_timeout = g_timeout_add(300, (GSourceFunc) end_wink, NULL);
674 
675 	wink_destroy = g_signal_connect_swapped(widget, "destroy",
676 				G_CALLBACK(wink_widget_died), NULL);
677 }
678 
idle_destroy_cb(GtkWidget * widget)679 static gboolean idle_destroy_cb(GtkWidget *widget)
680 {
681 	gtk_widget_unref(widget);
682 	gtk_widget_destroy(widget);
683 	return FALSE;
684 }
685 
686 /* Destroy the widget in an idle callback */
destroy_on_idle(GtkWidget * widget)687 void destroy_on_idle(GtkWidget *widget)
688 {
689 	gtk_widget_ref(widget);
690 	g_idle_add((GSourceFunc) idle_destroy_cb, widget);
691 }
692 
693 /* Spawn a child process (as spawn_full), and report errors.
694  * Returns the child's PID on succes, or 0 on failure.
695  */
rox_spawn(const gchar * dir,const gchar ** argv)696 gint rox_spawn(const gchar *dir, const gchar **argv)
697 {
698 	GError	*error = NULL;
699 	gint pid = 0;
700 
701 	if (!g_spawn_async_with_pipes(dir, (gchar **) argv, NULL,
702 			G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL |
703 			G_SPAWN_SEARCH_PATH,
704 			NULL, NULL,		/* Child setup fn */
705 			&pid,			/* Child PID */
706 			NULL, NULL, NULL,	/* Standard pipes */
707 			&error))
708 	{
709 		delayed_error("%s", error ? error->message : "(null)");
710 		g_error_free(error);
711 
712 		return 0;
713 	}
714 
715 	return pid;
716 }
717 
button_new_image_text(GtkWidget * image,const char * message)718 GtkWidget *button_new_image_text(GtkWidget *image, const char *message)
719 {
720 	GtkWidget *button, *align, *hbox, *label;
721 
722 	button = gtk_button_new();
723 	label = gtk_label_new_with_mnemonic(message);
724 	gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
725 
726 	hbox = gtk_hbox_new(FALSE, 2);
727 
728 	align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
729 
730 	gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
731 	gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
732 
733 	gtk_container_add(GTK_CONTAINER(button), align);
734 	gtk_container_add(GTK_CONTAINER(align), hbox);
735 	gtk_widget_show_all(align);
736 
737 	return button;
738 }
739 
button_new_mixed(const char * stock,const char * message)740 GtkWidget *button_new_mixed(const char *stock, const char *message)
741 {
742 	return button_new_image_text(gtk_image_new_from_stock(stock,
743 					       GTK_ICON_SIZE_BUTTON),
744 					message);
745 }
746 
747 /* Highlight entry in red if 'error' is TRUE */
entry_set_error(GtkWidget * entry,gboolean error)748 void entry_set_error(GtkWidget *entry, gboolean error)
749 {
750 	const GdkColor red = {0, 0xffff, 0, 0};
751 	const GdkColor white = {0, 0xffff, 0xffff, 0xffff};
752 
753 	gtk_widget_modify_text(entry, GTK_STATE_NORMAL, error ? &red : NULL);
754 	gtk_widget_modify_base(entry, GTK_STATE_NORMAL, error ? &white : NULL);
755 }
756 
757 /* Change stacking position of higher to be just above lower.
758  * If lower is NULL, put higher at the bottom of the stack.
759  */
window_put_just_above(GdkWindow * higher,GdkWindow * lower)760 void window_put_just_above(GdkWindow *higher, GdkWindow *lower)
761 {
762 	if (o_override_redirect.int_value && lower)
763 	{
764 		XWindowChanges restack;
765 
766 		gdk_error_trap_push();
767 
768 		restack.stack_mode = Above;
769 
770 		restack.sibling = GDK_WINDOW_XWINDOW(lower);
771 
772 		XConfigureWindow(gdk_display, GDK_WINDOW_XWINDOW(higher),
773 				CWSibling | CWStackMode, &restack);
774 
775 		gdk_flush();
776 		if (gdk_error_trap_pop())
777 			g_warning("window_put_just_above()\n");
778 	}
779 	else
780 		gdk_window_lower(higher);	/* To bottom of stack */
781 }
782 
783 /* Copied from Gtk */
fixed_get_child(GtkFixed * fixed,GtkWidget * widget)784 static GtkFixedChild* fixed_get_child(GtkFixed *fixed, GtkWidget *widget)
785 {
786 	GList *children;
787 
788 	children = fixed->children;
789 	while (children)
790 	{
791 		GtkFixedChild *child;
792 
793 		child = children->data;
794 		children = children->next;
795 
796 		if (child->widget == widget)
797 			return child;
798 	}
799 
800 	return NULL;
801 }
802 
803 /* Like gtk_fixed_move(), except not insanely slow */
fixed_move_fast(GtkFixed * fixed,GtkWidget * widget,int x,int y)804 void fixed_move_fast(GtkFixed *fixed, GtkWidget *widget, int x, int y)
805 {
806 	GtkFixedChild *child;
807 
808 	child = fixed_get_child(fixed, widget);
809 
810 	g_assert(child);
811 
812 	gtk_widget_freeze_child_notify(widget);
813 
814 	child->x = x;
815 	gtk_widget_child_notify(widget, "x");
816 
817 	child->y = y;
818 	gtk_widget_child_notify(widget, "y");
819 
820 	gtk_widget_thaw_child_notify(widget);
821 
822 	if (GTK_WIDGET_VISIBLE(widget) && GTK_WIDGET_VISIBLE(fixed))
823 	{
824 		int border_width = GTK_CONTAINER(fixed)->border_width;
825 		GtkAllocation child_allocation;
826 		GtkRequisition child_requisition;
827 
828 		gtk_widget_get_child_requisition(child->widget,
829 					&child_requisition);
830 		child_allocation.x = child->x + border_width;
831 		child_allocation.y = child->y + border_width;
832 
833 		child_allocation.x += GTK_WIDGET(fixed)->allocation.x;
834 		child_allocation.y += GTK_WIDGET(fixed)->allocation.y;
835 
836 		child_allocation.width = child_requisition.width;
837 		child_allocation.height = child_requisition.height;
838 		gtk_widget_size_allocate(child->widget, &child_allocation);
839 	}
840 }
841 
842 /* Draw the black border */
tooltip_draw(GtkWidget * w)843 static gint tooltip_draw(GtkWidget *w)
844 {
845 	gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
846 			w->allocation.width - 1, w->allocation.height - 1);
847 
848 	return FALSE;
849 }
850 
851 /* When the tips window closed, record the time. If we try to open another
852  * tip soon, it will appear more quickly.
853  */
tooltip_destroyed(gpointer data)854 static void tooltip_destroyed(gpointer data)
855 {
856 	time(&tip_time);
857 }
858 
859 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
860  * NULL, close any current tooltip.
861  */
tooltip_show(guchar * text)862 void tooltip_show(guchar *text)
863 {
864 	GtkWidget *label;
865 	int	x, y, py;
866 	int	w, h;
867 	int m;
868 
869 	if (tip_timeout)
870 	{
871 		g_source_remove(tip_timeout);
872 		tip_timeout = 0;
873 	}
874 
875 	if (tip_widget)
876 	{
877 		gtk_widget_destroy(tip_widget);
878 		tip_widget = NULL;
879 	}
880 
881 	if (!text)
882 		return;
883 
884 	/* Show the tip */
885 	tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
886 	gtk_widget_set_app_paintable(tip_widget, TRUE);
887 	gtk_widget_set_name(tip_widget, "gtk-tooltips");
888 
889 	g_signal_connect_swapped(tip_widget, "expose_event",
890 			G_CALLBACK(tooltip_draw), tip_widget);
891 
892 	label = gtk_label_new(text);
893 	gtk_misc_set_padding(GTK_MISC(label), 4, 2);
894 	gtk_container_add(GTK_CONTAINER(tip_widget), label);
895 	gtk_widget_show(label);
896 	gtk_widget_realize(tip_widget);
897 
898 	w = tip_widget->allocation.width;
899 	h = tip_widget->allocation.height;
900 	gdk_window_get_pointer(NULL, &x, &py, NULL);
901 
902 	m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x, py);
903 
904 	x -= w / 2;
905 	y = py + 12; /* I don't know the pointer height so I use a constant */
906 
907 	/* Now check for screen boundaries */
908 	x = CLAMP(x, monitor_geom[m].x,
909 			monitor_geom[m].x + monitor_geom[m].width - w);
910 	y = CLAMP(y, monitor_geom[m].y,
911 			monitor_geom[m].y + monitor_geom[m].height - h);
912 
913 	/* And again test if pointer is over the tooltip window */
914 	if (py >= y && py <= y + h)
915 		y = py - h - 2;
916 	gtk_window_move(GTK_WINDOW(tip_widget), x, y);
917 	gtk_widget_show(tip_widget);
918 
919 	g_signal_connect_swapped(tip_widget, "destroy",
920 			G_CALLBACK(tooltip_destroyed), NULL);
921 	time(&tip_time);
922 }
923 
924 /* Call callback(user_data) after a while, unless cancelled.
925  * Object is refd now and unref when cancelled / after callback called.
926  */
tooltip_prime(GtkFunction callback,GObject * object)927 void tooltip_prime(GtkFunction callback, GObject *object)
928 {
929 	time_t  now;
930 	int	delay;
931 
932 	g_return_if_fail(tip_timeout == 0);
933 
934 	time(&now);
935 	delay = now - tip_time > 2 ? 1000 : 200;
936 
937 	g_object_ref(object);
938 	tip_timeout = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
939 					 delay,
940 					 (GSourceFunc) callback,
941 					 object,
942 					 g_object_unref);
943 }
944 
945 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
widget_modify_font(GtkWidget * widget,PangoFontDescription * font_desc)946 void widget_modify_font(GtkWidget *widget, PangoFontDescription *font_desc)
947 {
948 	GtkRcStyle *rc_style;
949 
950 	g_return_if_fail(GTK_IS_WIDGET(widget));
951 
952 	rc_style = gtk_widget_get_modifier_style(widget);
953 
954 	if (rc_style->font_desc)
955 		pango_font_description_free(rc_style->font_desc);
956 
957 	rc_style->font_desc = font_desc
958 				? pango_font_description_copy(font_desc)
959 				: NULL;
960 
961 	gtk_widget_modify_style(widget, rc_style);
962 }
963 
964 /* Confirm the action with the user. If action is NULL, the text from stock
965  * is used.
966  */
confirm(const gchar * message,const gchar * stock,const gchar * action)967 gboolean confirm(const gchar *message, const gchar *stock, const gchar *action)
968 {
969 	return get_choice(PROJECT, message, 2,
970 			  GTK_STOCK_CANCEL, NULL,
971 			  stock, action) == 1;
972 }
973 
974 struct _Radios {
975 	GList *widgets;
976 
977 	void (*changed)(Radios *, gpointer data);
978 	gpointer changed_data;
979 };
980 
981 /* Create a new set of radio buttons.
982  * Use radios_add to add options, then radios_pack to put them into something.
983  * The radios object will self-destruct with the first widget it contains.
984  * changed(data) is called (if not NULL) when pack is called, and on any
985  * change after that.
986  */
radios_new(void (* changed)(Radios *,gpointer data),gpointer data)987 Radios *radios_new(void (*changed)(Radios *, gpointer data), gpointer data)
988 {
989 	Radios *radios;
990 
991 	radios = g_new(Radios, 1);
992 
993 	radios->widgets = NULL;
994 	radios->changed = changed;
995 	radios->changed_data = data;
996 
997 	return radios;
998 }
999 
radios_free(GtkWidget * radio,Radios * radios)1000 static void radios_free(GtkWidget *radio, Radios *radios)
1001 {
1002 	g_return_if_fail(radios != NULL);
1003 
1004 	g_list_free(radios->widgets);
1005 	g_free(radios);
1006 }
1007 
radios_add(Radios * radios,const gchar * tip,gint value,const gchar * label,...)1008 void radios_add(Radios *radios, const gchar *tip, gint value,
1009 		const gchar *label, ...)
1010 {
1011 	GtkWidget *radio;
1012 	GSList *group = NULL;
1013 	gchar *s;
1014 	va_list args;
1015 
1016 	g_return_if_fail(radios != NULL);
1017 	g_return_if_fail(label != NULL);
1018 
1019 	va_start(args, label);
1020 	s = g_strdup_vprintf(label, args);
1021 	va_end(args);
1022 
1023 	if (radios->widgets)
1024 	{
1025 		GtkRadioButton *first = GTK_RADIO_BUTTON(radios->widgets->data);
1026 		group = gtk_radio_button_get_group(first);
1027 	}
1028 
1029 	radio = gtk_radio_button_new_with_label(group, s);
1030 	gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(radio)->child), TRUE);
1031 	gtk_widget_show(radio);
1032 	if (tip)
1033 		gtk_tooltips_set_tip(tooltips, radio, tip, NULL);
1034 	if (!group)
1035 		g_signal_connect(G_OBJECT(radio), "destroy",
1036 				G_CALLBACK(radios_free), radios);
1037 
1038 	radios->widgets = g_list_prepend(radios->widgets, radio);
1039 	g_object_set_data(G_OBJECT(radio), "rox-radios-value",
1040 			  GINT_TO_POINTER(value));
1041 }
1042 
radio_toggled(GtkToggleButton * button,Radios * radios)1043 static void radio_toggled(GtkToggleButton *button, Radios *radios)
1044 {
1045 	g_return_if_fail(radios != NULL);
1046 
1047 	if (button && !gtk_toggle_button_get_active(button))
1048 		return;	/* Stop double-notifies */
1049 
1050 	if (radios->changed)
1051 		radios->changed(radios, radios->changed_data);
1052 }
1053 
radios_pack(Radios * radios,GtkBox * box)1054 void radios_pack(Radios *radios, GtkBox *box)
1055 {
1056 	GList *next;
1057 
1058 	g_return_if_fail(radios != NULL);
1059 
1060 	for (next = g_list_last(radios->widgets); next; next = next->prev)
1061 	{
1062 		GtkWidget *button = GTK_WIDGET(next->data);
1063 
1064 		gtk_box_pack_start(box, button, FALSE, TRUE, 0);
1065 		g_signal_connect(button, "toggled",
1066 				G_CALLBACK(radio_toggled), radios);
1067 	}
1068 	radio_toggled(NULL, radios);
1069 }
1070 
radios_set_value(Radios * radios,gint value)1071 void radios_set_value(Radios *radios, gint value)
1072 {
1073 	GList *next;
1074 
1075 	g_return_if_fail(radios != NULL);
1076 
1077 	for (next = radios->widgets; next; next = next->next)
1078 	{
1079 		GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
1080 		int radio_value;
1081 
1082 		radio_value = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(radio),
1083 						"rox-radios-value"));
1084 
1085 		if (radio_value == value)
1086 		{
1087 			gtk_toggle_button_set_active(radio, TRUE);
1088 			return;
1089 		}
1090 	}
1091 
1092 	g_warning("Value %d not in radio group!", value);
1093 }
1094 
radios_get_value(Radios * radios)1095 gint radios_get_value(Radios *radios)
1096 {
1097 	GList *next;
1098 
1099 	g_return_val_if_fail(radios != NULL, -1);
1100 
1101 	for (next = radios->widgets; next; next = next->next)
1102 	{
1103 		GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
1104 
1105 		if (gtk_toggle_button_get_active(radio))
1106 			return GPOINTER_TO_INT(g_object_get_data(
1107 					G_OBJECT(radio), "rox-radios-value"));
1108 	}
1109 
1110 	g_warning("Nothing in the radio group is selected!");
1111 
1112 	return -1;
1113 }
1114 
1115 /* Convert a list of URIs as a string into a GList of EscapedPath URIs.
1116  * No unescaping is done.
1117  * Lines beginning with # are skipped.
1118  * The text block passed in is zero terminated (after the final CRLF)
1119  */
uri_list_to_glist(const char * uri_list)1120 GList *uri_list_to_glist(const char *uri_list)
1121 {
1122 	GList   *list = NULL;
1123 
1124 	while (*uri_list)
1125 	{
1126 		char	*linebreak;
1127 		int	length;
1128 
1129 		linebreak = strchr(uri_list, 13);
1130 
1131 		if (!linebreak || linebreak[1] != 10)
1132 		{
1133 			g_warning("uri_list_to_glist: %s",
1134 					_("Incorrect or missing line "
1135 					  "break in text/uri-list data"));
1136 			/* If this is the first, append it anyway (Firefox
1137 			 * 3.5) */
1138 			if (!list && uri_list[0] != '#')
1139 				list = g_list_append(list, g_strdup(uri_list));
1140 			return list;
1141 		}
1142 
1143 		length = linebreak - uri_list;
1144 
1145 		if (length && uri_list[0] != '#')
1146 			list = g_list_append(list, g_strndup(uri_list, length));
1147 
1148 		uri_list = linebreak + 2;
1149 	}
1150 
1151 	return list;
1152 }
1153 
1154 typedef struct _SimpleImageClass SimpleImageClass;
1155 typedef struct _SimpleImage SimpleImage;
1156 
1157 struct _SimpleImageClass {
1158 	GtkWidgetClass parent;
1159 };
1160 
1161 struct _SimpleImage {
1162 	GtkWidget widget;
1163 
1164 	GdkPixbuf *pixbuf;
1165 	int	  width, height;
1166 };
1167 
1168 #define SIMPLE_IMAGE(obj) (GTK_CHECK_CAST((obj), \
1169 				simple_image_get_type(), SimpleImage))
1170 
simple_image_finialize(GObject * object)1171 static void simple_image_finialize(GObject *object)
1172 {
1173 	SimpleImage *image = SIMPLE_IMAGE(object);
1174 
1175 	g_object_unref(G_OBJECT(image->pixbuf));
1176 	image->pixbuf = NULL;
1177 }
1178 
simple_image_size_request(GtkWidget * widget,GtkRequisition * requisition)1179 static void simple_image_size_request(GtkWidget      *widget,
1180 				      GtkRequisition *requisition)
1181 {
1182 	SimpleImage *image = (SimpleImage *) widget;
1183 
1184 	requisition->width = image->width;
1185 	requisition->height = image->height;
1186 }
1187 
1188 /* Render a pixbuf without messing up the clipping */
render_pixbuf(GdkPixbuf * pixbuf,GdkDrawable * target,GdkGC * gc,int x,int y,int width,int height)1189 void render_pixbuf(GdkPixbuf *pixbuf, GdkDrawable *target, GdkGC *gc,
1190 		   int x, int y, int width, int height)
1191 {
1192 	gdk_draw_pixbuf(target, gc, pixbuf, 0, 0, x, y, width, height,
1193 		        GDK_RGB_DITHER_NORMAL, 0, 0);
1194 
1195 }
1196 
simple_image_expose(GtkWidget * widget,GdkEventExpose * event)1197 static gint simple_image_expose(GtkWidget *widget, GdkEventExpose *event)
1198 {
1199 	SimpleImage *image = (SimpleImage *) widget;
1200 	int x;
1201 
1202 	gdk_gc_set_clip_region(widget->style->black_gc, event->region);
1203 
1204 	x = widget->allocation.x +
1205 		(widget->allocation.width - image->width) / 2;
1206 
1207 	render_pixbuf(image->pixbuf, widget->window, widget->style->black_gc,
1208 			x, widget->allocation.y,
1209 			image->width, image->height);
1210 
1211 	gdk_gc_set_clip_region(widget->style->black_gc, NULL);
1212 	return FALSE;
1213 }
1214 
simple_image_class_init(gpointer gclass,gpointer data)1215 static void simple_image_class_init(gpointer gclass, gpointer data)
1216 {
1217 	GObjectClass *object = (GObjectClass *) gclass;
1218 	GtkWidgetClass *widget = (GtkWidgetClass *) gclass;
1219 
1220 	object->finalize = simple_image_finialize;
1221 	widget->size_request = simple_image_size_request;
1222 	widget->expose_event = simple_image_expose;
1223 }
1224 
simple_image_init(GTypeInstance * object,gpointer gclass)1225 static void simple_image_init(GTypeInstance *object, gpointer gclass)
1226 {
1227 	GTK_WIDGET_SET_FLAGS(object, GTK_NO_WINDOW);
1228 }
1229 
simple_image_get_type(void)1230 static GType simple_image_get_type(void)
1231 {
1232 	static GType type = 0;
1233 
1234 	if (!type)
1235 	{
1236 		static const GTypeInfo info =
1237 		{
1238 			sizeof (SimpleImageClass),
1239 			NULL,			/* base_init */
1240 			NULL,			/* base_finalise */
1241 			simple_image_class_init,
1242 			NULL,			/* class_finalise */
1243 			NULL,			/* class_data */
1244 			sizeof(SimpleImage),
1245 			0,			/* n_preallocs */
1246 			simple_image_init,
1247 		};
1248 
1249 		type = g_type_register_static(gtk_widget_get_type(),
1250 						"SimpleImage", &info, 0);
1251 	}
1252 
1253 	return type;
1254 }
1255 
simple_image_new(GdkPixbuf * pixbuf)1256 GtkWidget *simple_image_new(GdkPixbuf *pixbuf)
1257 {
1258 	SimpleImage *image;
1259 
1260 	g_return_val_if_fail(pixbuf != NULL, NULL);
1261 
1262 	image = g_object_new(simple_image_get_type(), NULL);
1263 
1264 	image->pixbuf = pixbuf;
1265 	g_object_ref(G_OBJECT(pixbuf));
1266 
1267 	image->width = gdk_pixbuf_get_width(pixbuf);
1268 	image->height = gdk_pixbuf_get_height(pixbuf);
1269 
1270 	return GTK_WIDGET(image);
1271 }
1272 
1273 /* Whether a line l1 long starting from n1 overlaps a line l2 from n2 */
gui_ranges_overlap(int n1,int l1,int n2,int l2)1274 inline static gboolean gui_ranges_overlap(int n1, int l1, int n2, int l2)
1275 {
1276 	return (n1 > n2 && n1 < n2 + l2) ||
1277 		(n1 + l1 > n2 && n1 + l1 < n2 + l2) ||
1278 		(n1 <= n2 && n1 + l1 >= n2 + l2);
1279 }
1280 
gui_get_monitor_adjacent(int monitor,MonitorAdjacent * adj)1281 static void gui_get_monitor_adjacent(int monitor, MonitorAdjacent *adj)
1282 {
1283 	int m;
1284 
1285 	adj->left = adj->right = adj->top = adj->bottom = FALSE;
1286 
1287 	for (m = 0; m < n_monitors; ++m)
1288 	{
1289 		if (m == monitor)
1290 			continue;
1291 		if (gui_ranges_overlap(monitor_geom[m].y,
1292 				monitor_geom[m].height,
1293 				monitor_geom[monitor].y,
1294 				monitor_geom[monitor].height))
1295 		{
1296 			if (monitor_geom[m].x < monitor_geom[monitor].x)
1297 			{
1298 				adj->left = TRUE;
1299 			}
1300 			else if (monitor_geom[m].x > monitor_geom[monitor].x)
1301 			{
1302 				adj->right = TRUE;
1303 			}
1304 		}
1305 		if (gui_ranges_overlap(monitor_geom[m].x,
1306 				monitor_geom[m].width,
1307 				monitor_geom[monitor].x,
1308 				monitor_geom[monitor].width))
1309 		{
1310 			if (monitor_geom[m].y < monitor_geom[monitor].y)
1311 			{
1312 				adj->top = TRUE;
1313 			}
1314 			else if (monitor_geom[m].y > monitor_geom[monitor].y)
1315 			{
1316 				adj->bottom = TRUE;
1317 			}
1318 		}
1319 	}
1320 }
1321 
rox_wmspec_change_state(gboolean add,GdkWindow * window,GdkAtom state1,GdkAtom state2)1322 static void rox_wmspec_change_state(gboolean add, GdkWindow *window,
1323 				    GdkAtom state1, GdkAtom state2)
1324 {
1325 	GdkDisplay *display = gdk_drawable_get_display(GDK_DRAWABLE(window));
1326 	XEvent xev;
1327 
1328 #define _NET_WM_STATE_REMOVE        0    /* remove/unset property */
1329 #define _NET_WM_STATE_ADD           1    /* add/set property */
1330 #define _NET_WM_STATE_TOGGLE        2    /* toggle property  */
1331 
1332 	xev.xclient.type = ClientMessage;
1333 	xev.xclient.serial = 0;
1334 	xev.xclient.send_event = True;
1335 	xev.xclient.window = GDK_WINDOW_XID(window);
1336 	xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display(
1337 			display, "_NET_WM_STATE");
1338 	xev.xclient.format = 32;
1339 	xev.xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1340 	xev.xclient.data.l[1] = gdk_x11_atom_to_xatom_for_display(display,
1341 			state1);
1342 	xev.xclient.data.l[2] = gdk_x11_atom_to_xatom_for_display(display,
1343 			state2);
1344 	xev.xclient.data.l[3] = 0;
1345 	xev.xclient.data.l[4] = 0;
1346 
1347 	XSendEvent(GDK_DISPLAY_XDISPLAY(display),
1348 		   GDK_WINDOW_XID(
1349 			gdk_screen_get_root_window(
1350 				gdk_drawable_get_screen(GDK_DRAWABLE(window)))),
1351 		   False,
1352 		   SubstructureRedirectMask | SubstructureNotifyMask,
1353 		   &xev);
1354 }
1355 
1356 /* Tell the window manager whether to keep this window below others. */
keep_below(GdkWindow * window,gboolean setting)1357 void keep_below(GdkWindow *window, gboolean setting)
1358 {
1359 	g_return_if_fail(GDK_IS_WINDOW(window));
1360 
1361 	if (GDK_WINDOW_DESTROYED(window))
1362 		return;
1363 
1364 	if (gdk_window_is_visible(window))
1365 	{
1366 		if (setting)
1367 		{
1368 			rox_wmspec_change_state(FALSE, window,
1369 				gdk_atom_intern("_NET_WM_STATE_ABOVE", FALSE),
1370 				GDK_NONE);
1371 		}
1372 		rox_wmspec_change_state(setting, window,
1373 				gdk_atom_intern("_NET_WM_STATE_BELOW", FALSE),
1374 				GDK_NONE);
1375 	}
1376 #if 0
1377 	else
1378 	{
1379 #if GTK_CHECK_VERSION(2,4,0)
1380 	  gdk_synthesize_window_state(window,
1381 				setting ? GDK_WINDOW_STATE_ABOVE :
1382 					GDK_WINDOW_STATE_BELOW,
1383 				setting ? GDK_WINDOW_STATE_BELOW : 0);
1384 #endif
1385 	}
1386 #endif
1387 }
1388 
1389 static void
size_prepared_cb(GdkPixbufLoader * loader,int width,int height,gpointer data)1390 size_prepared_cb (GdkPixbufLoader *loader,
1391 		  int              width,
1392 		  int              height,
1393 		  gpointer         data)
1394 {
1395 	struct {
1396 		gint width;
1397 		gint height;
1398 		gboolean preserve_aspect_ratio;
1399 	} *info = data;
1400 
1401 	g_return_if_fail (width > 0 && height > 0);
1402 
1403 	if(info->preserve_aspect_ratio) {
1404 		if ((double)height * (double)info->width >
1405 		    (double)width * (double)info->height) {
1406 			width = 0.5 + (double)width * (double)info->height / (double)height;
1407 			height = info->height;
1408 		} else {
1409 			height = 0.5 + (double)height * (double)info->width / (double)width;
1410 			width = info->width;
1411 		}
1412 	} else {
1413 		width = info->width;
1414 		height = info->height;
1415 	}
1416 
1417 	gdk_pixbuf_loader_set_size (loader, width, height);
1418 }
1419 
1420 /**
1421  * rox_pixbuf_new_from_file_at_scale:
1422  * @filename: Name of file to load.
1423  * @width: The width the image should have
1424  * @height: The height the image should have
1425  * @preserve_aspect_ratio: %TRUE to preserve the image's aspect ratio
1426  * @error: Return location for an error
1427  *
1428  * Creates a new pixbuf by loading an image from a file.  The file format is
1429  * detected automatically. If %NULL is returned, then @error will be set.
1430  * Possible errors are in the #GDK_PIXBUF_ERROR and #G_FILE_ERROR domains.
1431  * The image will be scaled to fit in the requested size, optionally preserving
1432  * the image's aspect ratio.
1433  *
1434  * Return value: A newly-created pixbuf with a reference count of 1, or %NULL
1435  * if any of several error conditions occurred:  the file could not be opened,
1436  * there was no loader for the file's format, there was not enough memory to
1437  * allocate the image buffer, or the image file contained invalid data.
1438  *
1439  * Taken from GTK 2.6.
1440  **/
1441 GdkPixbuf *
rox_pixbuf_new_from_file_at_scale(const char * filename,int width,int height,gboolean preserve_aspect_ratio,GError ** error)1442 rox_pixbuf_new_from_file_at_scale (const char *filename,
1443 				   int         width,
1444 				   int         height,
1445 				   gboolean    preserve_aspect_ratio,
1446 				   GError    **error)
1447 {
1448 
1449 	GdkPixbufLoader *loader;
1450 	GdkPixbuf       *pixbuf;
1451 
1452 	guchar buffer [4096];
1453 	int length;
1454 	FILE *f;
1455 	struct {
1456 		gint width;
1457 		gint height;
1458 		gboolean preserve_aspect_ratio;
1459 	} info;
1460 
1461 	g_return_val_if_fail (filename != NULL, NULL);
1462         g_return_val_if_fail (width > 0 && height > 0, NULL);
1463 
1464 	f = fopen (filename, "rb");
1465 	if (!f) {
1466                 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1467                                                            NULL, NULL, NULL);
1468                 g_set_error (error,
1469                              G_FILE_ERROR,
1470                              g_file_error_from_errno (errno),
1471                              _("Failed to open file '%s': %s"),
1472                              utf8_filename ? utf8_filename : "???",
1473                              g_strerror (errno));
1474                 g_free (utf8_filename);
1475 		return NULL;
1476         }
1477 
1478 	loader = gdk_pixbuf_loader_new ();
1479 
1480 	info.width = width;
1481 	info.height = height;
1482         info.preserve_aspect_ratio = preserve_aspect_ratio;
1483 
1484 	g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
1485 
1486 	while (!feof (f) && !ferror (f)) {
1487 		length = fread (buffer, 1, sizeof (buffer), f);
1488 		if (length > 0)
1489 			if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) {
1490 				gdk_pixbuf_loader_close (loader, NULL);
1491 				fclose (f);
1492 				g_object_unref (loader);
1493 				return NULL;
1494 			}
1495 	}
1496 
1497 	fclose (f);
1498 
1499 	if (!gdk_pixbuf_loader_close (loader, error)) {
1500 		g_object_unref (loader);
1501 		return NULL;
1502 	}
1503 
1504 	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1505 
1506 	if (!pixbuf) {
1507                 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1508                                                            NULL, NULL, NULL);
1509 
1510 		g_object_unref (loader);
1511 
1512                 g_set_error (error,
1513                              GDK_PIXBUF_ERROR,
1514                              GDK_PIXBUF_ERROR_FAILED,
1515                              _("Failed to load image '%s': reason not known, probably a corrupt image file"),
1516                              utf8_filename ? utf8_filename : "???");
1517                 g_free (utf8_filename);
1518 		return NULL;
1519 	}
1520 
1521 	g_object_ref (pixbuf);
1522 
1523 	g_object_unref (loader);
1524 
1525 	return pixbuf;
1526 }
1527 
1528 /* Make the name bolder and larger.
1529  * scale_factor can be PANGO_SCALE_X_LARGE, etc.
1530  */
make_heading(GtkWidget * label,double scale_factor)1531 void make_heading(GtkWidget *label, double scale_factor)
1532 {
1533 	PangoAttribute *attr;
1534 	PangoAttrList *list;
1535 
1536 	list = pango_attr_list_new();
1537 
1538 	attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
1539 	attr->start_index = 0;
1540 	attr->end_index = -1;
1541 	pango_attr_list_insert(list, attr);
1542 
1543 	attr = pango_attr_scale_new(scale_factor);
1544 	attr->start_index = 0;
1545 	attr->end_index = -1;
1546 	pango_attr_list_insert(list, attr);
1547 
1548 	gtk_label_set_attributes(GTK_LABEL(label), list);
1549 }
1550 
1551 /* Launch a program using 0launch.
1552  * If button-3 is used, open the GUI with -g.
1553  */
launch_uri(GObject * button,const char * uri)1554 void launch_uri(GObject *button, const char *uri)
1555 {
1556 	const char *argv[] = {"0launch", NULL, NULL, NULL};
1557 	const char *uri_0launch = "/uri/0install/zero-install.sourceforge.net"
1558 				  "/bin/0launch";
1559 
1560 	if (!available_in_path(argv[0]))
1561 	{
1562 		if (access(uri_0launch, X_OK) == 0)
1563 			argv[0] = uri_0launch;
1564 		else
1565 		{
1566 			const char *appname=g_object_get_data(button,
1567 							      "appname");
1568 
1569 			if (appname)
1570 			{
1571 				gchar *path=find_app(appname);
1572 				if(path)
1573 				{
1574 					run_by_path(path);
1575 					g_free(path);
1576 					return;
1577 				}
1578 			}
1579 
1580 			delayed_error(_("This program (%s) cannot be run, "
1581 				"as the 0launch command is not available. "
1582 				"It can be downloaded from here:\n\n"
1583 				"http://0install.net/injector.html"),
1584 				uri);
1585 			return;
1586 		}
1587 	}
1588 
1589 	if (current_event_button() == 3)
1590 	{
1591 		argv[1] = "-g";
1592 		argv[2] = uri;
1593 	}
1594 	else
1595 		argv[1] = uri;
1596 
1597 	rox_spawn(NULL, argv);
1598 }
1599 
button3_button_pressed(GtkButton * button,GdkEventButton * event,gpointer date)1600 static gint button3_button_pressed(GtkButton *button,
1601 				GdkEventButton *event,
1602 				gpointer date)
1603 {
1604 	if (event->button == 3)
1605 	{
1606 		gtk_grab_add(GTK_WIDGET(button));
1607 		gtk_button_pressed(button);
1608 
1609 		return TRUE;
1610 	}
1611 
1612 	return FALSE;
1613 }
1614 
button3_button_released(GtkButton * button,GdkEventButton * event,FilerWindow * filer_window)1615 static gint button3_button_released(GtkButton *button,
1616 				GdkEventButton *event,
1617 				FilerWindow *filer_window)
1618 {
1619 	if (event->button == 3)
1620 	{
1621 		gtk_grab_remove(GTK_WIDGET(button));
1622 		gtk_button_released(button);
1623 
1624 		return TRUE;
1625 	}
1626 
1627 	return FALSE;
1628 }
1629 
allow_right_click(GtkWidget * button)1630 void allow_right_click(GtkWidget *button)
1631 {
1632 	g_signal_connect(button, "button_press_event",
1633 		G_CALLBACK(button3_button_pressed), NULL);
1634 	g_signal_connect(button, "button_release_event",
1635 		G_CALLBACK(button3_button_released), NULL);
1636 }
1637 
1638 /* Return mouse button used in the current event, or -1 if none (no event,
1639  * or not a click).
1640  */
current_event_button(void)1641 gint current_event_button(void)
1642 {
1643 	GdkEventButton *bev;
1644 	gint button = -1;
1645 
1646 	bev = (GdkEventButton *) gtk_get_current_event();
1647 
1648 	if (bev &&
1649 	    (bev->type == GDK_BUTTON_PRESS || bev->type == GDK_BUTTON_RELEASE))
1650 		button = bev->button;
1651 
1652 	gdk_event_free((GdkEvent *) bev);
1653 
1654 	return button;
1655 }
1656 
1657 /* Create a new pixbuf by colourizing 'src' to 'color'. If the function fails,
1658  * 'src' will be returned (with an increased reference count, so it is safe to
1659  * g_object_unref() the return value whether the function fails or not).
1660  */
create_spotlight_pixbuf(GdkPixbuf * src,GdkColor * color)1661 GdkPixbuf *create_spotlight_pixbuf(GdkPixbuf *src, GdkColor *color)
1662 {
1663 	guchar opacity = 192;
1664 	guchar alpha = 255 - opacity;
1665 	GdkPixbuf *dst;
1666 	GdkColorspace colorspace;
1667 	int width, height, src_rowstride, dst_rowstride, x, y;
1668 	int n_channels, bps;
1669 	int r, g, b;
1670 	guchar *spixels, *dpixels, *src_pixels, *dst_pixels;
1671 	gboolean has_alpha;
1672 
1673 	has_alpha = gdk_pixbuf_get_has_alpha(src);
1674 	colorspace = gdk_pixbuf_get_colorspace(src);
1675 	n_channels = gdk_pixbuf_get_n_channels(src);
1676 	bps = gdk_pixbuf_get_bits_per_sample(src);
1677 
1678 	if ((colorspace != GDK_COLORSPACE_RGB) ||
1679 	    (!has_alpha && n_channels != 3) ||
1680 	    (has_alpha && n_channels != 4) ||
1681 	    (bps != 8))
1682 		goto error;
1683 
1684 	width = gdk_pixbuf_get_width(src);
1685 	height = gdk_pixbuf_get_height(src);
1686 
1687 	dst = gdk_pixbuf_new(colorspace, has_alpha, bps, width, height);
1688 	if (dst == NULL)
1689 		goto error;
1690 
1691 	src_pixels = gdk_pixbuf_get_pixels(src);
1692 	dst_pixels = gdk_pixbuf_get_pixels(dst);
1693 	src_rowstride = gdk_pixbuf_get_rowstride(src);
1694 	dst_rowstride = gdk_pixbuf_get_rowstride(dst);
1695 
1696 	r = opacity * (color->red >> 8);
1697 	g = opacity * (color->green >> 8);
1698 	b = opacity * (color->blue >> 8);
1699 
1700 	for (y = 0; y < height; y++)
1701 	{
1702 		spixels = src_pixels + y * src_rowstride;
1703 		dpixels = dst_pixels + y * dst_rowstride;
1704 		for (x = 0; x < width; x++)
1705 		{
1706 			*dpixels++ = (*spixels++ * alpha + r) >> 8;
1707 			*dpixels++ = (*spixels++ * alpha + g) >> 8;
1708 			*dpixels++ = (*spixels++ * alpha + b) >> 8;
1709 			if (has_alpha)
1710 				*dpixels++ = *spixels++;
1711 		}
1712 
1713 	}
1714 	return dst;
1715 
1716 error:
1717 	g_object_ref(src);
1718 	return src;
1719 }
1720 
1721 /* Load the Templates.ui file and build a component. */
get_gtk_builder(gchar ** ids)1722 GtkBuilder *get_gtk_builder(gchar **ids)
1723 {
1724 	GError	*error = NULL;
1725 	char *path;
1726 	GtkBuilder *builder = NULL;
1727 
1728 	builder = gtk_builder_new();
1729 	gtk_builder_set_translation_domain(builder, "ROX-Filer");
1730 
1731 	path = g_build_filename(app_dir, "Templates.ui", NULL);
1732 	if (!gtk_builder_add_objects_from_file(builder, path, ids, &error))
1733 	{
1734 		g_warning("Failed to load builder file %s: %s",
1735 				path, error->message);
1736 		g_error_free(error);
1737 	}
1738 
1739 	g_free(path);
1740 
1741 	return builder;
1742 }
1743 
add_stock_to_menu_item(GtkWidget * item,const char * stock)1744 void add_stock_to_menu_item(GtkWidget *item, const char *stock)
1745 {
1746 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
1747 			gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU));
1748 }
1749