PSIP
Artifact [84b841d274]
Not logged in

Artifact 84b841d274fbe342ece367760c9b6ecd1f5c07a4:


/*
 * PSIP - A lightweight GTK GUI for pjsip
 * (C) James Budiono 2011
 * License: GNU GPL Version 3
 */
 
#include <string.h>
#include "psip.h"

#ifdef RELEASE
	#include "psip.glade.h"
#else
	#define MAIN_GUI_GLADE "psip.glade"
	#define IM_GUI_GLADE "psip_im.glade"
#endif


typedef struct _call_info call_info;
struct _call_info {
	gchararray nick;
	gchararray address;
	GtkToolButton *b;
};

psip_state_struct *psip_state;

/* ====================== Utility functions ===================== */
/* all functions are not thread-safe, caller must call gdk_threads_enter */

/* === pulse progress bar until progress bar is hidden ===*/
gboolean pulse_progress_bar() {
	if (GTK_WIDGET_VISIBLE (psip_state->progress_window)) {
		gtk_progress_bar_pulse (GTK_PROGRESS_BAR (psip_state->progressbar));
	} else return FALSE;
}

/* === set the online status of a buddy === */
void buddyview_set_online_status (GtkTreeIter *i, gboolean status, gchararray status_text) {
	if (status == TRUE) {
		gtk_list_store_set (psip_state->buddylist, i, 4, "gtk-yes", 2, status, 3, status_text, -1);
	} else {
		gtk_list_store_set (psip_state->buddylist, i, 4, "gtk-no", 2, status, 3, status_text, -1);
	}
}

/* === get the currently selected row from the buddylist === 
 * Note: returned GtkIter must be freed using g_slice_free */

GtkTreeIter *buddyview_get_selected() {
	GtkTreeIter *i;
	
	i = g_slice_new(GtkTreeIter);	
	if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (psip_state->buddylistview), NULL, i)) {
		return i;
	} else {
		g_slice_free(GtkTreeIter,i);
		return NULL;
	}
}

/* === add a new buddy === */
void buddylist_add (gchararray nick, gchararray address, gboolean register_to_backend) {	
	GtkTreeIter *i;
	gint buddy_id = -1;

	i = g_slice_new(GtkTreeIter); //freed when buddy is deleted - see below.
	gtk_list_store_append (psip_state->buddylist, i);
	if (register_to_backend) buddy_id = backend_add_buddy (address, i);
	gtk_list_store_set (psip_state->buddylist, i, 0, nick, 1, address, 2, FALSE, 4, "gtk-no", 5, buddy_id, -1);
}

//* === delete existing buddy === */
void buddylist_del (GtkTreeIter *iter) {
	gint buddy_id;
	gpointer p = NULL;
	
	gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddylist), iter, 5, &buddy_id, -1);
	if (buddy_id != -1) p = backend_del_buddy (buddy_id);
	gtk_list_store_remove (psip_state->buddylist, iter);
	if (p) g_slice_free(GtkTreeIter, p);	
}



/* === open the call window and disable the call button === */
void open_call_window (gchararray title, gchararray text) {
	if (title) gtk_window_set_title (GTK_WINDOW (psip_state->call_window), title);
	if (text) gtk_label_set_text (psip_state->call_window_text, text);
	gtk_widget_show (psip_state->call_window);
	gtk_widget_set_sensitive (psip_state->call_buddy_button, FALSE);
}

/* === close the call window and enable the call button === */
void close_call_window() {
	gtk_widget_hide (psip_state->call_window);
	gtk_widget_set_sensitive (psip_state->call_buddy_button, TRUE);
	gtk_toggle_button_set_active (psip_state->call_mute_button, FALSE);	
}




/* ===================== Actions ====================== */

/* === login/logoff action === 
 * Note this is run as a separate thread */

