/* * PSIP - A lightweight GTK GUI for pjsip * (C) James Budiono 2011 * License: GNU GPL Version 3 or later, please see attached gpl-3.0.txt * or http://www.gnu.org/copyleft/gpl.html * * All the GUI stuff is here. * For all actual connections etc, see backend. */ #include #include #include #include #include #include #include #include "psip.h" #include "psip_icon.h" #define PROGRESS_BAR_SPEED 250 /* move bar every 250ms - that is, 4 moves per second*/ #define REGISTRATION_REFRESH_DELAY 3 /* how many seconds after registration, before we refresh presence status */ #ifdef RELEASE #include "psip.glade.h" #include #else #define MAIN_GUI_GLADE "psip.glade" #define IM_GUI_GLADE "psip_im.glade" #endif psip_state_struct *psip_state; GdkPixbuf *app_icon = NULL; GtkFileFilter *filter_all; GtkFileFilter *filter_wav; GtkFileFilter *filter_rtf; /* ====================== Thread-safe utility functions ===================== */ void lock_gdk() { if (g_thread_self() != psip_state->main_thread) { //g_print("locking\n"); gdk_threads_enter(); } } void unlock_gdk() { if (g_thread_self() != psip_state->main_thread ) { //g_print("unlocking\n"); gdk_threads_leave(); } } /* === start notification command to make sound === */ GPid start_notification_sound_command (gchararray cmd) { gint argc; gchar **argv; GError *err; GPid pid; if (!strlen(cmd)) return 0; if (!g_shell_parse_argv (cmd, &argc, &argv, &err)) return 0; if (!g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &err) ) { g_strfreev(argv); return 0; } g_strfreev(argv); return pid; } /* === stop notification command === */ void stop_notification_sound_command (GPid pid) { if (pid) { int status; kill (pid, SIGTERM); waitpid (pid, &status, 0); } } /* === get current time stamp - result must be g_free-d later === */ gchararray get_time_stamp() { gchararray outstr = g_malloc (200); time_t t; struct tm *tmp; t = time(NULL); tmp = localtime(&t); if (tmp == NULL) { g_free(outstr); return NULL; } if (strftime(outstr, 200, "%F %T ", tmp) == 0) { g_free(outstr); return NULL; } return outstr; } /* ====================== Non-thread-safe utility functions ===================== */ /* all functions are not thread-safe, caller must call lock_gdk() (or gdk_threads_enter) */ /* === push and pop status bar texts === */ void push_status (gchararray context, gchararray text) { guint id = gtk_statusbar_get_context_id (GTK_STATUSBAR (psip_state->main_statusbar), context); gtk_statusbar_push (GTK_STATUSBAR (psip_state->main_statusbar), id, text); } void pop_status (gchararray context) { gtk_statusbar_pop (GTK_STATUSBAR (psip_state->main_statusbar), gtk_statusbar_get_context_id (GTK_STATUSBAR (psip_state->main_statusbar), context)); } /* === create shared file filters === */ void create_file_filters() { filter_wav = gtk_file_filter_new (); gtk_file_filter_set_name (filter_wav, "WAV files"); gtk_file_filter_add_mime_type (filter_wav, "audio/x-wav"); filter_all = gtk_file_filter_new (); gtk_file_filter_set_name (filter_all, "All files"); gtk_file_filter_add_pattern (filter_all, "*"); filter_rtf = gtk_file_filter_new (); gtk_file_filter_set_name (filter_rtf, "RTF files"); gtk_file_filter_add_mime_type (filter_rtf, "text/rtf"); g_object_ref (filter_wav); g_object_ref (filter_all); g_object_ref (filter_rtf); } /* === get audio settings preference === */ void get_audio_preference_settings (audio_settings *as) { int i; gchararray s; as->input = as->output = NULL; for (i=0; i < MAX_AUDIO_DEVICES; i++) { if ( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(psip_state->audio_inputs[i])) ) { s = (gchararray) gtk_button_get_label (GTK_BUTTON(psip_state->audio_inputs[i])); if ( g_strcmp0 ("radiobutton", s) ) as->input = s; break; } } for (i=0; i < MAX_AUDIO_DEVICES; i++) { if ( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(psip_state->audio_outputs[i])) ) { s = (gchararray) gtk_button_get_label ( GTK_BUTTON(psip_state->audio_outputs[i])); if ( g_strcmp0 ("radiobutton", s) ) as->output = s; break; } } } /* === show error dialog === */ void show_error(gchararray s) { g_object_set (G_OBJECT (psip_state->error_dialog), "text", s, NULL); gtk_dialog_run (psip_state->error_dialog); gtk_widget_hide (GTK_WIDGET (psip_state->error_dialog)); } /* === 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)); return TRUE; } else return FALSE; } /* === returns an adhoc quoted address, NULL is cancelled === */ //address must be freed with g_free gchararray get_adhoc_address(gchararray title) { gchararray address, address_quoted; gtk_window_set_title (GTK_WINDOW (psip_state->adhoc_address_dialog), title); gtk_widget_grab_focus (psip_state->adhoc_address_field); gint result = gtk_dialog_run (psip_state->adhoc_address_dialog); if (result == GTK_RESPONSE_OK) { address = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->adhoc_address_field))); gtk_entry_set_text (psip_state->adhoc_address_field, address); address_quoted = g_strdup_printf ("<%s>", address); g_free(address); return address_quoted; } return NULL; } /* === get the currently selected row from the view (buddylist or call_list) === * Note: returned GtkIter must be freed using g_slice_free */ GtkTreeIter *view_get_selected(GtkTreeView *the_view, gboolean checkForParent) { GtkTreeIter iter, iter_root, *iter_copy; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (the_view), NULL, &iter)) { if (checkForParent && !gtk_tree_model_iter_parent ( gtk_tree_view_get_model (the_view), &iter_root, &iter)) return NULL; iter_copy = g_slice_new(GtkTreeIter); *iter_copy = iter; return iter_copy; } return NULL; } /* ================= PRESENCE utility functions ================= */ /* === set the online status of a buddy === */ void buddyview_set_online_status (GtkTreeIter *i, gboolean status, gchararray status_text) { GtkTreeIter iter_root, iter; gtk_tree_model_iter_parent (GTK_TREE_MODEL (psip_state->buddytree), &iter_root, i); if (status == TRUE) { gtk_tree_store_set (psip_state->buddytree, i, BUDDY_ICON, "gtk-yes", BUDDY_STATUS, status, BUDDY_STATUS_TEXT, status_text, GTK_END_OF_LIST); //any one of them online - the group is online gtk_tree_store_set (psip_state->buddytree, &iter_root, BUDDY_ICON, "gtk-yes", GTK_END_OF_LIST); } else { gtk_tree_store_set (psip_state->buddytree, i, BUDDY_ICON, "gtk-no", BUDDY_STATUS, status, BUDDY_STATUS_TEXT, status_text, GTK_END_OF_LIST); //check whether any remaining are online, if not, change the group to offline too. gboolean ok = gtk_tree_model_iter_children( GTK_TREE_MODEL (psip_state->buddytree), &iter, &iter_root); gboolean overall_status = FALSE; do { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), &iter, BUDDY_STATUS, &status, GTK_END_OF_LIST); overall_status |= status; ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter); } while (ok); if (!overall_status) gtk_tree_store_set (psip_state->buddytree, &iter_root, BUDDY_ICON, "gtk-no", GTK_END_OF_LIST); } } /* === add a new buddy === */ GtkTreeIter *find_category (GtkTreeIter *iter_root, gchararray category) { gchararray nick; GtkTreeIter *p = NULL; if (gtk_tree_model_get_iter_first ( GTK_TREE_MODEL(psip_state->buddytree), iter_root)) { do { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), iter_root, BUDDY_NICK, &nick, GTK_END_OF_LIST); if (!strcmp (nick, category)) { // found it p = iter_root; break; } g_free (nick); } while (gtk_tree_model_iter_next ( GTK_TREE_MODEL(psip_state->buddytree), iter_root)); } if (p) return p; //not found - have to create a new one gtk_tree_store_append (psip_state->buddytree, iter_root, NULL); gtk_tree_store_set (psip_state->buddytree, iter_root, BUDDY_NICK, category, GTK_END_OF_LIST); return iter_root; } void buddylist_add (gchararray nick, gchararray address, gchararray category) { GtkTreeIter *i, iter_root; gint buddy_id = UNREGISTERED_BUDDY; i = g_slice_new(GtkTreeIter); //freed when buddy is deleted - see below. gtk_tree_store_append (psip_state->buddytree, i, find_category (&iter_root, category)); buddy_id = backend_register_buddy (address, i); gtk_tree_store_set (psip_state->buddytree, i, BUDDY_NICK, nick, BUDDY_ADDRESS, address, BUDDY_STATUS, FALSE, BUDDY_ICON, "gtk-no", BUDDY_ID, buddy_id, GTK_END_OF_LIST); } //* === delete existing buddy === */ void buddylist_del (GtkTreeIter *iter) { GtkTreeIter iter_root; gint buddy_id; gpointer p = NULL; gtk_tree_model_iter_parent (GTK_TREE_MODEL (psip_state->buddytree), &iter_root, iter); gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), iter, BUDDY_ID, &buddy_id, GTK_END_OF_LIST); if (buddy_id != UNREGISTERED_BUDDY) p = backend_deregister_buddy (buddy_id); gtk_tree_store_remove (psip_state->buddytree, iter); //if all the entries in this category has been deleted, delete the category as well if (!gtk_tree_model_iter_has_child (GTK_TREE_MODEL (psip_state->buddytree), &iter_root)) gtk_tree_store_remove (psip_state->buddytree, &iter_root); if (p) g_slice_free(GtkTreeIter, p); } /* === register all existing buddies to backend === */ void buddylist_register_all() { GtkTreeIter iter_root, iter, *iter_copy; gchararray address; gint buddy_id; if (gtk_tree_model_get_iter_first( GTK_TREE_MODEL (psip_state->buddytree), &iter_root)) { do { if (gtk_tree_model_iter_children (GTK_TREE_MODEL (psip_state->buddytree), &iter, &iter_root)) { do { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), &iter, BUDDY_ADDRESS, &address, GTK_END_OF_LIST); g_print("Connecting presence: %s\n", address); iter_copy = g_slice_new(GtkTreeIter); *iter_copy = iter; buddy_id = backend_register_buddy (address, iter_copy); gtk_tree_store_set (psip_state->buddytree, &iter, BUDDY_ID, buddy_id, GTK_END_OF_LIST); g_free(address); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter)); } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter_root)); } } /* === de-register all existing buddies to backend === */ /* void buddylist_deregister_all() { GtkTreeIter iter_root, iter; gchararray address; gint buddy_id; gpointer p; if (gtk_tree_model_get_iter_first( GTK_TREE_MODEL (psip_state->buddytree), &iter_root)) { do { if (gtk_tree_model_iter_children (GTK_TREE_MODEL (psip_state->buddytree), &iter, &iter_root)) { do { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), &iter, BUDDY_ADDRESS, &address, BUDDY_ID, &buddy_id, GTK_END_OF_LIST); g_print("Disconnecting presence: %s\n", address); p = backend_deregister_buddy (buddy_id); if (p) g_slice_free (GtkTreeIter, p); buddyview_set_online_status (&iter, FALSE, ""); gtk_tree_store_set (psip_state->buddytree, &iter, BUDDY_ID, UNREGISTERED_BUDDY, GTK_END_OF_LIST); g_free(address); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter)); } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter_root)); } } */ /* === refresh buddy presence === */ gboolean buddylist_refresh_presence() { GtkTreeIter iter, iter_root; gchararray address; gint buddy_id; gboolean ok2 = gtk_tree_model_get_iter_first( GTK_TREE_MODEL (psip_state->buddytree), &iter_root); if (ok2) { do { gboolean ok = gtk_tree_model_iter_children( GTK_TREE_MODEL (psip_state->buddytree), &iter, &iter_root); if (ok) { do { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), &iter, BUDDY_ADDRESS, &address, BUDDY_ID, &buddy_id, GTK_END_OF_LIST); if (buddy_id != UNREGISTERED_BUDDY) { g_print("Refreshing: %s\n", address); backend_refresh_presence (buddy_id); } g_free(address); ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter); } while (ok); } ok2 = gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->buddytree), &iter_root); } while (ok2); } return FALSE; // so that glib timeout will terminate } /* ================= IM Window utility functions =============== */ /* === create new im window === */ im_data *create_new_im_window(gchararray address) { GtkBuilder *builder; GError *error = NULL; im_data *p; p = g_slice_new(im_data); p->address = address; p->is_typing = FALSE; g_hash_table_insert (psip_state->active_ims, address, p); /* 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 NULL; } /* Get UI components from builder */ p->w = GTK_WINDOW ( gtk_builder_get_object( builder, "im_window" ) ); p->msg = GTK_WIDGET( gtk_builder_get_object( builder, "message_text" ) ); p->history = GTK_WIDGET( gtk_builder_get_object( builder, "history_text" ) ); p->typing = GTK_WIDGET( gtk_builder_get_object( builder, "im_window_typing" ) ); //create tags GtkTextBuffer *history_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW (p->history)); p->local_prefix_tag = gtk_text_buffer_create_tag (history_buffer, "local_prefix", "weight", PANGO_WEIGHT_BOLD, "foreground", "#0000FF", NULL); p->remote_prefix_tag = gtk_text_buffer_create_tag (history_buffer, "remote_prefix", "weight", PANGO_WEIGHT_BOLD, "foreground", "#FF0000", NULL); /* Connect signals */ gtk_builder_connect_signals( builder, p); /* Destroy builder, since we don't need it anymore */ g_object_unref( G_OBJECT( builder ) ); //and display our window gtk_window_set_title (p->w, address); gtk_widget_show (GTK_WIDGET (p->w)); return p; } /* === close and destroy im window === */ void close_im_window(im_data *p) { //g_print("%s destroyed\n", p->address); g_hash_table_remove (psip_state->active_ims, p->address); g_free (p->address); gtk_widget_destroy ( GTK_WIDGET(p->w)); g_slice_free(im_data, p); } /* === insert text into im window's history field === */ void im_window_insert_history_text(im_data *p, gchararray from, gchararray text, GtkTextTag *tag) { GtkTextBuffer *history_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW (p->history)); GtkTextIter end_iter; gchararray when = get_time_stamp(); gchararray ss = g_strdup_printf ("%s - %s: ", when, from); //append that to the history window gtk_text_buffer_get_end_iter (history_buffer, &end_iter); gtk_text_buffer_insert_with_tags (history_buffer, &end_iter, ss, -1, tag, NULL); gtk_text_buffer_insert (history_buffer, &end_iter, text, -1); gtk_text_buffer_insert (history_buffer, &end_iter, "\n", -1); //scroll to end of newly inserted text gtk_text_buffer_place_cursor(history_buffer, &end_iter); gtk_text_view_scroll_to_mark( GTK_TEXT_VIEW (p->history), gtk_text_buffer_get_insert (history_buffer), 0.0, TRUE, 0.0, 1.0); //flash window gtk_widget_grab_focus (GTK_WIDGET(p->msg)); gtk_window_set_urgency_hint (p->w, TRUE); /* this doesn't always work, but anyway ... */ if (tag == p->local_prefix_tag) log_activity (g_strdup_printf("TEXT TO %s: %s", p->address, text )); else log_activity (g_strdup_printf("TEXT FROM %s: %s", from, text )); g_free(when); g_free(ss); } /* ================= Call Window utility functions =============== */ /* === add entry to active-call list (address & state must be g_malloc-ed, it will be freed here) === */ call_data *add_active_call (gchararray address, gchararray state, gint call_id) { call_data *p = g_slice_new (call_data); GtkTreeIter *iter = g_slice_new (GtkTreeIter); p->address = address; p->call_id = call_id; p->iter = iter; p->level = 1.0; p->hold = FALSE; g_hash_table_insert (psip_state->active_calls, address, p); gtk_list_store_append (psip_state->call_list, iter); gtk_list_store_set (psip_state->call_list, iter, CALL_ADDRESS, address, CALL_STATUS, state, CALL_DATA, p, CALL_HOLD, FALSE, GTK_END_OF_LIST); gtk_tree_selection_select_iter (gtk_tree_view_get_selection (psip_state->call_listview), iter); gtk_window_present (psip_state->call_window); gtk_widget_show ( GTK_WIDGET(psip_state->call_window)); g_free (state); return p; } /* === remove call from list === */ void remove_active_call (call_data *p) { g_hash_table_remove (psip_state->active_calls, p->address); gtk_list_store_remove (psip_state->call_list, p->iter); g_slice_free (GtkTreeIter,p->iter); g_free (p->address); g_slice_free (call_data, p); } /* === update call status === */ void update_call_state (call_data *p, gchararray state) { gtk_list_store_set (psip_state->call_list, p->iter, CALL_STATUS, state, GTK_END_OF_LIST); } /* === add entry to call history === */ void add_call_history_entry (gchararray from, gchararray action) { GtkTreeIter iter; gchararray when = get_time_stamp(); gtk_list_store_append (psip_state->callhistorylist, &iter); gtk_list_store_set (psip_state->callhistorylist, &iter, CALL_HISTORY_WHEN, when, CALL_HISTORY_FROM, from, CALL_HISTORY_ACTION, action, GTK_END_OF_LIST); log_activity (g_strdup_printf("CALL %s FROM/TO %s", action, from )); } /* === send notification messages via IM to all active call parties === */ //text must be g_strdup-ed, it will be g-freed here void notify_all_active_calls (gchararray text) { GtkTreeIter iter; gchararray address; gboolean ok = gtk_tree_model_get_iter_first( GTK_TREE_MODEL (psip_state->call_list), &iter); if (ok) { do { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), &iter, CALL_ADDRESS, &address, GTK_END_OF_LIST); //g_print("Sending notice to: %s: %s\n", address, text); backend_send_message (address, text); g_free(address); ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (psip_state->call_list), &iter); } while (ok); } g_free(text); } /*====================== Callbacks - Main Window =======================*/ /* update the registration status on the button, also hide the progress window if shown */ void update_registration_status (gboolean registered) { gtk_widget_hide (GTK_WIDGET(psip_state->progress_window)); gtk_widget_set_sensitive (GTK_WIDGET (psip_state->register_button), TRUE); if (registered) { gtk_tool_button_set_label (GTK_TOOL_BUTTON(psip_state->register_button),"Registered"); gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(psip_state->register_button), "gtk-yes"); gtk_widget_set_tooltip_text (GTK_WIDGET(psip_state->register_button), "Click here to de-register."); } else { gtk_tool_button_set_label (GTK_TOOL_BUTTON(psip_state->register_button),"Unregistered"); gtk_tool_button_set_stock_id (GTK_TOOL_BUTTON(psip_state->register_button), "gtk-no"); gtk_widget_set_tooltip_text (GTK_WIDGET(psip_state->register_button), "Click here to register."); } } /*==== callback: registration state toggled - register or deregister ===*/ void on_registration_clicked (GtkToolButton *b, gpointer p) { if (!backend_is_registered()) { //start registration gtk_widget_set_sensitive (GTK_WIDGET (b), FALSE); gtk_progress_bar_set_text(GTK_PROGRESS_BAR (psip_state->progressbar), "Registering ..."); gtk_widget_show (GTK_WIDGET (psip_state->progress_window)); gdk_threads_add_timeout (PROGRESS_BAR_SPEED, pulse_progress_bar, NULL); backend_register(); gdk_threads_add_timeout_seconds (REGISTRATION_REFRESH_DELAY, buddylist_refresh_presence, NULL); } else { update_registration_status (FALSE); backend_deregister(); } } /*==== callback: remove a buddy - confirm and then delete ===*/ void on_remove_buddy_clicked (GtkToolButton *b, gpointer p) { GtkTreeIter *iter = view_get_selected (psip_state->buddylistview, TRUE); gchararray s, nick; if (iter) { gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), iter, BUDDY_NICK, &nick, GTK_END_OF_LIST); s = g_strdup_printf("Removing <%s> from friend list.", nick); g_object_set (G_OBJECT (psip_state->confirm_dialog), "text", s, NULL); g_free(s); g_free(nick); //gtk_message_dialog_set_markup (psip_state->confirm_dialog, "Removing a friend."); if (gtk_dialog_run(psip_state->confirm_dialog) == GTK_RESPONSE_YES) { buddylist_del(iter); } gtk_widget_hide (GTK_WIDGET (psip_state->confirm_dialog)); g_slice_free (GtkTreeIter,iter); } else show_error ("Choose a friend from the list first."); } /*==== 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_entry_set_text (psip_state->addbuddy_category_field,""); //leave it as previous 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))); gchararray category = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->addbuddy_category_field))); //g_print ("Adding nick: %s address: %s\n", nick, address ); if (strlen(nick) && strlen(address) && strlen(category)) { buddylist_add (nick, address, category); g_free(nick); g_free(address); g_free(category); } else { show_error ("All fields must be filled-in."); g_free(nick); g_free(address); g_free(category); 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, category; GtkTreeIter iter_root, *i = view_get_selected (psip_state->buddylistview, TRUE); if (i) { // set fields from selected buddy gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), i, BUDDY_NICK, &nick, BUDDY_ADDRESS, &address, GTK_END_OF_LIST); gtk_entry_set_text (psip_state->addbuddy_nickname_field, nick); gtk_entry_set_text (psip_state->addbuddy_address_field, address); gtk_tree_model_iter_parent (GTK_TREE_MODEL (psip_state->buddytree), &iter_root, i); gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), &iter_root, BUDDY_NICK, &category, GTK_END_OF_LIST); gtk_entry_set_text (psip_state->addbuddy_category_field, category); gtk_widget_grab_focus (GTK_WIDGET (psip_state->addbuddy_nickname_field)); g_free(nick); g_free(address); g_free(category); 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))); category = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->addbuddy_category_field))); if (strlen(nick) && strlen(address) && strlen (category)) { buddylist_del (i); buddylist_add (nick, address, category); g_free(nick); g_free(address); g_free(category); } else { show_error ("All fields must be filled-in."); g_free(nick); g_free(address); g_free(category); goto try_again; } } gtk_widget_hide (GTK_WIDGET (psip_state->addbuddy_dialog)); g_slice_free (GtkTreeIter,i); } else show_error ("Choose a friend from the list first."); } /*==== callback: edit preferences ===*/ void on_preferences_clicked (GtkToolButton *b, gpointer p) { gchararray s; //prepare stuff before going in backend_prepare_preferences_dialog(); //show the dialog gtk_dialog_run (psip_state->preferences_dialog); //strip whitespaces from all account entries s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->account_sip_url_field))); gtk_entry_set_text (psip_state->account_sip_url_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->account_registrar_field))); gtk_entry_set_text (psip_state->account_registrar_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->account_realm_field))); gtk_entry_set_text (psip_state->account_realm_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->account_user_field))); gtk_entry_set_text (psip_state->account_user_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->account_password_field))); gtk_entry_set_text (psip_state->account_password_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->ring_command_field))); gtk_entry_set_text (psip_state->ring_command_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->help_command_field))); gtk_entry_set_text (psip_state->help_command_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->call_timeout_field))); gtk_entry_set_text (psip_state->call_timeout_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->im_command_field))); gtk_entry_set_text (psip_state->im_command_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->max_calls_field))); gtk_entry_set_text (psip_state->max_calls_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->sip_port_field))); gtk_entry_set_text (psip_state->sip_port_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->public_ip_field))); gtk_entry_set_text (psip_state->public_ip_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->stun_server_field))); gtk_entry_set_text (psip_state->stun_server_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->turn_server_field))); gtk_entry_set_text (psip_state->turn_server_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->turn_user_field))); gtk_entry_set_text (psip_state->turn_user_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->turn_password_field))); gtk_entry_set_text (psip_state->turn_password_field, s); g_free(s); s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->turn_realm_field))); gtk_entry_set_text (psip_state->turn_realm_field, s); g_free(s); s = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (psip_state->ring_wav_file_button)); if (psip_state->ring_filename) g_free (psip_state->ring_filename); psip_state->ring_filename = s; //process changes backend_reconfigure(); } /*==== callback: refresh presence ===*/ void on_refresh_presence_clicked (GtkToolButton *b, gpointer p) { buddylist_refresh_presence(); } /*==== callback: help ===*/ void on_help_clicked (GtkToolButton *b, gpointer user_data) { GError *err = NULL; g_spawn_command_line_async (gtk_entry_get_text(psip_state->help_command_field), &err); } /* === callback: test ringtone === */ void on_test_ringtone_clicked (GtkToolButton *b, gpointer user_data) { gchararray s = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (psip_state->ring_wav_file_button)); if (!s) return; if (psip_state->is_playing) backend_stop_playing(); psip_state->is_playing = TRUE; backend_start_playing (s); GtkDialog *d = GTK_DIALOG (gtk_message_dialog_new (GTK_WINDOW(psip_state->preferences_dialog), 0, GTK_MESSAGE_OTHER, GTK_BUTTONS_CLOSE, "Testing ringtone. Click \"Close\" when done.")); gtk_window_set_title (GTK_WINDOW(d), "Ringtone Test"); gtk_dialog_run (d); gtk_widget_destroy (GTK_WIDGET(d)); g_free(s); psip_state->is_playing = FALSE; backend_stop_playing(); } /*==== callback: set online status ===*/ void on_apply_online_status_clicked (GtkButton *b, gpointer p) { gchararray s; s = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->online_status_field))); gtk_entry_set_text (psip_state->online_status_field, s); g_free(s); backend_state_set_online_status(); } /*====================== Callbacks - Call Window & Call related functions =======================*/ /* === show/hide call history window === */ void on_open_call_history_clicked (GtkToolButton *b, gpointer p) { if (GTK_WIDGET_VISIBLE(psip_state->call_history_window)) { gtk_widget_hide (GTK_WIDGET(psip_state->call_history_window)); } else { gtk_widget_show (GTK_WIDGET(psip_state->call_history_window)); } } /* === show/hide call window === */ void on_open_call_window_clicked (GtkToolButton *b, gpointer p) { if (GTK_WIDGET_VISIBLE(psip_state->call_window)) { gtk_widget_hide (GTK_WIDGET(psip_state->call_window)); } else { //gint rootx, rooty, width, height; //gtk_window_get_size (psip_state->main_window, &width, &height); //gtk_window_get_position (psip_state->main_window, &rootx, &rooty); //rootx += width; //gtk_window_move (psip_state->call_window, rootx, rooty); gtk_widget_show (GTK_WIDGET(psip_state->call_window)); } } /* === utility function to make the call if address_quoted isn't already active === */ void make_the_call_unless_already_active (gchararray address_quoted) { call_data *p = g_hash_table_lookup (psip_state->active_calls, address_quoted); if (!p) { p = add_active_call (address_quoted, g_strdup("INITIATED"), INVALID_CALL_ID); add_call_history_entry (address_quoted, "OUTGOING"); if ( !backend_make_call (p) ) remove_active_call (p); } else { g_free (address_quoted); gtk_widget_show ( GTK_WIDGET(psip_state->call_window)); gtk_window_present (psip_state->call_window); } } /*==== callback: call a buddy ===*/ void on_call_buddy_clicked (GtkToolButton *b, gpointer p) { GtkTreeIter *iter; gchararray address_quoted; iter = view_get_selected(psip_state->buddylistview, TRUE); if (iter) { // valid selection, make the call gchararray address; gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), iter, BUDDY_ADDRESS, &address, GTK_END_OF_LIST); address_quoted = g_strdup_printf("<%s>", address); g_free(address); make_the_call_unless_already_active (address_quoted); g_slice_free (GtkTreeIter,iter); } else { // non-valid selection, prompt for adhoc address address_quoted = get_adhoc_address("Make Call"); if (address_quoted) make_the_call_unless_already_active (address_quoted); } } /* === callback: drop the selected call === */ void on_drop_call_clicked (GtkToolButton *b, gpointer not_used) { GtkTreeIter *iter = view_get_selected (psip_state->call_listview, FALSE); if (iter) { call_data *p; gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), iter, CALL_DATA, &p, GTK_END_OF_LIST); backend_drop_call (p); //remove_active_call (p); g_slice_free (GtkTreeIter, iter); } else show_error ("Choose a call from the list first."); } /*==== callback: call an address not listed in buddy list ===*/ void on_adhoc_call_clicked (GtkButton *b, gpointer p) { gchararray address = g_strstrip (g_strdup (gtk_entry_get_text(psip_state->adhoc_call_field))); if (strlen(address)) { gchararray address_quoted = g_strdup_printf("<%s>", address); make_the_call_unless_already_active (address_quoted); } g_free (address); } /* ==== callback: send dtmf digits === */ void on_send_dtmf(GtkButton *b) { GtkTreeIter *iter = view_get_selected (psip_state->call_listview, FALSE); if (iter) { call_data *p; gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), iter, CALL_DATA, &p, GTK_END_OF_LIST); g_print("sending digit: %s to call_id %d\n", gtk_button_get_label (b), p->call_id); backend_send_dtmf(p, (gchararray) gtk_button_get_label (b)); g_slice_free (GtkTreeIter, iter); } else show_error ("Choose a call from the list first."); } /* === callback: local mic volume control (0=mute local mic) === */ gboolean on_local_mic_volume_scaler_change_value (GtkRange *range, GtkScrollType scroll, gdouble value, gpointer user_data) { if (value < 0 ) value = 0; if (value > 1.0 ) value = 1.0; backend_adjust_local_mic_level (value); return FALSE; } /* === callback: remote speaker volume control (0=mute speaker) === */ gboolean on_remote_speaker_volume_scaler_change_value (GtkRange *range, GtkScrollType scroll, gdouble value, gpointer user_data) { if (value < 0 ) value = 0; if (value > 1.0 ) value = 1.0; GtkTreeIter *iter = view_get_selected (psip_state->call_listview, FALSE); if (iter) { call_data *p; gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), iter, CALL_DATA, &p, GTK_END_OF_LIST); backend_adjust_remote_speaker_level (p, value); g_slice_free (GtkTreeIter, iter); } else show_error ("Choose a call from the list first."); return FALSE; } /* === callback: selection of call_listview is changed === */ /* used to update display on various state - mainly, the remote speaker volume */ void on_call_listview_cursor_changed (GtkTreeView *treeview, gpointer user_data) { GtkTreeIter *iter = view_get_selected (psip_state->call_listview, FALSE); if (iter) { call_data *p; gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), iter, CALL_DATA, &p, GTK_END_OF_LIST); gtk_range_set_value (GTK_RANGE (psip_state->remote_speaker_volume_scaler), p->level); g_slice_free (GtkTreeIter, iter); } } void on_hold_call_toggled (GtkCellRendererToggle *cell, gchar *path, gpointer user_data) { GtkTreeIter iter; if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (psip_state->call_list), &iter, path)) { call_data *p; gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), &iter, CALL_DATA, &p, GTK_END_OF_LIST); if (p->hold) { //currently held - now unhold gtk_list_store_set (psip_state->call_list, &iter, CALL_HOLD, FALSE, GTK_END_OF_LIST); p->hold = FALSE; backend_unhold_call (p); } else { //currently unheld - now hold gtk_list_store_set (psip_state->call_list, &iter, CALL_HOLD, TRUE, GTK_END_OF_LIST); p->hold = TRUE; backend_hold_call (p); } } } /* === callback: record button activated === */ void on_record_call_toggled (GtkToggleToolButton *b, gpointer p) { if (gtk_toggle_tool_button_get_active(b) == TRUE && psip_state->is_recording == FALSE) { //start recording GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new ("Save recorded conversation as", psip_state->call_window, GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter_wav); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog), filter_all); gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); if (!psip_state->recording_filename) { gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), "/tmp"); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), "untitled.wav"); } else gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), psip_state->recording_filename); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { if (psip_state->recording_filename) g_free (psip_state->recording_filename); psip_state->recording_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); psip_state->is_recording = backend_start_recording (psip_state->recording_filename); if (psip_state->is_recording) notify_all_active_calls(g_strdup_printf("NOTICE: %s has started recording the conversation.",gtk_entry_get_text(psip_state->account_sip_url_field))); } gtk_widget_destroy (dialog); if (!psip_state->is_recording) gtk_toggle_tool_button_set_active (b, FALSE); } if (gtk_toggle_tool_button_get_active(b) == FALSE && psip_state->is_recording){ backend_stop_recording(); psip_state->is_recording = FALSE; notify_all_active_calls(g_strdup_printf("NOTICE: %s has stopped recording the conversation.",gtk_entry_get_text(psip_state->account_sip_url_field))); } } /* === callback: play button activated === */ void on_play_call_toggled (GtkToggleToolButton *b, gpointer p) { if (gtk_toggle_tool_button_get_active(b) == TRUE && psip_state->is_playing == FALSE) { //start playing GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new ("Open an audio file to play", psip_state->call_window, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter_wav); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog), filter_all); if (psip_state->playing_filename) gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), psip_state->playing_filename); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { if (psip_state->playing_filename) g_free (psip_state->playing_filename); psip_state->playing_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); psip_state->is_playing = backend_start_playing (psip_state->playing_filename); } gtk_widget_destroy (dialog); if (!psip_state->is_playing) gtk_toggle_tool_button_set_active (b, FALSE); } if (gtk_toggle_tool_button_get_active(b) == FALSE && psip_state->is_playing){ backend_stop_playing(); psip_state->is_playing = FALSE; } } /* === callback: clear history button === */ void on_call_history_clear_clicked (GtkButton *b, gpointer p) { gtk_list_store_clear (psip_state->callhistorylist); } /* === show call statistic === */ void on_call_statistic_clicked (GtkToolButton *b, gpointer p) { GtkTreeIter *iter = view_get_selected (psip_state->call_listview, FALSE); if (iter) { call_data *p; gchararray s, buffer = g_malloc (STATISTIC_BUFFER_SIZE); gtk_tree_model_get (GTK_TREE_MODEL (psip_state->call_list), iter, CALL_DATA, &p, GTK_END_OF_LIST); s = g_strdup_printf ("Call statistic for %s", p->address); if (backend_get_call_statistic(p, buffer)) { gtk_window_set_title (GTK_WINDOW(psip_state->call_statistic_dialog), s); g_free (s); s = g_markup_printf_escaped ("%s", buffer); gtk_label_set_markup (psip_state->call_statistic_detail, s); gtk_dialog_run (psip_state->call_statistic_dialog); } g_slice_free (GtkTreeIter, iter); g_free (buffer); g_free (s); } else show_error ("Choose a call from the list first."); } /*====================== Callbacks - IM Window & IM related functions =======================*/ /*==== callback: message a buddy - open new message window ===*/ void on_message_buddy_clicked (GtkToolButton *b, gpointer not_used) { GtkTreeIter *iter; gchararray address, address_quoted; iter = view_get_selected(psip_state->buddylistview, TRUE); if (iter) { // valid selection, send message to that address gtk_tree_model_get (GTK_TREE_MODEL (psip_state->buddytree), iter, BUDDY_ADDRESS, &address, GTK_END_OF_LIST); address_quoted = g_strdup_printf ("<%s>",address); g_free(address); im_data *p = g_hash_table_lookup(psip_state->active_ims, address_quoted); if (!p) { //im window for this user doesn't exist yet, create one p = create_new_im_window(address_quoted); //g_print("new im window for %s\n", address_quoted); } else { gtk_window_present (p->w); g_free (address_quoted); } g_slice_free (GtkTreeIter, iter); } else { // non-valid selection, prompt for adhoc address address_quoted = get_adhoc_address("Send Message"); if (address_quoted) create_new_im_window(address_quoted); } } /*==== callback: im window destroyed ===*/ void on_im_window_close_clicked (GtkToolButton *b, gpointer user_data) { close_im_window ((im_data *) user_data); } gboolean on_im_window_delete_event (GtkWidget *w, GdkEvent *e, gpointer user_data) { close_im_window ((im_data *) user_data); return FALSE; } /* === callback: window "focused" (ie activated) === */ gboolean on_im_window_focus_in_event (GtkWidget *w, GdkEventFocus *e, gpointer p) { gtk_window_set_urgency_hint (GTK_WINDOW(w), FALSE); // turn off "urgency" / flashing return FALSE; } /* === callback: send message === */ gboolean typing_indication_timeout (gpointer user_data); void on_im_window_apply_clicked (GtkToolButton *unused, gpointer user_data) { im_data *p = (im_data *) user_data; GtkTextBuffer *msg_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (p->msg)); GtkTextIter start, end; gchararray s; //get the text from the msg window gtk_text_buffer_get_bounds (msg_buffer, &start, &end); s = gtk_text_buffer_get_text (msg_buffer, &start, &end, FALSE); //g_print ("msg window content: %s\n", s); //append that to the history window im_window_insert_history_text (p, "You", s, p->local_prefix_tag); //and send it over the backend backend_send_message(p->address, s); //and clear msg window gtk_text_buffer_set_text (msg_buffer, "", -1); //and clear typing indication typing_indication_timeout (p); } /* === callback: typing indication timeout */ gboolean typing_indication_timeout (gpointer user_data) { im_data *p = (im_data *) user_data; if (p->is_typing) { p->is_typing = FALSE; backend_typing_indicator (p->address, FALSE); } return FALSE; } /* === callback: capture Enter key for sending message & also used for typing indication === */ gboolean on_message_text_key_press_event (GtkWidget *widget, GdkEventKey* pKey, gpointer user_data){ #define CONTROL_KEYS ((GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) im_data *p = (im_data *) user_data; if (!p->is_typing) { p->is_typing = TRUE; backend_typing_indicator (p->address, TRUE); gdk_threads_add_timeout_seconds (DEFAULT_TYPING_TIMEOUT, typing_indication_timeout, p); } if (pKey->type == GDK_KEY_PRESS && (pKey->keyval == GDK_Return || pKey->keyval == GDK_KP_Enter) && (pKey->state & CONTROL_KEYS) == 0) { on_im_window_apply_clicked (NULL, user_data); return TRUE; } return FALSE; } /* === callback: save message in im history window === */ void on_im_window_saveas_clicked (GtkButton *unused, gpointer user_data) { GtkWidget *dialog; gchararray filename; GError *err = NULL; im_data *p = (im_data *) user_data; dialog = gtk_file_chooser_dialog_new ("Save Message As", psip_state->call_window, GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter_rtf); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog), filter_all); gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); if (filename) { if (!rtf_text_buffer_export (gtk_text_view_get_buffer (GTK_TEXT_VIEW (p->history)), filename, &err)) show_error (err->message); g_free (filename); } } gtk_widget_destroy (dialog); } /* ==================== Icon and Status Icon functions ======================== */ /* === callback: status icon clicked === */ void on_status_icon_activated(GtkStatusIcon *g, gpointer p) { if (GTK_WIDGET_VISIBLE(psip_state->main_window)) { gtk_widget_hide (GTK_WIDGET(psip_state->main_window)); } else { gtk_window_deiconify (psip_state->main_window); gtk_widget_show (GTK_WIDGET(psip_state->main_window)); } } /* === callback: main_window window event change === */ void on_main_window_state_event (GtkWidget *w, GdkEventWindowState *e, gpointer p) { if (!gtk_toggle_button_get_active(psip_state->minimise_tray_field)) return; //we just want to detect whether it's minimised if ((e->new_window_state & GDK_WINDOW_STATE_ICONIFIED) == GDK_WINDOW_STATE_ICONIFIED) { gtk_widget_hide (GTK_WIDGET(psip_state->main_window)); } } void setup_status_icon() { GtkStatusIcon *g = gtk_status_icon_new_from_pixbuf (app_icon); gtk_status_icon_set_tooltip (g, "psip"); g_signal_connect(g, "activate", GTK_SIGNAL_FUNC (on_status_icon_activated), NULL); g_signal_connect(psip_state->main_window, "window-state-event", GTK_SIGNAL_FUNC (on_main_window_state_event), NULL); } void setup_window_icon() { GList *list; app_icon = gdk_pixbuf_new_from_inline (-1, psip_icon, FALSE, NULL); list = g_list_append(NULL, app_icon); gtk_window_set_default_icon_list (list); g_list_free (list); setup_status_icon(); } /*========= main ======= */ int main( int argc, char **argv ) { GtkBuilder *builder; GError *error = NULL; setlocale (LC_ALL, ""); bindtextdomain ("psip", "/usr/share/locale"); textdomain ("demo"); g_thread_init(NULL); gdk_threads_init(); /* allocate our data structure to hold applications states */ psip_state = g_slice_new(psip_state_struct); psip_state->is_playing = FALSE; psip_state->is_recording = FALSE; psip_state->recording_filename = NULL; psip_state->playing_filename = NULL; psip_state->ring_filename = NULL; psip_state->activity_log_file = NULL; psip_state->main_thread = g_thread_self(); /* Init GTK+ */ gtk_init( &argc, &argv ); /* Create new GtkBuilder object */ builder = gtk_builder_new(); /* Load main UI. */ #ifdef RELEASE /* gunzip and then load UI from memory. If error occurs, report it and quit application. */ z_stream zs; char *glade_ui; zs.next_in = MAIN_GUI_GLADE; zs.avail_in = sizeof (MAIN_GUI_GLADE); zs.next_out = glade_ui = g_malloc (MAIN_GUI_GLADE_ORIGINAL_SIZE); zs.avail_out = MAIN_GUI_GLADE_ORIGINAL_SIZE + 1; /* add one for null terminator */ glade_ui [MAIN_GUI_GLADE_ORIGINAL_SIZE] = 0; zs.zalloc = Z_NULL; zs.zfree = Z_NULL; zs.opaque = NULL; inflateInit2 (&zs, 15+32); //15 = size of window, 32 = allow gzip + zip encoding int result = inflate (&zs, Z_FINISH); inflateEnd (&zs); if (result != Z_STREAM_END) { g_warning( "Unable to decompress UI.\n" ); return (1); } if( ! gtk_builder_add_from_string( builder, glade_ui, -1, &error ) ) { g_warning( "%s", error->message ); return( 1 ); } g_free (glade_ui); #else /* Load UI from file. If error occurs, report it and quit application. */ if( ! gtk_builder_add_from_file( builder, MAIN_GUI_GLADE, &error ) ) { g_warning( "%s", error->message ); return( 1 ); } #endif /* Get main window pointer from UI */ psip_state->main_window = GTK_WINDOW( gtk_builder_get_object( builder, "main_window" )); /* get other objects we need from UI */ psip_state->progress_window = GTK_WINDOW (gtk_builder_get_object( builder, "progress_window" )); psip_state->progressbar = GTK_WIDGET (gtk_builder_get_object( builder, "progress_bar" )); psip_state->register_button = GTK_WIDGET (gtk_builder_get_object( builder, "registration" )); psip_state->main_statusbar = GTK_WIDGET (gtk_builder_get_object( builder, "main_statusbar" )); psip_state->online_status_field = GTK_ENTRY (gtk_builder_get_object( builder, "online_status" )); psip_state->buddytree = GTK_TREE_STORE (gtk_builder_get_object( builder, "buddytree" )); 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->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->addbuddy_category_field = GTK_ENTRY (gtk_builder_get_object( builder, "addbuddy_category" )); psip_state->preferences_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "preferences_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" )); psip_state->account_srtp_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "account_srtp" )); psip_state->loglevel_scaler = GTK_WIDGET (gtk_builder_get_object( builder, "loglevel_scaler" )); psip_state->ring_command_field = GTK_ENTRY (gtk_builder_get_object( builder, "ring_command" )); psip_state->help_command_field = GTK_ENTRY (gtk_builder_get_object( builder, "help_command" )); psip_state->call_timeout_field = GTK_ENTRY (gtk_builder_get_object( builder, "call_timeout" )); psip_state->minimise_tray_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "minimise_tray" )); psip_state->auto_register_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "auto_register" )); psip_state->ring_wav_file_button = GTK_FILE_CHOOSER_BUTTON (gtk_builder_get_object( builder, "ring_wav_file" )); psip_state->beep_on_im_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "beep_on_im" )); psip_state->im_command_field = GTK_ENTRY (gtk_builder_get_object( builder, "im_command" )); psip_state->activity_log_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "activity_log" )); psip_state->max_calls_field = GTK_ENTRY (gtk_builder_get_object( builder, "max_calls" )); psip_state->sip_port_field = GTK_ENTRY (gtk_builder_get_object( builder, "sip_port" )); psip_state->public_ip_field = GTK_ENTRY (gtk_builder_get_object( builder, "public_ip" )); psip_state->stun_server_field = GTK_ENTRY (gtk_builder_get_object( builder, "stun_server" )); psip_state->disable_vad_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "disable_vad" )); psip_state->enable_ice_field = GTK_TOGGLE_BUTTON (gtk_builder_get_object( builder, "enable_ice" )); psip_state->turn_server_field = GTK_ENTRY (gtk_builder_get_object( builder, "turn_server" )); psip_state->turn_user_field = GTK_ENTRY (gtk_builder_get_object( builder, "turn_user" )); psip_state->turn_password_field = GTK_ENTRY (gtk_builder_get_object( builder, "turn_password" )); psip_state->turn_realm_field = GTK_ENTRY (gtk_builder_get_object( builder, "turn_realm" )); create_file_filters(); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (psip_state->ring_wav_file_button), filter_wav); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (psip_state->ring_wav_file_button), filter_all); psip_state->call_window = GTK_WINDOW (gtk_builder_get_object( builder, "call_window" )); psip_state->call_list = GTK_LIST_STORE (gtk_builder_get_object( builder, "call_list" )); psip_state->call_listview = GTK_TREE_VIEW (gtk_builder_get_object( builder, "call_listview" )); psip_state->local_mic_volume_scaler = GTK_WIDGET (gtk_builder_get_object( builder, "local_mic_volume_scaler" )); psip_state->remote_speaker_volume_scaler = GTK_WIDGET (gtk_builder_get_object( builder, "remote_speaker_volume_scaler" )); gtk_range_set_value (GTK_RANGE (psip_state->local_mic_volume_scaler), 1.0); gtk_range_set_value (GTK_RANGE (psip_state->remote_speaker_volume_scaler), 1.0); psip_state->adhoc_call_field = GTK_ENTRY (gtk_builder_get_object( builder, "adhoc_call_entry" )); psip_state->call_history_window = GTK_WINDOW (gtk_builder_get_object( builder, "call_history_window" )); psip_state->callhistorylist = GTK_LIST_STORE (gtk_builder_get_object( builder, "callhistorylist" )); psip_state->call_statistic_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "statistic_dialog" )); psip_state->call_statistic_detail = GTK_LABEL (gtk_builder_get_object( builder, "statistic_detail" )); psip_state->adhoc_address_dialog = GTK_DIALOG (gtk_builder_get_object( builder, "adhoc_address_dialog" )); psip_state->adhoc_address_field = GTK_ENTRY (gtk_builder_get_object( builder, "adhoc_address" )); setup_window_icon(); #ifdef DISABLE_RECORDER gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object( builder, "record_call" ))); #endif //get the radio buttons int i = 0; for (i = 0; i < MAX_AUDIO_DEVICES; i++) { gchararray s; s = g_strdup_printf("audio_input%d",i); psip_state->audio_inputs[i] = GTK_WIDGET (gtk_builder_get_object( builder, s )); g_free(s); s = g_strdup_printf("audio_output%d",i); psip_state->audio_outputs[i] = GTK_WIDGET (gtk_builder_get_object( builder, s )); g_free(s); } /* 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 */ psip_state->active_ims = g_hash_table_new (g_str_hash, g_str_equal); psip_state->active_calls = g_hash_table_new (g_str_hash, g_str_equal); load_config(); if (backend_start() == TRUE) { /* Show window. All other widgets are automatically shown by GtkBuilder */ gdk_threads_enter(); buddylist_register_all(); gtk_widget_show( GTK_WIDGET(psip_state->main_window) ); if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (psip_state->auto_register_field))) on_registration_clicked (GTK_TOOL_BUTTON(psip_state->register_button), NULL); /* Start main loop */ gtk_main(); /* done */ //buddylist_deregister_all(); //seems unnecessary backend_stop(); save_config(); gdk_threads_leave(); } else backend_stop(); g_hash_table_destroy (psip_state->active_ims); g_hash_table_destroy (psip_state->active_calls); g_slice_free(psip_state_struct, psip_state); g_print( "\nDone.\n"); return( 0 ); }