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);