From 0023a2a60ac6a3d98348d525c6d7c5a0adb4f27a Mon Sep 17 00:00:00 2001 From: Rozhuk Ivan Date: Mon, 1 Feb 2021 09:55:15 +0300 Subject: [PATCH] Implement sound cards list auto update on devices connect/disconnect. Change API for window and devs_list_combo. --- README.md | 4 ++- gtk-mixer.project | 4 +-- gtk-mixer.workspace | 4 +-- src/gtk-mixer-devs_combo.c | 74 ++++++++++++++++++++++++-------------- src/gtk-mixer-window.c | 51 +++++++++++++------------- src/gtk-mixer.c | 70 +++++++++++++++++++++++++++--------- src/gtk-mixer.h | 22 ++++++------ src/plugin_api.c | 39 ++++++++++++++++++++ src/plugin_api.h | 1 + 9 files changed, 187 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index f3f525b..381b74d 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,12 @@ GPL2 licence.\ ## Features -* plugins to support different sound backens +* plugins for support different sound backens * change system default sound card * set volume per line/channel * enable/disable lines (mute/unmute) +* detect sound cards connect/disconnect +* detect default sound card change ## Compilation diff --git a/gtk-mixer.project b/gtk-mixer.project index da342f0..15a61c6 100644 --- a/gtk-mixer.project +++ b/gtk-mixer.project @@ -40,8 +40,8 @@ - - + + diff --git a/gtk-mixer.workspace b/gtk-mixer.workspace index b6fb789..08adfa8 100644 --- a/gtk-mixer.workspace +++ b/gtk-mixer.workspace @@ -2,11 +2,11 @@ - + - + diff --git a/src/gtk-mixer-devs_combo.c b/src/gtk-mixer-devs_combo.c index 29e5328..b3f3517 100644 --- a/src/gtk-mixer-devs_combo.c +++ b/src/gtk-mixer-devs_combo.c @@ -61,7 +61,7 @@ gtk_mixer_devs_combo_destroy(GtkWidget *combo __unused, gpointer user_data) { } GtkWidget * -gtk_mixer_devs_combo_create(gmp_dev_list_p dev_list, gmp_dev_p dev) { +gtk_mixer_devs_combo_create(void) { GtkWidget *combo; GtkListStore *list_store; GtkCellRenderer *renderer; @@ -87,18 +87,11 @@ gtk_mixer_devs_combo_create(gmp_dev_list_p dev_list, gmp_dev_p dev) { gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text", GM_CMB_DEVS_COLUMN_NAME); - gtk_mixer_devs_combo_update(combo, dev_list); - - if (NULL == dev) { - dev = gmp_dev_list_get_default(dev_list); - } - gtk_mixer_devs_combo_set_active_device(combo, dev); - return (combo); } gmp_dev_p -gtk_mixer_devs_combo_get_dev(GtkWidget *combo) { +gtk_mixer_devs_combo_cur_get(GtkWidget *combo) { if (NULL == combo) return (NULL); @@ -107,7 +100,7 @@ gtk_mixer_devs_combo_get_dev(GtkWidget *combo) { } void -gtk_mixer_devs_combo_set_active_device(GtkWidget *combo, +gtk_mixer_devs_combo_cur_set(GtkWidget *combo, gmp_dev_p dev) { gmp_dev_p device_cur = NULL; GtkTreeIter iter; @@ -134,11 +127,27 @@ gtk_mixer_devs_combo_set_active_device(GtkWidget *combo, } } +static inline void +gtk_mixer_devs_combo_dev_descr(gmp_dev_p dev, char *buf, size_t buf_size) { + + if (NULL == buf || 0 == buf_size) + return; + if (NULL == dev) { + buf[0] = 0x00; + return; + } + snprintf(buf, buf_size, + "%s: %s (%s)%s", + dev->plugin->descr->name, + dev->description, + dev->name, + ((gmp_dev_is_default(dev)) ? " [default]" : "")); +} + void -gtk_mixer_devs_combo_update(GtkWidget *combo, gmp_dev_list_p dev_list) { - gmp_dev_p device_cur = NULL; +gtk_mixer_devs_combo_dev_list_set(GtkWidget *combo, gmp_dev_list_p dev_list) { + gmp_dev_p dev = NULL; GtkTreeIter iter; - gboolean valid_iter; GtkListStore *list_store = g_object_get_data(G_OBJECT(combo), "__gtk_mixer_devs_combo_list_store"); char display_name[256]; @@ -146,25 +155,38 @@ gtk_mixer_devs_combo_update(GtkWidget *combo, gmp_dev_list_p dev_list) { if (NULL == list_store) return; - if (NULL != dev_list) { - for (size_t i = 0; i < dev_list->count; i ++) { - gtk_list_store_append(list_store, &iter); - gtk_list_store_set(list_store, &iter, - GM_CMB_DEVS_COLUMN_CARD, &dev_list->devs[i], -1); - } + gtk_list_store_clear(list_store); + if (NULL == dev_list) + return; + for (size_t i = 0; i < dev_list->count; i ++) { + gtk_list_store_append(list_store, &iter); + dev = &dev_list->devs[i]; + gtk_mixer_devs_combo_dev_descr(dev, + display_name, sizeof(display_name)); + gtk_list_store_set(list_store, &iter, + GM_CMB_DEVS_COLUMN_CARD, dev, + GM_CMB_DEVS_COLUMN_NAME, display_name, -1); } +} +void +gtk_mixer_devs_combo_update(GtkWidget *combo) { + gmp_dev_p dev = NULL; + GtkTreeIter iter; + gboolean valid_iter; + GtkListStore *list_store = g_object_get_data(G_OBJECT(combo), + "__gtk_mixer_devs_combo_list_store"); + char display_name[256]; + + if (NULL == list_store) + return; valid_iter = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(list_store), &iter); while (valid_iter) { gtk_tree_model_get(GTK_TREE_MODEL(list_store), - &iter, GM_CMB_DEVS_COLUMN_CARD, &device_cur, -1); - snprintf(display_name, sizeof(display_name), - "%s: %s (%s)%s", - device_cur->plugin->descr->name, - device_cur->description, - device_cur->name, - ((gmp_dev_is_default(device_cur)) ? " [default]" : "")); + &iter, GM_CMB_DEVS_COLUMN_CARD, &dev, -1); + gtk_mixer_devs_combo_dev_descr(dev, + display_name, sizeof(display_name)); gtk_list_store_set(list_store, &iter, GM_CMB_DEVS_COLUMN_NAME, display_name, -1); valid_iter = gtk_tree_model_iter_next( diff --git a/src/gtk-mixer-window.c b/src/gtk-mixer-window.c index fafc93a..11fa958 100644 --- a/src/gtk-mixer-window.c +++ b/src/gtk-mixer-window.c @@ -50,7 +50,7 @@ gtk_mixer_window_soundcard_changed(GtkWidget *combo __unused, gmp_dev_p dev; /* Update mixer controls for the active sound card */ - dev = gtk_mixer_devs_combo_get_dev(gm_win->soundcard_combo); + dev = gtk_mixer_devs_combo_cur_get(gm_win->soundcard_combo); if (NULL != dev) { snprintf(title, sizeof(title), "%s - %s", _("Audio Mixer"), @@ -72,11 +72,11 @@ gtk_mixer_makedef_button(GtkButton *button __unused, gpointer user_data) { gm_window_p gm_win = user_data; gmp_dev_p dev; - dev = gtk_mixer_devs_combo_get_dev(gm_win->soundcard_combo); + dev = gtk_mixer_devs_combo_cur_get(gm_win->soundcard_combo); if (NULL == dev) return; gmp_dev_set_default(dev); - gtk_mixer_devs_combo_update(gm_win->soundcard_combo, NULL); + gtk_mixer_devs_combo_update(gm_win->soundcard_combo); } static void @@ -91,10 +91,9 @@ gtk_mixer_window_destroy(GtkWidget *window __unused, gpointer user_data) { } GtkWidget * -gtk_mixer_window_create(gmp_dev_list_p dev_list) { +gtk_mixer_window_create(void) { gm_window_p gm_win; GtkWidget *label, *vbox, *hbox, *mixer_frame; - gmp_dev_p dev = NULL; gm_win = calloc(1, sizeof(gm_window_t)); if (NULL == gm_win) @@ -110,16 +109,6 @@ gtk_mixer_window_create(gmp_dev_list_p dev_list) { #if 0 g_object_get(gm_win->preferences, "window-width", &gm_win->width, "window-height", &gm_win->height, - "sound-card", &card_name, NULL); - - if (card_name != NULL) { - dev = gtk_mixer_get_card(card_name); - } else { - dev = gtk_mixer_get_default_card(); - g_object_set(gm_win->preferences, "sound-card", - gtk_mixer_get_card_internal_name(dev), NULL); - } - g_free(card_name); #endif /* Configure the main window. */ @@ -146,8 +135,7 @@ gtk_mixer_window_create(gmp_dev_list_p dev_list) { gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gtk_widget_show(label); - gm_win->soundcard_combo = gtk_mixer_devs_combo_create(dev_list, - dev); + gm_win->soundcard_combo = gtk_mixer_devs_combo_create(); g_signal_connect(gm_win->soundcard_combo, "changed", G_CALLBACK(gtk_mixer_window_soundcard_changed), gm_win); gtk_box_pack_start( @@ -199,17 +187,27 @@ gtk_mixer_window_connect_dev_changed(GtkWidget *window, } gmp_dev_p -gtk_mixer_window_get_dev(GtkWidget *window) { +gtk_mixer_window_dev_cur_get(GtkWidget *window) { gm_window_p gm_win = g_object_get_data(G_OBJECT(window), "__gtk_mixer_window"); if (NULL == gm_win) return (0); - return (gtk_mixer_devs_combo_get_dev(gm_win->soundcard_combo)); + return (gtk_mixer_devs_combo_cur_get(gm_win->soundcard_combo)); +} + +void +gtk_mixer_window_dev_cur_set(GtkWidget *window, gmp_dev_p dev) { + gm_window_p gm_win = g_object_get_data(G_OBJECT(window), + "__gtk_mixer_window"); + + if (NULL == gm_win) + return; + gtk_mixer_devs_combo_cur_set(gm_win->soundcard_combo, dev); } void -gtk_mixer_window_update_dev_list(GtkWidget *window) { +gtk_mixer_window_dev_list_update(GtkWidget *window, gmp_dev_list_p dev_list) { gmp_dev_p dev; gm_window_p gm_win = g_object_get_data(G_OBJECT(window), "__gtk_mixer_window"); @@ -218,16 +216,21 @@ gtk_mixer_window_update_dev_list(GtkWidget *window) { return; /* Update dev list only if it can be changed. */ - dev = gtk_mixer_devs_combo_get_dev(gm_win->soundcard_combo); - if (dev->plugin->descr->can_set_default_device) { - gtk_mixer_devs_combo_update(gm_win->soundcard_combo, NULL); + dev = gtk_mixer_devs_combo_cur_get(gm_win->soundcard_combo); + if (NULL != dev_list) { + gtk_mixer_devs_combo_dev_list_set(gm_win->soundcard_combo, + dev_list); + } + if (NULL != dev && + dev->plugin->descr->can_set_default_device) { + gtk_mixer_devs_combo_update(gm_win->soundcard_combo); gtk_widget_set_sensitive(gm_win->makedef_button, (0 == gmp_dev_is_default(dev))); } } void -gtk_mixer_window_update_lines(GtkWidget *window) { +gtk_mixer_window_lines_update(GtkWidget *window) { gm_window_p gm_win = g_object_get_data(G_OBJECT(window), "__gtk_mixer_window"); diff --git a/src/gtk-mixer.c b/src/gtk-mixer.c index e970152..2f59d7e 100644 --- a/src/gtk-mixer.c +++ b/src/gtk-mixer.c @@ -52,6 +52,8 @@ typedef struct gtk_mixer_app_s { size_t update_force_counter; } gm_app_t, *gm_app_p; +/* Check updates every 1s if no changes and every 100ms if something was + * changes in last 5 second. */ #define UPDATE_INTERVAL 100 /* If no chenges - check every (UPDATE_INTERVAL * UPDATE_SKIP_MAX_COUNT) ms. */ #define UPDATE_SKIP_MAX_COUNT 10 @@ -63,31 +65,50 @@ static gboolean gtk_mixer_check_update(gm_app_p app) { int error; size_t changes = 0; + gmp_dev_list_t dev_list; + gmp_dev_p dev = NULL; - if (NULL == app->dev) - return (TRUE); /* No device. */ /* GUI update rate scaler. */ app->update_skip_counter ++; if (UPDATE_SKIP_MAX_COUNT > app->update_skip_counter) return (TRUE); app->update_skip_counter = 0; - /* Default device change handle. */ - if (0 != gmp_is_def_dev_changed(app->plugins, - app->plugins_count)) { + /* Devices list update check. */ + if (gmp_is_list_devs_changed(app->plugins, app->plugins_count)) { changes ++; - gtk_mixer_window_update_dev_list(app->window); + memset(&dev_list, 0x00, sizeof(dev_list)); + error = gmp_list_devs(app->plugins, app->plugins_count, + &dev_list); + if (0 == error) { + /* Try to find old current dev in updated dev list. */ + dev = gmp_dev_find_same(&dev_list, app->dev); + gtk_mixer_window_dev_list_update(app->window, + &dev_list); + gmp_dev_list_clear(&app->dev_list); + app->dev_list = dev_list; + /* Select new current device. */ + if (NULL == dev) { + dev = gmp_dev_list_get_default(&app->dev_list); + } + gtk_mixer_window_dev_cur_set(app->window, dev); + } + } else if (0 != gmp_is_def_dev_changed(app->plugins, + app->plugins_count)) { /* Default device changed. */ + changes ++; + gtk_mixer_window_dev_list_update(app->window, NULL); } /* Check lines update for current device. */ - error = gmp_dev_read(app->dev, 1); - if (0 != error) - return (TRUE); - if (gmp_dev_is_updated(app->dev)) { - /* GUI update. */ - gtk_mixer_window_update_lines(app->window); - gtk_mixer_tray_icon_update(app->status_icon); - changes += gmp_dev_is_updated_clear(app->dev); + if (NULL != app->dev) { + error = gmp_dev_read(app->dev, 1); + if (0 == error && + gmp_dev_is_updated(app->dev)) { + /* GUI update. */ + gtk_mixer_window_lines_update(app->window); + gtk_mixer_tray_icon_update(app->status_icon); + changes += gmp_dev_is_updated_clear(app->dev); + } } /* GUI update rate scaler. */ @@ -111,7 +132,7 @@ gtk_mixer_soundcard_changed(GtkWidget *combo __unused, if (NULL == app) return; - app->dev = gtk_mixer_window_get_dev(app->window); + app->dev = gtk_mixer_window_dev_cur_get(app->window); /* Tray icon.*/ gtk_mixer_tray_icon_dev_set(app->status_icon, app->dev); @@ -204,6 +225,7 @@ int main(int argc, char **argv) { int error; gm_app_t app; + gmp_dev_p dev = NULL; memset(&app, 0x00, sizeof(gm_app_t)); @@ -224,7 +246,23 @@ main(int argc, char **argv) { gtk_window_set_default_icon_name(APP_ICON_NAME); /* Main window. */ - app.window = gtk_mixer_window_create(&app.dev_list); + app.window = gtk_mixer_window_create(); + gtk_mixer_window_dev_list_update(app.window, &app.dev_list); +#if 0 + if (card_name != NULL) { + dev = gtk_mixer_get_card(card_name); + } else { + dev = gtk_mixer_get_default_card(); + g_object_set(gm_win->preferences, "sound-card", + gtk_mixer_get_card_internal_name(dev), NULL); + } + g_free(card_name); +#endif + if (NULL == dev) { + dev = gmp_dev_list_get_default(&app.dev_list); + } + gtk_mixer_window_dev_cur_set(app.window, dev); + /* Tray icon. */ app.status_icon = gtk_mixer_tray_icon_create(); diff --git a/src/gtk-mixer.h b/src/gtk-mixer.h index 8c851eb..2f71d83 100644 --- a/src/gtk-mixer.h +++ b/src/gtk-mixer.h @@ -63,19 +63,19 @@ const char *volume_stock_from_level(const int is_mic, const int is_enabled, const int level, const char *cur_icon_name); -GtkWidget *gtk_mixer_window_create(gmp_dev_list_p dev_list); +GtkWidget *gtk_mixer_window_create(void); gulong gtk_mixer_window_connect_dev_changed(GtkWidget *window, GCallback c_handler, gpointer data); -gmp_dev_p gtk_mixer_window_get_dev(GtkWidget *window); -void gtk_mixer_window_update_dev_list(GtkWidget *window); -void gtk_mixer_window_update_lines(GtkWidget *window); - -GtkWidget *gtk_mixer_devs_combo_create(gmp_dev_list_p dev_list, - gmp_dev_p dev); -gmp_dev_p gtk_mixer_devs_combo_get_dev(GtkWidget *combo); -void gtk_mixer_devs_combo_set_active_device(GtkWidget *combo, - gmp_dev_p dev); -void gtk_mixer_devs_combo_update(GtkWidget *combo, gmp_dev_list_p dev_list); +gmp_dev_p gtk_mixer_window_dev_cur_get(GtkWidget *window); +void gtk_mixer_window_dev_cur_set(GtkWidget *window, gmp_dev_p dev); +void gtk_mixer_window_dev_list_update(GtkWidget *window, gmp_dev_list_p dev_list); +void gtk_mixer_window_lines_update(GtkWidget *window); + +GtkWidget *gtk_mixer_devs_combo_create(void); +gmp_dev_p gtk_mixer_devs_combo_cur_get(GtkWidget *combo); +void gtk_mixer_devs_combo_cur_set(GtkWidget *combo, gmp_dev_p dev); +void gtk_mixer_devs_combo_dev_list_set(GtkWidget *combo, gmp_dev_list_p dev_list); +void gtk_mixer_devs_combo_update(GtkWidget *combo); GtkWidget *gtk_mixer_container_create(void); void gtk_mixer_container_dev_set(GtkWidget *container, gmp_dev_p dev); diff --git a/src/plugin_api.c b/src/plugin_api.c index 179225b..0524dc7 100644 --- a/src/plugin_api.c +++ b/src/plugin_api.c @@ -41,6 +41,18 @@ #include "plugin_api.h" +static inline int +strcmp_safe(const char *s1, const char *s2) { + + if (s1 == s2) + return (0); + if (NULL == s1) + return (-127); + if (NULL == s2) + return (127); + return (strcmp(s1, s2)); +} + static inline int volume_apply_limits(const int vol) { @@ -82,6 +94,13 @@ gmp_init(gm_plugin_p *plugins, size_t *plugins_count) { if (NULL != plugin->descr->init) { if (0 != plugin->descr->init(plugin)) continue; + /* Init change detect. */ + if (NULL != plugin->descr->is_def_dev_changed) { + plugin->descr->is_def_dev_changed(plugin); + } + if (NULL != plugin->descr->is_list_devs_changed) { + plugin->descr->is_list_devs_changed(plugin); + } } j ++; } @@ -173,6 +192,26 @@ gmp_dev_list_clear(gmp_dev_list_p dev_list) { free(dev_list->devs); } +gmp_dev_p +gmp_dev_find_same(gmp_dev_list_p dev_list, gmp_dev_p dev) { + gmp_dev_p cur_dev; + + if (NULL == dev_list) + return (NULL); + + for (size_t i = 0; i < dev_list->count; i ++) { + cur_dev = &dev_list->devs[i]; + if (dev->plugin != cur_dev->plugin) + continue; + if (0 != strcmp_safe(dev->name, cur_dev->name) || + 0 != strcmp_safe(dev->description, cur_dev->description)) + continue; + return (cur_dev); + } + + return (NULL); +} + int gmp_dev_list_add(gm_plugin_p plugin, gmp_dev_list_p dev_list, gmp_dev_p dev) { diff --git a/src/plugin_api.h b/src/plugin_api.h index 4a9be53..4739746 100644 --- a/src/plugin_api.h +++ b/src/plugin_api.h @@ -258,6 +258,7 @@ int gmp_list_devs(gm_plugin_p plugins, const size_t plugins_count, gmp_dev_list_p dev_list); /* Does not free dev_list, work only with stored data. */ void gmp_dev_list_clear(gmp_dev_list_p dev_list); +gmp_dev_p gmp_dev_find_same(gmp_dev_list_p dev_list, gmp_dev_p dev); int gmp_dev_list_add(gm_plugin_p plugin, gmp_dev_list_p dev_list, gmp_dev_p dev); gmp_dev_p gmp_dev_list_get_default(gmp_dev_list_p dev_list);