/* This file is part of GNUnet. Copyright (C) 2010-2013 GNUnet e.V. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/identity/gnunet-identity-gtk.c * @brief Main function of gnunet-identity-gtk * @author Christian Grothoff */ #include "gnunet_gtk.h" #include #include "gnunet-identity-gtk_advertise.h" /** * Columns in the identity model. */ enum IDENTITY_ModelColumns { /** * A gchararray */ IDENTITY_MC_NAME = 0, /** * A gchararray */ IDENTITY_MC_IDENTIFIER = 1, /** * A 'struct GNUNET_IDENTIFIER_Ego' */ IDENTITY_MC_EGO = 2 }; /** * Handle to our main loop. */ static struct GNUNET_GTK_MainLoop *ml; /** * Handle to IDENTITY service. */ static struct GNUNET_IDENTITY_Handle *identity; /** * Main window list store. */ static GtkListStore *ls; /** * We need to track active operations with the identity service. */ struct OperationContext { /** * Kept in a DLL. */ struct OperationContext *next; /** * Kept in a DLL. */ struct OperationContext *prev; /** * Operation handle with the identity service. */ struct GNUNET_IDENTITY_Operation *op; }; /** * Head of operations. */ static struct OperationContext *oc_head; /** * Tail of operations. */ static struct OperationContext *oc_tail; /** * Get our configuration. * * @return configuration handle */ const struct GNUNET_CONFIGURATION_Handle * GIG_get_configuration () { return GNUNET_GTK_main_loop_get_configuration (ml); } /** * Get an object from the main window. * * @param name name of the object * @return NULL on error */ static GObject * get_object (const char *name) { return GNUNET_GTK_main_loop_get_object (ml, name); } /** * Identity operation was finished, clean up. * * @param cls the 'struct OperationContext' * @param emsg error message (NULL on success) */ static void operation_finished (void *cls, const char *emsg) { struct OperationContext *oc = cls; GNUNET_CONTAINER_DLL_remove (oc_head, oc_tail, oc); gtk_widget_set_sensitive (GTK_WIDGET (get_object ("GNUNET_GTK_identity_treeview")), TRUE); GNUNET_free (oc); } /** * Context for the advertise popup menu. */ struct AdvertisePopupContext { /** * Ego to advertise. */ struct GNUNET_IDENTITY_Ego *ego; }; /** * "Advertise" was selected in the current context menu. * * @param item the 'advertise' menu item * @param user_data the 'struct AdvertisePopupContext' of the menu */ static void advertise_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct AdvertisePopupContext *apc = user_data; const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; priv = GNUNET_IDENTITY_ego_get_private_key (apc->ego); GIG_advertise_dialog_start_ (priv); } /** * An item was selected from the context menu; destroy the menu shell. * * @param menushell menu to destroy * @param user_data the 'struct AdvertisePopupContext' of the menu */ static void advertise_popup_selection_done (GtkMenuShell *menushell, gpointer user_data) { struct AdvertisePopupContext *apc = user_data; gtk_widget_destroy (GTK_WIDGET (menushell)); GNUNET_free (apc); } /** * User clicked in the treeview widget. Check for right button * to possibly launch advertise window. * * @param widget the treeview widget * @param event the event, we only care about button events * @param user_data unused * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ gboolean GNUNET_GTK_identity_treeview_button_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GtkTreeView *tv = GTK_TREE_VIEW (widget); GdkEventButton *event_button = (GdkEventButton *) event; GtkTreeModel *tm; GtkTreePath *path; GtkTreeIter iter; GtkMenu *menu; GtkWidget *child; struct AdvertisePopupContext *apc; struct GNUNET_IDENTITY_Ego *ego; if ( (GDK_BUTTON_PRESS != event->type) || (3 != event_button->button) ) return FALSE; if (! gtk_tree_view_get_path_at_pos (tv, event_button->x, event_button->y, &path, NULL, NULL, NULL)) return FALSE; /* click outside of area with values, ignore */ tm = gtk_tree_view_get_model (tv); if (! gtk_tree_model_get_iter (tm, &iter, path)) { gtk_tree_path_free (path); return FALSE; /* not sure how we got a path but no iter... */ } gtk_tree_path_free (path); gtk_tree_model_get (GTK_TREE_MODEL (ls), &iter, IDENTITY_MC_EGO, &ego, -1); if (NULL == ego) return FALSE; apc = GNUNET_new (struct AdvertisePopupContext); apc->ego = ego; menu = GTK_MENU (gtk_menu_new ()); child = gtk_menu_item_new_with_label (_("_Advertise")); g_signal_connect (child, "activate", G_CALLBACK (advertise_ctx_menu), apc); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); gtk_menu_shell_append (GTK_MENU_SHELL (menu), child); g_signal_connect (menu, "selection-done", G_CALLBACK (advertise_popup_selection_done), apc); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event_button->button, event_button->time); return FALSE; } /** * User pushed a key (possibly DEL) in the treeview widget. * Delete the selected entry if the key was DEL. * * @param widget the entry widget * @param event the key stroke * @param user_data the main window context * @return FALSE if this was not ENTER, TRUE if it was */ gboolean GNUNET_GTK_identity_treeview_key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer user_data) { gchar *old; struct OperationContext *oc; GtkTreeSelection *sel; GtkTreeIter iter; if (GDK_KEY_Delete != event->keyval) return FALSE; sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (get_object ("GNUNET_GTK_identity_treeview"))); if (! gtk_tree_selection_get_selected (sel, NULL, &iter)) return FALSE; gtk_tree_model_get (GTK_TREE_MODEL (ls), &iter, IDENTITY_MC_NAME, &old, -1); oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); oc->op = GNUNET_IDENTITY_delete (identity, old, &operation_finished, oc); return TRUE; } /** * The user edited one of the names of the egos. Change it * in the IDENTITY service. * * @param renderer renderer where the change happened * @param path location in the model where the change happened * @param new_text updated text * @param user_data internal context (not used) */ void GNUNET_GTK_namespace_organizer_namespaces_treeview_column_name_text_edited_cb (GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data) { GtkTreePath *treepath; GtkTreeIter iter; struct GNUNET_IDENTITY_Ego *ego; gchar *old; struct OperationContext *oc; treepath = gtk_tree_path_new_from_string (path); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, treepath)) { GNUNET_break (0); gtk_tree_path_free (treepath); return; } gtk_tree_path_free (treepath); gtk_tree_model_get (GTK_TREE_MODEL (ls), &iter, IDENTITY_MC_NAME, &old, IDENTITY_MC_EGO, &ego, -1); gtk_widget_set_sensitive (GTK_WIDGET (get_object ("GNUNET_GTK_identity_treeview")), FALSE); oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); if (NULL == ego) { /* create operation */ oc->op = GNUNET_IDENTITY_create (identity, new_text, &operation_finished, oc); } else if (0 != strlen (new_text)) { /* rename operation */ oc->op = GNUNET_IDENTITY_rename (identity, old, new_text, &operation_finished, oc); } else { /* delete operation */ oc->op = GNUNET_IDENTITY_delete (identity, old, &operation_finished, oc); } } /** * Task run on shutdown. * * @param cls unused */ static void shutdown_task (void *cls) { struct OperationContext *oc; GIG_advertise_shutdown_ (); while (NULL != (oc = oc_head)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Operation not completed due to shutdown\n")); GNUNET_IDENTITY_cancel (oc->op); GNUNET_CONTAINER_DLL_remove (oc_head, oc_tail, oc); GNUNET_free (oc); } if (NULL != identity) { GNUNET_IDENTITY_disconnect (identity); identity = NULL; } GNUNET_GTK_main_loop_quit (ml); ml = NULL; } /** * Callback invoked if the application is supposed to exit. * * @param object * @param user_data unused */ void GNUNET_GTK_identity_quit_cb (GObject * object, gpointer user_data) { GNUNET_SCHEDULER_shutdown (); } /** * Add all updateable entries of the current namespace to the * tree store. * * @param cls our 'struct MainPublishingDialogContext' * @param ego identity of the namespace to add * @param ego_ctx where to store context data * @param name name of the namespace to add */ static void add_ego (void *cls, struct GNUNET_IDENTITY_Ego *ego, void **ego_ctx, const char *name) { GtkTreePath *path; GtkTreeRowReference *rr; GtkTreeIter iter; char *id; struct GNUNET_CRYPTO_EcdsaPublicKey pub; if (NULL == ego) return; /* nothing to be done */ rr = *ego_ctx; if (NULL == rr) { /* insert operation */ GNUNET_assert (NULL != name); GNUNET_IDENTITY_ego_get_public_key (ego, &pub); id = GNUNET_CRYPTO_ecdsa_public_key_to_string (&pub); gtk_list_store_insert_with_values (ls, &iter, G_MAXINT, IDENTITY_MC_NAME, name, IDENTITY_MC_IDENTIFIER, id, IDENTITY_MC_EGO, ego, -1); GNUNET_free (id); path = gtk_tree_model_get_path (GTK_TREE_MODEL (ls), &iter); rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ls), path); gtk_tree_path_free (path); *ego_ctx = rr; } else if (NULL == name) { /* delete operation */ path = gtk_tree_row_reference_get_path (rr); gtk_tree_row_reference_free (rr); GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, path)); gtk_tree_path_free (path); gtk_list_store_remove (ls, &iter); *ego_ctx = NULL; } else { /* rename operation */ path = gtk_tree_row_reference_get_path (rr); GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, path)); gtk_list_store_set (ls, &iter, IDENTITY_MC_NAME, name, -1); gtk_tree_path_free (path); } } /** * Actual main function run right after GNUnet's scheduler * is initialized. Initializes up GTK and Glade. * * @param cls NULL */ static void run (void *cls) { GtkWidget *main_window; GtkTreeIter iter; ml = cls; if (GNUNET_OK != GNUNET_GTK_main_loop_build_window (ml, NULL)) return; GNUNET_GTK_set_icon_search_path (); GNUNET_GTK_setup_nls (); /* setup main window */ main_window = GTK_WIDGET (get_object ("GNUNET_GTK_identity_window")); main_window = GNUNET_GTK_plug_me ("GNUNET_IDENTITY_GTK_PLUG", main_window); ls = GTK_LIST_STORE (get_object ("GNUNET_GTK_identity_liststore")); GNUNET_assert (NULL != ls); gtk_list_store_insert_with_values (ls, &iter, G_MAXINT, IDENTITY_MC_NAME, "", -1); gtk_window_maximize (GTK_WINDOW (main_window)); /* make GUI visible */ gtk_widget_show (main_window); gtk_window_present (GTK_WINDOW (main_window)); identity = GNUNET_IDENTITY_connect (GIG_get_configuration (), &add_ego, NULL); GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); } /** * Main function of gnunet-identity-gtk. * * @param argc number of arguments * @param argv arguments * @return 0 on success */ int main (int argc, char *const *argv) { static struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_OPTION_END }; if (GNUNET_OK != GNUNET_GTK_main_loop_start ("gnunet-identity-gtk", "GTK GUI for managing egos", argc, argv, options, "gnunet_identity_gtk_main_window.glade", &run)) return 1; return 0; } /* end of gnunet-identity-gtk.c */