Browser
/* $Id$ */
							/* Copyright (c) 2007-2021 Pierre Pronchery <khorben@defora.org> */
							/* This file is part of DeforaOS Desktop Browser */
							/* Redistribution and use in source and binary forms, with or without
							 * modification, are permitted provided that the following conditions are
							 * met:
							 *
							 * 1. Redistributions of source code must retain the above copyright notice,
							 *    this list of conditions and the following disclaimer.
							 *
							 * 2. Redistributions in binary form must reproduce the above copyright notice,
							 *    this list of conditions and the following disclaimer in the documentation
							 *    and/or other materials provided with the distribution.
							 *
							 * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
							 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
							 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
							 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
							 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
							 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
							 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
							 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
							 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
							 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
							/* TODO:
							 * - let the user define the desktop folder (possibly default to FDO's)
							 * - track multiple selection on delete/properties
							 * - use the MimeHandler class from libDesktop */
							#include <sys/wait.h>
							#include <unistd.h>
							#include <stdlib.h>
							#include <stdio.h>
							#include <string.h>
							#include <errno.h>
							#include <locale.h>
							#include <libintl.h>
							#include <X11/Xlib.h>
							#include <gtk/gtk.h>
							#include <gdk/gdkkeysyms.h>
							#include <gdk/gdkx.h>
							#include <X11/Xatom.h>
							#include <X11/extensions/Xrandr.h>
							#include <System.h>
							#include "desktopicon.h"
							#include "desktopiconwindow.h"
							#include "handler.h"
							#include "desktop.h"
							#include "../../config.h"
							#define _(string) gettext(string)
							#define N_(string) string
							/* constants */
							#ifndef PROGNAME_DESKTOP
							# define PROGNAME_DESKTOP	"desktop"
							#endif
							/* Desktop */
							/* private */
							/* types */
							struct _Desktop
							{
								DesktopPrefs prefs;
								PangoFontDescription * font;
							#if GTK_CHECK_VERSION(3, 0, 0)
								GdkRGBA background;
								GdkRGBA foreground;
							#else
								GdkColor background;
								GdkColor foreground;
							#endif
								/* workarea */
								GdkRectangle window;
								GdkRectangle workarea;
								/* icons */
								DesktopAlignment alignment;
								GtkIconTheme * theme;
								unsigned int icons_size;
								DesktopIconWindow ** icons;
								size_t icons_cnt;
								/* common */
								guint refresh_source;
								char const * home;
								Mime * mime;
								GdkPixbuf * file;
								GdkPixbuf * folder;
								/* handler */
								DesktopHandler * handler;
								/* preferences */
								GtkWidget * pr_window;
								GtkWidget * pr_color;
								GtkWidget * pr_background;
								GtkWidget * pr_background_how;
								GtkWidget * pr_background_extend;
								GtkWidget * pr_ilayout;
								GtkWidget * pr_imonitor;
								GtkWidget * pr_isize;
								GtkWidget * pr_ibcolor;
								GtkWidget * pr_ifcolor;
								GtkWidget * pr_ifont;
								GtkWidget * pr_monitors;
								GtkWidget * pr_monitors_res;
								GtkWidget * pr_monitors_size;
								/* internal */
								GdkScreen * screen;
								GdkDisplay * display;
								GdkWindow * root;
								GtkWidget * desktop;
								GdkWindow * back;
							#if GTK_CHECK_VERSION(3, 0, 0)
								cairo_t * cairo;
							#else
								GdkPixmap * pixmap;
							#endif
							};
							typedef enum _DesktopHows
							{
								DESKTOP_HOW_NONE = 0,
								DESKTOP_HOW_CENTERED,
								DESKTOP_HOW_SCALED,
								DESKTOP_HOW_SCALED_RATIO,
								DESKTOP_HOW_TILED
							} DesktopHows;
							#define DESKTOP_HOW_LAST	DESKTOP_HOW_TILED
							#define DESKTOP_HOW_COUNT	(DESKTOP_HOW_LAST + 1)
							/* constants */
							#define DESKTOPRC		".desktoprc"
							static const char * _desktop_hows[DESKTOP_HOW_COUNT] =
							{
								"none",
								"centered",
								"scaled",
								"scaled_ratio",
								"tiled"
							};
							static const char * _desktop_icons_config[DESKTOP_ICONS_COUNT] =
							{
								"none", "applications", "categories", "files", "homescreen"
							};
							static const char * _desktop_icons[DESKTOP_ICONS_COUNT] =
							{
								N_("Do not draw icons"),
								N_("Applications"),
								N_("Categories"),
								N_("Desktop contents"),
								N_("Home screen")
							};
							/* prototypes */
							static int _desktop_error(Desktop * desktop, char const * message,
									char const * error, int ret);
							/* accessors */
							static Config * _desktop_get_config(Desktop * desktop);
							static int _desktop_get_monitor_properties(Desktop * desktop, int monitor,
									GdkRectangle * geometry, GdkRectangle * workarea,
									int * scale, int * width_mm, int * height_mm);
							static int _desktop_get_properties(Desktop * desktop, GdkRectangle * geometry,
									GdkRectangle * workarea, int * scale,
									int * width_mm, int * height_mm);
							static int _desktop_get_workarea(Desktop * desktop);
							/* useful */
							#if GTK_CHECK_VERSION(3, 0, 0)
							static void _desktop_draw_background(Desktop * desktop, GdkRGBA * color,
									char const * filename, DesktopHows how, gboolean extend);
							#else
							static void _desktop_draw_background(Desktop * desktop, GdkColor * color,
									char const * filename, DesktopHows how, gboolean extend);
							#endif
							/* callbacks */
							static gboolean _desktop_on_preferences_closex(gpointer data);
							static void _desktop_on_preferences_monitors_changed(gpointer data);
							static void _desktop_on_preferences_monitors_refresh(gpointer data);
							static void _desktop_on_preferences_response(GtkWidget * widget, gint response,
									gpointer data);
							static void _desktop_on_preferences_response_apply(gpointer data);
							static void _desktop_on_preferences_response_cancel(gpointer data);
							static void _desktop_on_preferences_response_ok(gpointer data);
							static void _desktop_on_preferences_update_preview(gpointer data);
							static gboolean _desktop_on_refresh(gpointer data);
							/* public */
							/* functions */
							/* desktop_new */
							/* callbacks */
							static void _new_events(Desktop * desktop, GdkWindow * window,
									GdkEventMask mask);
							static void _new_filter(Desktop * desktop, GdkWindow * window);
							static void _new_icons(Desktop * desktop);
							static void _new_window(Desktop * desktop, GdkEventMask * mask);
							static int _on_message(void * data, uint32_t value1, uint32_t value2,
									uint32_t value3);
							#if GTK_CHECK_VERSION(3, 22, 0)
							static void _on_monitor_added(GdkDisplay * display, GdkMonitor * monitor,
									gpointer data);
							static void _on_monitor_removed(GdkDisplay * display, GdkMonitor * monitor,
									gpointer data);
							#endif
							static void _on_popup(gpointer data);
							static void _on_popup_event(gpointer data, XButtonEvent * xbev);
							static gboolean _on_desktop_button_press(GtkWidget * widget,
									GdkEventButton * event, gpointer data);
							static gboolean _on_desktop_key_press(GtkWidget * widget, GdkEventKey * event,
									gpointer data);
							static void _on_realize(gpointer data);
							static GdkFilterReturn _on_root_event(GdkXEvent * xevent, GdkEvent * event,
									gpointer data);
							Desktop * desktop_new(DesktopPrefs * prefs)
							{
								Desktop * desktop;
								char const * home;
							#if !GTK_CHECK_VERSION(2, 24, 0)
								gint depth;
							#endif
								GdkEventMask mask = GDK_PROPERTY_CHANGE_MASK;
								if((desktop = object_new(sizeof(*desktop))) == NULL)
									return NULL;
								memset(desktop, 0, sizeof(*desktop));
								/* set default foreground to white */
								memset(&desktop->foreground, 0xff, sizeof(desktop->foreground));
								desktop->prefs.alignment = DESKTOP_ALIGNMENT_VERTICAL;
								desktop->prefs.icons = DESKTOP_ICONS_FILES;
								desktop->prefs.monitor = -1;
								desktop->prefs.popup = -1;
								desktop->prefs.window = -1;
								if(prefs != NULL)
									desktop->prefs = *prefs;
								desktop->font = NULL;
								/* workarea */
								desktop->screen = gdk_screen_get_default();
								desktop->display = gdk_screen_get_display(desktop->screen);
							#if GTK_CHECK_VERSION(3, 22, 0)
								g_signal_connect(desktop->display, "monitor-added", G_CALLBACK(
											_on_monitor_added), desktop);
								g_signal_connect(desktop->display, "monitor-removed", G_CALLBACK(
											_on_monitor_removed), desktop);
							#endif
								desktop->root = gdk_screen_get_root_window(desktop->screen);
								/* icons */
								desktop->icons_size = 0;
								_new_icons(desktop);
								if((home = getenv("HOME")) == NULL
										&& (home = g_get_home_dir()) == NULL)
									home = "/";
								desktop->home = home;
								desktop->mime = mime_new(NULL);
								/* handler */
								desktop->handler = desktophandler_new(desktop, DESKTOP_ICONS_NONE);
								/* check for errors */
								if(desktop->mime == NULL || desktop->handler == NULL)
								{
									desktop_delete(desktop);
									return NULL;
								}
								/* internal */
								desktop_message_register(NULL, DESKTOP_CLIENT_MESSAGE, _on_message,
										desktop);
								/* query the root window */
							#if GTK_CHECK_VERSION(2, 24, 0)
								gdk_window_get_position(desktop->root, &desktop->window.x,
										&desktop->window.y);
								desktop->window.width = gdk_window_get_width(desktop->root);
								desktop->window.height = gdk_window_get_height(desktop->root);
							#else
								gdk_window_get_geometry(desktop->root, &desktop->window.x,
										&desktop->window.y, &desktop->window.width,
										&desktop->window.height, &depth);
							#endif
								_new_window(desktop, &mask);
								/* manage events on the root window */
								_new_events(desktop, desktop->root, mask);
								_new_filter(desktop, desktop->root);
								return desktop;
							}
							static void _new_events(Desktop * desktop, GdkWindow * window,
									GdkEventMask mask)
							{
								(void) desktop;
								mask = gdk_window_get_events(window) | mask;
								gdk_window_set_events(window, mask);
							}
							static void _new_filter(Desktop * desktop, GdkWindow * window)
							{
								gdk_window_add_filter(window, _on_root_event, desktop);
							}
							static void _new_icons(Desktop * desktop)
							{
								const char * file[] = { "gnome-fs-regular",
							#if GTK_CHECK_VERSION(2, 6, 0)
									GTK_STOCK_FILE,
							#endif
									GTK_STOCK_MISSING_IMAGE, NULL };
								const char * folder[] = { "gnome-fs-directory",
							#if GTK_CHECK_VERSION(2, 6, 0)
									GTK_STOCK_DIRECTORY,
							#endif
									GTK_STOCK_MISSING_IMAGE, NULL };
								char const ** p;
								GdkPixbuf * icon;
								desktop->theme = gtk_icon_theme_get_default();
								for(p = file, icon = NULL; *p != NULL && icon == NULL; p++)
									icon = gtk_icon_theme_load_icon(desktop->theme, *p,
											desktop->icons_size, 0, NULL);
								desktop->file = icon;
								for(p = folder, icon = NULL; *p != NULL && icon == NULL; p++)
									icon = gtk_icon_theme_load_icon(desktop->theme, *p,
											desktop->icons_size, 0, NULL);
								desktop->folder = icon;
							}
							static void _new_window(Desktop * desktop, GdkEventMask * mask)
							{
								Config * config;
								String const * p;
								if(desktop->prefs.window < 0
										&& (config = _desktop_get_config(desktop)) != NULL)
								{
									if((p = config_get(config, "background", "window")) != NULL)
										desktop->prefs.window = strtol(p, NULL, 10);
									config_delete(config);
								}
							#if GTK_CHECK_VERSION(3, 0, 0)
								if(desktop->prefs.window != 0)
							#else
								if(desktop->prefs.window > 0)
							#endif
								{
									/* create the desktop window */
									desktop->desktop = gtk_window_new(GTK_WINDOW_TOPLEVEL);
									gtk_window_set_default_size(GTK_WINDOW(desktop->desktop),
											desktop->window.width, desktop->window.height);
									gtk_window_set_type_hint(GTK_WINDOW(desktop->desktop),
											GDK_WINDOW_TYPE_HINT_DESKTOP);
									/* support pop-up menus on the desktop window if enabled */
									if(desktop->prefs.popup)
									{
										g_signal_connect(desktop->desktop, "button-press-event",
												G_CALLBACK(_on_desktop_button_press),
												desktop);
										g_signal_connect(desktop->desktop, "key-press-event",
												G_CALLBACK(_on_desktop_key_press),
												desktop);
									}
									/* draw the icons and background when realized */
									g_signal_connect_swapped(desktop->desktop, "realize",
											G_CALLBACK(_on_realize), desktop);
									gtk_window_move(GTK_WINDOW(desktop->desktop), desktop->window.x,
											desktop->window.y);
									gtk_widget_show(desktop->desktop);
								}
								else
								{
									desktop->desktop = NULL;
									desktop->back = desktop->root;
									/* draw the icons and background when idle */
									desktop_reset(desktop);
									/* support pop-up menus on the root window if enabled */
									if(desktop->prefs.popup)
										*mask |= GDK_BUTTON_PRESS_MASK;
								}
							}
							static int _on_message(void * data, uint32_t value1, uint32_t value2,
									uint32_t value3)
							{
								Desktop * desktop = data;
								DesktopMessage message;
								DesktopAlignment alignment;
								DesktopIcons icons;
								DesktopLayout layout;
								(void) value3;
								switch((message = value1))
								{
									case DESKTOP_MESSAGE_SET_ALIGNMENT:
										alignment = value2;
										desktop_set_alignment(desktop, alignment);
										desktop_icons_align(desktop);
										break;
									case DESKTOP_MESSAGE_SET_ICONS:
										icons = value2;
										desktop_set_icons(desktop, icons);
										break;
									case DESKTOP_MESSAGE_SET_LAYOUT:
										layout = value2;
										desktop_set_layout(desktop, layout);
										break;
									case DESKTOP_MESSAGE_SHOW:
										if(value2 == DESKTOP_SHOW_SETTINGS)
											desktop_show_preferences(desktop);
										break;
								}
								return GDK_FILTER_CONTINUE;
							}
							#if GTK_CHECK_VERSION(3, 22, 0)
							static void _on_monitor_added(GdkDisplay * display, GdkMonitor * monitor,
									gpointer data)
							{
								Desktop * desktop = data;
								(void) monitor;
								if(desktop->display != display)
									return;
								desktop_reset(desktop);
								if(desktop->pr_window != NULL)
									_desktop_on_preferences_monitors_refresh(desktop);
							}
							static void _on_monitor_removed(GdkDisplay * display, GdkMonitor * monitor,
									gpointer data)
							{
								Desktop * desktop = data;
								_on_monitor_added(display, monitor, desktop);
							}
							#endif
							static void _on_popup(gpointer data)
							{
								Desktop * desktop = data;
								_on_popup_event(desktop, NULL);
							}
							static void _on_popup_event(gpointer data, XButtonEvent * xbev)
							{
								Desktop * desktop = data;
								desktophandler_popup(desktop->handler, xbev);
							}
							static gboolean _on_desktop_button_press(GtkWidget * widget,
									GdkEventButton * event, gpointer data)
							{
								Desktop * desktop = data;
								(void) widget;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								if(event->type != GDK_BUTTON_PRESS || event->button != 3)
									return FALSE;
								_on_popup(desktop);
								return TRUE;
							}
							static gboolean _on_desktop_key_press(GtkWidget * widget, GdkEventKey * event,
									gpointer data)
							{
								Desktop * desktop = data;
								DesktopIcon ** selected;
								(void) widget;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								if(event->type != GDK_KEY_PRESS)
									return FALSE;
								if(event->keyval == GDK_KEY_uparrow)
								{
									if((selected = desktop_get_icons_selected(desktop)) == NULL)
										return TRUE;
									desktop_unselect_all(desktop);
									desktop_select_above(desktop, selected[0]);
									free(selected);
								}
								else if(event->keyval == GDK_KEY_downarrow)
								{
									if((selected = desktop_get_icons_selected(desktop)) == NULL)
										return TRUE;
									desktop_unselect_all(desktop);
									desktop_select_under(desktop, selected[0]);
									free(selected);
								}
								else /* not handling it */
									return FALSE;
								return TRUE;
							}
							static void _on_realize(gpointer data)
							{
								Desktop * desktop = data;
								GdkEventMask mask = desktop->prefs.popup ? GDK_BUTTON_PRESS_MASK : 0;
							#if GTK_CHECK_VERSION(2, 14, 0)
								desktop->back = gtk_widget_get_window(desktop->desktop);
							#else
								desktop->back = desktop->desktop->window;
							#endif
								/* support pop-up menus on the desktop window if enabled */
								if(mask != 0)
									_new_events(desktop, desktop->back, mask);
								desktop_reset(desktop);
							}
							static GdkFilterReturn _event_button_press(XButtonEvent * xbev,
									Desktop * desktop);
							static GdkFilterReturn _event_configure(XConfigureEvent * xevent,
									Desktop * desktop);
							static GdkFilterReturn _event_property(XPropertyEvent * xevent,
									Desktop * desktop);
							static GdkFilterReturn _on_root_event(GdkXEvent * xevent, GdkEvent * event,
									gpointer data)
							{
								Desktop * desktop = data;
								XEvent * xev = xevent;
								(void) event;
								if(xev->type == ButtonPress)
									return _event_button_press(xevent, desktop);
								else if(xev->type == ConfigureNotify)
									return _event_configure(xevent, desktop);
								else if(xev->type == PropertyNotify)
									return _event_property(xevent, desktop);
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() %d\n", __func__, xev->type);
							#endif
								return GDK_FILTER_CONTINUE;
							}
							static GdkFilterReturn _event_button_press(XButtonEvent * xbev,
									Desktop * desktop)
							{
								_on_popup_event(desktop, xbev);
								return GDK_FILTER_CONTINUE;
							}
							static GdkFilterReturn _event_configure(XConfigureEvent * xevent,
									Desktop * desktop)
							{
								desktop->window.x = xevent->x;
								desktop->window.y = xevent->y;
								desktop->window.width = xevent->width;
								desktop->window.height = xevent->height;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() (%dx%d) @ (%d,%d))\n", __func__,
										desktop->window.width, desktop->window.height,
										desktop->window.x, desktop->window.y);
							#endif
								/* FIXME run it directly? */
								desktop_reset(desktop);
								return GDK_FILTER_CONTINUE;
							}
							static GdkFilterReturn _event_property(XPropertyEvent * xevent,
									Desktop * desktop)
							{
								Atom atom;
								atom = gdk_x11_get_xatom_by_name("_NET_WORKAREA");
								if(xevent->atom != atom)
									return GDK_FILTER_CONTINUE;
								_desktop_get_workarea(desktop);
								return GDK_FILTER_CONTINUE;
							}
							/* desktop_delete */
							void desktop_delete(Desktop * desktop)
							{
								if(desktop->refresh_source != 0)
									g_source_remove(desktop->refresh_source);
								desktop_icons_remove_all(desktop);
								if(desktop->handler != NULL)
									desktophandler_delete(desktop->handler);
								if(desktop->folder != NULL)
									g_object_unref(desktop->folder);
								if(desktop->file != NULL)
									g_object_unref(desktop->file);
								if(desktop->mime != NULL)
									mime_delete(desktop->mime);
								if(desktop->desktop != NULL)
									gtk_widget_destroy(desktop->desktop);
								if(desktop->font != NULL)
									pango_font_description_free(desktop->font);
								object_delete(desktop);
							}
							/* accessors */
							/* desktop_get_drag_data */
							int desktop_get_drag_data(Desktop * desktop, GtkSelectionData * seldata)
							{
							#if GTK_CHECK_VERSION(3, 0, 0)
								/* FIXME implement */
								return -1;
							#else
								int ret = 0;
								size_t i;
								DesktopIcon * icon;
								size_t len;
								char const * path;
								unsigned char * p;
								seldata->format = 0;
								seldata->data = NULL;
								seldata->length = 0;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									if(desktopicon_get_selected(icon) != TRUE)
										continue;
									if((path = desktopicon_get_path(icon)) == NULL)
										continue;
									len = strlen(path + 1);
									if((p = realloc(seldata->data, seldata->length + len)) == NULL)
									{
										ret = -error_set_code(1, "%s", strerror(errno));
										continue;
									}
									seldata->data = p;
									memcpy(&p[seldata->length], path, len);
									seldata->length += len;
								}
								return ret;
							#endif
							}
							/* desktop_get_file */
							GdkPixbuf * desktop_get_file(Desktop * desktop)
							{
								g_object_ref(desktop->file);
								return desktop->file;
							}
							/* desktop_get_folder */
							GdkPixbuf * desktop_get_folder(Desktop * desktop)
							{
								g_object_ref(desktop->folder);
								return desktop->folder;
							}
							/* desktop_get_handler */
							DesktopHandler * desktop_get_handler(Desktop * desktop)
							{
								return desktop->handler;
							}
							/* desktop_get_home */
							String const * desktop_get_home(Desktop * desktop)
							{
								return desktop->home;
							}
							/* desktop_get_icon_size */
							void desktop_get_icon_size(Desktop * desktop, unsigned int * width,
									unsigned int * height, unsigned int * size)
							{
								unsigned int h;
								if(width != NULL)
									*width = desktop->icons_size * 2;
								if(height != NULL)
								{
									if((h = pango_font_description_get_size(desktop->font)) > 0)
										*height = desktop->icons_size + ((h >> 10) * 3 + 8);
									else
										*height = desktop->icons_size * 2;
								}
								if(size != NULL)
									*size = desktop->icons_size;
							}
							/* desktop_get_icons_selected */
							DesktopIcon ** desktop_get_icons_selected(Desktop * desktop)
							{
								DesktopIcon ** icons = NULL;
								size_t cnt = 0;
								DesktopIcon * desktopicon;
								DesktopIcon ** p;
								size_t i;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									desktopicon = desktopiconwindow_get_icon(desktop->icons[i]);
									if(desktopicon_get_selected(desktopicon) == FALSE)
										continue;
									if((p = realloc(icons, sizeof(*p) * (cnt + 1))) == NULL)
									{
										desktop_perror(NULL, "realloc", -errno);
										free(icons);
										return NULL;
									}
									icons = p;
									icons[cnt++] = desktopicon;
								}
								if(icons != NULL)
									icons[cnt] = NULL;
								return icons;
							}
							/* desktop_get_mime */
							Mime * desktop_get_mime(Desktop * desktop)
							{
								if(desktop->handler == NULL)
									return NULL;
								return desktop->mime;
							}
							/* desktop_get_theme */
							GtkIconTheme * desktop_get_theme(Desktop * desktop)
							{
								return desktop->theme;
							}
							/* desktop_get_window */
							GtkWidget * desktop_get_window(Desktop * desktop)
							{
								return desktop->desktop;
							}
							/* desktop_set_alignment */
							void desktop_set_alignment(Desktop * desktop, DesktopAlignment alignment)
							{
								desktop->alignment = alignment;
							}
							/* desktop_set_icons */
							void desktop_set_icons(Desktop * desktop, DesktopIcons icons)
							{
								desktophandler_set_icons(desktop->handler, icons);
								desktop->prefs.icons = icons;
								desktop_refresh(desktop);
							}
							/* desktop_set_layout */
							int desktop_set_layout(Desktop * desktop, DesktopLayout layout)
							{
								XRRScreenConfiguration * sc;
								Rotation r;
								SizeID size;
								sc = XRRGetScreenInfo(GDK_DISPLAY_XDISPLAY(desktop->display),
										GDK_WINDOW_XID(desktop->root));
								size = XRRConfigCurrentConfiguration(sc, &r);
								switch(layout)
								{
									case DESKTOP_LAYOUT_NORMAL:
									case DESKTOP_LAYOUT_LANDSCAPE:
										r = RR_Rotate_0;
										break;
									case DESKTOP_LAYOUT_PORTRAIT:
										r = RR_Rotate_90;
										break;
									case DESKTOP_LAYOUT_ROTATE:
										r <<= 1;
										r = (r > 16) ? 1 : r;
										break;
									case DESKTOP_LAYOUT_TOGGLE:
										r = (r != RR_Rotate_0) ? RR_Rotate_0 : RR_Rotate_90;
										break;
								}
								gdk_error_trap_push();
								XRRSetScreenConfig(GDK_DISPLAY_XDISPLAY(desktop->display), sc,
										GDK_WINDOW_XID(desktop->root), size, r, CurrentTime);
								return gdk_error_trap_pop();
							}
							/* useful */
							/* desktop_error */
							int desktop_error(Desktop * desktop, char const * message, char const * error,
									int ret)
							{
								return _desktop_error(desktop, message, error, ret);
							}
							/* desktop_perror */
							int desktop_perror(Desktop * desktop, char const * message, int ret)
							{
								return _desktop_error(desktop, message, strerror(errno), ret);
							}
							/* desktop_serror */
							int desktop_serror(Desktop * desktop, char const * message, int ret)
							{
								return _desktop_error(desktop, message, error_get(NULL), ret);
							}
							/* desktop_refresh */
							static gboolean _refresh_on_idle(gpointer data);
							void desktop_refresh(Desktop * desktop)
							{
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								if(desktop->refresh_source != 0)
									g_source_remove(desktop->refresh_source);
								desktop->refresh_source = g_idle_add(_refresh_on_idle, desktop);
							}
							static gboolean _refresh_on_idle(gpointer data)
							{
								Desktop * desktop = data;
								size_t i;
								DesktopIcon * icon;
								desktop->refresh_source = 0;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									desktopicon_set_updated(icon, FALSE);
								}
								desktophandler_refresh(desktop->handler);
								return FALSE;
							}
							/* desktop_reset */
							static void _reset_background(Desktop * desktop, Config * config);
							static void _reset_icons(Desktop * desktop, Config * config);
							static void _reset_icons_colors(Desktop * desktop, Config * config);
							static void _reset_icons_font(Desktop * desktop, Config * config);
							static void _reset_icons_size(Desktop * desktop, Config * config);
							static void _reset_icons_monitor(Desktop * desktop, Config * config);
							/* callbacks */
							static gboolean _reset_on_idle(gpointer data);
							void desktop_reset(Desktop * desktop)
							{
								if(desktop->refresh_source != 0)
									g_source_remove(desktop->refresh_source);
								desktop->refresh_source = g_idle_add(_reset_on_idle, desktop);
							}
							static void _reset_background(Desktop * desktop, Config * config)
							{
							#if GTK_CHECK_VERSION(3, 0, 0)
								GdkRGBA color = { 0.0, 0.0, 0.0, 0.0 };
							#else
								GdkColor color = { 0, 0, 0, 0 };
							#endif
								char const * filename;
								DesktopHows how = DESKTOP_HOW_SCALED;
								gboolean extend = FALSE;
								size_t i;
								char const * p;
								if((p = config_get(config, "background", "color")) != NULL)
							#if GTK_CHECK_VERSION(3, 0, 0)
									gdk_rgba_parse(&color, p);
							#else
									gdk_color_parse(p, &color);
							#endif
								filename = config_get(config, "background", "wallpaper");
								if((p = config_get(config, "background", "how")) != NULL)
									for(i = 0; i < DESKTOP_HOW_COUNT; i++)
										if(strcmp(_desktop_hows[i], p) == 0)
											how = i;
								if((p = config_get(config, "background", "extend")) != NULL)
									extend = strtol(p, NULL, 10) ? TRUE : FALSE;
								_desktop_draw_background(desktop, &color, filename, how, extend);
							}
							static void _reset_icons(Desktop * desktop, Config * config)
							{
								String const * p;
								size_t i;
								DesktopIcon * icon;
								_reset_icons_colors(desktop, config);
								_reset_icons_font(desktop, config);
								_reset_icons_size(desktop, config);
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									desktopicon_set_background(icon, &desktop->background);
									desktopicon_set_font(icon, desktop->font);
									desktopicon_set_foreground(icon, &desktop->foreground);
								}
								_reset_icons_monitor(desktop, config);
								/* icons layout */
								if(desktop->prefs.icons < 0
										&& (p = config_get(config, "icons", "layout")) != NULL)
								{
									for(i = 0; i < DESKTOP_ICONS_COUNT; i++)
										if(strcmp(_desktop_icons_config[i], p) == 0)
										{
											desktop->prefs.icons = i;
											break;
										}
								}
								if(desktop->prefs.icons < 0
										|| desktop->prefs.icons >= DESKTOP_ICONS_COUNT)
									desktop->prefs.icons = DESKTOP_ICONS_FILES;
								/* icons alignment */
								if(desktop->prefs.alignment < 0)
									desktop->prefs.alignment = (desktop->prefs.icons
											== DESKTOP_ICONS_FILES)
										? DESKTOP_ALIGNMENT_VERTICAL
										: DESKTOP_ALIGNMENT_HORIZONTAL;
							}
							static void _reset_icons_colors(Desktop * desktop, Config * config)
							{
								String const * p;
							#if GTK_CHECK_VERSION(3, 0, 0)
								GdkRGBA color;
							#else
								GdkColor color;
							#endif
								if((p = config_get(config, "icons", "background")) != NULL)
								{
							#if GTK_CHECK_VERSION(3, 0, 0)
									gdk_rgba_parse(&color, p);
							#else
									gdk_color_parse(p, &color);
							#endif
									desktop->background = color;
								}
								if((p = config_get(config, "icons", "foreground")) != NULL)
								{
							#if GTK_CHECK_VERSION(3, 0, 0)
									gdk_rgba_parse(&color, p);
							#else
									gdk_color_parse(p, &color);
							#endif
									desktop->foreground = color;
								}
							}
							static void _reset_icons_font(Desktop * desktop, Config * config)
							{
								String const * p;
								if(desktop->font != NULL)
									pango_font_description_free(desktop->font);
								if((p = config_get(config, "icons", "font")) != NULL)
									desktop->font = pango_font_description_from_string(p);
								else
								{
									desktop->font = pango_font_description_new();
									pango_font_description_set_weight(desktop->font,
											PANGO_WEIGHT_BOLD);
								}
							}
							static void _reset_icons_monitor(Desktop * desktop, Config * config)
							{
								String const * p;
								char * q;
								/* icons monitor */
								if(desktop->prefs.monitor < 0
										&& (p = config_get(config, "icons", "monitor")) != NULL)
								{
									desktop->prefs.monitor = strtol(p, &q, 10);
									if(p[0] == '\0' || *q != '\0')
										desktop->prefs.monitor = -1;
								}
							}
							static void _reset_icons_size(Desktop * desktop, Config * config)
							{
								String const * p;
								char * q;
								int size;
								/* icons size */
								if(desktop->icons_size == 0)
								{
									if((p = config_get(config, "icons", "size")) == NULL
											|| (size = strtol(p, &q, 10)) <= 0)
										size = DESKTOPICON_ICON_SIZE;
									desktop->icons_size = size;
								}
							}
							/* callbacks */
							static gboolean _reset_on_idle(gpointer data)
							{
								Desktop * desktop = data;
								Config * config;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								desktop->refresh_source = 0;
								if((config = _desktop_get_config(desktop)) == NULL)
									return FALSE;
								_reset_background(desktop, config);
								_reset_icons(desktop, config);
								config_delete(config);
								_desktop_get_workarea(desktop);
								desktop_set_icons(desktop, desktop->prefs.icons);
								g_idle_add(_desktop_on_refresh, desktop);
								return FALSE;
							}
							/* desktop_icon_add */
							void desktop_icon_add(Desktop * desktop, DesktopIcon * icon, gboolean align)
							{
								DesktopIconWindow * window;
								DesktopIconWindow ** p;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(\"%s\", %s)\n", __func__,
										desktopicon_get_name(icon),
										align ? "TRUE" : "FALSE");
							#endif
								if((p = realloc(desktop->icons, sizeof(*p) * (desktop->icons_cnt + 1)))
										== NULL)
								{
									desktop_perror(desktop, desktopicon_get_name(icon), 1);
									return;
								}
								desktop->icons = p;
								if((window = desktopiconwindow_new(icon)) == NULL)
								{
									desktop_serror(desktop, desktopicon_get_name(icon), 1);
									return;
								}
								desktop->icons[desktop->icons_cnt++] = window;
								desktopicon_set_background(icon, &desktop->background);
								desktopicon_set_font(icon, desktop->font);
								desktopicon_set_foreground(icon, &desktop->foreground);
								if(align)
									desktop_icons_align(desktop);
								desktopiconwindow_show(window);
							}
							/* desktop_icon_remove */
							int desktop_icon_remove(Desktop * desktop, DesktopIcon * icon, gboolean align)
							{
								size_t i;
								DesktopIcon * j;
								DesktopIconWindow ** p;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									j = desktopiconwindow_get_icon(desktop->icons[i]);
									if(j != icon)
										continue;
									desktopiconwindow_delete(desktop->icons[i]);
									for(desktop->icons_cnt--; i < desktop->icons_cnt; i++)
										desktop->icons[i] = desktop->icons[i + 1];
									if((p = realloc(desktop->icons, sizeof(*p)
													* (desktop->icons_cnt)))
											!= NULL)
										desktop->icons = p; /* we can ignore errors... */
									else if(desktop->icons_cnt == 0)
										desktop->icons = NULL; /* ...except when it's not one */
									if(align)
										desktop_icons_align(desktop);
									return 0;
								}
								return 1;
							}
							/* desktop_icons_align */
							static int _align_compare(const void * a, const void * b);
							static void _align_horizontal(Desktop * desktop);
							static void _align_vertical(Desktop * desktop);
							void desktop_icons_align(Desktop * desktop)
							{
								qsort(desktop->icons, desktop->icons_cnt, sizeof(void *),
										_align_compare);
								switch(desktop->alignment)
								{
									case DESKTOP_ALIGNMENT_VERTICAL:
										_align_vertical(desktop);
										break;
									case DESKTOP_ALIGNMENT_HORIZONTAL:
										_align_horizontal(desktop);
										break;
								}
							}
							static int _align_compare(const void * a, const void * b)
							{
								DesktopIconWindow * wina = *(DesktopIconWindow**)a;
								DesktopIconWindow * winb = *(DesktopIconWindow**)b;
								DesktopIcon * icona;
								DesktopIcon * iconb;
								gboolean firsta;
								gboolean firstb;
								gboolean dira;
								gboolean dirb;
								icona = desktopiconwindow_get_icon(wina);
								iconb = desktopiconwindow_get_icon(winb);
								firsta = desktopicon_get_first(icona);
								firstb = desktopicon_get_first(iconb);
								if(firsta && !firstb)
									return -1;
								else if(!firsta && firstb)
									return 1;
								dira = desktopicon_get_isdir(icona);
								dirb = desktopicon_get_isdir(iconb);
								if(dira && !dirb)
									return -1;
								else if(!dira && dirb)
									return 1;
								return strcmp(desktopicon_get_name(icona), desktopicon_get_name(iconb));
							}
							static void _align_horizontal(Desktop * desktop)
							{
								size_t i;
								unsigned int x = desktop->workarea.x;
								unsigned int y = desktop->workarea.y;
								unsigned int width = x + desktop->workarea.width;
								unsigned int iwidth;
								unsigned int iheight;
								desktop_get_icon_size(desktop, &iwidth, &iheight, NULL);
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									if(x + iwidth > width)
									{
										y += iheight;
										x = desktop->workarea.x;
									}
									desktopiconwindow_move(desktop->icons[i], x, y);
									x += iwidth;
								}
							}
							static void _align_vertical(Desktop * desktop)
							{
								size_t i;
								unsigned int x = desktop->workarea.x;
								unsigned int y = desktop->workarea.y;
								unsigned int height = desktop->workarea.y + desktop->workarea.height;
								unsigned int iwidth;
								unsigned int iheight;
								desktop_get_icon_size(desktop, &iwidth, &iheight, NULL);
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									if(y + iheight > height)
									{
										x += iwidth;
										y = desktop->workarea.y;
									}
									desktopiconwindow_move(desktop->icons[i], x, y);
									y += iheight;
								}
							}
							/* desktop_icons_cleanup */
							void desktop_icons_cleanup(Desktop * desktop, gboolean align)
							{
								size_t i;
								DesktopIcon * icon;
								for(i = 0; i < desktop->icons_cnt;)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									if(desktopicon_get_immutable(icon) == TRUE)
										i++;
									else if(desktopicon_get_updated(icon) != TRUE)
										desktop_icon_remove(desktop, icon, FALSE);
									else
									{
										desktopicon_set_updated(icon, FALSE);
										i++;
									}
								}
								if(align)
									desktop_icons_align(desktop);
							}
							/* desktop_icons_lookup */
							DesktopIcon * desktop_icons_lookup(Desktop * desktop, String const * name)
							{
								size_t i;
								DesktopIcon * icon;
								char const * p;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									if(desktopicon_get_updated(icon) == TRUE)
										continue;
									if((p = desktopicon_get_path(icon)) == NULL
											|| (p = strrchr(p, '/')) == NULL)
										continue;
									if(strcmp(name, ++p) != 0)
										continue;
									return icon;
								}
								return NULL;
							}
							/* desktop_icons_remove_all */
							void desktop_icons_remove_all(Desktop * desktop)
							{
								size_t i;
								for(i = 0; i < desktop->icons_cnt; i++)
									desktopiconwindow_delete(desktop->icons[i]);
								desktop->icons_cnt = 0;
								free(desktop->icons);
								desktop->icons = NULL;
							}
							/* desktop_select_all */
							void desktop_select_all(Desktop * desktop)
							{
								size_t i;
								DesktopIcon * icon;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									desktopicon_set_selected(icon, TRUE);
								}
							}
							/* desktop_select_above */
							void desktop_select_above(Desktop * desktop, DesktopIcon * icon)
								/* FIXME icons may be wrapped */
							{
								size_t i;
								DesktopIcon * j;
								for(i = 1; i < desktop->icons_cnt; i++)
								{
									j = desktopiconwindow_get_icon(desktop->icons[i]);
									if(j == icon)
									{
										desktopicon_set_selected(j, TRUE);
										return;
									}
								}
							}
							/* desktop_select_under */
							void desktop_select_under(Desktop * desktop, DesktopIcon * icon)
								/* FIXME icons may be wrapped */
							{
								size_t i;
								DesktopIcon * j;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									j = desktopiconwindow_get_icon(desktop->icons[i]);
									if(j == icon && i + 1 < desktop->icons_cnt)
									{
										desktopicon_set_selected(j, TRUE);
										return;
									}
								}
							}
							/* desktop_show_preferences */
							static void _preferences_background(Desktop * desktop, GtkWidget * notebook);
							static void _preferences_icons(Desktop * desktop, GtkWidget * notebook);
							static void _preferences_monitors(Desktop * desktop, GtkWidget * notebook);
							static void _preferences_set(Desktop * desktop);
							static void _preferences_set_color(Config * config, char const * section,
									char const * variable, char const * fallback,
									GtkWidget * widget);
							void desktop_show_preferences(Desktop * desktop)
							{
								GtkWidget * vbox;
								GtkWidget * notebook;
								_on_popup_event(desktop, NULL);
								if(desktop->pr_window != NULL)
								{
									gtk_window_present(GTK_WINDOW(desktop->pr_window));
									return;
								}
								/* window */
								desktop->pr_window = gtk_dialog_new_with_buttons(
										_("Desktop preferences"), NULL,
										GTK_DIALOG_DESTROY_WITH_PARENT,
										GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
										GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
										GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
								gtk_window_set_position(GTK_WINDOW(desktop->pr_window),
										GTK_WIN_POS_CENTER);
								gtk_window_set_resizable(GTK_WINDOW(desktop->pr_window), FALSE);
								if(desktop->desktop != NULL)
									gtk_window_set_transient_for(GTK_WINDOW(desktop->pr_window),
											GTK_WINDOW(desktop->desktop));
								g_signal_connect_swapped(desktop->pr_window, "delete-event",
										G_CALLBACK(_desktop_on_preferences_closex), desktop);
								g_signal_connect(desktop->pr_window, "response", G_CALLBACK(
											_desktop_on_preferences_response), desktop);
							#if GTK_CHECK_VERSION(2, 14, 0)
								vbox = gtk_dialog_get_content_area(GTK_DIALOG(desktop->pr_window));
							#else
								vbox = GTK_DIALOG(desktop->pr_window)->vbox;
							#endif
								/* notebook */
								notebook = gtk_notebook_new();
								gtk_container_set_border_width(GTK_CONTAINER(notebook), 4);
								_preferences_background(desktop, notebook);
								_preferences_icons(desktop, notebook);
								_preferences_monitors(desktop, notebook);
								_desktop_on_preferences_monitors_refresh(desktop);
								gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
								/* container */
								_preferences_set(desktop);
								gtk_widget_show_all(desktop->pr_window);
							}
							static void _preferences_background(Desktop * desktop, GtkWidget * notebook)
							{
								GtkSizeGroup * group;
								GtkWidget * vbox2;
								GtkWidget * hbox;
								GtkWidget * label;
								GtkFileFilter * filter;
								vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
								gtk_container_set_border_width(GTK_CONTAINER(vbox2), 4);
								group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Default color: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_color = gtk_color_button_new();
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_color, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Wallpaper: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_background = gtk_file_chooser_button_new(_("Background"),
										GTK_FILE_CHOOSER_ACTION_OPEN);
								filter = gtk_file_filter_new();
								gtk_file_filter_set_name(filter, _("Picture files"));
								gtk_file_filter_add_mime_type(filter, "image/bmp");
								gtk_file_filter_add_mime_type(filter, "image/gif");
								gtk_file_filter_add_mime_type(filter, "image/jpeg");
								gtk_file_filter_add_mime_type(filter, "image/pbm");
								gtk_file_filter_add_mime_type(filter, "image/png");
								gtk_file_filter_add_mime_type(filter, "image/svg+xml");
								gtk_file_filter_add_mime_type(filter, "image/xpm");
								gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(desktop->pr_background),
										filter);
								filter = gtk_file_filter_new();
								gtk_file_filter_set_name(filter, _("All files"));
								gtk_file_filter_add_pattern(filter, "*");
								gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(desktop->pr_background),
										filter);
								gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(
											desktop->pr_background), gtk_image_new());
								g_signal_connect_swapped(desktop->pr_background, "update-preview",
										G_CALLBACK(_desktop_on_preferences_update_preview),
										desktop);
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_background, TRUE, TRUE,
										0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Position: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
							#if GTK_CHECK_VERSION(2, 24, 0)
								desktop->pr_background_how = gtk_combo_box_text_new();
								gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(
											desktop->pr_background_how), _("Do not draw"));
								gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(
											desktop->pr_background_how), _("Centered"));
								gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(
											desktop->pr_background_how), _("Scaled"));
								gtk_combo_box_text_append_text(
										GTK_COMBO_BOX_TEXT(desktop->pr_background_how),
										_("Scaled (keep ratio)"));
								gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(
											desktop->pr_background_how), _("Tiled"));
							#else
								desktop->pr_background_how = gtk_combo_box_new_text();
								gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_background_how),
										_("Do not draw"));
								gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_background_how),
										_("Centered"));
								gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_background_how),
										_("Scaled"));
								gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_background_how),
										_("Scaled (keep ratio)"));
								gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_background_how),
										_("Tiled"));
							#endif
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_background_how, TRUE,
										TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								desktop->pr_background_extend = gtk_check_button_new_with_mnemonic(
										_("E_xtend background to all monitors"));
								gtk_box_pack_start(GTK_BOX(vbox2), desktop->pr_background_extend, FALSE,
										TRUE, 0);
								gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox2, gtk_label_new(
											_("Background")));
							}
							static void _preferences_icons(Desktop * desktop, GtkWidget * notebook)
							{
								GtkSizeGroup * group;
								GtkWidget * vbox2;
								GtkWidget * hbox;
								GtkWidget * label;
								size_t i;
								vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
								gtk_container_set_border_width(GTK_CONTAINER(vbox2), 4);
								group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
								/* icons */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Layout: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
							#if GTK_CHECK_VERSION(2, 24, 0)
								desktop->pr_ilayout = gtk_combo_box_text_new();
							#else
								desktop->pr_ilayout = gtk_combo_box_new_text();
							#endif
								for(i = 0; i < sizeof(_desktop_icons) / sizeof(*_desktop_icons);
										i++)
							#if GTK_CHECK_VERSION(2, 24, 0)
									gtk_combo_box_text_append_text(
											GTK_COMBO_BOX_TEXT(desktop->pr_ilayout),
											_(_desktop_icons[i]));
							#else
									gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_ilayout),
											_(_desktop_icons[i]));
							#endif
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_ilayout, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* monitor */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Monitor: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
							#if GTK_CHECK_VERSION(2, 24, 0)
								desktop->pr_imonitor = gtk_combo_box_text_new();
							#else
								desktop->pr_imonitor = gtk_combo_box_new_text();
							#endif
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_imonitor, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* size */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Size: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_isize = gtk_spin_button_new_with_range(16, 256, 1);
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_isize, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* background color */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Background color: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_ibcolor = gtk_color_button_new();
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_ibcolor, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* foreground color */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Foreground color: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_ifcolor = gtk_color_button_new();
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_ifcolor, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* font */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Font: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_ifont = gtk_font_button_new();
								gtk_font_button_set_use_font(GTK_FONT_BUTTON(desktop->pr_ifont), TRUE);
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_ifont, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox2, gtk_label_new(
											_("Icons")));
							}
							static void _preferences_monitors(Desktop * desktop, GtkWidget * notebook)
							{
								GtkSizeGroup * group;
								GtkWidget * vbox2;
								GtkWidget * hbox;
								GtkWidget * label;
								GtkWidget * widget;
								group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
								vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
								gtk_container_set_border_width(GTK_CONTAINER(vbox2), 4);
								/* selector */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Monitor: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
							#if GTK_CHECK_VERSION(2, 24, 0)
								desktop->pr_monitors = gtk_combo_box_text_new();
							#else
								desktop->pr_monitors = gtk_combo_box_new_text();
							#endif
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_monitors, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* geometry */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Resolution: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_monitors_res = gtk_label_new(NULL);
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(desktop->pr_monitors_res, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(desktop->pr_monitors_res), 0.0, 0.5);
							#endif
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_monitors_res, TRUE, TRUE,
										0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* size */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(_("Size: "));
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(label, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								desktop->pr_monitors_size = gtk_label_new(NULL);
							#if GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(desktop->pr_monitors_size, "halign", GTK_ALIGN_START,
										NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(desktop->pr_monitors_size), 0.0, 0.5);
							#endif
								gtk_box_pack_start(GTK_BOX(hbox), desktop->pr_monitors_size, TRUE, TRUE,
										0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* refresh */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
								label = gtk_label_new(NULL);
								gtk_size_group_add_widget(group, label);
								gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
								widget = gtk_button_new_from_stock(GTK_STOCK_REFRESH);
								g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
											_desktop_on_preferences_monitors_refresh),
										desktop);
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
								/* updates */
								g_signal_connect_swapped(desktop->pr_monitors, "changed", G_CALLBACK(
											_desktop_on_preferences_monitors_changed),
										desktop);
								gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox2, gtk_label_new(
											_("Monitors")));
							}
							static void _preferences_set(Desktop * desktop)
							{
								Config * config;
								String const * p;
								String * q;
								String const * filename = NULL;
								const char black[] = "#000000000000";
								const char white[] = "#ffffffffffff";
								int how;
								gboolean extend = FALSE;
								size_t i;
								int j;
								/* FIXME:
								 * - cache the current configuration within the Desktop class
								 * - apply the configuration values to the widgets */
								if((config = _desktop_get_config(desktop)) != NULL)
								{
									/* background */
									filename = config_get(config, "background", "wallpaper");
									_preferences_set_color(config, "background", "color", NULL,
											desktop->pr_color);
									how = 0;
									if((p = config_get(config, "background", "how")) != NULL)
										for(i = 0; i < DESKTOP_HOW_COUNT; i++)
											if(strcmp(_desktop_hows[i], p) == 0)
											{
												how = i;
												break;
											}
									gtk_combo_box_set_active(GTK_COMBO_BOX(
												desktop->pr_background_how), how);
									if((p = config_get(config, "background", "extend")) != NULL)
										extend = strtol(p, NULL, 10) ? TRUE : FALSE;
									/* icons */
									how = DESKTOP_ICONS_FILES;
									if((p = config_get(config, "icons", "layout")) != NULL)
										for(i = 0; i < DESKTOP_ICONS_COUNT; i++)
											if(strcmp(_desktop_icons_config[i], p) == 0)
											{
												how = i;
												break;
											}
									gtk_combo_box_set_active(GTK_COMBO_BOX(desktop->pr_ilayout),
											how);
									gtk_spin_button_set_value(GTK_SPIN_BUTTON(desktop->pr_isize),
											desktop->icons_size);
									_preferences_set_color(config, "icons", "background", black,
											desktop->pr_ibcolor);
									_preferences_set_color(config, "icons", "foreground", white,
											desktop->pr_ifcolor);
									if((p = config_get(config, "icons", "font")) != NULL)
										gtk_font_button_set_font_name(GTK_FONT_BUTTON(
													desktop->pr_ifont), p);
									else if((q = pango_font_description_to_string(desktop->font))
											!= NULL)
									{
										gtk_font_button_set_font_name(GTK_FONT_BUTTON(
													desktop->pr_ifont), q);
										g_free(q);
									}
									if((p = config_get(config, "icons", "monitor")) != NULL)
									{
										j = strtol(p, &q, 10);
										if(p[0] == '\0' || *q != '\0' || j < 0)
											j = -1;
										gtk_combo_box_set_active(GTK_COMBO_BOX(
													desktop->pr_imonitor), j + 1);
									}
									config_delete(config);
								}
								else
								{
									gtk_combo_box_set_active(GTK_COMBO_BOX(
												desktop->pr_background_how), 0);
									gtk_combo_box_set_active(GTK_COMBO_BOX(desktop->pr_ilayout),
											DESKTOP_ICONS_FILES);
									gtk_combo_box_set_active(GTK_COMBO_BOX(desktop->pr_imonitor),
											0);
								}
								if(filename != NULL)
									gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(
												desktop->pr_background), filename);
								else
									gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(
												desktop->pr_background));
								gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
											desktop->pr_background_extend), extend);
							}
							static void _preferences_set_color(Config * config, char const * section,
									char const * variable, char const * fallback,
									GtkWidget * widget)
							{
								char const * p;
							#if GTK_CHECK_VERSION(3, 0, 0)
								GdkRGBA color = { 0.0, 0.0, 0.0, 0.0 };
							#else
								GdkColor color = { 0, 0, 0, 0 };
							#endif
								if((p = config_get(config, section, variable)) == NULL)
									p = fallback;
							#if GTK_CHECK_VERSION(3, 4, 0)
								if(p != NULL && gdk_rgba_parse(&color, p) == TRUE)
									gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(widget), &color);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								if(p != NULL && gdk_rgba_parse(&color, p) == TRUE)
									gtk_color_button_set_rgba(GTK_COLOR_BUTTON(widget), &color);
							#else
								if(p != NULL && gdk_color_parse(p, &color) == TRUE)
									gtk_color_button_set_color(GTK_COLOR_BUTTON(widget), &color);
							#endif
							}
							/* desktop_unselect_all */
							void desktop_unselect_all(Desktop * desktop)
							{
								size_t i;
								DesktopIcon * icon;
								for(i = 0; i < desktop->icons_cnt; i++)
								{
									icon = desktopiconwindow_get_icon(desktop->icons[i]);
									desktopicon_set_selected(icon, FALSE);
								}
							}
							/* private */
							/* functions */
							/* desktop_error */
							static int _error_text(char const * message, char const * error, int ret);
							static int _desktop_error(Desktop * desktop, char const * message,
									char const * error, int ret)
							{
								GtkWidget * dialog;
								if(desktop == NULL)
									return _error_text(message, error, ret);
								dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR,
										GTK_BUTTONS_CLOSE, "%s%s%s",
										(message != NULL) ? message : "",
										(message != NULL && error != NULL) ? ": " : "",
							#if GTK_CHECK_VERSION(2, 6, 0)
										_("Error"));
								gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
										"%s%s%s", (message != NULL) ? message : "",
										(message != NULL && error != NULL) ? ": " : "",
							#endif
										error);
								gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
								if(ret < 0)
								{
									g_signal_connect(dialog, "response", G_CALLBACK(gtk_main_quit),
											NULL);
									ret = -ret;
								}
								else
									g_signal_connect(dialog, "response", G_CALLBACK(
												gtk_widget_destroy), NULL);
								gtk_widget_show(dialog);
								return ret;
							}
							static int _error_text(char const * message, char const * error, int ret)
							{
								fprintf(stderr, "%s: %s%s%s\n", PROGNAME_DESKTOP,
										(message != NULL) ? message : "",
										(message != NULL) ? ": " : "", error);
								return ret;
							}
							/* desktop_get_config */
							static Config * _desktop_get_config(Desktop * desktop)
							{
								Config * config;
								String * pathname = NULL;
								if((config = config_new()) == NULL
										|| (pathname = string_new_append(desktop->home,
												"/" DESKTOPRC, NULL)) == NULL)
								{
									if(config != NULL)
										config_delete(config);
									if(pathname != NULL)
										object_delete(pathname);
									desktop_serror(NULL, _("Could not load preferences"), FALSE);
									return NULL;
								}
								config_load(config, pathname); /* XXX ignore errors */
								return config;
							}
							/* desktop_get_monitor_properties */
							#if !GTK_CHECK_VERSION(3, 22, 0)
							static void _monitor_properties_workarea(Desktop * desktop, int monitor,
									GdkRectangle * workarea);
							#endif
							static int _desktop_get_monitor_properties(Desktop * desktop, int monitor,
									GdkRectangle * geometry, GdkRectangle * workarea,
									int * scale, int * width_mm, int * height_mm)
							{
							#if GTK_CHECK_VERSION(3, 22, 0)
								GdkMonitor * m;
								int s;
							#endif
								if(monitor < 0)
								{
									_desktop_get_properties(desktop, geometry, workarea, scale,
											width_mm, height_mm);
									return 0;
								}
							#if GTK_CHECK_VERSION(3, 22, 0)
								if((m = gdk_display_get_monitor(desktop->display, monitor)) == NULL)
									return -1;
								if(geometry != NULL)
									gdk_monitor_get_geometry(m, geometry);
								if(workarea != NULL)
									gdk_monitor_get_workarea(m, workarea);
								if(scale != NULL || geometry != NULL)
								{
									if((s = gdk_monitor_get_scale_factor(m)) != 1
											&& geometry != NULL)
									{
										geometry->x *= s;
										geometry->y *= s;
										geometry->width *= s;
										geometry->height *= s;
									}
									if(scale != NULL)
										*scale = s;
								}
								if(width_mm != NULL)
									*width_mm = gdk_monitor_get_width_mm(m);
								if(height_mm != NULL)
									*height_mm = gdk_monitor_get_height_mm(m);
							#else
								if(geometry != NULL)
									gdk_screen_get_monitor_geometry(desktop->screen, monitor,
											geometry);
								if(workarea != NULL)
									_monitor_properties_workarea(desktop, monitor, workarea);
								if(scale != NULL)
									*scale = 1;
								if(width_mm != NULL)
									*width_mm = gdk_screen_get_monitor_width_mm(desktop->screen,
											monitor);
								if(height_mm != NULL)
									*height_mm = gdk_screen_get_monitor_height_mm(desktop->screen,
											monitor);
							#endif
								return 0;
							}
							#if !GTK_CHECK_VERSION(3, 22, 0)
							static void _monitor_properties_workarea(Desktop * desktop, int monitor,
									GdkRectangle * workarea)
							{
								GdkRectangle d;
								_desktop_get_properties(desktop, NULL, &d, NULL, NULL, NULL);
								gdk_screen_get_monitor_geometry(desktop->screen, monitor, workarea);
								if(d.x >= workarea->x
										&& d.x + d.width <= workarea->x + workarea->width
										&& d.y >= workarea->y
										&& d.y + d.height <= workarea->y + workarea->height)
									*workarea = d;
							# ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() (%d, %d) %dx%d\n", __func__,
										workarea->x, workarea->y,
										workarea->width, workarea->height);
							# endif
							}
							#endif
							/* desktop_get_properties */
							static void _properties_workarea(Desktop * desktop, GdkRectangle * workarea);
							static int _desktop_get_properties(Desktop * desktop, GdkRectangle * geometry,
									GdkRectangle * workarea, int * scale,
									int * width_mm, int * height_mm)
							{
								if(geometry != NULL)
								{
									geometry->x = 0;
									geometry->y = 0;
									geometry->width = gdk_screen_get_width(desktop->screen);
									geometry->height = gdk_screen_get_height(desktop->screen);
								}
								if(workarea != NULL)
									_properties_workarea(desktop, workarea);
								if(scale != NULL)
									*scale = 1;
								if(width_mm != NULL)
									*width_mm = gdk_screen_get_width_mm(desktop->screen);
								if(height_mm != NULL)
									*height_mm = gdk_screen_get_height_mm(desktop->screen);
								return 0;
							}
							static void _properties_workarea(Desktop * desktop, GdkRectangle * workarea)
							{
								Atom atom;
								Atom type;
								int format;
								unsigned long cnt;
								unsigned long bytes;
								unsigned char * p = NULL;
								unsigned long * u;
								int res;
								atom = gdk_x11_get_xatom_by_name("_NET_WORKAREA");
								gdk_error_trap_push();
								res = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(desktop->display),
											GDK_WINDOW_XID(desktop->root), atom, 0,
											G_MAXLONG, False, XA_CARDINAL, &type, &format,
											&cnt, &bytes, &p);
								if(gdk_error_trap_pop() || res != Success || cnt < 4)
									gdk_screen_get_monitor_geometry(desktop->screen, 0, workarea);
								else
								{
									u = (unsigned long *)p;
									workarea->x = u[0];
									workarea->y = u[1];
									if((workarea->width = u[2]) == 0
											|| (workarea->height = u[3]) == 0)
										gdk_screen_get_monitor_geometry(desktop->screen, 0,
												workarea);
								}
								if(p != NULL)
									XFree(p);
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() (%d, %d) %dx%d\n", __func__,
										workarea->x, workarea->y,
										workarea->width, workarea->height);
							#endif
							}
							/* desktop_get_workarea */
							static int _desktop_get_workarea(Desktop * desktop)
							{
								_desktop_get_monitor_properties(desktop, desktop->prefs.monitor, NULL,
										&desktop->workarea, NULL, NULL, NULL);
								desktop_icons_align(desktop);
								return 0;
							}
							/* useful */
							/* desktop_draw_background */
							static gboolean _background_how_centered(Desktop * desktop,
									GdkRectangle * window, char const * filename, GError ** error);
							static gboolean _background_how_scaled(Desktop * desktop, GdkRectangle * window,
									char const * filename, GError ** error);
							static gboolean _background_how_scaled_ratio(Desktop * desktop,
									GdkRectangle * window, char const * filename, GError ** error);
							static gboolean _background_how_tiled(Desktop * desktop, GdkRectangle * window,
									char const * filename, GError ** error);
							static void _background_monitor(Desktop * desktop, char const * filename,
									DesktopHows how, gboolean extend, GdkRectangle * window,
									int monitor);
							static void _background_monitors(Desktop * desktop, char const * filename,
									DesktopHows how, gboolean extend, GdkRectangle * window);
							#if GTK_CHECK_VERSION(3, 0, 0)
							static void _desktop_draw_background(Desktop * desktop, GdkRGBA * color,
									char const * filename, DesktopHows how, gboolean extend)
							#else
							static void _desktop_draw_background(Desktop * desktop, GdkColor * color,
									char const * filename, DesktopHows how, gboolean extend)
							#endif
							{
							#if !GTK_CHECK_VERSION(3, 0, 0)
								GdkGC * gc;
								GtkStyle * style;
							#endif
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(\"%s\", %u, %s)\n", __func__, filename, how,
										extend ? "TRUE" : "FALSE");
							#endif
								if(how == DESKTOP_HOW_NONE)
									return;
							#if GTK_CHECK_VERSION(3, 0, 0)
								desktop->cairo = gdk_cairo_create(desktop->back != NULL
										? desktop->back : desktop->root);
								cairo_set_source_rgba(desktop->cairo, color->red, color->green,
										color->blue, 1.0);
								gdk_cairo_rectangle(desktop->cairo, &desktop->window);
								if(filename != NULL)
									/* draw the background */
									_background_monitors(desktop, filename, how, extend,
											&desktop->window);
								cairo_paint(desktop->cairo);
								cairo_destroy(desktop->cairo);
								desktop->cairo = NULL;
							#else
								/* draw default color */
								desktop->pixmap = gdk_pixmap_new(desktop->back, desktop->window.width,
										desktop->window.height, -1);
								gc = gdk_gc_new(desktop->pixmap);
								gdk_gc_set_rgb_fg_color(gc, color);
								gdk_draw_rectangle(desktop->pixmap, gc, TRUE, 0, 0,
										desktop->window.width, desktop->window.height);
								if(filename != NULL)
									/* draw the background */
									_background_monitors(desktop, filename, how, extend,
											&desktop->window);
								if(desktop->desktop != NULL)
								{
									style = gtk_style_new();
									style->bg_pixmap[GTK_STATE_NORMAL] = desktop->pixmap;
									gtk_widget_set_style(desktop->desktop, style);
								}
								else
								{
									gdk_window_set_back_pixmap(desktop->back, desktop->pixmap,
											FALSE);
									gdk_window_clear(desktop->back);
									gdk_pixmap_unref(desktop->pixmap);
								}
								desktop->pixmap = NULL;
							#endif
							}
							static gboolean _background_how_centered(Desktop * desktop,
									GdkRectangle * window, char const * filename, GError ** error)
							{
								GdkPixbuf * background;
								gint w;
								gint h;
								gint x;
								gint y;
								if((background = gdk_pixbuf_new_from_file(filename, error)) == NULL)
									return FALSE;
								w = gdk_pixbuf_get_width(background);
								h = gdk_pixbuf_get_height(background);
								x = (window->width - w) / 2 + window->x;
								y = (window->height - h) / 2 + window->y;
							#if GTK_CHECK_VERSION(3, 0, 0)
								gdk_cairo_set_source_pixbuf(desktop->cairo, background, x, y);
							#else
								gdk_draw_pixbuf(desktop->pixmap, NULL, background, 0, 0, x, y, w, h,
										GDK_RGB_DITHER_NONE, 0, 0);
							#endif
								g_object_unref(background);
								return TRUE;
							}
							static gboolean _background_how_scaled(Desktop * desktop, GdkRectangle * window,
									char const * filename, GError ** error)
							{
								GdkPixbuf * background;
								gint w;
								gint h;
								gint x;
								gint y;
							#if GTK_CHECK_VERSION(2, 6, 0)
								background = gdk_pixbuf_new_from_file_at_scale(filename, window->width,
										window->height, FALSE, error);
							#elif GTK_CHECK_VERSION(2, 4, 0)
								background = gdk_pixbuf_new_from_file_at_size(filename, window->width,
										window->height, error);
							#else
								background = gdk_pixbuf_new_from_file(filename, error);
							#endif
								if(background == NULL)
									return FALSE;
								w = gdk_pixbuf_get_width(background);
								h = gdk_pixbuf_get_height(background);
								x = (window->width - w) / 2 + window->x;
								y = (window->height - h) / 2 + window->y;
							#if GTK_CHECK_VERSION(3, 0, 0)
								gdk_cairo_set_source_pixbuf(desktop->cairo, background, x, y);
							#else
								gdk_draw_pixbuf(desktop->pixmap, NULL, background, 0, 0, x, y, w, h,
										GDK_RGB_DITHER_NONE, 0, 0);
							#endif
								g_object_unref(background);
								return TRUE;
							}
							static gboolean _background_how_scaled_ratio(Desktop * desktop,
									GdkRectangle * window, char const * filename, GError ** error)
							{
							#if GTK_CHECK_VERSION(2, 4, 0)
								GdkPixbuf * background;
								gint w;
								gint h;
								gint x;
								gint y;
								background = gdk_pixbuf_new_from_file_at_size(filename, window->width,
										window->height, error);
								if(background == NULL)
									return FALSE;
								w = gdk_pixbuf_get_width(background);
								h = gdk_pixbuf_get_height(background);
								x =(window->width - w) / 2 + window->x;
								y = (window->height - h) / 2 + window->y;
							#if GTK_CHECK_VERSION(3, 0, 0)
								gdk_cairo_set_source_pixbuf(desktop->cairo, background, x, y);
							#else
								gdk_draw_pixbuf(desktop->pixmap, NULL, background, 0, 0, x, y, w, h,
										GDK_RGB_DITHER_NONE, 0, 0);
							#endif
								g_object_unref(background);
								return TRUE;
							#else
								return _background_how_scaled(desktop, window, filename, error);
							#endif
							}
							static gboolean _background_how_tiled(Desktop * desktop, GdkRectangle * window,
									char const * filename, GError ** error)
							{
								GdkPixbuf * background;
								gint w;
								gint h;
								gint i;
								gint j;
								if((background = gdk_pixbuf_new_from_file(filename, error)) == NULL)
									return FALSE;
								w = gdk_pixbuf_get_width(background);
								h = gdk_pixbuf_get_height(background);
								for(j = 0; j < window->height; j += h)
									for(i = 0; i < window->width; i += w)
							#if GTK_CHECK_VERSION(3, 0, 0)
										gdk_cairo_set_source_pixbuf(desktop->cairo, background,
												i + window->x, j + window->y);
							#else
										gdk_draw_pixbuf(desktop->pixmap, NULL, background, 0, 0,
												i + window->x, j + window->y, w, h,
												GDK_RGB_DITHER_NONE, 0, 0);
							#endif
								g_object_unref(background);
								return TRUE;
							}
							static void _background_monitor(Desktop * desktop, char const * filename,
									DesktopHows how, gboolean extend, GdkRectangle * window,
									int monitor)
							{
								GError * error = NULL;
								if(extend != TRUE)
									_desktop_get_monitor_properties(desktop, monitor, window, NULL,
											NULL, NULL, NULL);
								switch(how)
								{
									case DESKTOP_HOW_NONE:
										break;
									case DESKTOP_HOW_CENTERED:
										_background_how_centered(desktop, window, filename,
												&error);
										break;
									case DESKTOP_HOW_SCALED_RATIO:
										_background_how_scaled_ratio(desktop, window, filename,
												&error);
										break;
									case DESKTOP_HOW_TILED:
										_background_how_tiled(desktop, window, filename,
												&error);
										break;
									case DESKTOP_HOW_SCALED:
										_background_how_scaled(desktop, window, filename,
												&error);
										break;
								}
								if(error != NULL)
								{
									desktop_error(desktop, NULL, error->message, 1);
									g_error_free(error);
								}
							}
							static void _background_monitors(Desktop * desktop, char const * filename,
									DesktopHows how, gboolean extend, GdkRectangle * window)
							{
								gint n;
								gint i;
								n = (extend != TRUE) ?
							#if GTK_CHECK_VERSION(3, 22, 0)
									gdk_display_get_n_monitors(desktop->display)
							#else
									gdk_screen_get_n_monitors(desktop->screen)
							#endif
									: 1;
								for(i = 0; i < n; i++)
									_background_monitor(desktop, filename, how, extend, window, i);
							}
							/* callbacks */
							/* desktop_on_preferences_closex */
							static gboolean _desktop_on_preferences_closex(gpointer data)
							{
								_desktop_on_preferences_response_cancel(data);
								return TRUE;
							}
							/* desktop_on_preferences_monitors_changed */
							static void _desktop_on_preferences_monitors_changed(gpointer data)
							{
								Desktop * desktop = data;
								gint active;
								GdkRectangle geometry;
								gint width;
								gint height;
								char buf[64];
								active = gtk_combo_box_get_active(GTK_COMBO_BOX(desktop->pr_monitors));
								if(active < 0
										|| _desktop_get_monitor_properties(desktop, active,
											&geometry, NULL, NULL, &width, &height) != 0)
									_desktop_get_properties(desktop, &geometry, NULL, NULL,
											&width, &height);
								if(width < 0 || height < 0)
									snprintf(buf, sizeof(buf), "%s", _("Unknown size"));
								else
									snprintf(buf, sizeof(buf), _("%dx%d (at %d,%d)"),
											geometry.width, geometry.height,
											geometry.x, geometry.y);
								gtk_label_set_text(GTK_LABEL(desktop->pr_monitors_res), buf);
								if(width < 0 || height < 0)
									snprintf(buf, sizeof(buf), "%s", _("Unknown resolution"));
								else
									snprintf(buf, sizeof(buf), _("%dx%d mm (%.0fx%.0f DPI)"),
											width, height, geometry.width * 25.4 / width,
											geometry.height * 25.4 / height);
								gtk_label_set_text(GTK_LABEL(desktop->pr_monitors_size), buf);
							}
							/* desktop_on_preferences_monitors_refresh */
							static void _desktop_on_preferences_monitors_refresh(gpointer data)
							{
								Desktop * desktop = data;
								GtkTreeModel * model1;
								GtkTreeModel * model2;
								gint active;
							#if GTK_CHECK_VERSION(2, 14, 0)
								gint n;
								gint i;
								gchar * name;
								char buf[32];
							# if GTK_CHECK_VERSION(3, 22, 0)
								GdkMonitor * monitor;
								char const * manufacturer;
								char const * model;
								char buf2[64];
							# endif
							#endif
								active = gtk_combo_box_get_active(GTK_COMBO_BOX(desktop->pr_imonitor));
								model1 = gtk_combo_box_get_model(GTK_COMBO_BOX(desktop->pr_imonitor));
								model2 = gtk_combo_box_get_model(GTK_COMBO_BOX(desktop->pr_monitors));
								gtk_list_store_clear(GTK_LIST_STORE(model1));
								gtk_list_store_clear(GTK_LIST_STORE(model2));
							#if GTK_CHECK_VERSION(2, 24, 0)
								gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(desktop->pr_imonitor),
										_("Default monitor"));
							#else
								gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_imonitor),
										_("Default monitor"));
							#endif
							#if GTK_CHECK_VERSION(2, 14, 0)
							# if GTK_CHECK_VERSION(3, 22, 0)
								n = gdk_display_get_n_monitors(desktop->display);
							# else
								n = gdk_screen_get_n_monitors(desktop->screen);
							# endif
								for(i = 0; i < n; i++)
								{
									snprintf(buf, sizeof(buf), _("Monitor %d"), i);
							# if GTK_CHECK_VERSION(3, 22, 0)
									if((monitor = gdk_display_get_monitor(desktop->display, i))
											== NULL)
										continue;
									manufacturer = gdk_monitor_get_manufacturer(monitor);
									model = gdk_monitor_get_model(monitor);
									if(manufacturer != NULL || model != NULL)
									{
										snprintf(buf2, sizeof(buf2), "%s%s%s",
												(manufacturer != NULL)
												? manufacturer : "",
												(manufacturer != NULL && model != NULL)
												? " " : "",
												(model != NULL) ? model : "");
										name = g_strdup(buf2);
									}
									else
										name = NULL;
							# else
									name = gdk_screen_get_monitor_plug_name(desktop->screen, i);
							# endif
							# if GTK_CHECK_VERSION(2, 24, 0)
									gtk_combo_box_text_append_text(
											GTK_COMBO_BOX_TEXT(desktop->pr_imonitor),
											(name != NULL) ? name : buf);
									gtk_combo_box_text_append_text(
											GTK_COMBO_BOX_TEXT(desktop->pr_monitors),
											(name != NULL) ? name : buf);
							# else
									gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_imonitor),
											(name != NULL) ? name : buf);
									gtk_combo_box_append_text(GTK_COMBO_BOX(desktop->pr_monitors),
											(name != NULL) ? name : buf);
							# endif
									g_free(name);
								}
							#endif
								gtk_combo_box_set_active(GTK_COMBO_BOX(desktop->pr_imonitor), active);
								gtk_combo_box_set_active(GTK_COMBO_BOX(desktop->pr_monitors), 0);
							}
							/* desktop_on_preferences_response */
							static void _desktop_on_preferences_response(GtkWidget * widget, gint response,
									gpointer data)
							{
								Desktop * desktop = data;
								(void) widget;
								if(response == GTK_RESPONSE_OK)
									_desktop_on_preferences_response_ok(desktop);
								else if(response == GTK_RESPONSE_APPLY)
									_desktop_on_preferences_response_apply(desktop);
								else if(response == GTK_RESPONSE_CANCEL)
									_desktop_on_preferences_response_cancel(desktop);
							}
							/* desktop_on_preferences_response_apply */
							static void _desktop_on_preferences_response_apply(gpointer data)
							{
								Desktop * desktop = data;
								int i;
								/* XXX not very efficient */
								desktop_reset(desktop);
								/* icons */
								desktop->prefs.icons = gtk_combo_box_get_active(
										GTK_COMBO_BOX(desktop->pr_ilayout));
								desktop->icons_size = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(
											desktop->pr_isize));
								/* monitor */
								i = gtk_combo_box_get_active(GTK_COMBO_BOX(desktop->pr_imonitor));
								desktop->prefs.monitor = (i >= 0) ? i - 1 : i;
							}
							/* desktop_on_preferences_response_cancel */
							static void _desktop_on_preferences_response_cancel(gpointer data)
							{
								Desktop * desktop = data;
								gtk_widget_hide(desktop->pr_window);
								_preferences_set(desktop);
							}
							/* desktop_on_preferences_response_ok */
							static void _desktop_on_preferences_response_ok(gpointer data)
							{
								Desktop * desktop = data;
								Config * config;
							#if GTK_CHECK_VERSION(3, 0, 0)
								GdkRGBA color;
							#else
								GdkColor color;
							#endif
								char * p;
								char const * q;
								int i;
								char buf[12];
								gtk_widget_hide(desktop->pr_window);
								_desktop_on_preferences_response_apply(desktop);
								if((config = _desktop_get_config(desktop)) == NULL)
									return;
								/* background */
								p = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(
											desktop->pr_background));
								config_set(config, "background", "wallpaper", p);
								g_free(p);
							#if GTK_CHECK_VERSION(3, 4, 0)
								gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(desktop->pr_color),
										&color);
								p = gdk_rgba_to_string(&color);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								gtk_color_button_get_rgba(GTK_COLOR_BUTTON(desktop->pr_color), &color);
								p = gdk_rgba_to_string(&color);
							#else
								gtk_color_button_get_color(GTK_COLOR_BUTTON(desktop->pr_color), &color);
								p = gdk_color_to_string(&color);
							#endif
								config_set(config, "background", "color", p);
								g_free(p);
								i = gtk_combo_box_get_active(GTK_COMBO_BOX(desktop->pr_background_how));
								if(i >= 0 && i < DESKTOP_HOW_COUNT)
									config_set(config, "background", "how", _desktop_hows[i]);
								p = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
											desktop->pr_background_extend)) ? "1" : "0";
								config_set(config, "background", "extend", p);
								/* icons */
								i = gtk_combo_box_get_active(GTK_COMBO_BOX(desktop->pr_ilayout));
								if(desktop->prefs.icons >= 0
										&& desktop->prefs.icons < DESKTOP_ICONS_COUNT)
									config_set(config, "icons", "layout",
											_desktop_icons_config[desktop->prefs.icons]);
								snprintf(buf, sizeof(buf), "%u", desktop->icons_size);
								config_set(config, "icons", "size", buf);
							#if GTK_CHECK_VERSION(3, 4, 0)
								gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(desktop->pr_ibcolor),
										&color);
								p = gdk_rgba_to_string(&color);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								gtk_color_button_get_rgba(GTK_COLOR_BUTTON(desktop->pr_ibcolor),
										&color);
								p = gdk_rgba_to_string(&color);
							#else
								gtk_color_button_get_color(GTK_COLOR_BUTTON(desktop->pr_ibcolor),
										&color);
								p = gdk_color_to_string(&color);
							#endif
								config_set(config, "icons", "background", p);
								g_free(p);
							#if GTK_CHECK_VERSION(3, 4, 0)
								gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(desktop->pr_ifcolor),
										&color);
								p = gdk_rgba_to_string(&color);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								gtk_color_button_get_rgba(GTK_COLOR_BUTTON(desktop->pr_ifcolor),
										&color);
								p = gdk_rgba_to_string(&color);
							#else
								gtk_color_button_get_color(GTK_COLOR_BUTTON(desktop->pr_ifcolor),
										&color);
								p = gdk_color_to_string(&color);
							#endif
								config_set(config, "icons", "foreground", p);
								g_free(p);
								q = gtk_font_button_get_font_name(GTK_FONT_BUTTON(desktop->pr_ifont));
								config_set(config, "icons", "font", q);
								/* monitor */
								snprintf(buf, sizeof(buf), "%d", desktop->prefs.monitor);
								config_set(config, "icons", "monitor", buf);
								/* XXX code duplication */
								if((p = string_new_append(desktop->home, "/" DESKTOPRC, NULL)) != NULL)
								{
									if(config_save(config, p) != 0)
										error_print(PROGNAME_DESKTOP);
									string_delete(p);
								}
								config_delete(config);
							}
							/* desktop_on_preferences_update_preview */
							static void _desktop_on_preferences_update_preview(gpointer data)
							{
								Desktop * desktop = data;
							#if !GTK_CHECK_VERSION(2, 6, 0)
								gint ratio = desktop->window.width / desktop->window.height;
							#endif
								GtkFileChooser * chooser = GTK_FILE_CHOOSER(desktop->pr_background);
								GtkWidget * widget;
								char * filename;
								GdkPixbuf * pixbuf;
								gboolean active = FALSE;
								GError * error = NULL;
								widget = gtk_file_chooser_get_preview_widget(chooser);
								if((filename = gtk_file_chooser_get_preview_filename(chooser)) != NULL)
								{
							#if GTK_CHECK_VERSION(2, 6, 0)
									pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 96, -1,
											TRUE, &error);
							#else
									pixbuf = gdk_pixbuf_new_from_file_at_size(filename, 96,
											96 / ratio, &error);
							#endif
									if(error != NULL)
									{
							#ifdef DEBUG
										_desktop_error(NULL, NULL, error->message, 1);
							#endif
										g_error_free(error);
									}
									if(pixbuf != NULL)
									{
										gtk_image_set_from_pixbuf(GTK_IMAGE(widget), pixbuf);
										g_object_unref(pixbuf);
										active = TRUE;
									}
								}
								g_free(filename);
								gtk_file_chooser_set_preview_widget_active(chooser, active);
							}
							/* desktop_on_refresh */
							static gboolean _desktop_on_refresh(gpointer data)
							{
								Desktop * desktop = data;
								desktop->refresh_source = 0;
								desktop_refresh(desktop);
								return FALSE;
							}
							