#include #include #include #include #include #include #include #include #include #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_controls_cb_data { GtkWidget *combo; GtkWidget *slider; int type; }; static void update_volume_slider(GtkWidget *slider, int type) { char devnode_mixer[PATH_MAX]; unsigned int devnum; size_t len = sizeof(int); int mixer_fd, pcm_curr, vol_curr; /* associate mixer to dsp */ sysctlbyname("hw.snd.default_unit", &devnum, &len, NULL, len); snprintf(devnode_mixer, PATH_MAX, "/dev/mixer%u", devnum); gdouble vol_avg = 0.0; if (type == DEVTYPE_PLAY) { if ((mixer_fd = open(devnode_mixer, O_RDONLY, 0)) > 0) { ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_VOLUME), &vol_curr); ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_PCM), &pcm_curr); /* average of 4 channels - vol/pcm left/right */ vol_avg = (double)((((vol_curr & 0xff) + ((vol_curr >> 8) & 0xff)) + ((pcm_curr & 0xff) + ((pcm_curr >> 8) & 0xff))) >> 2); (void)close(mixer_fd); } } else if (type == DEVTYPE_REC) { if ((mixer_fd = open(devnode_mixer, O_RDONLY, 0)) > 0) { ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_MIC), &vol_curr); /* average of 2 channels - left/right */ vol_avg = (double)(((vol_curr & 0xff) + ((vol_curr >> 8) & 0xff)) >> 1); (void)close(mixer_fd); } } if (vol_avg) { gtk_range_set_value(GTK_RANGE(slider), vol_avg); } } static void apply_device_selections(GtkWidget *combo, int type) { GtkTreeModel *model; GtkTreeIter iter; gchar *devnode_oss; int voss_ctrl_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, &devnode_oss, -1); if ((voss_ctrl_fd = open(VOSS_DEV, O_WRONLY)) > 0) { snprintf(options, VIRTUAL_OSS_OPTIONS_MAX, "-%c %s", voss_opts[type], devnode_oss); ioctl(voss_ctrl_fd, VIRTUAL_OSS_ADD_OPTIONS, &options); close(voss_ctrl_fd); } /* set the default mixer device - playback only */ unsigned int devnum; size_t len = sizeof(int); if ((type == DEVTYPE_PLAY) && (sscanf(devnode_oss, "/dev/dsp%u", &devnum) == 1)) { sysctlbyname("hw.snd.default_unit", NULL, &len, &devnum, len); } g_free(devnode_oss); } } static void cb_toggle_enabled(GtkToggleButton *button, gpointer user_data) { struct device_controls_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); gtk_widget_set_sensitive(GTK_WIDGET(data->slider), !checked); char options[VIRTUAL_OSS_OPTIONS_MAX]; int voss_ctrl_fd; /* if disabled, set to /dev/null */ if (checked) { if ((voss_ctrl_fd = open(VOSS_DEV, O_WRONLY)) > 0) { snprintf(options, VIRTUAL_OSS_OPTIONS_MAX, "-%c /dev/null", voss_opts[data->type]); ioctl(voss_ctrl_fd, VIRTUAL_OSS_ADD_OPTIONS, &options); close(voss_ctrl_fd); } } else { apply_device_selections(GTK_WIDGET(data->combo), data->type); update_volume_slider(GTK_WIDGET(data->slider), data->type); } } static void cb_dev_selected(GtkComboBox *combo, gpointer user_data) { int type = GPOINTER_TO_INT(user_data); assert(type == DEVTYPE_PLAY || type == DEVTYPE_REC); apply_device_selections(GTK_WIDGET(combo), type); } static gboolean cb_volume_set(GtkWidget *slider, GdkEventButton *event, gpointer user_data) { char devnode_mixer[PATH_MAX]; unsigned int devnum; size_t len = sizeof(int); int mixer_fd, vol_targ, slider_val; (void)event; //unused unsigned int type = GPOINTER_TO_INT(user_data); /* associate mixer to dsp */ sysctlbyname("hw.snd.default_unit", &devnum, &len, NULL, len); snprintf(devnode_mixer, PATH_MAX, "/dev/mixer%u", devnum); slider_val = (int)gtk_range_get_value(GTK_RANGE(slider)); vol_targ = slider_val | (slider_val << 8); if (type == DEVTYPE_PLAY) { if ((mixer_fd = open(devnode_mixer, O_WRONLY, 0)) > 0) { ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_VOLUME), (int *)&vol_targ); ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_PCM), (int *)&vol_targ); (void)close(mixer_fd); } } else if (type == DEVTYPE_REC) { if ((mixer_fd = open(devnode_mixer, O_WRONLY, 0)) > 0) { ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_MIC), (int *)&vol_targ); (void)close(mixer_fd); } } /* button release callback needs a return value */ return FALSE; } 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_controls(GtkListStore *store, GtkWidget *combo, GtkWidget *check, GtkWidget *slider, 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_ctrl_fd; char options[VIRTUAL_OSS_OPTIONS_MAX]; /* the same for Virtual OSS */ if ((voss_ctrl_fd = open(VOSS_DEV, O_WRONLY)) == -1) { printf("Failed to open device %s: %s\n", VOSS_DEV, strerror(errno)); } if (ioctl(voss_ctrl_fd, VIRTUAL_OSS_GET_SYSTEM_INFO, &vsi) < 0) { printf("Cannot get Virtual OSS system info: %s\n", strerror(errno)); } close(voss_ctrl_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_ctrl_fd = open(VOSS_DEV, O_WRONLY)) > 0) { snprintf(options, VIRTUAL_OSS_OPTIONS_MAX, "-%c /dev/null", voss_opts[type]); ioctl(voss_ctrl_fd, VIRTUAL_OSS_ADD_OPTIONS, &options); close(voss_ctrl_fd); } } if (!active) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE); gtk_widget_set_sensitive(combo, FALSE); gtk_widget_set_sensitive(slider, FALSE); gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); } 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); gtk_scale_set_draw_value(GTK_SCALE(slider), FALSE); update_volume_slider(slider, type); struct device_controls_cb_data *cb_data = g_new(struct device_controls_cb_data, 1); cb_data->combo = combo; cb_data->slider = slider; cb_data->type = type; g_signal_connect(combo, "changed", G_CALLBACK(cb_dev_selected), GINT_TO_POINTER(type)); g_signal_connect(check, "toggled", G_CALLBACK(cb_toggle_enabled), cb_data); g_signal_connect(slider, "button-release-event", G_CALLBACK(cb_volume_set), GINT_TO_POINTER(type)); } static void cb_init(GtkApplication* app, gpointer user_data) { GtkWidget *window, *vbox_main, *hbox_play, *combo_play, *check_play, *slider_play, *hbox_rec, *combo_rec, *check_rec, *slider_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"); slider_play = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.0, 100.0, 1.0); setup_device_controls(store_play, combo_play, check_play, slider_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"); slider_rec = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.0, 100.0, 1.0); setup_device_controls(store_rec, combo_rec, check_rec, slider_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), slider_play, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox_main), hbox_rec, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox_main), slider_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; }