/* This file is part of GNUnet. Copyright (C) 2010-2014 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/conversation/gnunet-conversation-gtk_phone.c * @brief main logic to manage phone calls * @author yids * @author hark * @author Christian Grothoff */ #include "gnunet-conversation-gtk.h" #include "gnunet-conversation-gtk_egos.h" #include "gnunet-conversation-gtk_history.h" #include "gnunet-conversation-gtk_import.h" #include "gnunet-conversation-gtk_log.h" #include "gnunet-conversation-gtk_phone.h" #include "gnunet-conversation-gtk_zones.h" /** * Active calls treeview columns */ enum ActiveCallsTreeViewColumns { /** * A `gchar *` giving the name of the remote user. */ GCG_PHONE_LS_REMOTE_USER_NAME, /** * A `struct IncomingCall *` for incoming calls, otherwise NULL. */ GCG_PHONE_LS_INCOMING_CALL, /** * A `gint` uniquely identifying the call to the user. */ GCG_PHONE_LS_NUMBER, /** * A `gint` for a `enum TypeOfConversation`. */ GCG_PHONE_LS_TYPE, /** * A `gint` for a `enum InCallState`. */ GCG_PHONE_LS_IN_CALL_STATE, /** * A `struct OutgoingCall *` for outgoing calls, otherwise NULL. */ GCG_PHONE_LS_OUTGOING_CALL, /** * A `gint` for a `enum OutCallState`. */ GCG_PHONE_LS_OUT_CALL_STATE, /** * A `gchararray` describing the state of the call. */ GCG_PHONE_LS_STATE_NAME, /** * A `GdkPixbuf` visualizing the state of the call. * Not yet used. */ GCG_PHONE_LS_STATE_PIXBUF }; /** * Types of conversations */ enum TypeOfConversation { /** * Incoming phone call. */ CALL_IN, /** * Outgoing call. */ CALL_OUT }; /** * States for current incoming call. */ enum InCallState { /** * No incoming call. */ IN_STATE_NONE, /** * Our phone is ringing. */ IN_STATE_RINGING, /** * The other party accepted our call and we are now connected. */ IN_STATE_CONNECTED, /** * The call is currently suspended (by us). */ IN_STATE_SUSPENDED_LOCAL, /** * The call is currently suspended (by remote). */ IN_STATE_SUSPENDED_REMOTE, /** * The call is currently suspended (by both). */ IN_STATE_SUSPENDED_BOTH }; /** * Possible states of outgoing phone calls. */ enum OutCallState { /** * Phone state we use if this is not an outgoing call. */ OUT_STATE_NONE, /** * This phone call is currently resolving. */ OUT_STATE_RESOLVING, /** * This phone call is currently ringing. */ OUT_STATE_RINGING, /** * This phone call is currently active. */ OUT_STATE_CONNECTED, /** * This phone call is currently suspended by local. */ OUT_STATE_SUSPENDED_LOCAL, /** * This phone call is currently suspended by remote. */ OUT_STATE_SUSPENDED_REMOTE, /** * This phone call is currently suspended from both sides. */ OUT_STATE_SUSPENDED_BOTH }; /** * List of incoming calls */ struct IncomingCall { /** * Handle to hang up or activate. */ struct GNUNET_CONVERSATION_Caller *caller; /** * Public key identifying the caller. */ struct GNUNET_IDENTITY_PublicKey caller_id; /** * Caller ID as human-readable string. */ char *caller_id_str; /** * Namestore handle for our reverse lookup (to convert * @e caller_id into *nice* human-readable @e caller_id_str). */ struct GNUNET_NAMESTORE_QueueEntry *qe; /** * Location of this call in the list. */ GtkTreeRowReference *rr; /** * Unique number of the caller. */ guint caller_num; /** * State for this call. */ enum InCallState state; }; /** * Information we keep for an outgoing call. */ struct OutgoingCall { /** * Associated conversation handle. */ struct GNUNET_CONVERSATION_Call *call; /** * Name of conversation partner. */ char *caller_id_str; /** * Location of this call in the list. */ GtkTreeRowReference *rr; /** * Unique number of the caller. */ guint caller_num; /** * State for this call. */ enum OutCallState state; }; /** * List of active calls */ static GtkListStore *active_liststore; /** * List of active calls */ static GtkTreeView *active_treeview; /** * Phone handle */ static struct GNUNET_CONVERSATION_Phone *phone; /** * Our speaker. */ static struct GNUNET_SPEAKER_Handle *speaker; /** * Our microphone. */ static struct GNUNET_MICROPHONE_Handle *mic; /** * Counts the number of incoming or outgoing calls we have had so far. */ static guint caller_num_gen; /** * Number of incoming calls that are currently ringing. */ static unsigned int in_ring_counter; /** * Caller handle (for active incoming call, or NULL). */ static struct IncomingCall *active_in; /** * Call handle (of the active outgoing call, or NULL). */ static struct OutgoingCall *active_out; /** * The "add contact" (or "> contact") button. */ static GtkWidget *b_add_contact; /** * The "accept" button. */ static GtkWidget *b_accept; /** * The "refuse" button. */ static GtkWidget *b_refuse; /** * The "pause" button. */ static GtkWidget *b_suspend; /** * The "resume" button. */ static GtkWidget *b_resume; /** * The "connect" button. */ static GtkWidget *b_call; /** * The "disconnect" button. */ static GtkWidget *b_hangup; /** * The "copy" button. */ static GtkWidget *b_copy; /** * Our address entry. */ static GtkEntry *address_entry; /** * Convert in call state to human readable string. * * @param is state to convert * @return human readable description */ static const char * in_call_state_to_string (enum InCallState is) { switch (is) { case IN_STATE_NONE: return _ ("none"); case IN_STATE_RINGING: return _ ("ringing"); case IN_STATE_CONNECTED: return _ ("connected"); case IN_STATE_SUSPENDED_LOCAL: return _ ("suspended"); case IN_STATE_SUSPENDED_REMOTE: return _ ("on hold"); case IN_STATE_SUSPENDED_BOTH: return _ ("suspended and on hold"); } return NULL; } /** * Convert out call state to human readable string. * * @param os state to convert * @return human readable description */ static const char * out_call_state_to_string (enum OutCallState os) { switch (os) { case OUT_STATE_NONE: return _ ("none"); case OUT_STATE_RESOLVING: return _ ("resolving"); case OUT_STATE_RINGING: return _ ("calling"); case OUT_STATE_CONNECTED: return _ ("connected"); case OUT_STATE_SUSPENDED_LOCAL: return _ ("suspended"); case OUT_STATE_SUSPENDED_REMOTE: return _ ("on hold"); case OUT_STATE_SUSPENDED_BOTH: return _ ("suspended and on hold"); } return NULL; } /** * Free data structure associated with an incoming call. * * @param ic incoming call to free */ static void destroy_in (struct IncomingCall *ic) { if (NULL != ic->caller) { GNUNET_CONVERSATION_caller_hang_up (ic->caller); ic->caller = NULL; } GNUNET_free (ic->caller_id_str); if (NULL != ic->qe) GNUNET_NAMESTORE_cancel (ic->qe); if (NULL != ic->rr) gtk_tree_row_reference_free (ic->rr); GNUNET_free (ic); } /** * Free data structure associated with an outgoing call. * * @param oc outgoing call to free */ static void destroy_out (struct OutgoingCall *oc) { if (NULL != oc->call) { GNUNET_CONVERSATION_call_stop (oc->call); oc->call = NULL; } GNUNET_free (oc->caller_id_str); if (NULL != oc->rr) gtk_tree_row_reference_free (oc->rr); GNUNET_free (oc); } /** * Update status based on current phone state. */ static void do_status () { if ((NULL == active_in) && (NULL == active_out) && (0 != in_ring_counter)) { GCG_update_status_bar (_ ("The phone is ringing (%u calls waiting)"), in_ring_counter); return; } if ((NULL == active_in) && (NULL == active_out)) { if (NULL != phone) { GCG_update_status_bar ("%s", _ ("We are listening for incoming calls")); } else { if (NULL == GCG_EGOS_get_selected_ego ()) { GCG_update_status_bar ("%s", _ ("No ego selected, phone is down.")); } else { GCG_update_status_bar ("%s", _ ("Failed to setup phone (internal error)")); } } return; } if (NULL != active_in) { switch (active_in->state) { case IN_STATE_NONE: GNUNET_break (0); break; case IN_STATE_RINGING: GNUNET_break (0); break; case IN_STATE_CONNECTED: GCG_update_status_bar (_ ("In a conversation with `%s'."), active_in->caller_id_str); break; case IN_STATE_SUSPENDED_LOCAL: GNUNET_break (0); break; case IN_STATE_SUSPENDED_REMOTE: GCG_update_status_bar (_ ("On hold in a conversation with `%s'."), active_in->caller_id_str); break; case IN_STATE_SUSPENDED_BOTH: GNUNET_break (0); break; } return; } if (NULL != active_out) { switch (active_out->state) { case OUT_STATE_NONE: GNUNET_break (0); break; case OUT_STATE_RESOLVING: GCG_update_status_bar (_ ("Resolving `%s'."), active_out->caller_id_str); break; case OUT_STATE_RINGING: GCG_update_status_bar (_ ("Calling `%s'."), active_out->caller_id_str); break; case OUT_STATE_CONNECTED: GCG_update_status_bar (_ ("In a conversation with `%s'."), active_out->caller_id_str); break; case OUT_STATE_SUSPENDED_LOCAL: GNUNET_break (0); break; case OUT_STATE_SUSPENDED_REMOTE: GCG_update_status_bar (_ ("On hold in a conversation with `%s'."), active_out->caller_id_str); break; case OUT_STATE_SUSPENDED_BOTH: GNUNET_break (0); break; } return; } } /** * Check if the conditions are met for the "call" button to * be sensitive again. Those conditions are that we must * have a phone that is ready to "listen" (no error or active * call), and that some address text is in the address entry. */ static void check_call_sensitivity () { gboolean sens; const char *tld; sens = (NULL == active_in) && (NULL == active_out); if (0 == strlen (gtk_entry_get_text (address_entry))) sens = FALSE; if (NULL == phone) sens = FALSE; if (NULL == GCG_ZONES_get_selected_zone (&tld)) sens = FALSE; gtk_widget_set_sensitive (b_call, sens); } /** * Update sensitivity of buttons associated with the call list. * * @param sel selection that changed (belongs to #active_treeview) */ static void update_call_buttons (GtkTreeSelection *sel) { GtkTreeIter iter; GtkTreeModel *model; struct IncomingCall *ic; gint type; gint in_state; gint out_state; gboolean in_call; if (! gtk_tree_selection_get_selected (sel, &model, &iter)) { /* nothing selected, disable buttons */ gtk_widget_set_sensitive (b_add_contact, FALSE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, FALSE); gtk_widget_set_sensitive (b_resume, FALSE); return; } in_call = (NULL != active_in) || (NULL != active_out); gtk_tree_model_get (model, &iter, GCG_PHONE_LS_TYPE, &type, GCG_PHONE_LS_OUT_CALL_STATE, &out_state, GCG_PHONE_LS_IN_CALL_STATE, &in_state, GCG_PHONE_LS_INCOMING_CALL, &ic, -1); switch ((enum TypeOfConversation) type) { case CALL_IN: switch ((enum InCallState) in_state) { case IN_STATE_NONE: GNUNET_break (0); break; case IN_STATE_RINGING: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, ! in_call); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, FALSE); break; case IN_STATE_CONNECTED: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, (active_in != ic)); gtk_widget_set_sensitive (b_resume, FALSE); break; case IN_STATE_SUSPENDED_LOCAL: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, TRUE); break; case IN_STATE_SUSPENDED_REMOTE: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, FALSE); break; case IN_STATE_SUSPENDED_BOTH: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, TRUE); break; } break; case CALL_OUT: switch ((enum OutCallState) out_state) { case OUT_STATE_NONE: GNUNET_break (0); break; case OUT_STATE_RESOLVING: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, FALSE); gtk_widget_set_sensitive (b_resume, FALSE); break; case OUT_STATE_RINGING: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, ! in_call); gtk_widget_set_sensitive (b_refuse, FALSE); gtk_widget_set_sensitive (b_resume, ! in_call); break; case OUT_STATE_CONNECTED: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, FALSE); gtk_widget_set_sensitive (b_resume, FALSE); break; case OUT_STATE_SUSPENDED_LOCAL: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, ! in_call); break; case OUT_STATE_SUSPENDED_REMOTE: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, FALSE); break; case OUT_STATE_SUSPENDED_BOTH: gtk_widget_set_sensitive (b_add_contact, TRUE); gtk_widget_set_sensitive (b_accept, FALSE); gtk_widget_set_sensitive (b_refuse, TRUE); gtk_widget_set_sensitive (b_resume, ! in_call); break; } break; } } /** * @brief executed when selecting a different item in active call list * * @param sel selection that changed (belongs to #active_treeview) * @param user_data builder context (unused) */ void gnunet_conversation_gtk_active_calls_treeview_selection_changed_cb ( GtkTreeSelection *sel, gpointer user_data) { update_call_buttons (sel); } /** * Update all aspects of the GUI (visibility and sensitivity * of all buttons) based on the current state. */ static void update_gui () { if ((NULL != active_in) || (NULL != active_out)) { /* in a call, show hangup/suspend buttons */ gtk_widget_show (b_suspend); gtk_widget_show (b_hangup); gtk_widget_hide (b_call); gtk_widget_set_sensitive (b_hangup, TRUE); if ((NULL != active_in) || ((NULL != active_out) && (OUT_STATE_RESOLVING != active_out->state) && (OUT_STATE_RINGING != active_out->state))) gtk_widget_set_sensitive (b_suspend, TRUE); else gtk_widget_set_sensitive (b_suspend, FALSE); gtk_widget_set_sensitive (GTK_WIDGET (address_entry), FALSE); } else { /* not in a call, show 'call' button */ gtk_widget_hide (b_suspend); gtk_widget_hide (b_hangup); gtk_widget_show (b_call); gtk_widget_set_sensitive (b_hangup, FALSE); gtk_widget_set_sensitive (b_suspend, FALSE); gtk_widget_set_sensitive (GTK_WIDGET (address_entry), TRUE); check_call_sensitivity (); } update_call_buttons (gtk_tree_view_get_selection (active_treeview)); do_status (); } /** * Set state of outgoing call. * * @param oc outgoing call to change state of * @param state new state of the call */ static void set_outgoing_call_state (struct OutgoingCall *oc, enum OutCallState state) { GtkTreePath *path; GtkTreeIter iter; oc->state = state; path = gtk_tree_row_reference_get_path (oc->rr); GNUNET_assert ( gtk_tree_model_get_iter (GTK_TREE_MODEL (active_liststore), &iter, path)); gtk_tree_path_free (path); switch (state) { case OUT_STATE_NONE: gtk_list_store_remove (active_liststore, &iter); return; case OUT_STATE_RESOLVING: break; case OUT_STATE_RINGING: break; case OUT_STATE_CONNECTED: break; case OUT_STATE_SUSPENDED_LOCAL: break; case OUT_STATE_SUSPENDED_REMOTE: break; case OUT_STATE_SUSPENDED_BOTH: break; } gtk_list_store_set (active_liststore, &iter, GCG_PHONE_LS_OUT_CALL_STATE, state, GCG_PHONE_LS_STATE_NAME, out_call_state_to_string (state), -1); update_call_buttons (gtk_tree_view_get_selection (active_treeview)); } /** * Set call state of a incoming call * * @param ic incoming call to change state of * @param state new state of the call */ static void set_incoming_call_state (struct IncomingCall *ic, enum InCallState state) { GtkTreePath *path; GtkTreeIter iter; ic->state = state; path = gtk_tree_row_reference_get_path (ic->rr); GNUNET_assert ( gtk_tree_model_get_iter (GTK_TREE_MODEL (active_liststore), &iter, path)); gtk_tree_path_free (path); switch (state) { case IN_STATE_NONE: gtk_list_store_remove (active_liststore, &iter); return; case IN_STATE_RINGING: break; case IN_STATE_CONNECTED: break; case IN_STATE_SUSPENDED_LOCAL: break; case IN_STATE_SUSPENDED_REMOTE: break; case IN_STATE_SUSPENDED_BOTH: break; } gtk_list_store_set (active_liststore, &iter, GCG_PHONE_LS_IN_CALL_STATE, state, GCG_PHONE_LS_STATE_NAME, in_call_state_to_string (state), -1); update_call_buttons (gtk_tree_view_get_selection (active_treeview)); } /** * Process a record that was stored in the namestore. * * @param cls closure with the `struct IncomingCall *` * @param zone private key of the zone; NULL on disconnect * @param label label of the records; NULL on disconnect * @param rd_count number of entries in @a rd array, 0 if label was deleted * @param rd array of records with data to store */ static void handle_reverse_lookup_result (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct IncomingCall *ic = cls; ic->qe = NULL; GNUNET_free (ic->caller_id_str); ic->caller_id_str = GNUNET_strdup (label); } /** * Failed to talk to namestore. */ static void handle_reverse_error (void *cls) { struct IncomingCall *ic = cls; ic->qe = NULL; GCG_log (_ ("Error communicating with namestore!\n")); } /** * Function called with an event emitted by a phone. * * @param cls closure * @param code type of the event * @param caller handle for the caller * @param caller_id public key of the caller (in GNS) */ static void phone_event_handler (void *cls, enum GNUNET_CONVERSATION_PhoneEventCode code, struct GNUNET_CONVERSATION_Caller *caller, const struct GNUNET_IDENTITY_PublicKey *caller_id) { GtkTreeIter iter; gboolean valid; struct IncomingCall *ic; GtkTreePath *path; struct GNUNET_IDENTITY_Ego *my_zone; const char *tld; switch (code) { case GNUNET_CONVERSATION_EC_PHONE_RING: in_ring_counter++; ic = GNUNET_new (struct IncomingCall); ic->caller = caller; ic->state = IN_STATE_RINGING; ic->caller_id = *caller_id; ic->caller_id_str = GNUNET_strdup (GNUNET_GNSRECORD_pkey_to_zkey (caller_id)); my_zone = GCG_ZONES_get_selected_zone (&tld); ic->qe = GNUNET_NAMESTORE_zone_to_name (GCG_IMPORT_get_namestore (), GNUNET_IDENTITY_ego_get_private_key ( my_zone), &ic->caller_id, &handle_reverse_error, ic, &handle_reverse_lookup_result, ic); ic->caller_num = caller_num_gen++; gtk_list_store_insert_with_values (active_liststore, &iter, -1, GCG_PHONE_LS_REMOTE_USER_NAME, ic->caller_id_str, GCG_PHONE_LS_INCOMING_CALL, ic, GCG_PHONE_LS_NUMBER, ic->caller_num, GCG_PHONE_LS_IN_CALL_STATE, ic->state, GCG_PHONE_LS_STATE_NAME, in_call_state_to_string (ic->state), GCG_PHONE_LS_TYPE, CALL_IN, -1); path = gtk_tree_model_get_path (GTK_TREE_MODEL (active_liststore), &iter); ic->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (active_liststore), path); gtk_tree_path_free (path); GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_CALL, ic->caller_id_str, ic->caller_num); break; case GNUNET_CONVERSATION_EC_PHONE_HUNG_UP: GNUNET_break (in_ring_counter > 0); in_ring_counter--; valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (active_liststore), &iter); GNUNET_break (valid); while (valid) { gtk_tree_model_get (GTK_TREE_MODEL (active_liststore), &iter, GCG_PHONE_LS_INCOMING_CALL, &ic, -1); if ((NULL != ic) && (caller == ic->caller)) { if (ic == active_in) { active_in = NULL; GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP, ic->caller_id_str, ic->caller_num); } else { if (IN_STATE_RINGING == ic->state) GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_MISSED, ic->caller_id_str, ic->caller_num); else GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP, ic->caller_id_str, ic->caller_num); } set_incoming_call_state (ic, IN_STATE_NONE); ic->caller = NULL; destroy_in (ic); break; } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (active_liststore), &iter); } GNUNET_break (valid); break; } update_gui (); } /** * Function called with an event emitted by a caller. * * @param cls closure with the `struct IncomingCall` of the caller * @param code type of the event issued by the caller */ static void caller_event_handler (void *cls, enum GNUNET_CONVERSATION_CallerEventCode code) { struct IncomingCall *ic = cls; enum InCallState is; if (NULL == ic) { GNUNET_break (0); return; } switch (code) { case GNUNET_CONVERSATION_EC_CALLER_SUSPEND: GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_REMOTE, ic->caller_id_str, ic->caller_num); is = ic->state; switch (ic->state) { case IN_STATE_NONE: GNUNET_assert (0); break; case IN_STATE_RINGING: GNUNET_assert (0); break; case IN_STATE_CONNECTED: is = IN_STATE_SUSPENDED_REMOTE; break; case IN_STATE_SUSPENDED_LOCAL: is = IN_STATE_SUSPENDED_BOTH; break; case IN_STATE_SUSPENDED_REMOTE: GNUNET_assert (0); break; case IN_STATE_SUSPENDED_BOTH: GNUNET_assert (0); break; } set_incoming_call_state (ic, is); break; case GNUNET_CONVERSATION_EC_CALLER_RESUME: GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_REMOTE, ic->caller_id_str, ic->caller_num); is = ic->state; switch (ic->state) { case IN_STATE_NONE: GNUNET_assert (0); break; case IN_STATE_RINGING: GNUNET_assert (0); break; case IN_STATE_CONNECTED: GNUNET_assert (0); break; case IN_STATE_SUSPENDED_LOCAL: GNUNET_assert (0); break; case IN_STATE_SUSPENDED_REMOTE: is = IN_STATE_CONNECTED; break; case IN_STATE_SUSPENDED_BOTH: is = IN_STATE_SUSPENDED_LOCAL; break; } set_incoming_call_state (ic, is); break; } do_status (); } /** * The "accept" button was clicked. Accept selected incoming call. * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_accept_button_clicked_cb (GtkButton *button, gpointer user_data) { struct IncomingCall *ic; GtkTreeSelection *sel; GtkTreeIter iter; GtkTreeModel *model; if ((NULL != active_in) || (NULL != active_out)) { /* accept button should not have been sensitive! */ GNUNET_break (0); return; } sel = gtk_tree_view_get_selection (active_treeview); if (! gtk_tree_selection_get_selected (sel, &model, &iter)) { /* accept button should not have been sensitive! */ GNUNET_break (0); return; } gtk_tree_model_get (model, &iter, GCG_PHONE_LS_INCOMING_CALL, &ic, -1); if (NULL == ic) { /* accept button should not have been sensitive! */ GNUNET_break (0); return; } active_in = ic; set_incoming_call_state (ic, IN_STATE_CONNECTED); GNUNET_CONVERSATION_caller_pick_up (ic->caller, &caller_event_handler, ic, speaker, mic); GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_ACCEPTED, ic->caller_id_str, ic->caller_num); gtk_entry_set_text (address_entry, ic->caller_id_str); update_gui (); } /** * The "reject" button was clicked. Reject incoming call. * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_refuse_button_clicked_cb (GtkButton *button, gpointer user_data) { struct IncomingCall *ic; struct OutgoingCall *oc; GtkTreeSelection *sel; GtkTreeIter iter; GtkTreeModel *model; sel = gtk_tree_view_get_selection (active_treeview); if (! gtk_tree_selection_get_selected (sel, &model, &iter)) { /* reject button should not have been sensitive! */ GNUNET_break (0); return; } gtk_tree_model_get (model, &iter, GCG_PHONE_LS_INCOMING_CALL, &ic, GCG_PHONE_LS_OUTGOING_CALL, &oc, -1); if (NULL != ic) { if (active_in == ic) { /* reject button should not have been sensitive! */ GNUNET_break (0); return; } GNUNET_break (in_ring_counter > 0); in_ring_counter--; GCG_HISTORY_add (GCG_HISTORY_TYPE_INCOMING_REJECTED, ic->caller_id_str, ic->caller_num); set_incoming_call_state (ic, IN_STATE_NONE); destroy_in (ic); do_status (); return; } if (NULL != oc) { /* terminate suspended outgoing call */ if (active_out == oc) { /* refuse button should not have been sensitive! */ GNUNET_break (0); return; } GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP, oc->caller_id_str, oc->caller_num); set_outgoing_call_state (oc, OUT_STATE_NONE); destroy_out (oc); do_status (); return; } /* reject button should not have been sensitive! */ GNUNET_break (0); } /** * The "resume" button was clicked. Resume a call! * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_resume_button_clicked_cb (GtkButton *button, gpointer user_data) { struct IncomingCall *ic; struct OutgoingCall *oc; GtkTreeSelection *sel; GtkTreeIter iter; GtkTreeModel *model; if ((NULL != active_in) || (NULL != active_out)) { /* resume button should have been inactive */ GNUNET_break (0); return; } sel = gtk_tree_view_get_selection (active_treeview); if (! gtk_tree_selection_get_selected (sel, &model, &iter)) { /* resume button should not have been sensitive! */ GNUNET_break (0); return; } gtk_tree_model_get (model, &iter, GCG_PHONE_LS_INCOMING_CALL, &ic, GCG_PHONE_LS_OUTGOING_CALL, &oc, -1); if (NULL != ic) { active_in = ic; set_incoming_call_state (ic, IN_STATE_CONNECTED); GNUNET_CONVERSATION_caller_resume (ic->caller, speaker, mic); GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_LOCAL, ic->caller_id_str, ic->caller_num); gtk_entry_set_text (address_entry, ic->caller_id_str); update_gui (); return; } if (NULL != oc) { active_out = oc; GNUNET_CONVERSATION_call_resume (oc->call, speaker, mic); set_outgoing_call_state (oc, OUT_STATE_CONNECTED); GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_LOCAL, oc->caller_id_str, oc->caller_num); gtk_entry_set_text (address_entry, active_out->caller_id_str); update_gui (); return; } /* resume button should not have been sensitive! */ GNUNET_break (0); } /** * The "suspend" button was clicked. Pause a call. * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_suspend_button_clicked_cb (GtkButton *button, gpointer user_data) { enum OutCallState os; enum InCallState is; if ((NULL != active_in) && (NULL != active_out)) { GNUNET_break (0); return; } if (NULL != active_out) { /* outgoing */ GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_LOCAL, active_out->caller_id_str, active_out->caller_num); switch ((os = active_out->state)) { case OUT_STATE_NONE: GNUNET_assert (0); break; case OUT_STATE_RESOLVING: GNUNET_assert (0); break; case OUT_STATE_RINGING: GNUNET_assert (0); break; case OUT_STATE_CONNECTED: os = OUT_STATE_SUSPENDED_LOCAL; break; case OUT_STATE_SUSPENDED_LOCAL: GNUNET_assert (0); break; case OUT_STATE_SUSPENDED_REMOTE: os = OUT_STATE_SUSPENDED_BOTH; break; case OUT_STATE_SUSPENDED_BOTH: GNUNET_assert (0); break; } GNUNET_CONVERSATION_call_suspend (active_out->call); set_outgoing_call_state (active_out, os); active_out = NULL; update_gui (); return; } if (NULL != active_in) { /* incoming */ GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_LOCAL, active_in->caller_id_str, active_in->caller_num); GNUNET_CONVERSATION_caller_suspend (active_in->caller); switch ((is = active_in->state)) { case IN_STATE_NONE: GNUNET_assert (0); break; case IN_STATE_RINGING: GNUNET_assert (0); break; case IN_STATE_CONNECTED: is = IN_STATE_SUSPENDED_LOCAL; break; case IN_STATE_SUSPENDED_LOCAL: GNUNET_assert (0); break; case IN_STATE_SUSPENDED_REMOTE: is = IN_STATE_SUSPENDED_BOTH; break; case IN_STATE_SUSPENDED_BOTH: GNUNET_assert (0); break; } set_incoming_call_state (active_in, is); active_in = NULL; update_gui (); return; } GNUNET_break (0); } /** * The "hangup" button was clicked. Hang up. * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_hangup_button_clicked_cb (GtkButton *button, gpointer user_data) { if ((NULL == active_in) && (NULL == active_out)) { GNUNET_break (0); return; } if (NULL != active_out) { /* if current call is outgoing, stop it */ GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP, active_out->caller_id_str, active_out->caller_num); set_outgoing_call_state (active_out, OUT_STATE_NONE); destroy_out (active_out); active_out = NULL; update_gui (); return; } if (NULL != active_in) { /* if selected call is incoming, hang it up */ GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP, active_in->caller_id_str, active_in->caller_num); set_incoming_call_state (active_in, IN_STATE_NONE); destroy_in (active_in); active_in = NULL; update_gui (); return; } GNUNET_break (0); } /** * Function called with an event emitted by a call. * * @param cls our `struct OutgoingCall` * @param code type of the event on the call */ static void call_event_handler (void *cls, enum GNUNET_CONVERSATION_CallEventCode code) { struct OutgoingCall *oc = cls; enum OutCallState os; switch (code) { case GNUNET_CONVERSATION_EC_CALL_RINGING: GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING_RINGING, oc->caller_id_str, oc->caller_num); GNUNET_break (OUT_STATE_RESOLVING == oc->state); GNUNET_break (active_out == oc); set_outgoing_call_state (oc, OUT_STATE_RINGING); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_PICKED_UP: GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING_ACCEPTED, oc->caller_id_str, oc->caller_num); GNUNET_break (OUT_STATE_RINGING == oc->state); set_outgoing_call_state (oc, OUT_STATE_CONNECTED); GNUNET_break (active_out == oc); gtk_widget_set_sensitive (b_suspend, TRUE); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL: GCG_HISTORY_add (GCG_HISTORY_TYPE_FAILED, oc->caller_id_str, oc->caller_num); GNUNET_break (OUT_STATE_RESOLVING == oc->state); GNUNET_break (active_out == oc); set_outgoing_call_state (oc, OUT_STATE_NONE); oc->call = NULL; destroy_out (oc); active_out = NULL; update_gui (); break; case GNUNET_CONVERSATION_EC_CALL_HUNG_UP: GCG_HISTORY_add (GCG_HISTORY_TYPE_HANGUP, oc->caller_id_str, oc->caller_num); set_outgoing_call_state (oc, OUT_STATE_NONE); GNUNET_break (active_out == oc); oc->call = NULL; destroy_out (oc); active_out = NULL; update_gui (); break; case GNUNET_CONVERSATION_EC_CALL_SUSPENDED: GCG_HISTORY_add (GCG_HISTORY_TYPE_SUSPEND_REMOTE, oc->caller_id_str, oc->caller_num); os = oc->state; switch (oc->state) { case OUT_STATE_NONE: GNUNET_assert (0); break; case OUT_STATE_RESOLVING: GNUNET_assert (0); break; case OUT_STATE_RINGING: GNUNET_assert (0); break; case OUT_STATE_CONNECTED: os = OUT_STATE_SUSPENDED_REMOTE; break; case OUT_STATE_SUSPENDED_LOCAL: os = OUT_STATE_SUSPENDED_BOTH; break; case OUT_STATE_SUSPENDED_REMOTE: GNUNET_assert (0); break; case OUT_STATE_SUSPENDED_BOTH: GNUNET_assert (0); break; } set_outgoing_call_state (oc, os); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_RESUMED: GCG_HISTORY_add (GCG_HISTORY_TYPE_RESUMED_REMOTE, oc->caller_id_str, oc->caller_num); os = oc->state; switch (oc->state) { case OUT_STATE_NONE: GNUNET_assert (0); break; case OUT_STATE_RESOLVING: GNUNET_assert (0); break; case OUT_STATE_RINGING: GNUNET_assert (0); break; case OUT_STATE_CONNECTED: GNUNET_assert (0); break; case OUT_STATE_SUSPENDED_LOCAL: GNUNET_assert (0); break; case OUT_STATE_SUSPENDED_REMOTE: os = OUT_STATE_CONNECTED; break; case OUT_STATE_SUSPENDED_BOTH: os = OUT_STATE_SUSPENDED_LOCAL; break; } set_outgoing_call_state (oc, os); if (OUT_STATE_CONNECTED == os) GNUNET_break (active_out == oc); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_ERROR: GCG_HISTORY_add (GCG_HISTORY_TYPE_FAILED, oc->caller_id_str, oc->caller_num); set_outgoing_call_state (oc, OUT_STATE_NONE); oc->call = NULL; if (active_out == oc) active_out = NULL; destroy_out (oc); update_gui (); break; } } /** * The "connect" button was clicked. Initiate a call! * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_call_button_clicked_cb (GtkButton *button, gpointer user_data) { struct GNUNET_IDENTITY_Ego *caller_id; GtkTreePath *path; GtkTreeIter iter; struct OutgoingCall *oc; const char *arg; arg = gtk_entry_get_text (address_entry); caller_id = GCG_EGOS_get_selected_ego (); if ((NULL == caller_id) || (NULL != active_out) || (NULL != active_in)) { GNUNET_break (0); return; } oc = GNUNET_new (struct OutgoingCall); oc->caller_id_str = GNUNET_strdup (arg); oc->state = OUT_STATE_RESOLVING; oc->caller_num = caller_num_gen++; GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING_CALL, oc->caller_id_str, oc->caller_num); oc->call = GNUNET_CONVERSATION_call_start (GCG_get_configuration (), caller_id, arg, speaker, mic, &call_event_handler, oc); if (NULL == oc->call) { GCG_HISTORY_add (GCG_HISTORY_TYPE_FAILED, oc->caller_id_str, oc->caller_num); destroy_out (oc); return; } active_out = oc; gtk_list_store_insert_with_values (active_liststore, &iter, -1, GCG_PHONE_LS_REMOTE_USER_NAME, oc->caller_id_str, GCG_PHONE_LS_INCOMING_CALL, NULL, GCG_PHONE_LS_NUMBER, oc->caller_num, GCG_PHONE_LS_IN_CALL_STATE, IN_STATE_NONE, GCG_PHONE_LS_TYPE, CALL_OUT, GCG_PHONE_LS_OUTGOING_CALL, oc, GCG_PHONE_LS_OUT_CALL_STATE, oc->state, GCG_PHONE_LS_STATE_NAME, out_call_state_to_string (oc->state), -1); path = gtk_tree_model_get_path (GTK_TREE_MODEL (active_liststore), &iter); oc->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (active_liststore), path); gtk_tree_path_free (path); update_gui (); } /** * The user has changed the address entry. Consider activating the * "call" button. * * @param editable entry that was changed * @param user_data builder (unused) */ void gnunet_conversation_gtk_address_entry_changed_cb (GtkEditable *editable, gpointer user_data) { check_call_sensitivity (); } /** * Update sensitivity of the "copy" button. */ static void update_copy_sensitivity () { const gchar *label; struct GNUNET_IDENTITY_Ego *ego; ego = GCG_EGOS_get_selected_ego (); label = gtk_entry_get_text (GTK_ENTRY ( GCG_get_main_window_object ("gnunet_conversation_gtk_ego_label_entry"))); if ((NULL == ego) || (NULL == label) || (0 == strlen (label))) gtk_widget_set_sensitive (b_copy, FALSE); else gtk_widget_set_sensitive (b_copy, TRUE); } /** * Our phone record has changed, update the information * published in the GNU Name System. */ static void update_phone_record () { struct GNUNET_GNSRECORD_Data rd; const gchar *label; if (NULL == phone) { GSC_remove_phone (); return; } label = gtk_entry_get_text (GTK_ENTRY ( GCG_get_main_window_object ("gnunet_conversation_gtk_ego_label_entry"))); if ((NULL == label) || (0 == strlen (label))) { GSC_remove_phone (); return; } GNUNET_CONVERSATION_phone_get_record (phone, &rd); GSC_add_phone (label, &rd); } /** * The user has edited the label. Check if the new label is valid, * and if so commit the change to the model and update the phone * record. * * @param entry the entry used to edit the label * @param preedit new label * @param user_data builder (unused) */ void gnunet_conversation_gtk_ego_label_entry_preedit_changed_cb (GtkEntry *entry, gchar *preedit, gpointer user_data) { GtkComboBoxText *cbt; if (GNUNET_OK != GNUNET_DNSPARSER_check_label (preedit)) { GCG_log (_ ("Invalid label `%s'\n"), preedit); gdk_display_beep (gdk_display_get_default ()); return; } cbt = GTK_COMBO_BOX_TEXT (GCG_get_main_window_object ( "gnunet_conversation_gtk_ego_label_comboboxtext")); gtk_combo_box_text_insert (cbt, -1, NULL, preedit); update_copy_sensitivity (); update_phone_record (); } /** * The user has changed the selected label in the combo box. Commit * the change to the model and update the phone record. * * @param widget the combo box * @param user_data builder (unused) */ void gnunet_conversation_gtk_ego_label_comboboxtext_changed_cb (GtkComboBox *widget, gpointer user_data) { update_copy_sensitivity (); update_phone_record (); } /** * @brief outgoing ego selector changed, (re)start the phone. * * @param widget the combo box that changed * @param user_data builder (unused) */ void gnunet_conversation_gtk_ego_combobox_changed_cb (GtkComboBox *widget, gpointer user_data) { struct GNUNET_IDENTITY_Ego *caller_id; const struct GNUNET_CONFIGURATION_Handle *cfg; unsigned long long line; if (NULL != phone) { GSC_remove_phone (); GNUNET_CONVERSATION_phone_destroy (phone); phone = NULL; } caller_id = GCG_EGOS_get_selected_ego (); if (NULL == caller_id) { do_status (); check_call_sensitivity (); return; } cfg = GCG_get_configuration (); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg, "CONVERSATION", "LINE", &line)) { GCG_log (_ ("No phone line specified in configuration!\n")); do_status (); check_call_sensitivity (); return; } GCG_log (_ ("Initializing phone on line %llu\n"), line); phone = GNUNET_CONVERSATION_phone_create (cfg, caller_id, &phone_event_handler, NULL); if (NULL == phone) { check_call_sensitivity (); do_status (); return; } update_phone_record (); update_copy_sensitivity (); check_call_sensitivity (); do_status (); } /** * Initialize phone subsystem. */ void GCG_PHONE_init () { const struct GNUNET_CONFIGURATION_Handle *cfg; cfg = GCG_get_configuration (); speaker = GNUNET_SPEAKER_create_from_hardware (cfg); mic = GNUNET_MICROPHONE_create_from_hardware (cfg); b_add_contact = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_add_contact_button")); b_accept = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_accept_button")); b_refuse = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_refuse_button")); b_suspend = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_suspend_button")); b_resume = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_resume_button")); b_call = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_call_button")); b_hangup = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_hangup_button")); b_copy = GTK_WIDGET ( GCG_get_main_window_object ("gnunet_conversation_gtk_ego_copy_button")); address_entry = GTK_ENTRY ( GCG_get_main_window_object ("gnunet_conversation_gtk_address_entry")); active_liststore = GTK_LIST_STORE (GCG_get_main_window_object ( "gnunet_conversation_gtk_active_calls_liststore")); active_treeview = GTK_TREE_VIEW (GCG_get_main_window_object ( "gnunet_conversation_gtk_active_calls_treeview")); do_status (); } /** * Shutdown phone subsystem. */ void GCG_PHONE_shutdown () { if (NULL != active_in) { destroy_in (active_in); active_in = NULL; } if (NULL != active_out) { destroy_out (active_out); active_out = NULL; } if (NULL != phone) { GSC_remove_phone (); GNUNET_CONVERSATION_phone_destroy (phone); phone = NULL; } gtk_list_store_clear (active_liststore); GNUNET_SPEAKER_destroy (speaker); speaker = NULL; GNUNET_MICROPHONE_destroy (mic); mic = NULL; } /* end of gnunet-conversation-gtk_phone.c */