/* * Copyright (c) Linux community. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include // round() #include "support.h" #include "audio-sources.h" #include "rec-window.h" #include "dbus-server.h" #include "audio-sources.h" #include "media-profiles.h" #include "timer.h" #include "rec-manager.h" #include "utility.h" #include "dconf.h" #include "log.h" #include "win-settings.h" #include "auto-start.h" #include "about.h" #include "help.h" #include "gtklevelbar.h" // Level bar widget // Main window and all its widgets. MainWindow g_win; // Command line options, arguments. static guint g_version_info = -1; static guint g_show_window = -1; static guint g_show_tray_icon = -1; static guint g_debug_levels = -1; static gchar *g_command_arg = NULL; static GOptionEntry option_entries[] = { // Translators: This is a command line option. { "version", 'v', 0, G_OPTION_ARG_NONE, &g_version_info, N_("Print program name and version."), NULL}, // Translators: This is a command line option. { "show-window", 'w', 0, G_OPTION_ARG_INT, &g_show_window, N_("Show application window at startup (0=hide main window, 1=force display of main window)."), NULL}, // Translators: This is a command line option. { "show-icon", 'i', 0, G_OPTION_ARG_INT, &g_show_tray_icon, N_("Show icon on the system tray (0=hide icon, 1=force display of icon)."), NULL}, // Translators: This is a command line option. // Output audio level values in a terminal window. This makes it easier to set correct level (dB or %) value in the Timer. { "debug-signal", 'd', 0, G_OPTION_ARG_NONE, &g_debug_levels, N_("List signal level values in a terminal window."), NULL}, // Translators: This is a command line option. Notice: Do not translate the "status,start,stop,pause,show and quit" words. // Control the recorder from command line with the --command option. // --command=status returns one of: "not running" | "on" | "off" | "paused". { "command", 'c', 0, G_OPTION_ARG_STRING, &g_command_arg, N_("Send a command to the recorder. Valid commands are; status,start,stop,pause,show,hide and quit. The status argument returns; 'not running','on','off' or 'paused'."), NULL}, { NULL }, }; static void *win_update_gui_ex(gpointer user_data); static void *win_set_filename_ex(gpointer user_data); static void *win_set_error_text_ex(gpointer user_data); static void *win_show_window_ex(gpointer user_data); static gboolean win_delete_cb(GtkWidget *widget, GdkEvent *event, gpointer data); static void *send_client_request(gpointer data); static void win_call_gui_func(GThreadFunc func, gpointer user_data) { // Create a thread to call GUI (GTK) related functions. // GUI functions are called from many places (inside/outside threads) // and we have to set gdk_threads_enter() and gdk_threads_leave(). GError *error = NULL; g_thread_create(func, user_data, FALSE, &error); if (error) { LOG_ERROR("Cannot start thread. %s.\n", error->message); g_error_free(error); } } void win_update_gui() { // Update GUI. win_call_gui_func(win_update_gui_ex, NULL); } static void *win_update_gui_ex(gpointer user_data) { // Important: We are calling GTK from a thread. Set lock. gdk_threads_enter(); // Get recording state gint state = -1; gint pending = -1; rec_manager_get_state(&state, &pending); if (state == GST_STATE_PAUSED && pending == GST_STATE_NULL) { state = GST_STATE_NULL; } gchar *image_file = NULL; gchar *label = NULL; gboolean active = FALSE; switch (state) { case GST_STATE_PLAYING: // Recording is on image_file = (gchar*)get_image_path("audio-recorder-on-small.png"); // Translators: This is a button label, also used in the menu. label = _("Stop recording"); active = TRUE; break; case GST_STATE_PAUSED: // Paused image_file = (gchar*)get_image_path("audio-recorder-paused-small.png"); // Translators: This is a button label, also used in the menu. label = _("Continue recording"); active = TRUE; break; case GST_STATE_READY: default: // Stopped/off image_file = (gchar*)get_image_path("audio-recorder-off-small.png"); // Translators: This is a button label, also used in the menu. label = _("Start recording"); active = FALSE; } // Set label if (GTK_IS_BUTTON(g_win.recorder_button)) { gtk_button_set_label(GTK_BUTTON(g_win.recorder_button), label); // Set image (a small color dot on the button) GtkWidget *image = gtk_image_new_from_file(image_file); gtk_button_set_image(GTK_BUTTON(g_win.recorder_button), image); } g_free(image_file); // Reset amplitude/level bar if (!active) { win_update_level_bar(0.0, 0.0); } // Update systray icon and its menu selections systray_set_menu_items1(state); // Important: We are calling GTK from a thread. Free the lock. gdk_threads_leave(); return 0; } void win_set_filename(gchar *filename) { // Set filename label win_call_gui_func(win_set_filename_ex, g_strdup(filename)); } static void *win_set_filename_ex(gpointer user_data) { // Take filename gchar *filename = (gchar*)user_data; // Important: We are calling GTK from a thread. Set lock. gdk_threads_enter(); // Remove path from filename gchar *path = NULL; gchar *fname = NULL; split_filename2(filename, &path, &fname); // Show the filename if (GTK_IS_WIDGET(g_win.filename)) { gtk_entry_set_text(GTK_ENTRY(g_win.filename), (fname ? fname : "")); } g_free(path); g_free(fname); // Important: We are calling GTK from a thread. Free the lock. gdk_threads_leave(); // Free the data g_free(filename); return 0; } const gchar *win_level_value_to_text(gdouble peak, gdouble peak_dB) { static gchar text[16]; if (peak <= 0.01) { return NULL; } // Enum LevelTextType level_ttype; switch (g_win.level_ttype) { case LEVEL_TEXT_PERCENT: g_snprintf(text, 10, "%d%%", (int)round(peak*100.0)); break; case LEVEL_TEXT_DB: g_snprintf(text, 10, "%ddB", (int)round(peak_dB)); break; default: // Do not show any value or text return NULL; } return text; } void win_update_level_bar(gdouble peak, gdouble peak_dB) { // Set amplitude/level bar if (!GTK_IS_LEVEL_BAR(g_win.level_bar)) return; // Value text (eg. "10%" or "-20dB" or simply NULL) const gchar *value_text = win_level_value_to_text(peak, peak_dB); // Set level value and text gtk_level_bar_set_fraction(GTK_LEVEL_BAR(g_win.level_bar), peak, value_text); } void win_set_time_label(gchar *time_txt) { // Set time label ##:##:## if (GTK_IS_WIDGET(g_win.time_label)) { gtk_label_set_text(GTK_LABEL(g_win.time_label), time_txt); } } void win_set_size_label(gchar *size_txt) { // Set label for filesize (recorded filesize) if (GTK_IS_WIDGET(g_win.time_label)) { gtk_label_set_text(GTK_LABEL(g_win.size_label), size_txt); } } void win_set_error_text(gchar *error_txt) { // Show red error label. Set error text. win_call_gui_func(win_set_error_text_ex, g_strdup(error_txt)); } static void *win_set_error_text_ex(gpointer user_data) { // Error text gchar *error_txt = (gchar*)user_data; // Long error messages make the GUI too tall/wide if (str_length(error_txt, 1024) > 256) { *(error_txt + 256) = '\0'; } // Remove last "\n" (it adds an empty line to the label) if (error_txt) { gchar *p = g_strrstr(error_txt, "\n"); if (p) { *p = '\0'; } } // Important: We are calling GTK from a thread. Set lock. gdk_threads_enter(); // Get the label widget GtkWidget *label = NULL; if (GTK_IS_WIDGET(g_win.error_box)) { label = (GtkWidget*)g_object_get_data(G_OBJECT(g_win.error_box), "label-widget"); } if (!GTK_IS_WIDGET(label)) goto LBL_1; if (str_length(error_txt, 1024) < 1) { // Hide the error label gtk_label_set_text(GTK_LABEL(label), ""); gtk_widget_hide(g_win.error_box); } else { // Set and show the error label gtk_label_set_text(GTK_LABEL(label), error_txt); gtk_widget_show(g_win.error_box); } LBL_1: // Free the data g_free(error_txt); // Important: We are calling GTK from a thread. Free the lock. gdk_threads_leave(); return 0; } void win_flip_recording_cb(GtkButton *button, gpointer user_data) { // Start, continue or stop recording rec_manager_flip_recording(); } gboolean win_recording_button_cb(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { // Click on g_win.recorder_button if (event->button == 3) { // Show right click menu win_show_right_click_menu(); } return FALSE; } void win_show_right_click_menu() { // Show a popup menu. Borrow menu from the systray module. GtkWidget *popup_menu = systray_create_menu(FALSE/*not for systray*/); gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, -1, gtk_get_current_event_time()); } static void win_expander_click_cb(GObject *object, GParamSpec *param_spec, gpointer userdata) { // Handle click on GUI Exapnder> GtkExpander *expander = GTK_EXPANDER(object); gboolean expanded = gtk_expander_get_expanded(expander); gchar *expander_name = (gchar*)userdata; // Save the expanded status conf_save_boolean_value(expander_name, expanded); if (!g_strcmp0(expander_name, "timer-expanded")) { // Save the timer text win_timer_save_text_cb(g_win.timer_save_button, NULL); } else if (!g_strcmp0(expander_name, "settings-expanded")) { // Show/hide [Additional settings] button if (expanded) gtk_widget_show(g_win.settings_button); else gtk_widget_hide(g_win.settings_button); } } void win_add_to_changed_cb(GtkToggleButton *togglebutton, gpointer user_data) { // Click on g_win.add_to_file checkbox // It is active? gboolean active = gtk_toggle_button_get_active(togglebutton); // Save in GConf registry conf_save_boolean_value("append-to-file", active); // No reason to let the timer know about this. // Commented out: // timer_settings_changed(); } void win_timer_active_cb(GtkToggleButton *togglebutton, gpointer user_data) { // Click on g_win.timer_active checkbox // Timer is active? gboolean active = gtk_toggle_button_get_active(togglebutton); if (GTK_IS_WIDGET(g_win.timer_text)) { // Always keep the edit field enabled. // Commented out: // gtk_widget_set_sensitive(g_win.timer_text, active); } // Save in GConf registry conf_save_boolean_value("timer-active", active); // Let the timer know that the settings have been altered timer_settings_changed(); } void combo_select_string(GtkComboBox *combo, gchar *str, gint sel_row) { // Find str in the GtkComboBox (combo). // If str is not found then select sel_row line. // If sel_row < 0, then add a new row (with str) and select it. GtkTreeModel *model = gtk_combo_box_get_model(combo); GtkTreeIter iter; // Loop for all rows gint ret = gtk_tree_model_get_iter_first(model, &iter); while (ret && str) { gchar *val = NULL; gtk_tree_model_get(model, &iter, 0, &val, -1); // Strings match? if (strcmp(val, str) == 0) { // Select this item gtk_combo_box_set_active_iter(combo, &iter); g_free(val); // And quit return; } g_free(val); ret = gtk_tree_model_iter_next(model, &iter); } // Item was not found. Select sel_row. gtk_combo_box_set_active(combo, sel_row); } void window_show_timer_help() { // Show help file help_show_page("timer-syntax.html"); } gboolean win_window_is_visible() { // Window is visible? return gtk_widget_get_visible(g_win.window); } void win_show_window(gboolean show) { // Show or hide window win_call_gui_func(win_show_window_ex, GINT_TO_POINTER(show)); } static void *win_show_window_ex(gpointer user_data) { gboolean show = GPOINTER_TO_INT(user_data); // Important: We are calling GTK from a thread. Set lock. gdk_threads_enter(); // Close about dialog if open about_destroy_dialog(); // Show or hide the main window if (!GTK_IS_WINDOW(g_win.window)) goto L_END; if (show) { // Show window. Also bring up if window is minimized/iconified. gtk_widget_show(g_win.window); gtk_window_deiconify(GTK_WINDOW(g_win.window)); gtk_window_present(GTK_WINDOW(g_win.window)); } else { // Close settings dialog if open win_settings_destroy_dialog(); // Hide window gtk_widget_hide(g_win.window); } // Update menu on the systray systray_set_menu_items2(show); L_END: // Important: We are calling GTK from a thread. Free the lock. gdk_threads_leave(); return 0; } void test_0_cb(GtkWidget *widget, gpointer data) { // For testing and debugging } void win_settings_cb(GtkWidget *widget, gpointer data) { // Show the [Additional settings] dialog win_settings_show_dialog(GTK_WINDOW(g_win.window)); } void win_refresh_device_list() { // Refresh/reload the list of audio devices and Media Players, Skype // Reset error message win_set_error_text(NULL); // Get current selection gchar *dev_name; gchar *dev_id; gint dev_type; audio_sources_combo_get_values(g_win.audio_device, &dev_name, &dev_id, &dev_type); // Load the audio sources (device names) from PulseAudio (or Gstreamer) and fill the combobox. // Detect and add also Media Players, Skype (if installed) to the list. These can control the recording via DBus. audio_source_fill_combo(g_win.audio_device); if (dev_id) { // Set/show the current value audio_sources_combo_set_id(g_win.audio_device, dev_id); } // Free the values g_free(dev_name); g_free(dev_id); } void win_set_device_id() { // Simply set the value of g_win.audio_device combo gchar *str_value = NULL; conf_get_string_value("audio-device-id", &str_value); audio_sources_combo_set_id(g_win.audio_device, str_value); g_free(str_value); } void win_device_list_changed_cb(GtkWidget *widget, gpointer data) { // Selection in the device combo // Reset error message (message label) win_set_error_text(NULL); // Get values gchar *dev_name; gchar *dev_id; gint dev_type; audio_sources_combo_get_values(g_win.audio_device, &dev_name, &dev_id, &dev_type); LOG_DEBUG("-----------------------\n"); LOG_DEBUG("Selected device or media player, etc. (g_win.audio_device):%s\n", dev_id); LOG_DEBUG("name:%s\n", dev_name); LOG_DEBUG("type:%d\n", dev_type); LOG_DEBUG("-----------------------\n"); // Save id conf_save_string_value("audio-device-id", check_null(dev_id)); // Save name conf_save_string_value("audio-device-name", dev_name); // Save type conf_save_int_value("audio-device-type", dev_type); // Tell audio_sources that the device has changed. // This will disconnect/re-connect all DBus signals to Media Players, Skype. audio_sources_device_changed(dev_id); // Let timer know that the settings have been altered. timer_settings_changed(); // Free the values g_free(dev_name); g_free(dev_id); } void win_audio_format_changed_cb(GtkWidget *widget, gpointer data) { // Save media-profile (id) gchar *txt = media_profiles_get_selected_id(widget/*=g_win.media_format*/); LOG_DEBUG("Selected audio format (g_win.media_format):%s\n", txt); conf_save_string_value("media-format", txt); g_free(txt); } void win_timer_text_changed_cb(GtkTextBuffer *textbuffer, gpointer user_data) { // Timer text changed. Show [Save] button. gtk_widget_show(g_win.timer_save_button); } void win_timer_text_insert_cb(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, gpointer user_data) { #define MAX_TEXT_LEN 3500 // Get buffer start and end GtkTextIter start, end; gtk_text_buffer_get_bounds(textbuffer, &start, &end); // Get approx. character count gint text_len = gtk_text_iter_get_offset(&end); if (text_len > MAX_TEXT_LEN) { // Buffer becomes too large. Stop this insert! g_signal_stop_emission_by_name(textbuffer, "insert_text"); } } void win_timer_save_text_cb(GtkWidget *widget, gpointer user_data) { // Save timer text GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_win.timer_text)); // Read timer text GtkTextIter start, end; gtk_text_buffer_get_start_iter(buffer, &start); gtk_text_buffer_get_end_iter(buffer, &end); gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); // Save it conf_save_string_value("timer-text", text); g_free(text); // Hide the [Save] button if (gtk_widget_get_visible(widget)) { gtk_widget_hide(widget); } // Let the timer know that the settings have been altered timer_settings_changed(); } void win_close_button_cb(GtkButton *button, gpointer user_data) { // Click on [Close] button gboolean force_quit = (gboolean)GPOINTER_TO_INT(user_data); // Has icon on the systen tray? if (!force_quit && systray_icon_is_installed()) { // Hide this window win_show_window(FALSE); return; } // Quit application, destroy this window. about_destroy_dialog(); win_delete_cb(g_win.window, NULL, NULL); gtk_main_quit(); gtk_widget_destroy(g_win.window); g_win.window = NULL; } void win_quit_application() { // Quit this application win_close_button_cb(NULL, (gpointer)TRUE); } gboolean win_close_button_press_cb(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { // Right click on [Close] button? if (event->button == 3) { // Show a small "Quit" menu GtkWidget *menu = gtk_menu_new(); // Translators: This is a small right-click-menu on the GUI's [Close] button. GtkWidget *menu_item = gtk_menu_item_new_with_label(_("Quit")); gtk_widget_show(menu_item); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); g_signal_connect(menu_item, "activate", G_CALLBACK(win_close_button_cb), GINT_TO_POINTER(TRUE)); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, -1, gtk_get_current_event_time()); return TRUE; } return FALSE; } gboolean win_delete_cb(GtkWidget *widget, GdkEvent *event, gpointer data) { // Called prior exit. LOG_DEBUG("win_delete_cb() called.\n"); dbus_service_module_exit(); systray_module_exit(); rec_manager_exit(); audio_sources_exit(); media_profiles_exit(); timer_module_exit(); // Allow exit. return FALSE; } void win_destroy_cb(GtkWidget *widget, gpointer data) { // Quit/exit this application gtk_main_quit (); } static gboolean win_state_event_cb(GtkWidget *widget, GdkEventWindowState *event, gpointer data) { // Save window's state. // We are interested in GDK_WINDOW_STATE_ICONIFIED and 0 (normal state). g_win.state = event->new_window_state; if (g_win.state == 0) { // Enable/disbale menu items (on systray icon) systray_set_menu_items2(TRUE); } else if (g_win.state == GDK_WINDOW_STATE_ICONIFIED) { // Enable/disbale menu items (on systray icon) systray_set_menu_items2(FALSE); } return TRUE; } GdkWindowState win_get_window_state() { // Return current window state return g_win.state; } gboolean win_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { // Key press event on the main window if (event->state & GDK_CONTROL_MASK) { if (event->keyval == 's' || event->keyval == 'S') { // Control + S saves timer text win_timer_save_text_cb(g_win.timer_save_button, NULL); } else if (event->keyval == 'x' || event->keyval == 'X') { // Control + X stops recording rec_manager_stop_recording(); } else if (event->keyval == 'p' || event->keyval == 'P') { // Control + P pauses recording rec_manager_pause_recording(); } else if (event->keyval == 'r' || event->keyval == 'R') { // Control + R starts recording rec_manager_start_recording(); } } // Pass this event further return FALSE; } void win_show_settings_dialog() { win_settings_show_dialog(GTK_WINDOW(g_win.window)); } void win_level_bar_clicked(GtkWidget *widget, GdkEvent *event, gpointer data) { // User clicked the level bar. // Change the level text. Valid types are %-value, dB-value or nothing. GdkEventButton *ev = (GdkEventButton*)event; if (ev->button != 1) { // return; } // Get from DConf gint text_type = (gint)LEVEL_TEXT_NONE; conf_get_int_value("level-bar-text-type", &text_type); // Take next type text_type += 1; if (text_type < (gint)LEVEL_TEXT_NONE/*first enum*/ || text_type > (gint)LEVEL_TEXT_DB/* last enum*/) { text_type = (gint)LEVEL_TEXT_NONE; } g_win.level_ttype = text_type; // Save in DConf conf_save_int_value("level-bar-text-type", text_type); } void win_create_window() { // Create main window and all its widgets g_win.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(g_win.window), PREF_WINDOW_WIDTH, -1); gtk_window_set_position(GTK_WINDOW(g_win.window), GTK_WIN_POS_MOUSE); // Show on all desktops gtk_window_stick(GTK_WINDOW(g_win.window)); // Keep this window on top, above other windows gtk_window_set_keep_above(GTK_WINDOW(g_win.window), TRUE); gtk_window_set_default_icon_name("audio-recorder"); gchar *prog_name = get_program_name(); gtk_window_set_title(GTK_WINDOW(g_win.window), prog_name); g_free(prog_name); // Set resizable to FALSE. // Note: This makes the window to shrink when GtkExpanders are shrunk/reduced/contracted. gtk_window_set_resizable(GTK_WINDOW(g_win.window), FALSE); g_signal_connect(g_win.window, "delete-event", G_CALLBACK(win_delete_cb), NULL); g_signal_connect(g_win.window, "destroy", G_CALLBACK(win_destroy_cb), NULL); g_signal_connect(g_win.window, "window-state-event", G_CALLBACK (win_state_event_cb), NULL); // Actions on Control + S/R/P/X keys g_signal_connect(g_win.window, "key-press-event", G_CALLBACK(win_key_press_cb), NULL); GtkWidget *frame = gtk_frame_new(NULL); gtk_widget_show(frame); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(g_win.window), frame); gtk_container_set_border_width(GTK_CONTAINER(frame), 7); GtkWidget *vbox0 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); gtk_widget_show(vbox0); gtk_container_add(GTK_CONTAINER(frame), vbox0); #ifdef APP_HAS_MENU // Create menubar. // EDIT: I have remove the menubar. GtkWidget *menubar = win_create_menubar(); gtk_widget_show(menubar); gtk_box_pack_start(GTK_BOX(vbox0), menubar, FALSE, FALSE, 0); #endif GtkWidget *vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3); gtk_widget_show(vbox1); gtk_box_pack_start(GTK_BOX(vbox0), vbox1, FALSE, TRUE, 0); GtkWidget *hbox0 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(hbox0); gtk_box_pack_start(GTK_BOX(vbox1), hbox0, TRUE, TRUE, 0); // Get saved values gchar *last_track_name = NULL; // Last saved filename conf_get_string_value("track/last-track-name", &last_track_name); gboolean append_to_file = FALSE; conf_get_boolean_value("append-to-file", &append_to_file); // [Start/Stop/Continue recording] button g_win.recorder_button = gtk_button_new_with_mnemonic(""); gtk_widget_show(g_win.recorder_button); gtk_box_pack_start(GTK_BOX (hbox0), g_win.recorder_button, FALSE, FALSE, 0); g_signal_connect(g_win.recorder_button, "clicked", G_CALLBACK(win_flip_recording_cb), NULL); g_signal_connect(g_win.recorder_button, "button-press-event",G_CALLBACK(win_recording_button_cb), NULL); gtk_button_set_use_underline(GTK_BUTTON(g_win.recorder_button), TRUE); // Time label g_win.time_label = gtk_label_new("00:00:00"); gtk_widget_show(g_win.time_label); gtk_box_pack_start(GTK_BOX(hbox0), g_win.time_label, FALSE, FALSE, 2); gtk_widget_set_sensitive(g_win.time_label, FALSE); // Label for filesize g_win.size_label = gtk_label_new("0.0 KB"); gtk_widget_show(g_win.size_label); gtk_box_pack_start(GTK_BOX(hbox0), g_win.size_label, FALSE, FALSE, 7); gtk_widget_set_sensitive(g_win.size_label, FALSE); // Show current file size if (g_file_test(last_track_name, G_FILE_TEST_EXISTS)) { struct stat fstat; if (!stat(last_track_name, &fstat)) { gchar *size_txt = format_file_size(fstat.st_size); gtk_label_set_text(GTK_LABEL(g_win.size_label), size_txt); g_free(size_txt); } } // Indicator for sound level/amplitude (GtkLevelBar widget) // Put it in an GtkEventBox so we can catch clicked event. // User can change the text/label type (%, dB, NULL) by clicking it. GtkWidget *event_box = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(hbox0), event_box, TRUE, TRUE, 0); gtk_widget_show(event_box); // Allow mouse click events gtk_widget_set_events(event_box, GDK_BUTTON_PRESS_MASK); g_signal_connect(event_box, "button_press_event", G_CALLBACK(win_level_bar_clicked), NULL); // Create GtkLevelBar widget and put it in the GtkEventBox g_win.level_bar = gtk_level_bar_new(); gtk_widget_show(g_win.level_bar); gtk_container_add(GTK_CONTAINER(event_box), g_win.level_bar); gtk_level_bar_set_fraction(GTK_LEVEL_BAR(g_win.level_bar), 0.0, NULL); // How to display label/text on the level bar? // Get from DConf gint text_ttype = (gint)LEVEL_TEXT_NONE; conf_get_int_value("level-bar-text-type", &text_ttype); g_win.level_ttype = text_ttype; // Notice: User can change this by clicking on the level-bar. GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(hbox1); gtk_box_pack_start(GTK_BOX(vbox1), hbox1, TRUE, TRUE, 0); // Translators: This is a GUI label. Keep it short. GtkWidget *label0 = gtk_label_new(_("File:")); gtk_widget_show(label0); gtk_box_pack_start(GTK_BOX(hbox1), label0, FALSE, FALSE, 0); gtk_misc_set_alignment(GTK_MISC(label0), 0.01, 0.5); g_win.filename = gtk_entry_new(); gtk_widget_show(g_win.filename); gtk_box_pack_start(GTK_BOX(hbox1), g_win.filename, TRUE, TRUE, 0); gtk_entry_set_invisible_char(GTK_ENTRY(g_win.filename), 9679); // Show lastly saved filename; basename.ext gchar *path = NULL; gchar *fname = NULL; split_filename2(last_track_name, &path, &fname); gtk_entry_set_text(GTK_ENTRY(g_win.filename), (fname ? fname : "")); g_free(path); g_free(fname); // Free last_track_name g_free(last_track_name); // "Add to file" label. // Translators: This is a GUI label. Keep it VERY short. g_win.add_to_file = gtk_check_button_new_with_mnemonic(_("Add.")); gtk_widget_show(g_win.add_to_file); gtk_box_pack_start(GTK_BOX(hbox1), g_win.add_to_file, FALSE, FALSE, 0); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_win.add_to_file), append_to_file); g_signal_connect(g_win.add_to_file, "toggled", G_CALLBACK(win_add_to_changed_cb), NULL); // Timer interface gchar *str_value = NULL; gboolean bool_value = FALSE; // "Timer>" GUI expander. // Translators: This is a GUI label. Keep it short. GtkWidget *timer_expander = gtk_expander_new(_("Timer.")); gtk_widget_show(timer_expander); gtk_box_pack_start(GTK_BOX(vbox1), timer_expander, TRUE, TRUE, 0); conf_get_boolean_value("timer-expanded", &bool_value); gtk_expander_set_expanded(GTK_EXPANDER(timer_expander), bool_value); g_signal_connect(timer_expander, "notify::expanded", G_CALLBACK(win_expander_click_cb), "timer-expanded"); GtkWidget *hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(hbox2); gtk_container_add(GTK_CONTAINER(timer_expander), hbox2); GtkWidget *vbox22 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show(vbox22); gtk_box_pack_start(GTK_BOX(hbox2), vbox22, FALSE, FALSE, 0); // Timer checkbox g_win.timer_active = gtk_check_button_new(); gtk_widget_show(g_win.timer_active); gtk_box_pack_start(GTK_BOX(vbox22), g_win.timer_active, FALSE, FALSE, 0); g_signal_connect(g_win.timer_active, "toggled", G_CALLBACK(win_timer_active_cb), NULL); conf_get_boolean_value("timer-active", &bool_value); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_win.timer_active), bool_value); // Timer text/commands; GtkTextView, multiline text view. GtkWidget *vbox20 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show(vbox20); gtk_box_pack_start(GTK_BOX(hbox2), vbox20, TRUE, TRUE, 0); // Put g_win.timer_text to a GtkFrame so it gets a visible border/frame GtkWidget *frame2 = gtk_frame_new(NULL); gtk_widget_show(frame2); gtk_box_pack_start(GTK_BOX(vbox20), frame2, TRUE, TRUE, 0); g_win.timer_text = gtk_text_view_new(); gtk_widget_show(g_win.timer_text); gtk_container_add(GTK_CONTAINER(frame2), g_win.timer_text); // Timer [Save] button g_win.timer_save_button = gtk_button_new_from_stock(GTK_STOCK_SAVE); // Hide it gtk_widget_hide(g_win.timer_save_button); g_signal_connect(g_win.timer_save_button, "clicked", G_CALLBACK(win_timer_save_text_cb), NULL); GtkWidget *hbox22 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(hbox22); gtk_container_add(GTK_CONTAINER(vbox20), hbox22); gtk_box_pack_end(GTK_BOX(hbox22), g_win.timer_save_button, FALSE, FALSE, 0); GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_win.timer_text)); g_signal_connect(buffer, "changed", G_CALLBACK(win_timer_text_changed_cb), NULL); g_signal_connect(buffer, "insert-text", G_CALLBACK(win_timer_text_insert_cb), NULL); // Get current timer text conf_get_string_value("timer-text", &str_value); if (str_length(str_value, 4096) > 0) { gtk_text_buffer_set_text(buffer, str_value, -1); } else { // Add some examples GString *str = g_string_new(NULL); g_string_append(str, "start at 08:20 pm\n"); g_string_append_printf(str, "# stop after 1 h 30 min\n"); g_string_append_printf(str, "# stop if silence | 100MB\n"); g_string_append_printf(str, "# start if voice 5s 6%%"); gchar *str_txt = g_string_free(str, FALSE); gtk_text_buffer_set_text(buffer, str_txt, -1); conf_save_string_value("timer-text", str_txt); g_free(str_txt); } g_free(str_value); // The [Info] button GtkWidget *button0 = gtk_button_new(); gtk_widget_show(button0); GtkWidget *image = gtk_image_new_from_stock("gtk-info", GTK_ICON_SIZE_BUTTON); gtk_widget_show(image); gtk_button_set_image(GTK_BUTTON(button0), image); g_signal_connect(button0, "clicked", G_CALLBACK(window_show_timer_help), NULL); vbox22 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show(vbox22); gtk_box_pack_start(GTK_BOX(hbox2), vbox22, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox22), button0, FALSE, FALSE, 0); GtkWidget *hseparator0 = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_widget_show(hseparator0); gtk_box_pack_start(GTK_BOX(vbox0), hseparator0, FALSE, TRUE, 0); // "Audio settings>" GUI expander. // Translators: This is a GUI label. GtkWidget *setting_expander = gtk_expander_new(_("Audio settings.")); gtk_widget_show(setting_expander); gtk_box_pack_start(GTK_BOX(vbox0), setting_expander, TRUE, TRUE, 2); conf_get_boolean_value("settings-expanded", &bool_value); gtk_expander_set_expanded(GTK_EXPANDER(setting_expander), bool_value); g_signal_connect(setting_expander, "notify::expanded", G_CALLBACK(win_expander_click_cb), "settings-expanded"); GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3); gtk_widget_show(vbox2); gtk_container_add(GTK_CONTAINER(setting_expander), vbox2); GtkWidget *table0 = gtk_table_new(5, 3, FALSE); gtk_widget_show(table0); gtk_box_pack_start(GTK_BOX (vbox2), table0, FALSE, FALSE, 0); gtk_table_set_row_spacings(GTK_TABLE(table0), 2); // Audio Source label (meaning Audio Source, the device or program that produces sound). // Translators: This is a GUI label. Keep it short. label0 = gtk_label_new(_("Source:")); gtk_widget_show(label0); gtk_table_attach (GTK_TABLE(table0), label0, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(label0), 0, 0.5); // Create combobox for audio devices and Media Players, Skype, etc. g_win.audio_device = audio_sources_create_combo(); gtk_widget_show(g_win.audio_device); gtk_table_attach(GTK_TABLE(table0), g_win.audio_device, 1, 2, 0, 1,(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (GTK_FILL), 0, 0); // Combobox "changed" signal gulong signal_id = g_signal_connect(g_win.audio_device, "changed", G_CALLBACK(win_device_list_changed_cb), NULL); // Save signal handle so we can block/unblock it g_object_set_data(G_OBJECT(g_win.audio_device), "selection-changed-signal", GINT_TO_POINTER(signal_id)); // Fill the combo with audio devices and media players. win_refresh_device_list(); // Set/show the current value. conf_get_string_value("audio-device-name", &str_value); // Has value? if (str_length(str_value, 1024) < 1) { // Set first (Audio Output) device as default. GList *lst = audio_sources_get_for_type(AUDIO_SINK_MONITOR); DeviceItem *dev_item = g_list_nth_data(lst, 0); if (dev_item) { // Save values conf_save_string_value("audio-device-name", check_null(dev_item->description)); conf_save_string_value("audio-device-id", dev_item->id); conf_save_int_value("audio-device-type", dev_item->type); } // Free the list and its data audio_sources_free_list(lst); lst = NULL; } g_free(str_value); // Add [Reload] button button0 = gtk_button_new(); gtk_widget_show(button0); image = gtk_image_new_from_stock("gtk-refresh", GTK_ICON_SIZE_BUTTON); gtk_widget_show(image); gtk_button_set_image(GTK_BUTTON(button0), image); gtk_table_attach(GTK_TABLE(table0), button0, 2, 3, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); g_signal_connect(button0, "clicked", G_CALLBACK(win_refresh_device_list), NULL); // Audio format; .OGG, .MP3, .WAV, etc. See media-profiles.c. // Translators: This is a GUI label. Keep it short. label0 = gtk_label_new (_("Format:")); gtk_widget_show(label0); gtk_table_attach(GTK_TABLE(table0), label0, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment(GTK_MISC(label0), 0, 0.5); // Get the media-profiles combobox filled with values. // Start dconf/gconf-editor and browse to: system -> gstreamer -> 0.10 -> audio -> profiles. g_win.media_format = media_profiles_create_combobox(); gtk_widget_show(g_win.media_format); gtk_table_attach(GTK_TABLE(table0), g_win.media_format, 1, 2, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (GTK_FILL), 0, 0); // Combox "changed" signal g_signal_connect(g_win.media_format, "changed", G_CALLBACK(win_audio_format_changed_cb), NULL); // Show current value conf_get_string_value("media-format", &str_value); combo_select_string(GTK_COMBO_BOX(g_win.media_format), str_value, 0/*select 0'th row if str_value not found*/); g_free(str_value); // Separator hseparator0 = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_widget_show(hseparator0); gtk_box_pack_start(GTK_BOX(vbox0), hseparator0, FALSE, TRUE, 1); // Place for error messages { g_win.error_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Hide it gtk_widget_hide(g_win.error_box); gtk_box_pack_start(GTK_BOX(vbox0), g_win.error_box, TRUE, FALSE, 0); // Label label0 = gtk_label_new(""); gtk_widget_show(label0); gtk_box_pack_start(GTK_BOX(g_win.error_box), label0, TRUE, FALSE, 0); // Left align it gtk_misc_set_alignment(GTK_MISC(label0), 0.0f, 0.0f); // Set red foreground/text color GdkColor color; gdk_color_parse("red", &color); gtk_widget_modify_fg(label0, GTK_STATE_NORMAL, &color); // Wrap lines gtk_label_set_line_wrap(GTK_LABEL(label0), TRUE); gtk_label_set_line_wrap_mode(GTK_LABEL(label0), PANGO_WRAP_WORD); // Remember the label widget g_object_set_data(G_OBJECT(g_win.error_box), "label-widget", label0); // Separator hseparator0 = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_widget_show(hseparator0); gtk_box_pack_start(GTK_BOX(g_win.error_box), hseparator0, FALSE, TRUE, 0); } // Buttons GtkWidget *hbox4 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(hbox4); gtk_box_pack_start(GTK_BOX(vbox0), hbox4, FALSE, TRUE, 0); button0 = gtk_button_new_from_stock("gtk-close"); gtk_widget_show(button0); gtk_box_pack_end(GTK_BOX(hbox4), button0, FALSE, FALSE, 0); g_signal_connect(button0, "clicked", G_CALLBACK(win_close_button_cb), GINT_TO_POINTER(FALSE)); // Show a small menu on right-mouse click g_signal_connect(button0, "button-press-event", G_CALLBACK(win_close_button_press_cb), NULL); // [Additional settings] button. // Translators: This is a label on the [Additional settings] button. g_win.settings_button = gtk_button_new_with_label(_("Additional settings")); // Settings> box is expanded? conf_get_boolean_value("settings-expanded", &bool_value); if (bool_value) gtk_widget_show(g_win.settings_button); else gtk_widget_hide(g_win.settings_button); g_signal_connect(g_win.settings_button, "clicked", G_CALLBACK(win_settings_cb), NULL); gtk_box_pack_end(GTK_BOX(hbox4), g_win.settings_button, FALSE, TRUE, 0); // This button is for testing and debugging button0 = gtk_button_new_with_label("Test button"); // ATM hide it gtk_widget_hide(button0); gtk_box_pack_end(GTK_BOX(hbox4), button0, FALSE, FALSE, 0); g_signal_connect(button0, "clicked", G_CALLBACK(test_0_cb), NULL); #ifdef APP_HAS_MENU // Show/hide main menu? gboolean bool_val = FALSE; conf_get_boolean_value("show-main-menu", &bool_val); win_set_main_menu(bool_val); #endif // Hide the [Save] widget (if it's visible) gtk_widget_hide(g_win.timer_save_button); // Hide the error_box widget (if it's visible) gtk_widget_hide(g_win.error_box); // Set/reset button images and labels win_update_gui(); } int main(int argc, char *argv[]) { g_type_init(); // Parse command line arguments GError *error = NULL; GOptionContext *context = g_option_context_new("Command line aguments."); g_option_context_set_help_enabled(context, TRUE); g_option_context_add_main_entries(context, option_entries, GETTEXT_PACKAGE); GOptionGroup *opt_group = gtk_get_option_group(TRUE); g_option_context_add_group(context, opt_group); g_option_context_parse(context, &argc, &argv, &error); if (error) { LOG_ERROR("Invalid command line argument. %s\n", error->message); g_error_free(error); } g_option_context_free(context); context = NULL; LOG_DEBUG("Value of --version (-v)=%d\n", g_version_info); LOG_DEBUG("Value of --show-icon (-i)=%d\n", g_show_tray_icon); LOG_DEBUG("Value of --show-window (-w)=%d\n", g_show_window); LOG_DEBUG("Value of --debug-signal (-d)=%d\n", g_debug_levels); LOG_DEBUG("Value of --command (-c)=%s\n", g_command_arg); if (g_version_info != -1) { // Print program name and version info // Eg. "Audio Recorder 1.0" gchar *name = about_program_name(); g_print("%s\n", name); g_free(name); // Exit exit(0); } // Initialize the i18n stuff bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); setlocale(LC_ALL, ""); // Initialize all modules gtk_init(&argc, &argv); gdk_init(&argc, &argv); g_thread_init(NULL); gdk_threads_init(); gst_init(&argc, &argv); // Check if user wants to control the recorder (the running instance of the recorder) from the command line. if (g_command_arg != NULL) { // $ audio-recorder --command // Check if we find existing instance of Audio-Recorder. Do not start this program twice. // Get state returns: "not running" | "on" | "off" | "paused" gchar *state = dbus_service_client_request("get_state", NULL); // Got a valid answer ("on", "off" or "paused") from existing instance? // Or got "not running" or NULL? if (state && (!g_strrstr(state, "not"))) { // Send client request (execute methode call over DBus) send_client_request(g_command_arg); g_free(state); state = NULL; // And kill this duplicate instance of audio-recorder exit(0); } g_free(state); // Let this instance run // ... } // Simply print recording status (not running | on | off | paused) and exit? if (!g_strcmp0(g_command_arg, "status")) { send_client_request(g_command_arg); // This will call exit() } // Simply quit? else if (!g_strcmp0(g_command_arg, "quit")) { send_client_request(g_command_arg); // This will call exit() } // Initialize modules media_profiles_init(); rec_manager_init(); audio_sources_init(); timer_module_init(); // Setup DBus server for this program dbus_service_module_init(); systray_module_init(); // Show button images (normally not shown in the GNOME) // See also gconf-editor, setting '/desktop/gnome/interface' GtkSettings *settings = gtk_settings_get_default(); g_object_set(settings, "gtk-button-images", TRUE, NULL); // Create main window and all its widgets win_create_window(); // Determine how to display the window and tray icon gboolean show_icon = FALSE; conf_get_boolean_value("show-systray-icon", &show_icon); gboolean show_window = TRUE; if (!g_show_tray_icon) { // Override the setting conf_save_boolean_value("show-systray-icon", FALSE); // Cannot hide both window and the tray icon g_show_window = 1/*TRUE*/; // g_show_tray_icon != -1: if --show-icon or -i option was seen } else if (g_show_tray_icon && g_show_tray_icon !=-1) { // Override the setting conf_save_boolean_value("show-systray-icon", TRUE); } // Show/hide tray icon systray_icon_setup(); // Running this application first time? gboolean first_time = FALSE; conf_get_boolean_value("started-first-time", &first_time); if (first_time) { conf_save_boolean_value("started-first-time", FALSE); // Force: show window g_show_window = TRUE; // Auto-start this application on login autostart_set(TRUE); } if (!g_show_window) { show_window = FALSE; // g_show_window != -1: if --show-window or -w option was seen } else if (g_show_window && g_show_window != -1) { show_window = TRUE; } // Let "--command show" and "--command hide" override the window visibility if (g_strrstr0(g_command_arg, "show")) { show_window = TRUE; } else if (g_strrstr0(g_command_arg, "hide")) { show_window = FALSE; } if (show_window) { // Show win_show_window(TRUE); } else { // Hide win_show_window(FALSE); } // Debugging: // Show values from the gst-listener.c process. This will print out audio level values. // User must start this application from a terminal window to see these values. if (g_debug_levels == 1) { // Set flag via timer.c timer_set_debug_flag(TRUE); } // g_win.audio_device must be set here at very end. // Otherwise Media Players and Skype may show up before this app's main window. Skype may even block. win_set_device_id(); // Send client request (execute method call over DBus). // We must call this from a thread. Otherwise DBus server (in dbus-service.c) and // client request (also in dbus-service.c) will conflict & block. g_thread_create(send_client_request, (gpointer)g_command_arg, FALSE, NULL); // Main loop gdk_threads_enter(); gtk_main (); gdk_threads_leave(); return 0; } static void *send_client_request(gpointer data) { // Send command via DBus to the existing instance of audio-recorder // $ audio-recorder --command start // $ audio-recorder --command stop // $ audio-recorder --command pause // $ audio-recorder --command status // Print status; "not running" | "on" | "off" | "paused" // $ audio-recorder --command show // $ audio-recorder --command quit // You can also combine // $ audio-recorder --command start+show // $ audio-recorder --command stop+hide // $ audio-recorder --command stop+quit if (!data) return 0; // To lower case gchar *command = g_ascii_strdown((gchar*)data, -1); gboolean done = FALSE; gchar *ret = NULL; gint exit_val = 0; if (!g_strcmp0(command, "status")) { // Call get_state() ret = dbus_service_client_request("get_state", NULL/*no args*/); if (!ret) { // Audio-recorder is not running ret = g_strdup("not running"); exit_val = -1; } g_print("%s\n", ret); g_free(ret); // Exit exit(exit_val); } // ------------------------------------------------- if (g_strrstr(command, "start")) { // Call set_state("start") ret = dbus_service_client_request("set_state", "start"); if (g_strcmp0(ret, "OK")) { LOG_ERROR("Cannot execute client/dbus request %s.\n", "set_state(\"start\")"); } g_free(ret); done = TRUE; } if (g_strrstr(command, "stop")) { // Call set_state("stop") ret = dbus_service_client_request("set_state", "stop"); if (g_strcmp0(ret, "OK")) { LOG_ERROR("Cannot execute client/dbus request %s.\n", "set_state(\"stop\")"); } g_free(ret); done = TRUE; } if (g_strrstr(command, "pause")) { // Call set_state("pause") ret = dbus_service_client_request("set_state", "pause"); if (g_strcmp0(ret, "OK")) { LOG_ERROR("Cannot execute client/dbus request %s.\n", "set_state(\"pause\")"); } g_free(ret); done = TRUE; } if (g_strrstr(command, "show")) { // Call set_state("show") ret = dbus_service_client_request("set_state", "show"); if (g_strcmp0(ret, "OK")) { LOG_ERROR("Cannot execute client/dbus request %s.\n", "set_state(\"show\")"); } g_free(ret); done = TRUE; } if (g_strrstr(command, "hide")) { // Call set_state("hide") ret = dbus_service_client_request("set_state", "hide"); if (g_strcmp0(ret, "OK")) { LOG_ERROR("Cannot execute client/dbus request %s.\n", "set_state(\"hide\")"); } g_free(ret); done = TRUE; } if (g_strrstr(command, "status")) { // Call get_state() ret = dbus_service_client_request("get_state", NULL /*no args*/); if (!ret) { // Audio-recorder is not running ret = g_strdup("not running"); } // Print state; not running|on|off|paused g_print("%s\n", ret); g_free(ret); done = TRUE; } if (g_strrstr(command, "quit")) { // Call set_state("quit"). Terminate running instance of this application. ret = dbus_service_client_request("set_state", "quit"); g_free(ret); // Exit also this instance exit(0); } if (!done) { LOG_ERROR("Invalid argument in --command=%s. See --help for more information.\n", command); } g_free(command); // FALSE: Remove idle function return 0; }