/* This file is part of GNUnet. (C) 2010-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/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_log.h" #include "gnunet-conversation-gtk_phone.h" /** * Active calls treeview columns */ enum ActiveCallsTreeViewColumns { /** * A `gchar *` */ GCG_PHONE_LS_CALLER_ID, /** * A `struct IncomingCall *` */ GCG_PHONE_LS_CALLER, /** * A `gint`. */ GCG_PHONE_LS_CALLER_NUM, /** * A `gint` for a `enum TypeOfConversation` (FIXME: remove?) */ GCG_PHONE_LS_TYPE, /** * A `gint` for a `enum InCallState` (FIXME: replace with char *!) */ GCG_PHONE_LS_CALLER_STATE, /** * A `struct OutgoingCall *`. */ GCG_PHONE_LS_CALL, /** * A `gint` for a `enum OutCallState` (FIXME: replace with char *, * combine with #GCG_PHONE_LS_CALLER_STATE). */ GCG_PHONE_LS_CALL_STATE, /** * A `gint`. FIXME: combine with #GCG_PHONE_LS_CALLER_NUM! */ GCG_PHONE_LS_CALL_NUM }; /** * Types of conversations */ enum TypeOfConversation { /** * Incoming phone call. */ CALL_IN, /** * Outgoing call. */ CALL_OUT }; /** * Possible states of the phone. */ enum PhoneState { /** * We're waiting for the user to select a caller ID. */ PS_LOOKUP_EGO, /** * We're listening for calls */ PS_LISTEN, /** * We accepted an incoming phone call. */ PS_ACCEPTED, /** * Internal error */ PS_ERROR }; /** * 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_ACTIVE, /** * This phone call is currently suspended. */ OUT_STATE_SUSPENDED }; /** * List of incoming calls */ struct IncomingCall { /** * Handle to hang up or activate. */ struct GNUNET_CONVERSATION_Caller *caller; /** * String identifying the caller. */ char *caller_id; /** * Location of this call in the list. */ GtkTreeRowReference *rr; /** * Unique number of the caller. */ unsigned int 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 *peer_name; /** * Location of this call in the list. */ GtkTreeRowReference *rr; /** * Unique number of the caller. */ unsigned int 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; /** * Our phone's current state. */ static enum PhoneState phone_state; /** * Counts the number of incoming calls we have had so far. */ static unsigned int caller_num_gen; /** * Unique number of call (outgoing) */ static unsigned int call_counter; /** * 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; /** * Our address entry. */ static GtkEntry *address_entry; /** * 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); 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->peer_name); gtk_tree_row_reference_free (oc->rr); GNUNET_free (oc); } /** * Update status based on current phone state. * * @param args arguments given to the command */ 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); GCG_set_status_icon ("gnunet-conversation-gtk-tray-call-incoming"); return; } if ( (NULL == active_in) && (NULL == active_out) && (PS_LISTEN == phone_state) ) { GCG_update_status_bar ("%s", _("We are listening for incoming calls")); GCG_set_status_icon ("gnunet-conversation-gtk-tray-available"); } } /** * 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; sens = (PS_LISTEN == phone_state); if (0 == strlen (gtk_entry_get_text (address_entry))) 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; 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_CALL_STATE, &out_state, GCG_PHONE_LS_CALLER_STATE, &in_state, -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, ! in_call); 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, FALSE); 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); 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); 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; default: GNUNET_assert (0); } 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, TRUE); gtk_widget_set_sensitive (b_resume, FALSE); 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, TRUE); gtk_widget_set_sensitive (b_resume, ! in_call); case OUT_STATE_ACTIVE: 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); case OUT_STATE_SUSPENDED: 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, ! in_call); default: GNUNET_assert (0); } break; default: GNUNET_assert (0); } } /** * @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); } /** * 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_ACTIVE: break; case OUT_STATE_SUSPENDED: break; } gtk_list_store_set (active_liststore, &iter, GCG_PHONE_LS_CALL_STATE, 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_CALLER_STATE, state, -1); update_call_buttons (gtk_tree_view_get_selection (active_treeview)); } /** * 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 name of the caller in GNS */ static void phone_event_handler (void *cls, enum GNUNET_CONVERSATION_PhoneEventCode code, struct GNUNET_CONVERSATION_Caller *caller, const char *caller_id) { GtkTreeIter iter; gboolean valid; struct IncomingCall *ic; GtkTreePath *path; switch (code) { case GNUNET_CONVERSATION_EC_PHONE_RING: in_ring_counter++; ic = GNUNET_new (struct IncomingCall); ic->caller = caller; ic->caller_id = GNUNET_strdup (caller_id); ic->caller_num = caller_num_gen++; gtk_list_store_insert_with_values (active_liststore, &iter, -1, GCG_PHONE_LS_CALLER_ID, caller_id, GCG_PHONE_LS_CALLER, ic, GCG_PHONE_LS_CALLER_NUM, caller_num_gen, GCG_PHONE_LS_CALLER_STATE, IN_STATE_RINGING, 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_log (_("A Incoming call from `%s' with number %u\n"), caller_id, caller_num_gen); break; case GNUNET_CONVERSATION_EC_PHONE_HUNG_UP: 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_CALLER, &ic, -1); if (caller == ic->caller) { GCG_log (_("phone hung up: %s number: %u"), ic->caller_id, ic->caller_num); set_incoming_call_state (ic, IN_STATE_NONE); break; } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (active_liststore), &iter); } GNUNET_break (valid); phone_state = PS_LISTEN; break; } do_status(); } /** * 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: 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; default: GNUNET_assert (0); break; } set_incoming_call_state (ic, is); GCG_log (_("Call from `%s' suspended by other user\n"), ic->caller_id); break; case GNUNET_CONVERSATION_EC_CALLER_RESUME: 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; default: GNUNET_assert (0); break; } set_incoming_call_state (ic, is); GCG_log (_("Call from `%s' resumed by other user\n"), ic->caller_id); 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_on_accept_clicked (GtkButton *button, gpointer user_data) { struct IncomingCall *ic; GtkTreeSelection *sel; GtkTreeIter iter; GtkTreeModel *model; if ( (NULL != active_in) || (NULL != active_out) || (PS_LISTEN != phone_state) ) { /* accept button should not have been sensitive! */ GNUNET_break (0); return; } phone_state = PS_ACCEPTED; 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_CALLER_STATE, &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); GCG_update_status_bar (_("Started a conversation with `%s'.\n"), ic->caller_id); GCG_set_status_icon ("gnunet-conversation-call-active"); GNUNET_CONVERSATION_caller_pick_up (ic->caller, &caller_event_handler, ic, speaker, mic); GCG_HISTORY_add (GCG_HISTORY_TYPE_ACCEPTED, ic->caller_id); do_status (); } /** * The "reject" button was clicked. Reject incoming call. * * @param button the button * @param user_data builder (unused) */ void GNUNET_CONVERSATION_GTK_on_reject_clicked (GtkButton *button, gpointer user_data) { // do_reject (); // FIXME! do_status (); } /** * User clicked the '> contact' button to move the selected * caller's information into our address book. * * @param button the button * @param user_data builder (unused) */ void GNUNET_CONVERSATION_GTK_use_current_button_clicked (GtkButton *button, gpointer *user_data) { // FIXME: implement, use "GSC_add_contact" #if 0 const gchar *target; target = gtk_entry_get_text (address_entry); gtk_entry_set_text (address_entry, "FIXME"); #endif } /** * The "resume" button was clicked. Resume a call! * * @param button the button * @param user_data builder (unused) */ void GNUNET_CONVERSATION_GTK_on_resume_clicked (GtkButton *button, gpointer user_data) { enum TypeOfConversation toc; struct IncomingCall *ic; struct OutgoingCall *oc; if ( (NULL != active_in) || (NULL != active_out) || (! (PS_LISTEN == phone_state) || (PS_ERROR == phone_state) ) ) { /* resume button should have been inactive */ GNUNET_break(0); return; } toc = 42; // FIXME: get from selection! switch (toc) { case CALL_IN: ic = NULL; // FIXME: get from selection GNUNET_CONVERSATION_caller_resume (ic->caller, speaker, mic); set_incoming_call_state (ic, IN_STATE_CONNECTED); phone_state = PS_ACCEPTED; GCG_update_status_bar (_("Resumed a conversation with `%s'.\n"), ic->caller_id); GCG_set_status_icon ("gnunet-conversation-call-active"); // FIXME: update visibility/sensitivity do_status (); return; case CALL_OUT: oc = NULL; // FIXME: get from selection GNUNET_CONVERSATION_call_resume (oc->call, speaker, mic); set_outgoing_call_state (oc, OUT_STATE_ACTIVE); // FIXME: update visibility/sensitivity do_status (); return; } GNUNET_break (0); } /** * The "suspend" button was clicked. Pause a call. * * @param button the button * @param user_data builder (unused) */ void GNUNET_CONVERSATION_GTK_on_pause_clicked (GtkButton *button, gpointer user_data) { if ( (NULL != active_in) && (NULL != active_out) ) { GNUNET_break(0); return; } if (NULL != active_out) { /* outgoing */ GNUNET_CONVERSATION_call_suspend (active_out->call); set_outgoing_call_state (active_out, OUT_STATE_SUSPENDED); active_out = NULL; 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 (); do_status (); // FIXME: logging return; } if (NULL != active_in) { /* incoming */ GNUNET_CONVERSATION_caller_suspend (active_in->caller); set_incoming_call_state (active_in, IN_STATE_SUSPENDED_LOCAL); active_in = NULL; phone_state = PS_LISTEN; // FIXME: visibility // FIXME: logging return; } GNUNET_break (0); } /** * The "hangup" button was clicked. Hang up. * * @param button the button * @param user_data builder (unused) */ void GNUNET_CONVERSATION_GTK_on_hangup_clicked (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 */ set_outgoing_call_state (active_out, OUT_STATE_NONE); // FIXME: rather: remove call state! GNUNET_CONVERSATION_call_stop (active_out->call); active_out = NULL; // FIXME: logging 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 (); do_status (); return; } if (NULL != active_in) { /* if selected call is incoming, hang it up */ set_incoming_call_state (active_in, IN_STATE_NONE); // FIXME: rather: remove call state! GNUNET_CONVERSATION_caller_hang_up (active_in->caller); phone_state = PS_LISTEN; active_in = NULL; // FIXME: logging // FIXME: visibility 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; set_outgoing_call_state (oc, code); switch (code) { case GNUNET_CONVERSATION_EC_CALL_RINGING: GNUNET_break (OUT_STATE_RESOLVING == oc->state); GCG_log (_("Resolved address of `%s'. Now ringing other party."), oc->peer_name); // FIXME: use oc->rr here! set_outgoing_call_state (oc, OUT_STATE_RINGING); GCG_log (_("Ringing `%s'.\n"), oc->peer_name); GCG_update_status_bar (_("Ringing `%s'."), oc->peer_name); GCG_set_status_icon ("gnunet-conversation-gtk-tray-call-ringing"); break; case GNUNET_CONVERSATION_EC_CALL_PICKED_UP: GNUNET_break (OUT_STATE_RINGING == oc->state); set_outgoing_call_state (oc, OUT_STATE_ACTIVE); gtk_widget_set_sensitive (b_suspend, TRUE); GCG_log (_("Connection established to `%s'."), oc->peer_name); GCG_update_status_bar (_("Talking to `%s'."), oc->peer_name); GCG_set_status_icon ("gnunet-conversation-gtk-tray-call-active"); break; case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL: GNUNET_break (OUT_STATE_RESOLVING == oc->state); set_outgoing_call_state (oc, OUT_STATE_NONE); // FIXME: rather: remove from list! GCG_log (_("Failed to resolve %s in current zone."), oc->peer_name); GNUNET_free (oc); active_out = NULL; 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 (GTK_WIDGET (address_entry), TRUE); check_call_sensitivity (); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_HUNG_UP: GCG_log ("%s", _("Call terminated")); set_outgoing_call_state (oc, OUT_STATE_NONE); // FIXME: rather: remove from list! GNUNET_free (oc); active_out = NULL; 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 (); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_SUSPENDED: GNUNET_break (OUT_STATE_ACTIVE == oc->state); set_outgoing_call_state (oc, OUT_STATE_SUSPENDED); GCG_log (_("Connection to `%s' suspended (by other user)\n"), oc->peer_name); active_out = NULL; 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 (); do_status (); break; case GNUNET_CONVERSATION_EC_CALL_RESUMED: GNUNET_break (OUT_STATE_ACTIVE == oc->state); GCG_log (_("Connection to `%s' resumed\n"), oc->peer_name); GCG_update_status_bar (_("Talking to `%s'."), oc->peer_name); GCG_set_status_icon ("gnunet-conversation-gtk-tray-call-active"); active_out = oc; set_outgoing_call_state (oc, OUT_STATE_ACTIVE); gtk_widget_show (b_suspend); gtk_widget_show (b_hangup); gtk_widget_hide (b_call); gtk_widget_set_sensitive (b_hangup, TRUE); gtk_widget_set_sensitive (b_suspend, TRUE); gtk_widget_set_sensitive (GTK_WIDGET (address_entry), FALSE); break; case GNUNET_CONVERSATION_EC_CALL_ERROR: GCG_log ("GNUNET_CONVERSATION_EC_CALL_ERROR %s", oc->peer_name); set_outgoing_call_state (oc, OUT_STATE_NONE); // FIXME: rather: remove from list! active_out = NULL; 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 (); do_status (); break; } } /** * Initiate a new call. * * @param arg address of the user to call */ void GSC_PHONE_make_call (const char *arg) { struct GNUNET_IDENTITY_Ego *caller_id; GtkTreePath *path; GtkTreeIter iter; struct OutgoingCall *oc; gtk_entry_set_text (address_entry, arg); caller_id = GCG_EGOS_get_selected_ego (); if (NULL == caller_id) { /* can happen if user activated address in phone book while our phone was still down (no ego selected) */ GCG_log (_("Caller ID unavailable, cannot initiate call to `%s'.\n"), arg); return; } if (NULL != active_out) { if (0 == strcmp (active_out->peer_name, arg)) return; /* user likely simply clicked a bit too often, ignore */ GCG_log (_("You are on the phone with `%s', suspend or hang up before trying to call `%s'!\n"), active_out->peer_name, arg); return; } switch (phone_state) { case PS_LOOKUP_EGO: GCG_log ("%s\n", _("Caller ID unavailable, cannot initiate call.")); return; case PS_LISTEN: /* ok to call! */ break; case PS_ACCEPTED: /* `call` should be non-NULL */ GNUNET_break (0); return; case PS_ERROR: /* ok to call, we got an ego just could not init the phone */ break; } call_counter++; oc = GNUNET_new (struct OutgoingCall); oc->peer_name = GNUNET_strdup (arg); oc->state = OUT_STATE_RESOLVING; oc->call = GNUNET_CONVERSATION_call_start (GCG_get_configuration (), caller_id, arg, speaker, mic, &call_event_handler, oc); gtk_list_store_insert_with_values (active_liststore, &iter, -1, GCG_PHONE_LS_CALLER_ID, oc->peer_name, GCG_PHONE_LS_CALLER, NULL, GCG_PHONE_LS_CALLER_NUM, 0, GCG_PHONE_LS_CALLER_STATE, IN_STATE_NONE, GCG_PHONE_LS_TYPE, CALL_OUT, GCG_PHONE_LS_CALL, oc, GCG_PHONE_LS_CALL_NUM, call_counter, GCG_PHONE_LS_CALL_STATE, OUT_STATE_RESOLVING, -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); /* log event */ GCG_log (_("Resolving `%s'.\n"), oc->peer_name); GCG_update_status_bar (_("Resolving `%s'."), oc->peer_name); GCG_set_status_icon ("gnunet-conversation-gtk-tray-call-pending"); GCG_HISTORY_add (GCG_HISTORY_TYPE_OUTGOING, oc->peer_name); /* hide "call" button, make address entry insensitive; show suspend button (but not sensitive), show hangup button (and make sensitive) */ gtk_widget_hide (b_call); gtk_widget_set_sensitive (GTK_WIDGET (address_entry), FALSE); gtk_widget_show (b_suspend); gtk_widget_show (b_hangup); gtk_widget_set_sensitive (b_hangup, TRUE); } /** * 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) { GSC_PHONE_make_call (gtk_entry_get_text (address_entry)); } /** * 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 (); } /** * @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_GNSRECORD_Data rd; struct GNUNET_IDENTITY_Ego *caller_id; if (NULL != phone) { GNUNET_CONVERSATION_phone_destroy (phone); phone = NULL; } caller_id = GCG_EGOS_get_selected_ego (); if (NULL == caller_id) { GCG_update_status_bar ("%s", _("No ego selected, phone is now down.")); GCG_log ("%s\n", _("No ego selected, phone is now down.")); GCG_set_status_icon ("gnunet-conversation-gtk-tray-pending"); phone_state = PS_LOOKUP_EGO; check_call_sensitivity (); return; } phone = GNUNET_CONVERSATION_phone_create (GCG_get_configuration (), caller_id, &phone_event_handler, NULL); if (NULL == phone) { GCG_update_status_bar ("%s", _("Failed to setup phone (internal error)\n")); GCG_log ("%s", _("Failed to setup phone (internal error)\n")); GCG_set_status_icon ("gnunet-conversation-offline"); phone_state = PS_ERROR; check_call_sensitivity (); return; } GNUNET_CONVERSATION_phone_get_record (phone, &rd); /* FIXME: publish record to GNS! */ GCG_log ("%s\n", _("Phone active")); phone_state = PS_LISTEN; 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_hangup_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")); 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")); } /** * 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) { 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; phone_state = PS_ERROR; } /* end of gnunet-conversation-gtk_phone.c */