summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWalker Thompson <walker.thompson@urz.uni-heidelberg.de>2026-07-03 00:18:20 +0200
committerWalker Thompson <walker.thompson@urz.uni-heidelberg.de>2026-07-03 00:18:20 +0200
commit37dd4d6f995587cb597e366a377391b264716f01 (patch)
treea2f55c0118993a8fe78c0daaf8ec712099b19a5d
parentdaac2f3315a6eca712071ab21e2e4b9ab2189537 (diff)
Add GTK version
-rwxr-xr-xcompile.sh12
-rw-r--r--voss_devctl.c2
-rw-r--r--voss_devctl_gtk.c310
3 files changed, 319 insertions, 5 deletions
diff --git a/compile.sh b/compile.sh
index c8025fb..2ef54e3 100755
--- a/compile.sh
+++ b/compile.sh
@@ -1,6 +1,10 @@
#!/bin/sh
-cd "$(dirname "$0")" || exit 1
-cc voss_devctl.c -g -o voss-devctl \
+cd "$(dirname "$0")" || exit
+#cc voss_devctl.c -g -o voss-devctl \
+# -Wall -Wpedantic -Werror -Wextra \
+# -I/usr/local/include \
+# -L/usr/local/lib -lXm -lXt -lX11
+cc voss_devctl_gtk.c -g -o voss-devctl \
-Wall -Wpedantic -Werror -Wextra \
- -I/usr/local/include \
- -L/usr/local/lib -lXm -lXt -lX11
+ `pkg-config --cflags gtk+-3.0` \
+ `pkg-config --libs gtk+-3.0`
diff --git a/voss_devctl.c b/voss_devctl.c
index db86df6..1739b19 100644
--- a/voss_devctl.c
+++ b/voss_devctl.c
@@ -155,7 +155,7 @@ void oss_enum_devs(int fd, int oss_numaudios)
if (ai.caps & (PCM_CAP_OUTPUT | PCM_CAP_VIRTUAL)) {
oss_devlist_add(&devs_play, &ai);
}
- if (ai.caps & (PCM_CAP_INPUT | PCM_CAP_VIRTUAL)) {
+ if (ai.caps & PCM_CAP_INPUT) {
oss_devlist_add(&devs_rec, &ai);
}
}
diff --git a/voss_devctl_gtk.c b/voss_devctl_gtk.c
new file mode 100644
index 0000000..27a85b2
--- /dev/null
+++ b/voss_devctl_gtk.c
@@ -0,0 +1,310 @@
+#include <gtk/gtk.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/sysctl.h>
+
+#include <stdbool.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "virtual_oss.h"
+
+#define OSS_DEV "/dev/dsp0"
+#define VOSS_DEV "/dev/dsp.ctl"
+
+enum model_columns {
+ COL_TEXT,
+ COL_VALUE,
+ _NUM_COLS
+};
+
+enum dev_types {
+ DEVTYPE_PLAY,
+ DEVTYPE_REC,
+ _NUM_DEVTYPES
+};
+
+char voss_opts[2] = {
+ [DEVTYPE_PLAY] = 'P',
+ [DEVTYPE_REC] = 'R'
+};
+
+struct device_selection_cb_data {
+ GtkWidget *combo;
+ int type;
+};
+
+
+static void
+voss_cmd_from_selections(GtkWidget *combo, int type)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *oss_devnode;
+ int voss_dsp_fd;
+ char options[VIRTUAL_OSS_OPTIONS_MAX];
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) {
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ gtk_tree_model_get(model, &iter, COL_VALUE, &oss_devnode, -1);
+
+ if ((voss_dsp_fd = open(VOSS_DEV, O_WRONLY)) > 0) {
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX,
+ "-%c %s", voss_opts[type], oss_devnode);
+ ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options);
+ close(voss_dsp_fd);
+ }
+
+ g_free(oss_devnode);
+ }
+}
+
+
+static void
+cb_toggle_enabled(GtkToggleButton *button, gpointer user_data)
+{
+ struct device_selection_cb_data *data = user_data;
+ gboolean checked = gtk_toggle_button_get_active(button);
+
+ assert(data->type < _NUM_DEVTYPES);
+
+ /* disable the combo when checked */
+ gtk_widget_set_sensitive(data->combo, !checked);
+
+ char options[VIRTUAL_OSS_OPTIONS_MAX];
+ int voss_dsp_fd;
+
+ /* if disabled, set to /dev/null */
+ if (checked) {
+ if ((voss_dsp_fd = open(VOSS_DEV, O_WRONLY)) > 0) {
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX,
+ "-%c /dev/null", voss_opts[data->type]);
+ ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options);
+ close(voss_dsp_fd);
+ }
+ }
+ else {
+ voss_cmd_from_selections(GTK_WIDGET(data->combo), data->type);
+ }
+}
+
+
+static void
+cb_dev_selected(GtkComboBox *combo, gpointer user_data)
+{
+ int type = GPOINTER_TO_INT(user_data);
+ printf("%d\n", type);
+ assert(type == DEVTYPE_PLAY || type == DEVTYPE_REC);
+ voss_cmd_from_selections(GTK_WIDGET(combo), type);
+}
+
+
+static void
+cb_window_realize(GtkWidget *widget, gpointer user_data)
+{
+ (void)user_data; //unused
+ GdkWindow *window = gtk_widget_get_window(widget);
+ if (GDK_IS_WINDOW(window)) {
+ gdk_window_set_type_hint(window, GDK_WINDOW_TYPE_HINT_UTILITY);
+ }
+}
+
+
+static void
+combo_select_by_value(GtkWidget *combo, const char *val_target)
+{
+ GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ GtkTreeIter iter;
+
+ gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
+ while (valid) {
+ gchar *val = NULL;
+
+ gtk_tree_model_get(model, &iter, COL_VALUE, &val, -1);
+
+ if (g_strcmp0(val, val_target) == 0) {
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
+ g_free(val);
+ break;
+ }
+
+ g_free(val);
+ valid = gtk_tree_model_iter_next(model, &iter);
+ }
+}
+
+
+static void
+setup_device_selection(GtkListStore *store, GtkWidget *combo,
+ GtkWidget *check, int type)
+{
+ assert(type < _NUM_DEVTYPES);
+
+ struct oss_sysinfo si;
+ int oss_dsp_fd;
+
+ /* initialize oss global information */
+ if ((oss_dsp_fd = open(OSS_DEV, O_WRONLY)) == -1) {
+ printf("Failed to open device %s: %s\n",
+ OSS_DEV, strerror(errno));
+ }
+ if (ioctl(oss_dsp_fd, SNDCTL_SYSINFO, &si) < 0) {
+ printf("Cannot get OSS system info: %s\n",
+ strerror(errno));
+ }
+
+ int flags = (type == DEVTYPE_REC) ? PCM_CAP_INPUT : PCM_CAP_OUTPUT;
+
+ for (int i = 0; i < si.numaudios; i++) {
+ struct oss_audioinfo ai = { .dev = i };
+
+ if (ioctl(oss_dsp_fd, SNDCTL_AUDIOINFO, &ai) < 0) {
+ printf("OSS: could not get device %d info: %s\n",
+ i, strerror(errno));
+ continue;
+ }
+
+ if (!ai.enabled) {
+ continue;
+ }
+
+ if (ai.caps & (flags | PCM_CAP_VIRTUAL)) {
+ gtk_list_store_insert_with_values(store, NULL, -1, COL_TEXT, ai.name,
+ COL_VALUE, ai.devnode, -1);
+ }
+ }
+
+ close(oss_dsp_fd);
+
+ struct virtual_oss_system_info vsi;
+ int voss_dsp_fd;
+ char options[VIRTUAL_OSS_OPTIONS_MAX];
+
+ /* the same for Virtual OSS */
+ if ((voss_dsp_fd = open(VOSS_DEV, O_WRONLY)) == -1) {
+ printf("Failed to open device %s: %s\n",
+ VOSS_DEV, strerror(errno));
+ }
+ if (ioctl(voss_dsp_fd, VIRTUAL_OSS_GET_SYSTEM_INFO, &vsi) < 0) {
+ printf("Cannot get Virtual OSS system info: %s\n",
+ strerror(errno));
+ }
+
+ close(voss_dsp_fd);
+
+ char current_dev[OSS_DEVNODE_SIZE];
+ if (type == DEVTYPE_PLAY) {
+ strncpy(current_dev, vsi.tx_device_name, OSS_DEVNODE_SIZE);
+ }
+ else if (type == DEVTYPE_REC) {
+ strncpy(current_dev, vsi.rx_device_name, OSS_DEVNODE_SIZE);
+ }
+
+ bool active = true;
+ if (strncmp("/dev/null", current_dev, OSS_DEVNODE_SIZE)) {
+ combo_select_by_value(combo, current_dev);
+ }
+ else {
+ active = false;
+ }
+
+ /* if no match and not already /dev/null, revert to /dev/null */
+ if (active && gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) {
+ active = false;
+ if ((voss_dsp_fd = open(VOSS_DEV, O_WRONLY)) > 0) {
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX,
+ "-%c /dev/null", voss_opts[type]);
+ ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options);
+ close(voss_dsp_fd);
+ }
+ }
+
+ if (!active) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
+ gtk_widget_set_sensitive(combo, FALSE);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
+ }
+
+ struct device_selection_cb_data *data = g_new(struct device_selection_cb_data, 1);
+ data->combo = combo;
+ data->type = type;
+
+ GtkCellRenderer *render = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), render, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), render,
+ "text", COL_TEXT, NULL);
+
+ g_signal_connect(combo, "changed", G_CALLBACK(cb_dev_selected),
+ GINT_TO_POINTER(type));
+ g_signal_connect(check, "toggled", G_CALLBACK(cb_toggle_enabled), data);
+}
+
+
+static void
+cb_init(GtkApplication* app, gpointer user_data)
+{
+ GtkWidget *window, *vbox_main,
+ *hbox_play, *combo_play, *check_play,
+ *hbox_rec, *combo_rec, *check_rec;
+ GtkListStore *store_play, *store_rec;
+
+ (void)user_data;
+
+ window = gtk_application_window_new(app);
+ gtk_window_set_title(GTK_WINDOW(window), "Virtual OSS Device Selector");
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+
+ g_signal_connect(window, "realize", G_CALLBACK(cb_window_realize), NULL);
+
+ store_play = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+ combo_play = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store_play));
+ check_play = gtk_check_button_new_with_label("Disabled");
+ setup_device_selection(store_play, combo_play, check_play, DEVTYPE_PLAY);
+
+ store_rec = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+ combo_rec = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store_rec));
+ check_rec = gtk_check_button_new_with_label("Disabled");
+ setup_device_selection(store_rec, combo_rec, check_rec, DEVTYPE_REC);
+
+ hbox_play = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
+ gtk_box_pack_start(GTK_BOX(hbox_play), combo_play, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox_play), check_play, FALSE, FALSE, 0);
+
+ hbox_rec = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
+ gtk_box_pack_start(GTK_BOX(hbox_rec), combo_rec, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox_rec), check_rec, FALSE, FALSE, 0);
+
+ vbox_main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
+ gtk_box_pack_start(GTK_BOX(vbox_main), hbox_play, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox_main), hbox_rec, FALSE, FALSE, 0);
+
+ gtk_widget_set_margin_start(vbox_main, 12);
+ gtk_widget_set_margin_end(vbox_main, 12);
+ gtk_widget_set_margin_top(vbox_main, 12);
+ gtk_widget_set_margin_bottom(vbox_main, 12);
+
+ gtk_container_add(GTK_CONTAINER(window), vbox_main);
+
+ gtk_widget_show_all(window);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ GtkApplication *app;
+ int ret;
+
+ app = gtk_application_new("org.freebsd.voss-devctl",
+ G_APPLICATION_DEFAULT_FLAGS);
+
+ g_signal_connect(app, "activate", G_CALLBACK(cb_init), NULL);
+ ret = g_application_run(G_APPLICATION(app), argc, argv);
+ g_object_unref(app);
+
+ return ret;
+}