gpointer login_logoff_action (gpointer p) {
	GtkToggleToolButton *b = (GtkToggleToolButton *)p;
	gboolean request;
	gboolean result;
	
	gdk_threads_enter();
	request = gtk_toggle_tool_button_get_active(b);
	gdk_threads_leave();

	//g_usleep(5*G_USEC_PER_SEC);
	result = request;	
	if (request == TRUE) {
		result = backend_login();
	} else { 
		backend_logout();
	}

	//done, update UI now
	gdk_threads_enter();	
	if (result == TRUE ) {
		gtk_tool_button_set_label (GTK_TOOL_BUTTON(b),"Online");
		gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(b), "gtk-yes");
		gtk_widget_set_tooltip_text (GTK_WIDGET(b), "Click here to go offline.");
		gtk_widget_set_sensitive (psip_state->function_toolbar, TRUE);
	} else {
		gtk_toggle_tool_button_set_active (b, FALSE);
		gtk_tool_button_set_label (GTK_TOOL_BUTTON(b),"Offline");
		gtk_tool_button_set_stock_id (GTK_TOOL_BUTTON(b), "gtk-no");
		gtk_widget_set_tooltip_text (GTK_WIDGET(b), "Click here to go online.");
		gtk_widget_set_sensitive (psip_state->function_toolbar, FALSE);
	}
	gtk_widget_hide (GTK_WIDGET(psip_state->progress_window));
	gdk_threads_leave ();
}

/* === call action === 
 * Note this is run as a separate thread 
 * ci, nick and address must be freed */

gpointer call_action (gpointer p) {
	call_info *ci = (call_info *) p;
	
	//g_usleep (5*G_USEC_PER_SEC);	
	if (backend_make_call (ci->address)) {	
		gdk_threads_enter();
		gtk_label_set_text (psip_state->call_window_text, "Speaking ...");
		gdk_threads_leave();
	} else {
		gdk_threads_enter();
		close_call_window();
		gdk_threads_leave();
	}
	
	g_free (ci->nick);
	g_free (ci->address);
	g_slice_free (call_info,ci);
}


/*====================== Callbacks =======================*/

/*==== callback: online state toggled - go online or go offline ===*/
void on_online_toggled (GtkToggleToolButton *b, gpointer p) {
	if (gtk_toggle_tool_button_get_active(b) == TRUE)
		gtk_progress_bar_set_text(GTK_PROGRESS_BAR (psip_state->progressbar), "Connecting");
	else 
		gtk_progress_bar_set_text(GTK_PROGRESS_BAR (psip_state->progressbar), "Disconnecting");
		
	gtk_widget_show (GTK_WIDGET (psip_state->progress_window));
	gdk_threads_add_timeout (250,pulse_progress_bar,NULL);
	g_thread_create (login_logoff_action,b,FALSE,NULL);	
}

/*==== callback: remove a buddy - confirm and then delete ===*/
void on_remove_buddy_clicked (GtkToolButton *b, gpointer p) {
	GtkTreeIter *i = buddyview_get_selected();
	if (i) {
		
		g_object_set (G_OBJECT (psip_state->confirm_dialog), "text", "Removing a friend.", NULL);
		//gtk_message_dialog_set_markup (psip_state->confirm_dialog, "<b>Removing a friend.</b>");
		if (gtk_dialog_run(psip_state->confirm_dialog) == GTK_RESPONSE_YES) {
			buddylist_del(i);
		}
		gtk_widget_hide (GTK_WIDGET (psip_state->confirm_dialog));
		g_slice_free (GtkTreeIter,i);
	}	
}

/*==== callback: add a buddy ===*/
void on_add_buddy_clicked (GtkToolButton *b, gpointer p) {
	// clear fields from previous invocation
	gtk_entry_set_text (psip_state->addbuddy_nickname_field,"");	
	gtk_entry_set_text (psip_state->addbuddy_address_field,"");
	gtk_widget_grab_focus (GTK_WIDGET (psip_state->addbuddy_nickname_field));
	
try_again:	
	if (gtk_dialog_run(psip_state->addbuddy_dialog) == GTK_RESPONSE_OK) {
		gchararray nick = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->addbuddy_nickname_field)));
		gchararray address = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->addbuddy_address_field)));
		//g_print ("Adding nick: %s address: %s\n", nick, address );
		if (strlen(nick) && strlen(address)) {
			buddylist_add (nick, address, TRUE);
			g_free(nick);
			g_free(address);
		} else {
			g_object_set (G_OBJECT (psip_state->error_dialog), "text", "Both nick name and address must be filled-in.", NULL);
			gtk_dialog_run (psip_state->error_dialog);
			gtk_widget_hide (GTK_WIDGET (psip_state->error_dialog));
			g_free(nick);
			g_free(address);			
			goto try_again;
		}
	}
	gtk_widget_hide (GTK_WIDGET (psip_state->addbuddy_dialog));
}

