/*
* 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 );
}