/* This file is part of GNUnet (C) 2003, 2004, 2005, 2006 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 2, 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/common/helper.c * @brief This file contains some GUI helper functions * @author Igor Wronsky * @author Christian Grothoff */ #include "platform.h" #include "gnunetgtk_common.h" #include #define HELPER_DEBUG NO #ifdef WITH_LIBNOTIFY #include #endif #ifdef WITH_LIBGKSU2 /*#include * commented because support is broken in Debian, * and these headers are not really useful for us */ #endif typedef struct { struct SEMAPHORE * sem; void * args; SimpleCallback func; int destroyed; } SaveCall; typedef struct Plugin { struct Plugin * next; char * name; void * library; } Plugin; static GladeXML * mainXML; static char * gladeFile; static GladeXML * statusXML; static GtkWidget * infoWindow; static GtkWidget * infoWindowTextView; static GdkWindowState main_window_state; /** * the main thread */ static struct PTHREAD_T * mainThread; static SaveCall ** psc; static unsigned int pscCount; static struct MUTEX * sclock; static int saveCallsUp; static Plugin * plugin; static void * shutdown_function; #ifdef WINDOWS static void CALLBACK sigalrmHandler(DWORD sig) { } #endif static gboolean saveCallWrapper(gpointer data) { SaveCall * call = data; int i; /* clearly, we are no longer pending, so remove from psc */ if (call->sem != NULL) { MUTEX_LOCK(sclock); for (i=0;ifunc(call->args); if (call->sem != NULL) SEMAPHORE_UP(call->sem); return FALSE; } /** * Call a callback function from the mainloop/main thread ("SaveCall"). * Since GTK doesn't work with multi-threaded applications under Windows, * all GTK operations have to be done in the main thread */ void gtkSaveCall(SimpleCallback func, void * args) { SaveCall call; MUTEX_LOCK(sclock); if ( (saveCallsUp == NO) || (! PTHREAD_SELF_TEST(mainThread)) ) { call.args = args; call.func = func; call.sem = SEMAPHORE_NEW(0); call.destroyed = 0; GROW(psc, pscCount, pscCount+1); psc[pscCount-1] = &call; gtk_idle_add(&saveCallWrapper, &call); MUTEX_UNLOCK(sclock); PTHREAD_STOP_SLEEP(mainThread); SEMAPHORE_DOWN(call.sem, YES); SEMAPHORE_FREE(call.sem, YES); } else { MUTEX_UNLOCK(sclock); func(args); } } /** * Callback for handling "delete_event": close the window */ gint on_statusWindow_delete_event(GtkWidget * widget, GdkEvent * event, gpointer data) { return FALSE; } /** * Closure for doInfoMessage. */ typedef struct { int doPopup; char * note; } InfoMessage; /** * Callback for infoMessage() */ static void doInfoMessage(void * args) { const InfoMessage * info = args; GtkTextIter iter; GtkTextBuffer * buffer; if (info->doPopup==YES) gtk_widget_show(infoWindow); buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(infoWindowTextView)); gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); gtk_text_buffer_insert(buffer, &iter, info->note, -1); } /** * Appends a message to the info window * * @param doPopup do we open the window, YES or NO */ void infoMessage(int doPopup, const char * format, ...) { va_list args; InfoMessage info; va_start(args, format); info.note = g_strdup_vprintf(format, args); va_end(args); info.doPopup = doPopup; gtkSaveCall(&doInfoMessage, &info); g_free(info.note); } static void saveAddLogEntry(void * args) { static GtkWidget * s = NULL; static int once = 1; static guint id; if (once) { once = 0; s = glade_xml_get_widget(mainXML, "statusbar"); id = gtk_statusbar_get_context_id(GTK_STATUSBAR(s), "LOG"); } else gtk_statusbar_pop(GTK_STATUSBAR(s), id); gtk_statusbar_push(GTK_STATUSBAR(s), id, (const char*) args); } /** * Appends a log entry to the info window * * @param txt the log entry * */ void addLogEntry(const char * txt, ...) { va_list args; gchar * note; va_start(args, txt); note = g_strdup_vprintf(txt, args); va_end(args); infoMessage(NO, note); gtkSaveCall(&saveAddLogEntry, (void*) note); g_free(note); } /** * Simple accessor method. */ const char * getGladeFileName() { return gladeFile; } /** * Simple accessor method. */ GladeXML * getMainXML() { return mainXML; } static void connector(const gchar *handler_name, GObject *object, const gchar *signal_name, const gchar *signal_data, GObject *connect_object, gboolean after, gpointer user_data) { GladeXML * xml = user_data; Plugin * plug; void * method; plug = plugin; method = NULL; while (plug != NULL) { method = trybindDynamicMethod(plug->library, "", handler_name); if (method != NULL) break; plug = plug->next; } if (0 == strcmp(handler_name, "gnunet_gtk_main_quit")) method = shutdown_function; if (method == NULL) { GE_LOG(ectx, GE_WARNING | GE_DEVELOPER | GE_IMMEDIATE, _("Failed to find handler for `%s'\n"), handler_name); return; } glade_xml_signal_connect(xml, handler_name, (GCallback) method); } void connectGladeWithPlugins(GladeXML * xml) { glade_xml_signal_autoconnect_full(xml, &connector, xml); } typedef void (*PlainCall)(void); static void loadPlugin(const char * name) { Plugin * p; void * lib; PlainCall init; lib = loadDynamicLibrary("libgnunetgtkmodule_", name); if (lib == NULL) { GE_LOG(ectx, GE_WARNING | GE_ADMIN | GE_IMMEDIATE, _("Failed to load plugin `%s'\n"), name); return; } p = MALLOC(sizeof(Plugin)); p->name = STRDUP(name); p->next = plugin; p->library = lib; plugin = p; init = trybindDynamicMethod(lib, "init_", name); if (init != NULL) init(); } static void loadPlugins(const char * names) { char * dup; char * next; const char * pos; if (names == NULL) return; dup = STRDUP(names); next = dup; do { while (*next == ' ') next++; pos = next; while ( (*next != '\0') && (*next != ' ') ) next++; if (*next == '\0') { next = NULL; /* terminate! */ } else { *next = '\0'; /* add 0-termination for pos */ next++; } if (strlen(pos) > 0) loadPlugin(pos); } while (next != NULL); FREE(dup); } static void unloadPlugin(Plugin * plug) { PlainCall done; done = trybindDynamicMethod(plug->library, "done_", plug->name); if (done != NULL) done(); unloadDynamicLibrary(plug->library); FREE(plug->name); FREE(plug); } void initGNUnetGTKCommon(void * callback) { char * load; shutdown_function = callback; sclock = MUTEX_CREATE(YES); PTHREAD_GET_SELF(&mainThread); saveCallsUp = YES; /* load the interface */ #ifdef MINGW gladeFile = MALLOC(_MAX_PATH + 1); plibc_conv_to_win_path(PACKAGE_DATA_DIR"/gnunet-gtk.glade", gladeFile); #else gladeFile = STRDUP(PACKAGE_DATA_DIR"/gnunet-gtk.glade"); #endif mainXML = glade_xml_new(gladeFile, "mainWindow", PACKAGE_NAME); if (mainXML == NULL) errexit(_("Failed to open `%s'.\n"), gladeFile); statusXML = glade_xml_new(getGladeFileName(), "statusWindow", PACKAGE_NAME); infoWindow = glade_xml_get_widget(statusXML, "statusWindow"); infoWindowTextView = glade_xml_get_widget(statusXML, "messageWindowTextView"); /* load the plugins */ load = getConfigurationString("GNUNET-GTK", "PLUGINS"); if (load == NULL) load = STRDUP("about daemon fs"); loadPlugins(load); FREE(load); connectGladeWithPlugins(mainXML); connectGladeWithPlugins(statusXML); } void shutdownPlugins() { int i; /* unload the plugins */ while (plugin != NULL) { Plugin * next; next = plugin->next; unloadPlugin(plugin); plugin = next; } gtk_widget_destroy(infoWindow); infoWindow = NULL; UNREF(statusXML); UNREF(mainXML); mainXML = NULL; FREE(gladeFile); gladeFile = NULL; saveCallsUp = NO; MUTEX_LOCK(sclock); for (i=0;ifunc(psc[i]); i = pscCount; MUTEX_UNLOCK(sclock); /* wait until all PSC-jobs have left the gtkSaveCall method before destroying the mutex! */ while (i != 0) { gnunet_util_sleep(50 * cronMILLIS); MUTEX_LOCK(sclock); i = pscCount; MUTEX_UNLOCK(sclock); } } void doneGNUnetGTKCommon() { PTHREAD_REL_SELF(&mainThread); MUTEX_DESTROY(sclock); } struct rwsc_closure { struct SEMAPHORE * sig; PThreadMain realMain; void * arg; }; static void * shutdownCode(void * arg) { struct rwsc_closure * cls = arg; void * ret; ret = cls->realMain(cls->arg); SEMAPHORE_UP(cls->sig); PTHREAD_STOP_SLEEP(mainThread); return ret; } void run_with_save_calls(PThreadMain cb, void * arg) { struct PTHREAD_T * doneThread; void * unused; struct rwsc_closure cls; int i; cls.sig = SEMAPHORE_NEW(0); cls.realMain = cb; cls.arg = arg; doneThread = PTHREAD_CREATE(&shutdownCode, &cls, 64*1024); if (doneThread == NULL) GE_DIE_STRERROR(ectx, "pthread_create"); if (! PTHREAD_SELF_TEST(mainThread)) { /* another thread will run the save calls */ SEMAPHORE_DOWN(cls.sig, YES); } else { while (OK != SEMAPHORE_DOWN(cls.sig, NO)) { MUTEX_LOCK(sclock); if (pscCount > 0) { i = weak_randomi(pscCount); if (TRUE == g_idle_remove_by_data(psc[i])) saveCallWrapper(psc[i]); } else { i = -1; } MUTEX_UNLOCK(sclock); if ( (i == -1) && (OK != SEMAPHORE_DOWN(cls.sig, NO)) ) { gnunet_util_sleep(50 * cronMILLIS); } } } PTHREAD_JOIN(doneThread, &unused); SEMAPHORE_FREE(cls.sig); } /** * Simple glue to libnotify, and others? * */ void gnunetgtk_notify(int type, const char *message, ...) { #ifdef WITH_LIBNOTIFY static int once; char * msg; size_t size; va_list arg; GtkWidget * root; NotifyNotification *libnotify; NotifyUrgency libnotify_urgency = NOTIFY_URGENCY_NORMAL; long libnotify_expire_timeout = NOTIFY_EXPIRES_DEFAULT; if (! notify_is_initted()){ if (once == 1) return; if (! notify_init ("gnunet-gtk")) { once = 1; GE_LOG(ectx, GE_WARNING | GE_BULK | GE_USER | GE_ADMIN, _("Could not initialize libnotify\n")); return; } } root = glade_xml_get_widget(getMainXML(),"mainWindow"); if (gtk_window_is_active(GTK_WINDOW(root)) == FALSE) { if (type == NOTIFY_LOW) libnotify_urgency = NOTIFY_URGENCY_LOW; else if( type == NOTIFY_NORMAL) libnotify_urgency = NOTIFY_URGENCY_NORMAL; else libnotify_urgency = NOTIFY_URGENCY_CRITICAL; va_start(arg, message); size = vsnprintf(NULL, 0, message, arg); va_end(arg); msg = MALLOC(size+1); va_start(arg, message); vsnprintf(msg, size, message, arg); va_end(arg); libnotify = notify_notification_new("gnunet-gtk", msg, PACKAGE_DATA_DIR"/gnunet-gtk-notify.png", NULL); FREE(msg); notify_notification_set_timeout(libnotify, libnotify_expire_timeout); notify_notification_set_urgency(libnotify, libnotify_urgency); if (! notify_notification_show (libnotify, NULL)) { once = 1; GE_LOG(ectx, GE_WARNING | GE_USER | GE_ADMIN | GE_BULK, _("Could not send notification via libnotify\n")); } g_object_unref(G_OBJECT(libnotify)); notify_uninit(); } #endif } /** * Validate that a string is a Utf-8 string. * If validation fails, msg is freed and a valid * Utf-8 string is returned. */ char * validate_utf8(char * msg) { const gchar * end; char * ret; gsize send; end = NULL; if (TRUE == g_utf8_validate(msg, -1, &end)) return msg; /* hope that it is ISO8859-1 */ ret = g_convert_with_fallback(msg, -1, "UTF-8", "ISO8859-1", ".", NULL, &send, NULL); FREE(msg); msg = STRDUP(ret); g_free(ret); return msg; } /** * Gtk callback to save the main window state (tray icon use) */ void saveMainWindowState(GtkWidget *main_window, GdkEventWindowState *event, gpointer user_data) { main_window_state = (*event).new_window_state; return; } /** * Get the last main window state when restoring (tray icon use) */ GdkWindowState getMainWindowState() { return main_window_state; } /** * Start gnunet-setup, asking for a password if needed */ gboolean startGNUnetSetup (gboolean run_wizard) { int code; char *error_message; #ifdef WITH_LIBGKSU2 GError *gerror = NULL; if(run_wizard) { code = gksu_run("gnunet-setup -d wizard-gtk", &gerror); } else { code = gksu_run("gnunet-setup -d", &gerror); } if(code && !gerror) { error_message = STRDUP(_("GKSu encountered an unknown error running the configuration tool (gnunet-setup).")); } else if(code && gerror) { error_message = g_strdup_printf(_("GKSu returned:\n%s"), gerror->message); g_error_free(gerror); } else { error_message = NULL; } #elif defined(WINDOWS) /* FIXME: run gnunet-setup, assuming we can get the needed rights */ error_message = STRDUP("Not implemented yet !"); return TRUE; #else error_message = STRDUP(_("GKSu support is not enabled, impossible to get the needed rights. You should build gnunet-gtk with the --enable-libgksu2 option, or get the right binary package. Note you can still start the configuration tool (gnunet-setup) manually.")); return TRUE; #endif GtkWidget *mainWindow, *messageDialog; mainWindow = glade_xml_get_widget(getMainXML(), "mainWindow"); messageDialog = gtk_message_dialog_new (GTK_WINDOW(mainWindow), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, _("Failed to run the configuration tool (gnunet-setup): %s"), error_message); gtk_dialog_run(GTK_DIALOG(messageDialog)); gtk_widget_destroy (messageDialog); FREE(error_message); return code; } /* end of helper.c */