/* === callback: edit buddy === */
void on_edit_buddy_clicked (GtkToolButton *b, gpointer p) {
	gchararray nick, address;
	GtkTreeIter *i = buddyview_get_selected();
	
	if (i) {
		// set fields from selected buddy
		gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddylist), i, 0, &nick, 1, &address, -1);		
		gtk_entry_set_text (psip_state->addbuddy_nickname_field,nick);
		gtk_entry_set_text (psip_state->addbuddy_address_field,address);
		gtk_widget_grab_focus (GTK_WIDGET (psip_state->addbuddy_nickname_field));

try_again:	
		if (gtk_dialog_run(psip_state->addbuddy_dialog) == GTK_RESPONSE_OK) {
			nick = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->addbuddy_nickname_field)));
			address = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->addbuddy_address_field)));

			if (strlen(nick) && strlen(address)) {
				buddylist_del (i);
				buddylist_add (nick, address, TRUE);
				//gtk_list_store_set (psip_state->buddylist, i, 0, nick, 1, address, 2, FALSE, 4, "gtk-no", -1);
				g_free(nick);
				g_free(address);
				
			} else {
				g_object_set (G_OBJECT (psip_state->error_dialog), "text", "Both nick name and address must be filled-in.", NULL);
				gtk_dialog_run (psip_state->error_dialog);
				gtk_widget_hide (GTK_WIDGET (psip_state->error_dialog));
				g_free(nick);
				g_free(address);			
				goto try_again;
			}
		}
		gtk_widget_hide (GTK_WIDGET (psip_state->addbuddy_dialog));		
		g_slice_free (GtkTreeIter,i);		
	}		
}

/*==== callback: message a buddy - open new message window ===*/
void on_message_buddy_clicked (GtkToolButton *b, gpointer p) {
	GtkBuilder *builder;
	GtkWidget *im_window;
	GError     *error = NULL;
	
    /* Create new GtkBuilder object */
    builder = gtk_builder_new();
    /* Load UI from file. If error occurs, report it and quit application. */
#ifdef RELEASE
	if( ! gtk_builder_add_from_string( builder, IM_GUI_GLADE, -1, &error ) )
#else    
    if( ! gtk_builder_add_from_file( builder, IM_GUI_GLADE, &error ) )
#endif    
    {
        g_warning( "%s", error->message );
        return;
    }
 
    /* Get main window pointer from UI */
    im_window = GTK_WIDGET( gtk_builder_get_object( builder, "im_window" ) );
    
    /* Connect signals */
    gtk_builder_connect_signals( builder, NULL);
 
    /* Destroy builder, since we don't need it anymore */
    g_object_unref( G_OBJECT( builder ) );
    
    gtk_widget_show (im_window);
}


/*==== callback: call a buddy ===*/
void on_call_buddy_clicked (GtkToolButton *b, gpointer p) {
	GtkTreeIter *i;
		
	i = buddyview_get_selected();
	if (i) {
		gchararray nick, address;
		gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddylist), i, 0, &nick, 1, &address, -1);
		open_call_window (nick, "Calling ...");
				
		//ci, nick and address will be freed by call_action				
		call_info *ci;
		ci = g_slice_new(call_info);
		ci->nick = nick;
		ci->address = address;
		ci->b = b;		
		g_thread_create (call_action,ci,FALSE,NULL);
		
		g_slice_free (GtkTreeIter,i);		
	}
}

/*==== callback: mute call ===*/
void on_mute_call_toggled (GtkToggleButton *b, gpointer p) {
	if (gtk_toggle_button_get_active(b) == TRUE) {
		gtk_label_set_text (psip_state->call_window_text, "Muted ...");
		backend_mute();
	}
	else {
		gtk_label_set_text (psip_state->call_window_text, "Speaking ...");
		backend_unmute();
	}
}

/*==== callback: end call ===*/
void on_end_call_clicked (GtkButton *b, gpointer p) {
	backend_end_call();
	close_call_window();
}

/* ==== callback: send dtmf digits === */
void on_send_dtmf(GtkButton *b) {
	//g_print("digit: %s\n", gtk_button_get_label (b));
	backend_send_dtmf(gtk_button_get_label (b));
}

/* === test: set selected buddy to be online === */
void make_online (GtkToolButton *b, gpointer p) {
	GtkTreeIter *i = buddyview_get_selected();
	if (i) {
		buddyview_set_online_status (i, TRUE, "Available");
		g_slice_free (GtkTreeIter,i);
	}
}
/* === test: set selected buddy to be offline === */
void make_offline (GtkToolButton *b, gpointer p) {
	GtkTreeIter *i = buddyview_get_selected();
	if (i) {
		buddyview_set_online_status (i, FALSE, "Offline");
		g_slice_free (GtkTreeIter, i);
	}
}

