Coder
/* $Id$ */
							static char const _copyright[] =
							"Copyright © 2013-2020 Pierre Pronchery <khorben@defora.org>";
							/* This file is part of DeforaOS Desktop Coder */
							static char const _license[] =
							"This program is free software: you can redistribute it and/or modify\n"
							"it under the terms of the GNU General Public License as published by\n"
							"the Free Software Foundation, version 3 of the License.\n"
							"\n"
							"This program is distributed in the hope that it will be useful,\n"
							"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
							"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
							"GNU General Public License for more details.\n"
							"\n"
							"You should have received a copy of the GNU General Public License\n"
							"along with this program.  If not, see <http://www.gnu.org/licenses/>.";
							#include <sys/wait.h>
							#include <dirent.h>
							#include <stdlib.h>
							#include <stdio.h>
							#include <string.h>
							#include <errno.h>
							#include <libintl.h>
							#include <X11/Xlib.h>
							#include <X11/extensions/XTest.h>
							#include <gtk/gtk.h>
							#include <gdk/gdkkeysyms.h>
							#if GTK_CHECK_VERSION(3, 0, 0)
							# include <gtk/gtkx.h>
							#else
							# include <gdk/gdkx.h>
							#endif
							#include <System.h>
							#include <Desktop.h>
							#include "simulator.h"
							#include "../config.h"
							#define _(string) gettext(string)
							#define N_(string) (string)
							/* constants */
							#ifndef PROGNAME_SIMULATOR
							# define PROGNAME_SIMULATOR	"simulator"
							#endif
							#ifndef PROGNAME_XEPHYR
							# define PROGNAME_XEPHYR	"Xephyr"
							#endif
							#ifndef PREFIX
							# define PREFIX			"/usr/local"
							#endif
							#ifndef BINDIR
							# define BINDIR			PREFIX "/bin"
							#endif
							#ifndef DATADIR
							# define DATADIR		PREFIX "/share"
							#endif
							#ifndef MODELDIR
							# define MODELDIR		DATADIR "/" PACKAGE "/Simulator/models"
							#endif
							extern char ** environ;
							/* Simulator */
							/* private */
							/* types */
							typedef struct _SimulatorChild
							{
								unsigned int source;
								GPid pid;
							} SimulatorChild;
							struct _Simulator
							{
								char * model;
								char * title;
								char * command;
								char name[8];
								Display * display;
								int dpi;
								int width;
								int height;
								unsigned int source;
								SimulatorChild xephyr;
								SimulatorChild * children;
								size_t children_cnt;
								/* widgets */
								GtkWidget * window;
								GtkWidget * toolbar;
								GtkWidget * socket;
							};
							typedef struct _SimulatorData
							{
								Simulator * simulator;
								Config * config;
								GtkWidget * dpi;
								GtkWidget * width;
								GtkWidget * height;
							} SimulatorData;
							/* constants */
							static char const * _authors[] =
							{
								"Pierre Pronchery <khorben@defora.org>",
								NULL
							};
							/* prototypes */
							/* callbacks */
							static void _simulator_on_button_clicked(GtkToolButton * button, gpointer data);
							static void _simulator_on_child_watch(GPid pid, gint status, gpointer data);
							static void _simulator_on_children_watch(GPid pid, gint status, gpointer data);
							static void _simulator_on_close(gpointer data);
							static gboolean _simulator_on_closex(gpointer data);
							static void _simulator_on_plug_added(gpointer data);
							static void _simulator_on_file_quit(gpointer data);
							static void _simulator_on_file_run(gpointer data);
							static void _simulator_on_view_toggle_debugging_mode(gpointer data);
							static void _simulator_on_help_about(gpointer data);
							static void _simulator_on_help_contents(gpointer data);
							/* constants */
							/* menubar */
							static const DesktopMenu _simulator_file_menu[] =
							{
								{ N_("_Run..."), G_CALLBACK(_simulator_on_file_run), NULL,
									GDK_CONTROL_MASK, GDK_KEY_R },
								{ "", NULL, NULL, 0, 0 },
								{ N_("_Quit"), G_CALLBACK(_simulator_on_file_quit), GTK_STOCK_QUIT,
									GDK_CONTROL_MASK, GDK_KEY_Q },
								{ NULL, NULL, NULL, 0, 0 }
							};
							static const DesktopMenu _simulator_view_menu[] =
							{
								{ N_("Toggle _debugging mode"), G_CALLBACK(
										_simulator_on_view_toggle_debugging_mode),
									NULL, 0, 0 },
								{ NULL, NULL, NULL, 0, 0 }
							};
							static const DesktopMenu _simulator_help_menu[] =
							{
								{ N_("_Contents"), G_CALLBACK(_simulator_on_help_contents),
									"help-contents", 0, GDK_KEY_F1 },
							#if GTK_CHECK_VERSION(2, 6, 0)
								{ N_("About"), G_CALLBACK(_simulator_on_help_about), GTK_STOCK_ABOUT, 0,
									0 },
							#else
								{ N_("About"), G_CALLBACK(_simulator_on_help_about), NULL, 0, 0 },
							#endif
								{ NULL, NULL, NULL, 0, 0 }
							};
							static const DesktopMenubar _simulator_menubar[] =
							{
								{ N_("_File"), _simulator_file_menu },
								{ N_("_View"), _simulator_view_menu },
								{ N_("_Help"), _simulator_help_menu },
								{ NULL, NULL }
							};
							/* public */
							/* functions */
							/* simulator_new */
							static int _new_chooser(Simulator * simulator);
							static void _new_chooser_list(GtkTreeStore * store);
							static void _new_chooser_list_vendor(GtkTreeStore * store, char const * vendor,
									GtkTreeIter * parent);
							static void _new_chooser_load(SimulatorData * data, GtkWidget * combobox);
							static void _new_chooser_on_changed(GtkWidget * widget, gpointer data);
							static void _new_chooser_on_config(String const * section, void * data);
							static int _new_load(Simulator * simulator, char const * model);
							static Config * _new_load_config(char const * model);
							/* callbacks */
							static gint _new_chooser_list_sort(GtkTreeModel * model, GtkTreeIter * a,
									GtkTreeIter * b, gpointer data);
							static gboolean _new_on_idle(gpointer data);
							static gboolean _new_on_quit(gpointer data);
							static gboolean _new_on_xephyr(gpointer data);
							Simulator * simulator_new(SimulatorPrefs * prefs)
							{
								Simulator * simulator;
								if((simulator = object_new(sizeof(*simulator))) == NULL)
									return NULL;
								simulator->model = NULL;
								simulator->title = NULL;
								simulator->command = NULL;
								if(prefs != NULL)
								{
									simulator->model = (prefs->model != NULL)
										? strdup(prefs->model) : NULL;
									simulator->title = (prefs->title != NULL)
										? strdup(prefs->title) : NULL;
									simulator->command = (prefs->command != NULL)
										? strdup(prefs->command) : NULL;
									/* check for errors */
									if((prefs->model != NULL && simulator->model == NULL)
											|| (prefs->title != NULL
												&& simulator->title == NULL)
											|| (prefs->command != NULL
												&& simulator->command == NULL))
									{
										simulator_delete(simulator);
										return NULL;
									}
								}
								simulator->xephyr.source = 0;
								simulator->xephyr.pid = -1;
								simulator->children = NULL;
								simulator->children_cnt = 0;
								simulator->source = 0;
								simulator->window = NULL;
								simulator->toolbar = NULL;
								/* set default values */
								memset(&simulator->name, 0, sizeof(simulator->name));
								simulator->display = NULL;
								_new_load(simulator, NULL);
								if(prefs != NULL && prefs->chooser != 0)
								{
									if(_new_chooser(simulator) != 0)
									{
										simulator_delete(simulator);
										return NULL;
									}
								}
								else
								{
									/* load the configuration */
									/* XXX no longer ignore errors */
									_new_load(simulator, simulator->model);
									simulator->source = g_idle_add(_new_on_idle, simulator);
								}
								return simulator;
							}
							static int _new_chooser(Simulator * simulator)
							{
								GtkSizeGroup * lgroup;
								GtkSizeGroup * group;
								GtkWidget * dialog;
								GtkWidget * vbox;
								GtkWidget * hbox;
								GtkWidget * frame;
								GtkWidget * subvbox;
								GtkTreeStore * store;
								GtkTreeModel * model;
								GtkWidget * combobox;
								GtkTreeIter iter;
								GtkTreeIter siter;
								GtkCellRenderer * renderer;
								GtkWidget * widget;
								SimulatorData data;
								data.simulator = simulator;
								data.config = NULL;
								lgroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
								group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
								dialog = gtk_dialog_new_with_buttons(_("Simulator profiles"),
										NULL, 0, GTK_STOCK_QUIT, GTK_RESPONSE_CLOSE,
										GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
							#if GTK_CHECK_VERSION(2, 14, 0)
								vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
							#else
								vbox = GTK_DIALOG(dialog)->vbox;
							#endif
								gtk_box_set_spacing(GTK_BOX(vbox), 4);
								/* profile selector */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
								widget = gtk_label_new(_("Profile: "));
							#if GTK_CHECK_VERSION(3, 16, 0)
								gtk_label_set_xalign(GTK_LABEL(widget), 0.0);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(lgroup, widget);
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								store = gtk_tree_store_new(3,
										G_TYPE_STRING,				/* filename */
										GDK_TYPE_PIXBUF,			/* icon */
										G_TYPE_STRING);				/* name */
								gtk_tree_store_append(store, &iter, NULL);
								gtk_tree_store_set(store, &iter, 0, NULL, 2, _("Custom profile"), -1);
								_new_chooser_list(store);
								model = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(store));
								gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(model),
										_new_chooser_list_sort, simulator, NULL);
								combobox = gtk_combo_box_new_with_model(model);
								renderer = gtk_cell_renderer_pixbuf_new();
								gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, FALSE);
								gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), renderer,
										"pixbuf", 1, NULL);
								renderer = gtk_cell_renderer_text_new();
								gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
								gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), renderer,
										"text", 2, NULL);
								if(gtk_tree_model_sort_convert_child_iter_to_iter(
											GTK_TREE_MODEL_SORT(model), &siter, &iter))
									gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combobox), &siter);
								g_signal_connect(combobox, "changed", G_CALLBACK(
											_new_chooser_on_changed), &data);
								gtk_box_pack_end(GTK_BOX(hbox), combobox, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
								/* profile */
								frame = gtk_frame_new(NULL);
								gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
								subvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
								gtk_box_set_spacing(GTK_BOX(subvbox), 4);
								gtk_container_set_border_width(GTK_CONTAINER(subvbox), 4);
								gtk_container_add(GTK_CONTAINER(frame), subvbox);
								/* dpi */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
								widget = gtk_label_new(_("Resolution: "));
							#if GTK_CHECK_VERSION(3, 16, 0)
								gtk_label_set_xalign(GTK_LABEL(widget), 0.0);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(lgroup, widget);
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								data.dpi = gtk_spin_button_new_with_range(48.0, 300.0, 1.0);
								gtk_spin_button_set_value(GTK_SPIN_BUTTON(data.dpi), simulator->dpi);
								gtk_size_group_add_widget(group, data.dpi);
								gtk_box_pack_end(GTK_BOX(hbox), data.dpi, FALSE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(subvbox), hbox, FALSE, TRUE, 0);
								/* width */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
								widget = gtk_label_new(_("Width: "));
							#if GTK_CHECK_VERSION(3, 16, 0)
								gtk_label_set_xalign(GTK_LABEL(widget), 0.0);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(lgroup, widget);
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								data.width = gtk_spin_button_new_with_range(120, 1600, 1.0);
								gtk_spin_button_set_value(GTK_SPIN_BUTTON(data.width),
										simulator->width);
								gtk_size_group_add_widget(group, data.width);
								gtk_box_pack_end(GTK_BOX(hbox), data.width, FALSE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(subvbox), hbox, FALSE, TRUE, 0);
								/* height */
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
								widget = gtk_label_new(_("Height: "));
							#if GTK_CHECK_VERSION(3, 16, 0)
								gtk_label_set_xalign(GTK_LABEL(widget), 0.0);
							#elif GTK_CHECK_VERSION(3, 0, 0)
								g_object_set(widget, "halign", GTK_ALIGN_START, NULL);
							#else
								gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
							#endif
								gtk_size_group_add_widget(lgroup, widget);
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								data.height = gtk_spin_button_new_with_range(120, 1600, 1.0);
								gtk_spin_button_set_value(GTK_SPIN_BUTTON(data.height),
										simulator->height);
								gtk_size_group_add_widget(group, data.height);
								gtk_box_pack_end(GTK_BOX(hbox), data.height, FALSE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(subvbox), hbox, FALSE, TRUE, 0);
								gtk_widget_show_all(vbox);
								if(gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK)
								{
									gtk_widget_destroy(dialog);
									simulator->source = g_idle_add(_new_on_quit, simulator);
									return 0;
								}
								gtk_widget_hide(dialog);
								/* apply the values */
								simulator->dpi = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(
											data.dpi));
								simulator->width = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(
											data.width));
								simulator->height = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(
											data.height));
								_new_chooser_load(&data, combobox);
								gtk_widget_destroy(dialog);
								simulator->source = g_idle_add(_new_on_idle, simulator);
								return 0;
							}
							static void _new_chooser_list(GtkTreeStore * store)
							{
								GtkIconTheme * icontheme;
								GtkTreeIter iter;
								GtkTreeIter parent;
								int size = 16;
								char const models[] = MODELDIR;
								char const ext[] = ".conf";
								DIR * dir;
								struct dirent * de;
								size_t len;
								Config * config;
								char const * p;
								char const * q;
								String * title;
								GdkPixbuf * pixbuf = NULL;
								if((dir = opendir(models)) == NULL)
									return;
								icontheme = gtk_icon_theme_get_default();
								gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &size, &size);
								while((de = readdir(dir)) != NULL)
								{
									if(de->d_name[0] == '.')
										continue;
									if((len = strlen(de->d_name)) <= sizeof(ext))
										continue;
									if(strcmp(&de->d_name[len - sizeof(ext) + 1], ext) != 0)
										continue;
									de->d_name[len - sizeof(ext) + 1] = '\0';
									if((config = _new_load_config(de->d_name)) == NULL)
										continue;
									if((p = config_get(config, NULL, "icon")) != NULL)
										pixbuf = gtk_icon_theme_load_icon(icontheme, p, size, 0,
												NULL);
									q = config_get(config, NULL, "model");
									if((p = config_get(config, NULL, "vendor")) != NULL)
									{
										_new_chooser_list_vendor(store, p, &parent);
										gtk_tree_store_append(store, &iter, &parent);
										title = string_new_append((q != NULL)
												? " " : de->d_name, q, NULL);
									}
									else
									{
										gtk_tree_store_append(store, &iter, NULL);
										title = string_new_append((p != NULL) ? p : "",
												(q != NULL) ? " " : de->d_name, q, NULL);
									}
									gtk_tree_store_set(store, &iter, 0, de->d_name, 1, pixbuf,
											2, title, -1);
									string_delete(title);
									if(pixbuf != NULL)
									{
										g_object_unref(pixbuf);
										pixbuf = NULL;
									}
									config_delete(config);
								}
								closedir(dir);
							}
							static void _new_chooser_list_vendor(GtkTreeStore * store, char const * vendor,
									GtkTreeIter * parent)
							{
								GtkTreeModel * model = GTK_TREE_MODEL(store);
								GtkTreeIter iter;
								gboolean valid;
								gchar * v;
								int res;
								for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;
										valid = gtk_tree_model_iter_next(model, &iter))
								{
									gtk_tree_model_get(model, &iter, 2, &v, -1);
									res = strcmp(v, vendor);
									g_free(v);
									if(res == 0)
										break;
								}
								if(valid == TRUE && res == 0)
									*parent = iter;
								else
								{
									gtk_tree_store_append(store, parent, NULL);
									gtk_tree_store_set(store, parent, 2, vendor, -1);
								}
							}
							static void _new_chooser_load(SimulatorData * data, GtkWidget * combobox)
							{
								GtkTreeModel * smodel;
								GtkTreeIter siter;
								GtkTreeModel * model;
								GtkTreeIter iter;
								gchar * profile;
								if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combobox), &siter)
										== FALSE)
									return;
								smodel = gtk_combo_box_get_model(GTK_COMBO_BOX(combobox));
								gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(
											smodel), &iter, &siter);
								model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(smodel));
								gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &profile, -1);
								if(profile != NULL
										&& (data->config = _new_load_config(profile)) != NULL)
								{
									config_foreach(data->config, _new_chooser_on_config, data);
									config_delete(data->config);
									data->config = NULL;
								}
								g_free(profile);
							}
							static void _new_chooser_on_changed(GtkWidget * widget, gpointer data)
							{
								SimulatorData * d = data;
								GtkTreeIter siter;
								GtkTreeModel * smodel;
								GtkTreeIter iter;
								GtkTreeModel * model;
								gchar * name;
								if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &siter) == FALSE)
									return;
								smodel = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
								gtk_tree_model_sort_convert_iter_to_child_iter(
										GTK_TREE_MODEL_SORT(smodel), &iter, &siter);
								model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(smodel));
								gtk_tree_model_get(model, &iter, 0, &name, -1);
								if(_new_load(d->simulator, name) == 0)
								{
									gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->dpi),
											d->simulator->dpi);
									gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->width),
											d->simulator->width);
									gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->height),
											d->simulator->height);
								}
								g_free(name);
							}
							static void _new_chooser_on_config(String const * section, void * data)
							{
								SimulatorData * d = data;
								const String button[] = "button::";
								GtkWidget * image;
								GtkToolItem * toolitem;
								char const * p;
								char const * q;
								if(strncmp(section, button, sizeof(button) - 1) != 0)
									return;
								if(d->simulator->toolbar == NULL)
									d->simulator->toolbar = gtk_toolbar_new();
								if((p = config_get(d->config, section, "icon")) != NULL)
									image = gtk_image_new_from_icon_name(p,
											GTK_ICON_SIZE_LARGE_TOOLBAR);
								else
									image = NULL;
								p = config_get(d->config, section, "name");
								toolitem = gtk_tool_button_new(image, p);
								/* XXX memory leaks */
								if((p = config_get(d->config, section, "command")) != NULL)
										g_object_set_data(G_OBJECT(toolitem), "command",
												g_strdup(p));
								if((q = config_get(d->config, section, "keysym")) != NULL)
										g_object_set_data(G_OBJECT(toolitem), "keysym",
												g_strdup(q));
								if(p == NULL && q == NULL)
									gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
								else
									g_signal_connect(toolitem, "clicked",
											G_CALLBACK(_simulator_on_button_clicked),
											d->simulator);
								gtk_toolbar_insert(GTK_TOOLBAR(d->simulator->toolbar), toolitem, -1);
							}
							static int _new_load(Simulator * simulator, char const * model)
							{
								Config * config;
								char const * p;
								char * q;
								long l;
								char const * v;
								simulator->dpi = 96;
								simulator->width = 640;
								simulator->height = 480;
								if((config = _new_load_config(model)) == NULL)
									return -1;
								if((p = config_get(config, NULL, "dpi")) != NULL
										&& (l = strtol(p, &q, 10)) > 0
										&& p[0] != '\0' && *q == '\0')
									simulator->dpi = l;
								if((p = config_get(config, NULL, "width")) != NULL
										&& (l = strtol(p, &q, 10)) > 0
										&& p[0] != '\0' && *q == '\0')
									simulator->width = l;
								if((p = config_get(config, NULL, "height")) != NULL
										&& (l = strtol(p, &q, 10)) > 0
										&& p[0] != '\0' && *q == '\0')
									simulator->height = l;
								free(simulator->title);
								v = config_get(config, NULL, "vendor");
								if((p = config_get(config, NULL, "model")) != NULL)
									simulator->title = string_new_append((v != NULL) ? v : "",
											(v != NULL) ? " " : "", p, NULL);
								else
									simulator->title = NULL;
								config_delete(config);
								return 0;
							}
							static Config * _new_load_config(char const * model)
							{
								Config * config;
								char * p;
								int res = -1;
								if(model == NULL)
									model = "default";
								/* load the selected model */
								if((config = config_new()) == NULL)
									return NULL;
								p = string_new_append(MODELDIR "/", model, ".conf", NULL);
								if(p != NULL)
									res = config_load(config, p);
								free(p);
								if(res != 0)
								{
									config_delete(config);
									return NULL;
								}
								return config;
							}
							static gint _new_chooser_list_sort(GtkTreeModel * model, GtkTreeIter * a,
									GtkTreeIter * b, gpointer data)
							{
								gint ret;
								gchar * afilename;
								gchar * aname;
								gchar * bfilename;
								gchar * bname;
								(void) data;
								gtk_tree_model_get(model, a, 0, &afilename, 2, &aname, -1);
								gtk_tree_model_get(model, b, 0, &bfilename, 2, &bname, -1);
								if(afilename == NULL && bfilename != NULL)
									ret = -1;
								else if(afilename != NULL && bfilename == NULL)
									ret = 1;
								else
									ret = strcmp(aname, bname);
								g_free(afilename);
								g_free(aname);
								g_free(bfilename);
								g_free(bname);
								return ret;
							}
							static gboolean _new_on_idle(gpointer data)
							{
								Simulator * simulator = data;
								GtkAccelGroup * group;
								GtkWidget * vbox;
								GtkWidget * widget;
								char * p;
								/* widgets */
								group = gtk_accel_group_new();
								simulator->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
							#if GTK_CHECK_VERSION(2, 6, 0)
								gtk_window_set_icon_name(GTK_WINDOW(simulator->window),
										"stock_cell-phone");
							#endif
								gtk_widget_set_size_request(simulator->window, simulator->width,
										simulator->height);
								gtk_window_add_accel_group(GTK_WINDOW(simulator->window), group);
								if(simulator->title == NULL || (p = string_new_append(_("Simulator"),
												" - ", simulator->title, NULL)) == NULL)
									gtk_window_set_title(GTK_WINDOW(simulator->window),
											_("Simulator"));
								else
								{
									gtk_window_set_title(GTK_WINDOW(simulator->window), p);
									free(p);
								}
								gtk_window_set_resizable(GTK_WINDOW(simulator->window), FALSE);
								g_signal_connect_swapped(simulator->window, "delete-event", G_CALLBACK(
											_simulator_on_closex), simulator);
								vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
								/* menubar */
								widget = desktop_menubar_create(_simulator_menubar, simulator, group);
								g_object_unref(group);
								gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
								/* toolbar */
								if(simulator->toolbar != NULL)
									gtk_box_pack_start(GTK_BOX(vbox), simulator->toolbar, FALSE,
											TRUE, 0);
								/* view */
								simulator->socket = gtk_socket_new();
								g_signal_connect_swapped(simulator->socket, "plug-added", G_CALLBACK(
											_simulator_on_plug_added), simulator);
								gtk_box_pack_start(GTK_BOX(vbox), simulator->socket, TRUE, TRUE, 0);
								gtk_container_add(GTK_CONTAINER(simulator->window), vbox);
								gtk_widget_show_all(simulator->window);
								simulator->source = g_idle_add(_new_on_xephyr, simulator);
								return FALSE;
							}
							static gboolean _new_on_quit(gpointer data)
							{
								Simulator * simulator = data;
								simulator->source = 0;
								gtk_main_quit();
								return FALSE;
							}
							static gboolean _new_on_xephyr(gpointer data)
							{
								Simulator * simulator = data;
								char * argv[8] = { BINDIR "/" PROGNAME_XEPHYR, PROGNAME_XEPHYR };
								char parent[16];
								char dpi[16];
								char display[32];
								size_t i;
								GSpawnFlags flags = G_SPAWN_FILE_AND_ARGV_ZERO
									| G_SPAWN_DO_NOT_REAP_CHILD;
								GError * error = NULL;
								size_t pos = 2;
								simulator->source = 0;
								/* set the parent */
								argv[pos++] = "-parent";
								snprintf(parent, sizeof(parent), "%lu", gtk_socket_get_id(
											GTK_SOCKET(simulator->socket)));
								argv[pos++] = parent;
								/* set the DPI */
								argv[pos++] = "-dpi";
								snprintf(dpi, sizeof(dpi), "%u", simulator->dpi);
								argv[pos++] = dpi;
								/* detect the display */
								for(i = 0; i < 16; i++)
								{
									snprintf(display, sizeof(display), "%s%zu%s", "/tmp/.X", i,
											"-lock");
									if(access(display, R_OK) == 0)
										continue;
									snprintf(simulator->name, sizeof(simulator->name), ":%zu", i);
									argv[pos] = simulator->name;
									break;
								}
								if(argv[pos++] == NULL)
								{
									simulator_error(simulator, "No display available", 1);
									return FALSE;
								}
								argv[pos] = NULL;
								/* launch Xephyr */
								if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL,
											&simulator->xephyr.pid, &error) == FALSE)
								{
									simulator_error(simulator, error->message, 1);
									g_error_free(error);
								}
								else
									simulator->xephyr.source = g_child_watch_add(
											simulator->xephyr.pid,
											_simulator_on_child_watch, simulator);
								return FALSE;
							}
							/* simulator_delete */
							void simulator_delete(Simulator * simulator)
							{
								size_t i;
								for(i = 0; i < simulator->children_cnt; i++)
								{
									g_source_remove(simulator->children[i].source);
									g_spawn_close_pid(simulator->children[i].pid);
								}
								free(simulator->children);
								if(simulator->source > 0)
									g_source_remove(simulator->source);
								if(simulator->display != NULL)
									XCloseDisplay(simulator->display);
								if(simulator->xephyr.pid > 0)
								{
									kill(simulator->xephyr.pid, SIGTERM);
									g_source_remove(simulator->xephyr.source);
									g_spawn_close_pid(simulator->xephyr.pid);
								}
								if(simulator->window != NULL)
									gtk_widget_destroy(simulator->window);
								free(simulator->command);
								free(simulator->title);
								free(simulator->model);
								object_delete(simulator);
							}
							/* simulator_error */
							static int _error_text(char const * message, int ret);
							int simulator_error(Simulator * simulator, char const * message, int ret)
							{
								GtkWidget * dialog;
								if(simulator == NULL)
									return _error_text(message, ret);
								dialog = gtk_message_dialog_new(GTK_WINDOW(simulator->window),
										GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
										GTK_BUTTONS_CLOSE,
							# if GTK_CHECK_VERSION(2, 6, 0)
										"%s", _("Error"));
								gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
							# endif
										"%s", message);
								gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
								gtk_dialog_run(GTK_DIALOG(dialog));
								gtk_widget_destroy(dialog);
								return ret;
							}
							static int _error_text(char const * message, int ret)
							{
								fprintf(stderr, PROGNAME_SIMULATOR ": %s\n", message);
								return ret;
							}
							/* simulator_run */
							int simulator_run(Simulator * simulator, char const * command)
							{
								char const display[] = "DISPLAY=";
								char buf[16];
								char * argv[] = { "/bin/sh", "run", "-c", NULL, NULL };
								char ** envp = NULL;
								size_t i;
								char ** p;
								GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD
									| G_SPAWN_SEARCH_PATH | G_SPAWN_FILE_AND_ARGV_ZERO;
								GPid pid;
								GError * error = NULL;
								SimulatorChild * sc;
								/* prepare the arguments */
								if((argv[3] = strdup(command)) == NULL)
									return -simulator_error(simulator, strerror(errno), 1);
								/* prepare the environment */
								for(i = 0; environ[i] != NULL; i++)
								{
									if((p = realloc(envp, sizeof(*p) * (i + 2))) == NULL)
										break;
									envp = p;
									envp[i + 1] = NULL;
									if(strncmp(environ[i], display, sizeof(display) - 1) == 0)
									{
										snprintf(buf, sizeof(buf), "%s%s", "DISPLAY=",
												simulator->name);
										envp[i] = strdup(buf);
									}
									else
										envp[i] = strdup(environ[i]);
									if(envp[i] == NULL)
										break;
								}
								if(environ[i] != NULL)
								{
									for(i = 0; envp[i] != NULL; i++)
										free(envp[i]);
									free(envp);
									free(argv[3]);
									return -simulator_error(simulator, strerror(errno), 1);
								}
								if(g_spawn_async(NULL, argv, envp, flags, NULL, NULL, &pid,
											&error) == FALSE)
								{
									simulator_error(simulator, error->message, 1);
									g_error_free(error);
								}
								else if((sc = realloc(simulator->children, sizeof(*sc)
												* (simulator->children_cnt + 1)))
										!= NULL)
								{
									simulator->children = sc;
									sc = &simulator->children[simulator->children_cnt++];
									sc->source = g_child_watch_add(pid,
											_simulator_on_children_watch, simulator);
									sc->pid = pid;
								}
								else
									g_spawn_close_pid(pid);
								for(i = 0; envp[i] != NULL; i++)
									free(envp[i]);
								free(envp);
								free(argv[3]);
								return 0;
							}
							/* private */
							/* functions */
							/* callbacks */
							/* simulator_on_button_clicked */
							static void _simulator_on_button_clicked(GtkToolButton * button, gpointer data)
							{
								Simulator * simulator = data;
								char const * p;
								KeySym keysym;
								KeyCode keycode;
								/* run commands */
								if((p = g_object_get_data(G_OBJECT(button), "command")) != NULL)
									simulator_run(simulator, p);
								/* simulate keys */
								if(simulator->display == NULL)
									simulator->display = XOpenDisplay(simulator->name);
								if(simulator->display != NULL
										&& (p = g_object_get_data(G_OBJECT(button), "keysym"))
										&& (keysym = XStringToKeysym(p)) != NoSymbol
										&& (keycode = XKeysymToKeycode(simulator->display,
												keysym)) != NoSymbol)
								{
									XTestGrabControl(simulator->display, True);
									XTestFakeKeyEvent(simulator->display, keycode, True, 0);
									XTestFakeKeyEvent(simulator->display, keycode, False, 0);
									XTestGrabControl(simulator->display, False);
								}
							}
							/* simulator_on_child_watch */
							static void _simulator_on_child_watch(GPid pid, gint status, gpointer data)
							{
								Simulator * simulator = data;
								GError * error = NULL;
								if(simulator->xephyr.pid != pid)
									return;
								if(g_spawn_check_exit_status(status, &error) == FALSE)
								{
									simulator_error(simulator, error->message, 1);
									g_error_free(error);
								}
								memset(&simulator->name, 0, sizeof(simulator->name));
								if(simulator->display != NULL)
									XCloseDisplay(simulator->display);
								g_spawn_close_pid(pid);
								simulator->xephyr.pid = -1;
								simulator->xephyr.source = 0;
							}
							/* simulator_on_children_watch */
							static void _simulator_on_children_watch(GPid pid, gint status, gpointer data)
							{
								Simulator * simulator = data;
								size_t i;
								size_t s = sizeof(*simulator->children);
							#if GLIB_CHECK_VERSION(2, 34, 0)
								GError * error = NULL;
								if(g_spawn_check_exit_status(status, &error) == FALSE)
								{
									simulator_error(simulator, error->message, 1);
									g_error_free(error);
								}
							#else
								char buf[64];
								if(!(WIFEXITED(status) && WEXITSTATUS(status) == 0))
								{
									if(WIFEXITED(status))
										snprintf(buf, sizeof(buf), "%s%d",
												_("Child exited with error code "),
												WEXITSTATUS(status));
									else if(WIFSIGNALED(status))
										snprintf(buf, sizeof(buf), "%s%d",
												_("Child killed with signal "),
												WTERMSIG(status));
									else
										snprintf(buf, sizeof(buf), "%s",
												_("Child exited with an error"));
									simulator_error(simulator, buf, 1);
								}
							#endif
								g_spawn_close_pid(pid);
								for(i = 0; i < simulator->children_cnt; i++)
								{
									if(simulator->children[i].pid != pid)
										continue;
									memmove(&simulator->children[i], &simulator->children[i + 1],
											s * (--simulator->children_cnt - i));
									break;
								}
							}
							/* simulator_on_close */
							static void _simulator_on_close(gpointer data)
							{
								Simulator * simulator = data;
								gtk_widget_hide(simulator->window);
								gtk_main_quit();
							}
							/* simulator_on_closex */
							static gboolean _simulator_on_closex(gpointer data)
							{
								Simulator * simulator = data;
								_simulator_on_close(simulator);
								return TRUE;
							}
							/* simulator_on_file_close */
							static void _simulator_on_file_quit(gpointer data)
							{
								Simulator * simulator = data;
								_simulator_on_close(simulator);
							}
							/* simulator_on_file_run */
							static void _run_on_choose_response(GtkWidget * widget, gint arg1,
									gpointer data);
							static void _simulator_on_file_run(gpointer data)
							{
								Simulator * simulator = data;
								GtkWidget * dialog;
								GtkWidget * vbox;
								GtkWidget * hbox;
								GtkWidget * entry;
								GtkWidget * widget;
								GtkFileFilter * filter;
								int res;
								char const * command;
								if(simulator->name[0] == '\0')
								{
									simulator_error(simulator, _("Xephyr is not running"), 1);
									return;
								}
								dialog = gtk_dialog_new_with_buttons(_("Run..."),
										GTK_WINDOW(simulator->window),
										GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
										GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
										GTK_STOCK_EXECUTE, GTK_RESPONSE_ACCEPT, NULL);
								gtk_dialog_set_default_response(GTK_DIALOG(dialog),
										GTK_RESPONSE_ACCEPT);
								gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
							#if GTK_CHECK_VERSION(2, 14, 0)
								vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
							#else
								vbox = GTK_DIALOG(dialog)->vbox;
							#endif
								hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
								gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
								/* label */
								widget = gtk_label_new(_("Command:"));
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								/* entry */
								entry = gtk_entry_new();
								gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
								gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
								/* file chooser */
								widget = gtk_file_chooser_dialog_new(_("Run program..."),
										GTK_WINDOW(dialog),
										GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL,
										GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN,
										GTK_RESPONSE_ACCEPT, NULL);
								/* file chooser: file filters */
								filter = gtk_file_filter_new();
								gtk_file_filter_set_name(filter, _("Executable files"));
								gtk_file_filter_add_mime_type(filter, "application/x-executable");
								gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), filter);
								gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(widget), filter);
								filter = gtk_file_filter_new();
								gtk_file_filter_set_name(filter, _("Perl scripts"));
								gtk_file_filter_add_mime_type(filter, "application/x-perl");
								gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), filter);
								filter = gtk_file_filter_new();
								gtk_file_filter_set_name(filter, _("Python scripts"));
								gtk_file_filter_add_mime_type(filter, "text/x-python");
								gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), filter);
								filter = gtk_file_filter_new();
								gtk_file_filter_set_name(filter, _("Shell scripts"));
								gtk_file_filter_add_mime_type(filter, "application/x-shellscript");
								gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), 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(widget), filter);
								g_signal_connect(widget, "response", G_CALLBACK(
											_run_on_choose_response), entry);
								widget = gtk_file_chooser_button_new_with_dialog(widget);
								gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
								gtk_widget_show_all(vbox);
								/* run the dialog */
								res = gtk_dialog_run(GTK_DIALOG(dialog));
								gtk_widget_hide(dialog);
								if(res == GTK_RESPONSE_ACCEPT)
								{
									command = gtk_entry_get_text(GTK_ENTRY(entry));
									simulator_run(simulator, command);
								}
								gtk_widget_destroy(dialog);
							}
							static void _run_on_choose_response(GtkWidget * widget, gint arg1,
									gpointer data)
							{
								GtkWidget * entry = data;
								if(arg1 != GTK_RESPONSE_ACCEPT)
									return;
								gtk_entry_set_text(GTK_ENTRY(entry), gtk_file_chooser_get_filename(
											GTK_FILE_CHOOSER(widget)));
							}
							/* simulator_on_view_toggle_debugging_mode */
							static void _simulator_on_view_toggle_debugging_mode(gpointer data)
							{
								Simulator * simulator = data;
								kill(simulator->xephyr.pid, SIGUSR1);
							}
							/* simulator_on_help_about */
							static void _simulator_on_help_about(gpointer data)
							{
								Simulator * simulator = data;
								GtkWidget * dialog;
								dialog = desktop_about_dialog_new();
								gtk_window_set_transient_for(GTK_WINDOW(dialog),
										GTK_WINDOW(simulator->window));
								desktop_about_dialog_set_authors(dialog, _authors);
								desktop_about_dialog_set_comments(dialog,
										_("Simulator for the DeforaOS desktop"));
								desktop_about_dialog_set_copyright(dialog, _copyright);
								desktop_about_dialog_set_license(dialog, _license);
								desktop_about_dialog_set_logo_icon_name(dialog, "stock_cell-phone");
								desktop_about_dialog_set_name(dialog, "Simulator");
								desktop_about_dialog_set_version(dialog, VERSION);
								desktop_about_dialog_set_website(dialog, "https://www.defora.org/");
								gtk_dialog_run(GTK_DIALOG(dialog));
								gtk_widget_destroy(dialog);
							}
							/* simulator_on_help_contents */
							static void _simulator_on_help_contents(gpointer data)
							{
								(void) data;
								desktop_help_contents(PACKAGE, PROGNAME_SIMULATOR);
							}
							/* simulator_on_plug_added */
							static void _simulator_on_plug_added(gpointer data)
							{
								Simulator * simulator = data;
								if(simulator->command != NULL)
									simulator_run(simulator, simulator->command);
							}
							