/* * Copyright (c) 2011- Osmo Antero. * * 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 3 of the License (GPL3), or 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 3 for more details. * * You should have received a copy of the GNU Library General Public * License 3 along with this program; if not, see /usr/share/common-licenses/GPL file * or . */ #include #include #include #include #include // round() #include #include #include "levelbar.h" // Level bar widget #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 "settings.h" #include "auto-start.h" #include "about.h" #include "help.h" // Main window and all its widgets. MainWindow g_win; // Command line options, arguments. static gint g_version_info = -1; // Print version info then exit static gint g_show_window = -1; // Show/hide window static gint g_show_tray_icon = -1; // Show/hide tray icon static gint g_reset_settings = -1; // Reset all settings then restart static gint g_debug_threshold = -1; // Output RMS threshold value? static gchar *g_command_arg = NULL; // Argument of --command (-c) // Default timer text static const gchar *g_def_timer_text = "" "#start at 02:20 pm\n" "#stop at 15:00\n" "#stop after 1h 30min\n" "stop if silence 4s 20%\n" "#stop if silence | 100MB\n" "#start if voice 0.3\n" "#start if voice 30%"; 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. { "reset", 'r', 0, G_OPTION_ARG_NONE, &g_reset_settings, N_("Reset all settings and restart audio-recorder."), NULL }, // Translators: This is a command line option. // Output audio level values in a terminal window. // This makes it easier to set correct threshold (dB or %) value in the Timer. { "debug-signal", 'd', 0, G_OPTION_ARG_NONE, &g_debug_threshold, 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 gboolean win_delete_cb(GtkWidget *widget, GdkEvent *event, gpointer data); static gpointer send_client_request(gchar *argv[], gpointer data); static void contact_existing_instance(gchar *argv[]); static gboolean ar_is_running(); static void combo_select_string(GtkComboBox *combo, gchar *str, gint sel_row); void win_update_gui() { // 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-dot.svg"); // 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-dot.svg"); // 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-dot.svg"); // 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); } #if 0 void win_update_gui() { // 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; } const gchar *icon_name = NULL; gchar *label = NULL; gboolean active = FALSE; switch (state) { case GST_STATE_PLAYING: // Recording is on icon_name = "audio-recorder-on"; // Translators: This is a button label, also used in the menu. label = _("Stop recording"); active = TRUE; break; case GST_STATE_PAUSED: // Paused icon_name = "audio-recorder-paused"; // Translators: This is a button label, also used in the menu. label = _("Continue recording"); active = TRUE; break; case GST_STATE_READY: default: // Stopped/off icon_name = "audio-recorder-off"; // 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); GdkPixbuf *icon_pixbuf = load_icon_pixbuf((gchar*)icon_name); GtkWidget *image = NULL; if (GDK_IS_PIXBUF(icon_pixbuf)) { GdkPixbuf *tmp = gdk_pixbuf_scale_simple(icon_pixbuf, 14, 14, GDK_INTERP_NEAREST); g_object_unref(icon_pixbuf); icon_pixbuf = tmp; image = gtk_image_new_from_pixbuf(icon_pixbuf); } if (GTK_IS_WIDGET(image)) { gtk_button_set_image(GTK_BUTTON(g_win.recorder_button), image); g_object_unref(icon_pixbuf); } } // 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); } #endif void win_set_filename(gchar *filename) { // 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); } void win_update_level_bar(gdouble norm_rms, gdouble norm_peak) { // Set pulse on the levelbar if (!IS_LEVEL_BAR(g_win.level_bar)) return; // Show either RMS or peak-value on the levelbar. // Notice: This value has no GUI-setting. User must change it in the dconf-editor. level_bar_set_fraction(LEVEL_BAR(g_win.level_bar), (g_win.pulse_type == PULSE_RMS ? norm_rms : norm_peak)); } 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) { // Display error message (in GtkLabel) // Cut long messages 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'; } } // 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)) return; 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); } } 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+ version is >= 3.22 #if GTK_CHECK_VERSION(3, 22, 0) // Since: 3.22 gtk_menu_popup_at_pointer(GTK_MENU(popup_menu), NULL); #else gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, -1, gtk_get_current_event_time()); #endif } 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 win_refresh_profile_list() { // Save media-profile (id) gchar *id = profiles_get_selected_id(g_win.media_format); profiles_get_data(g_win.media_format); combo_select_string(GTK_COMBO_BOX(g_win.media_format), id, 0); g_free(id); } static 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. 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 (g_strcmp0(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); } GdkWindowState win_get_window_state() { // Return current window state return g_win.state; } void win_keep_on_top(gboolean on_top) { // Keep or unkeep on top of the desktop. // This needs quit and restart to work properly!. gtk_window_set_keep_above(GTK_WINDOW(g_win.window), on_top); if (on_top && gtk_widget_get_realized(g_win.window)) { win_show_window(TRUE); } } void win_show_window(gboolean show) { // Close about-dialog if open about_destroy_dialog(); // Show or hide the main window if (!GTK_IS_WINDOW(g_win.window)) return; if (show) { // Show window. Also bring up if window is minimized/iconified. gboolean on_top = FALSE; conf_get_boolean_value("keep-on-top", &on_top); // just a small trick! //gtk_window_set_keep_above(GTK_WINDOW(g_win.window), TRUE); gtk_widget_show(g_win.window); gtk_window_deiconify(GTK_WINDOW(g_win.window)); gtk_window_present(GTK_WINDOW(g_win.window)); //gtk_window_set_keep_above(GTK_WINDOW(g_win.window), on_top); } 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); } void test_0_cb(GtkWidget *widget, gpointer data) { } 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 audio sources (device ids and names) from Gstreamer and fill the combobox. // 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); // Stop recording if dev_id removed from the system DeviceItem *rec = audio_sources_find_id(dev_id); if (dev_id && rec == NULL) { gint state = -1; gint pending = -1; rec_manager_get_state(&state, &pending); if (state != GST_STATE_NULL) { // Stop recording rec_manager_stop_recording(); } } // 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() { // 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) { // Reset error message (message label) win_set_error_text(NULL); // Save media-profile (id) gchar *id = profiles_get_selected_id(widget/*=g_win.media_format*/); if (id == NULL) return; LOG_DEBUG("Selected audio format (g_win.media_format):%s\n", id); conf_save_string_value("media-format", id); // Enable this if you want check the Gstreamer-plugin for selected media type. // profiles_test_plugin(...) function will also install any missing plugin package. #if 1 // Test if the appropriate GStreamer plugin has been installed. gchar *err_msg = NULL; gboolean ok = profiles_test_plugin(id, &err_msg); if (!ok) { // Missing Gstreamer plugin. // Display error message in the GUI (red label) rec_manager_set_error_text(err_msg); LOG_ERROR(err_msg); g_free(err_msg); } #endif g_free(id); } 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 [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+ version is >= 3.22 #if GTK_CHECK_VERSION(3, 22, 0) // Since: 3.22 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, -1, gtk_get_current_event_time()); #endif 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; } 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 on the level bar. // Set BAR_VALUE or BAR_SHAPE. See levelbar.h. GdkEventButton *ev = (GdkEventButton*)event; if (ev->button == 1) { // LEFT mouse button // Get from DConf gint bar_value = (gint)VALUE_NONE; conf_get_int_value("level-bar-value", &bar_value); // Calc next enum value bar_value += 1; if (bar_value < (gint)VALUE_NONE/*first enum*/ || bar_value > (gint)VALUE_PERCENT/*last enum*/) { bar_value = (gint)VALUE_NONE; } // Update GUI level_bar_set_value_type(LEVEL_BAR(g_win.level_bar), bar_value); // Save in DConf conf_save_int_value("level-bar-value", bar_value); } else if (ev->button == 3) { // RIGHT mouse button // Get from DConf gint bar_shape = (gint)SHAPE_CIRCLE; conf_get_int_value("level-bar-shape", &bar_shape); // Calc next enum value bar_shape += 1; if (bar_shape < (gint)SHAPE_LEVELBAR/*first enum*/ || bar_shape > (gint)SHAPE_CIRCLE/*last enum*/) { bar_shape = (gint)SHAPE_LEVELBAR; } // Update GUI level_bar_set_shape(LEVEL_BAR(g_win.level_bar), bar_shape); // Save in DConf conf_save_int_value("level-bar-shape", bar_shape); } } void reset_all_settings() { // Reset all settings // Run: // $ gsettings reset-recursively org.gnome.audio-recorder gchar *args = g_strdup_printf("reset-recursively %s", APPLICATION_SETTINGS_SCHEMA); run_simple_command("gsettings", args); g_free(args); // Just in case the gsettings command failed conf_save_boolean_value("started-first-time", TRUE); conf_save_string_value("track/last-file-name", ""); conf_save_boolean_value("append-to-file", FALSE); conf_save_boolean_value("timer-expanded", FALSE); conf_save_boolean_value("timer-active", FALSE); conf_save_string_list("players/saved-player-list", NULL); // Clear profiles. Save empty array [] GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a(ssss)")); GVariant *variant = g_variant_new("a(ssss)", builder); conf_save_variant("saved-profiles", variant); g_variant_builder_unref(builder); // Flush Gsettings (write changes to disk) conf_flush_settings(); } 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)); 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); gboolean bool_value = FALSE; // Keep/unkeep window always on top? conf_get_boolean_value("keep-on-top", &bool_value); win_keep_on_top(bool_value); 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 removed 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_file_name = NULL; // Last saved filename conf_get_string_value("track/last-file-name", &last_file_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); gtk_button_set_always_show_image(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_file_name, G_FILE_TEST_EXISTS)) { // Ref: https://developer.gnome.org/glib/2.34/glib-File-Utilities.html#g-stat GStatBuf fstat; if (!g_stat(last_file_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); } } // Levelbar/pulsebar: Indicator for sound amplitude. // Put it in a GtkEventBox so we can catch click events. 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 a LevelBar widget and put it in the GtkEventBox g_win.level_bar = level_bar_new(); gtk_widget_show(g_win.level_bar); gtk_container_add(GTK_CONTAINER(event_box), g_win.level_bar); level_bar_set_fraction(LEVEL_BAR(g_win.level_bar), 0.0); // How to draw the level bar? // Get from DConf gint bar_shape = SHAPE_CIRCLE; conf_get_int_value("level-bar-shape", &bar_shape); level_bar_set_shape(LEVEL_BAR(g_win.level_bar), bar_shape); // Notice: User can change this by RIGHT-clicking on the level-bar // Type of value on the level bar? // Get from DConf gint bar_value = VALUE_NONE; conf_get_int_value("level-bar-value", &bar_value); level_bar_set_value_type(LEVEL_BAR(g_win.level_bar), bar_value); // Notice: User can change this by LEFT-clicking on the level-bar // Should we show RMS or peak-value on the levelbar? // Notice: This value has no GUI-setting in the audio-recorder. You can change it in dconf-editor. g_win.pulse_type = PULSE_RMS; // EDIT 03.jan.2017: Value disabled by Moma. Always show RMS value. // conf_get_int_value("level-bar-pulse-type", (gint*)&g_win.pulse_type); // Start a.r with -d or --debug-signal to study these values. 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_set_halign(label0, GTK_ALIGN_START); gtk_widget_show(label0); gtk_box_pack_start(GTK_BOX(hbox1), label0, FALSE, FALSE, 0); 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_file_name, &path, &fname); gtk_entry_set_text(GTK_ENTRY(g_win.filename), (fname ? fname : "")); g_free(path); g_free(fname); // Free last_file_name g_free(last_file_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; // "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_icon_name("document-save", GTK_ICON_SIZE_BUTTON); // 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); gtk_button_set_always_show_image(GTK_BUTTON(g_win.timer_save_button), TRUE); 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 { // Show g_def_timer_text gtk_text_buffer_set_text(buffer, g_def_timer_text, -1); conf_save_string_value("timer-text", (gchar *)g_def_timer_text); } g_free(str_value); // The [Info] button GtkWidget *button0 = gtk_button_new(); gtk_widget_show(button0); GtkWidget *image = gtk_image_new_from_icon_name("dialog-information", GTK_ICON_SIZE_BUTTON); gtk_widget_show(image); gtk_button_set_always_show_image(GTK_BUTTON(button0), TRUE); 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, FALSE, 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"); // Grid layout GtkWidget *grid0 = gtk_grid_new(); gtk_grid_set_row_homogeneous(GTK_GRID(grid0), FALSE); gtk_grid_set_column_homogeneous(GTK_GRID(grid0), FALSE); gtk_widget_show(grid0); // Add grid0 to the setting_expander gtk_container_add(GTK_CONTAINER(setting_expander), grid0); gtk_grid_set_row_spacing(GTK_GRID(grid0), 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_set_halign(label0, GTK_ALIGN_START); gtk_widget_show(label0); gtk_grid_attach(GTK_GRID(grid0), label0, 0, 0, 1, 1); // 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_grid_attach(GTK_GRID(grid0), g_win.audio_device, 1, 0, 1, 1); // 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)); // Refresh/reload the list of audio devices and Media Players, Skype // Reset error message win_set_error_text(NULL); // Load audio sources (device ids and names) from GStreamer and fill the combobox. // 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); // Set/show the current value. conf_get_string_value("audio-device-id", &str_value); DeviceItem *rec = audio_sources_find_id(str_value); if (rec) { // Set current value audio_sources_combo_set_id(g_win.audio_device, rec->id); } else { // Select first (Audio Output) device as default. GList *lst = audio_sources_get_for_type(AUDIO_SINK_MONITOR); DeviceItem *item = g_list_nth_data(lst, 0); if (item) { // Save values conf_save_string_value("audio-device-name", check_null(item->description)); conf_save_string_value("audio-device-id", item->id); conf_save_int_value("audio-device-type", 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_icon_name("view-refresh", GTK_ICON_SIZE_BUTTON); gtk_widget_show(image); gtk_button_set_always_show_image(GTK_BUTTON(button0), TRUE); gtk_button_set_image(GTK_BUTTON(button0), image); gtk_grid_attach(GTK_GRID(grid0), button0, 2, 0, 1, 1); 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_set_halign(label0, GTK_ALIGN_START); gtk_widget_show(label0); gtk_grid_attach(GTK_GRID(grid0), label0, 0, 2, 1, 1); // 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 = profiles_create_combobox(); gtk_widget_show(g_win.media_format); gtk_grid_attach(GTK_GRID(grid0), g_win.media_format, 1, 2, 1, 1); // 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_set_halign(label0, GTK_ALIGN_START); gtk_widget_show(label0); gtk_box_pack_start(GTK_BOX(g_win.error_box), label0, TRUE, FALSE, 0); // Ref: https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-override-color // gtk_widget_override_color has been deprecated since version 3.16 and should not be used in newly-written code. // Use a custom style provider and style classes instead. // Set red text color //GdkRGBA color; //gdk_rgba_parse(&color, "red"); //gtk_widget_override_color(label0, GTK_STATE_FLAG_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); gtk_label_set_max_width_chars(GTK_LABEL(label0), 60); // 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_icon_name("window-close", GTK_ICON_SIZE_BUTTON); gtk_button_set_always_show_image(GTK_BUTTON(button0), TRUE); 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[]) { // Parse command line arguments GError *error = NULL; GOptionContext *context = g_option_context_new("Command line arguments."); 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 --reset (-r)=%d\n", g_reset_settings); LOG_DEBUG("Value of --debug-signal (-d)=%d\n", g_debug_threshold); 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); } // Reset all settings and restart audio-recorder? if (g_reset_settings != -1) { // This will !reset! all settings after kill & restart conf_save_boolean_value("started-first-time", TRUE); // Flush the settings registry conf_flush_settings(); // Kill all previous instances send_client_request(argv, "simple-kill"); // Wait 0.2s g_usleep(G_USEC_PER_SEC * 0.2); // This will do a fork() + execvp() [*] g_command_arg = NULL; contact_existing_instance(argv); 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 libraries gtk_init(&argc, &argv); gdk_init(&argc, &argv); gst_init(&argc, &argv); gst_pb_utils_init(); // Started 1.st time or after --reset (-c) argument ? gboolean first_time = FALSE; conf_get_boolean_value("started-first-time", &first_time); if (first_time) { // Reset all settings reset_all_settings(); conf_save_boolean_value("started-first-time", FALSE); } // Did we get any --command (or -c) arguments? // $ audio-recorder --command // Print recording status, then exit? // The status is one of: "not running", "on", "off", "paused" if (!g_strcmp0(g_command_arg, "status")) { send_client_request(argv, g_command_arg); // This will call exit() } // Simply quit the recorder? else if (!g_strcmp0(g_command_arg, "quit")) { send_client_request(argv, g_command_arg); // This will call exit() } // Started without --command (-c) argument? if (g_command_arg == NULL) { // Try to contact already existing/running instance of audio-recorder gboolean is_running = ar_is_running(); if (is_running) { // We found it. // Notice: g_command_arg will now point to a constant string. Do not try to g_free() this. g_command_arg = "show"; // Now "show" the existing instance send_client_request(argv, g_command_arg); // And exit this duplicate instance exit(0); } } if (g_command_arg != NULL) { // Try contacting existings instance of the program. // Fork() + ecexvp() a new instance if necessary. contact_existing_instance(argv); } // Initialize local 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(); //gtk_settings_set_string_property(settings, "gtk-button-images", "TRUE", NULL); //g_object_set(settings, "gtk-button-images", TRUE, NULL); // Create main window and all its widgets win_create_window(); gboolean show_window = TRUE; // g_show_window == 0: if --show-window=0 or -w 0 option seen if (g_show_window == 0) { show_window = FALSE; } // Determine how to display the window and tray icon gboolean show_icon = FALSE; conf_get_boolean_value("show-systray-icon", &show_icon); // g_show_tray_icon != -1: if --show-icon or -i option was seen if (g_show_tray_icon !=-1) { show_icon = g_show_tray_icon; } if (!show_icon) { // Cannot hide both window and the tray icon show_window = TRUE; } // Show/hide tray icon systray_icon_setup(show_icon); // First time? if (first_time) { // Force show window show_window = TRUE; // Auto-start this application on login autostart_set(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: // Get and print level-values from the gst-vad.c module. // User must start this application from a terminal window to see these values. if (g_debug_threshold == 1) { // Set flag via timer.c timer_set_debug_flag(TRUE); } // g_win.audio_device must be set here at the very end. // Otherwise Media Players and Skype may show up before this app's main window. Skype may even block! win_set_device_id(); // Main loop gtk_main (); return 0; } static gpointer send_client_request(gchar *argv[], 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 string; "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); // Wait a while g_usleep(G_USEC_PER_SEC * 0.2); // Terminate possibly frozen instances of audio-recorder kill_frozen_instances(argv[0], -1/*all PIDs*/); // Exit also this instance exit(0); } if (g_strrstr(command, "simple-kill")) { // Call set_state("quit"). Terminate running instance of this application. ret = dbus_service_client_request("set_state", "quit"); g_free(ret); // Wait g_usleep(G_USEC_PER_SEC * 0.2); // Terminate possibly frozen instances of audio-recorder kill_frozen_instances(argv[0], getpid()); done = TRUE; } 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; } static void contact_existing_instance(gchar *argv[]) { // $ audio-recorder --command // Find existing instance of audio-recorder. Do not start this program twice. gboolean is_running = ar_is_running(); if (is_running) { // Found existing instance. // Send client request (execute methode call over DBus). send_client_request(argv, g_command_arg); // And exit this duplicate instance exit(0); } else { // Terminate all POSSIBLY FROZEN audio-recorder instances that do not respond to our dbus-requests. kill_frozen_instances(argv[0], getpid()/*will not kill myself*/); // Fork and exec a new instance of audio-recorder pid_t pid = 0; if((pid = fork()) < 0) { LOG_ERROR("Cannot execute fork(). Terminating program."); exit(1); } else if(pid == 0) { // Child process LOG_DEBUG("Fork() succeeded. Going to replace this child by execvp() call.\n"); // Replace this child with a new audio-recorder process. // One important thing: // We could let this child simply run, but the child and parent processes (after fork()) // can't share the same file descriptors (eg. fd sockets). // Parent will most likely block the child, and the latter couldn't be able to communicate // with the X-window system. // I've seen another solution where they call _exit() instead of exit(). // Ref: https://linux.die.net/man/3/execvp execvp(argv[0], argv); // Should not come here LOG_ERROR("Child process failed to spawn. Should not come here. Terminating child process.\n"); exit(0); } else { // Parent process // Sleep a while, let the child execvp() g_usleep(G_USEC_PER_SEC * 0.3); // Send request to the child (execute methode call over DBus) send_client_request(argv, g_command_arg); // Exit parent process. exit(0); } } } static gboolean ar_is_running() { // Try to contact existing instance of audio-recorder gchar *state = dbus_service_client_request("get_state", NULL); // Did we get an answer from existing instance? // The answers is "on", "off" or "paused" if a.r is already running. // Otherwise the reply is "not running" or NULL. gboolean ret = (state && (!g_strrstr(state, "not"))); g_free(state); return ret; }