/* === test: clear buddylist (leftover from glade design) === */
void clear_buddies() {
	/* int i;
	gchararray nick, address;
	for (i = 0; i < 10; i++) {
		nick = g_strdup_printf("user%d",i);
		address = g_strdup_printf("sip:user%d@iptel.org",i);
		buddylist_add (nick, address);
		g_free(nick);
		g_free(address);
	} */
	gtk_list_store_clear(psip_state->buddylist);
}

/*========= main ======= */
int
main( int    argc,
      char **argv )
{
    GtkBuilder *builder;
    GtkWidget  *window;
    GError     *error = NULL;
 
	g_thread_init(NULL);
	gdk_threads_init();
	
	/* allocate our data structure to hold applications states */
	psip_state = g_slice_new(psip_state_struct);
 
    /* Init GTK+ */
    gtk_init( &argc, &argv );
 
    /* Create new GtkBuilder object */
    builder = gtk_builder_new();
    /* Load UI from file. If error occurs, report it and quit application. */
#ifdef RELEASE
	if( ! gtk_builder_add_from_string( builder, MAIN_GUI_GLADE, -1, &error ) )
#else    
    if( ! gtk_builder_add_from_file( builder, MAIN_GUI_GLADE, &error ) )
#endif
    {
        g_warning( "%s", error->message );
        return( 1 );
    }
 
    /* Get main window pointer from UI */
    window = GTK_WIDGET( gtk_builder_get_object( builder, "main_window" ) );
    
    /* get other objects we need from UI */
    psip_state->main_window = window;
    psip_state->progress_window = GTK_WIDGET (gtk_builder_get_object( builder, "progress_window" ));
    psip_state->progressbar = GTK_WIDGET (gtk_builder_get_object( builder, "progress_bar" ));
    psip_state->function_toolbar = GTK_WIDGET (gtk_builder_get_object( builder, "function_toolbar" ));
    psip_state->call_buddy_button = GTK_WIDGET (gtk_builder_get_object( builder, "call_buddy" ));
    
    psip_state->buddylist = GTK_LIST_STORE (gtk_builder_get_object( builder, "buddylist" ));
    psip_state->buddylistview = GTK_TREE_VIEW (gtk_builder_get_object( builder, "buddylistview" ));
    psip_state->confirm_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "confirm_dialog" ));
    psip_state->error_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "error_dialog" ));
    
    psip_state->call_window = GTK_WIDGET (gtk_builder_get_object( builder, "call_window" ));
    psip_state->call_window_text = GTK_LABEL (gtk_builder_get_object( builder, "call_window_text" ));
    psip_state->call_mute_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "mute_call" ));
    
    psip_state->addbuddy_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "addbuddy_dialog" ));
    psip_state->addbuddy_nickname_field = GTK_ENTRY (gtk_builder_get_object( builder, "addbuddy_nickname" ));
    psip_state->addbuddy_address_field = GTK_ENTRY (gtk_builder_get_object( builder, "addbuddy_address" ));
 
    psip_state->editaccount_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "editaccount_dialog" ));
    psip_state->account_sip_url_field = GTK_ENTRY (gtk_builder_get_object( builder, "account_sip_url" ));  
    psip_state->account_registrar_field = GTK_ENTRY (gtk_builder_get_object( builder, "account_registrar" ));  
    psip_state->account_realm_field = GTK_ENTRY (gtk_builder_get_object( builder, "account_realm" ));
    psip_state->account_user_field = GTK_ENTRY (gtk_builder_get_object( builder, "account_user" ));  
    psip_state->account_password_field = GTK_ENTRY (gtk_builder_get_object( builder, "account_password" ));  
 
    /* Connect signals */
    gtk_builder_connect_signals( builder, NULL);
 
    /* Destroy builder, since we don't need it anymore */
    g_object_unref( G_OBJECT( builder ) );

	/* init system */
	clear_buddies();
	load_config();
  
	if (backend_start() == TRUE) {
		/* Show window. All other widgets are automatically shown by GtkBuilder */
		gdk_threads_enter();
		gtk_widget_show( window );
	 
		/* Start main loop */
		gtk_main();
		
		/* done */
		backend_stop();
		save_config();
		gdk_threads_leave();
	} else backend_stop();
     
	g_slice_free(psip_state_struct, psip_state);
	g_print( "\nDone.\n");
    return( 0 );
}