/* This file is part of GNUnet Copyright (C) 2012-2014 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file src/main/gnunet-gtk.c * @author Christian Grothoff * @brief Gtk user interface for GNUnet */ #include "gnunet_gtk.h" #if HAVE_GTK_GTKX_H #include #endif /** * Handle for a plugged in process. */ struct Plug { /** * Kept in a DLL. */ struct Plug *prev; /** * Kept in a DLL. */ struct Plug *next; /** * The socket. */ GtkWidget *s; /** * Name of the binary. */ const char *binary; /** * Environment variable we set. */ const char *env_var; /** * Handle to the child process. */ struct GNUNET_OS_Process *proc; /** * How long do we wait on restarts? */ struct GNUNET_TIME_Relative backoff; /** * Task to restart process after crash. */ struct GNUNET_SCHEDULER_Task * restart_task; /** * ID of the signal associated with the window. */ gulong sig_id; }; /** * Main loop handle. */ static struct GNUNET_GTK_MainLoop *ml; /** * Our configuration. */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Global return value (for success/failure of gnunet-gtk). */ static int gret; /** * Head of plugs. */ static struct Plug *p_head; /** * Tail of plugs. */ static struct Plug *p_tail; /** * Get an object from the main window. * * @param name name of the object * @return NULL on error, otherwise the object */ static GObject * get_object (const char *name) { if (NULL == ml) return NULL; return GNUNET_GTK_main_loop_get_object (ml, name); } /** * Actual main to tear down the system. * * @param cls the main loop handle * @param tc scheduler context */ static void cleanup_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct Plug *p; while (NULL != (p = p_head)) { if (NULL != p->proc) { (void) GNUNET_OS_process_kill (p->proc, SIGTERM); GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (p->proc)); GNUNET_OS_process_destroy (p->proc); p->proc = NULL; } if (NULL != p->restart_task) { GNUNET_SCHEDULER_cancel (p->restart_task); p->restart_task = NULL; } /* This object is long gone, as part of the cleanup of the window from gtk_window_dispose that is triggered with the shutdown; so we should not do it here. (Keeping commented out to show that there is symmetry with initialization -- kind-of) */ /* g_signal_handler_disconnect (p->s, p->sig_id); */ GNUNET_CONTAINER_DLL_remove (p_head, p_tail, p); GNUNET_free (p); } if (NULL == ml) { GNUNET_break (0); return; } GNUNET_GTK_main_loop_quit (ml); } /** * Callback invoked if the application is supposed to exit. */ void gnunet_gtk_quit_cb (GObject * object, gpointer user_data) { GNUNET_SCHEDULER_shutdown (); } #ifdef GDK_WINDOWING_X11 /** * Start the child process for the plug. * * @param p plug identification */ static void start_process (struct Plug *p) { char window_id[128]; Window w; w = gtk_socket_get_id (GTK_SOCKET (p->s)); GNUNET_snprintf (window_id, sizeof (window_id), "%llu", (unsigned long long) w); setenv (p->env_var, window_id, 1); p->proc = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, p->binary, p->binary, NULL); } /** * Restart crashed plugin process. * * @param cls the `struct Plug` of the plugin * @param tc scheduler context */ static void restart_process (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct Plug *p = cls; GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Restarting crashed plugin `%s'\n"), p->binary); p->restart_task = NULL; start_process (p); } /** * The window got detached, restart the child. * * @param sock socket the plug got detached from * @param userdata our `struct Plug *` * @return TRUE (keep socket open) */ static gboolean handle_remove (GtkSocket *sock, gpointer userdata) { struct Plug *p = userdata; if (NULL != p->proc) { (void) GNUNET_OS_process_kill (p->proc, SIGTERM); GNUNET_OS_process_destroy (p->proc); p->proc = NULL; } p->backoff = GNUNET_TIME_STD_BACKOFF (p->backoff); p->restart_task = GNUNET_SCHEDULER_add_delayed (p->backoff, &restart_process, p); return TRUE; } /** * Embed process in our GUI. * * @param container where to embed * @param binary name of the binary to embed * @param env_var name of the environment variable to set */ static void plug (const char *container, const char *binary, const char *env_var) { GtkWidget *v; GtkWidget *l; struct Plug *p; GtkNotebook *n; p = GNUNET_new (struct Plug); p->s = gtk_socket_new (); gtk_widget_set_events (p->s, GDK_ALL_EVENTS_MASK); n = GTK_NOTEBOOK (get_object ("gnunet_gtk_notebook")); v = GTK_WIDGET (get_object (container)); l = gtk_notebook_get_tab_label (n, v); /* remove old tab, replace with new tab; effectively, we preserve the label; but GtkNotebook does not allow us to directly substitute the body of the tab */ g_object_ref (l); gtk_notebook_remove_page (n, gtk_notebook_page_num (n, v)); gtk_notebook_append_page (n, p->s, l); g_object_unref (l); p->binary = binary; p->env_var = env_var; p->sig_id = g_signal_connect (p->s, "plug-removed", G_CALLBACK (handle_remove), p); start_process (p); gtk_widget_show (p->s); GNUNET_CONTAINER_DLL_insert (p_head, p_tail, p); } #endif /** * Actual main method that sets up the configuration window. * * @param cls the main loop handle * @param tc scheduler context */ static void run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { GtkWidget *main_window; ml = cls; cfg = GNUNET_GTK_main_loop_get_configuration (ml); if (GNUNET_OK != GNUNET_GTK_main_loop_build_window (ml, NULL)) return; GNUNET_GTK_set_icon_search_path (); GNUNET_GTK_setup_nls (); main_window = GTK_WIDGET (get_object ("gnunet_gtk_window")); GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup_task, NULL); #ifdef GDK_WINDOWING_X11 plug ("gnunet_statistics_hbox", "gnunet-statistics-gtk", "GNUNET_STATISTICS_GTK_PLUG"); plug ("gnunet_peerinfo_hbox", "gnunet-peerinfo-gtk", "GNUNET_PEERINFO_GTK_PLUG"); plug ("gnunet_namestore_hbox", "gnunet-namestore-gtk", "GNUNET_NAMESTORE_GTK_PLUG"); plug ("gnunet_fs_hbox", "gnunet-fs-gtk", "GNUNET_FS_GTK_PLUG"); plug ("gnunet_identity_hbox", "gnunet-identity-gtk", "GNUNET_IDENTITY_GTK_PLUG"); plug ("gnunet_conversation_hbox", "gnunet-conversation-gtk", "GNUNET_CONVERSATION_GTK_PLUG"); #if 0 plug ("gnunet_setup_hbox", "gnunet-setup", "GNUNET_SETUP_PLUG"); #endif #endif GNUNET_GTK_tray_icon_create (ml, GTK_WINDOW (main_window), "gnunet-fs-gtk", "gnunet-gtk"); gtk_widget_show (main_window); gtk_window_present (GTK_WINDOW (main_window)); } /** * Main function for gnunet-gtk. * * @param argc number of arguments * @param argv arguments * @return 0 on success */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_OPTION_END }; int ret; if (GNUNET_OK == GNUNET_GTK_main_loop_start ("gnunet-gtk", "gnunet-gtk", argc, argv, options, "gnunet_gtk.glade", &run)) ret = gret; else ret = 1; return ret; } /* end of gnunet-gtk.c */