summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWalker Thompson <walker.thompson@urz.uni-heidelberg.de>2026-04-17 11:14:52 +0000
committerWalker Thompson <walker.thompson@urz.uni-heidelberg.de>2026-04-17 11:14:52 +0000
commitc0b7b323a2f4372214897ca8b4dd349e7e6b3986 (patch)
tree03faebcee5681b178b4166ddb9c41f8d9dcc611d
Initial commit
-rwxr-xr-xcompile.sh6
-rw-r--r--virtual_oss.h206
-rw-r--r--voss_devctl.c573
3 files changed, 785 insertions, 0 deletions
diff --git a/compile.sh b/compile.sh
new file mode 100755
index 0000000..c8025fb
--- /dev/null
+++ b/compile.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+cd "$(dirname "$0")" || exit 1
+cc voss_devctl.c -g -o voss-devctl \
+ -Wall -Wpedantic -Werror -Wextra \
+ -I/usr/local/include \
+ -L/usr/local/lib -lXm -lXt -lX11
diff --git a/virtual_oss.h b/virtual_oss.h
new file mode 100644
index 0000000..616de2e
--- /dev/null
+++ b/virtual_oss.h
@@ -0,0 +1,206 @@
+/*-
+ * Copyright (c) 2012-2022 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _VIRTUAL_OSS_H_
+#define _VIRTUAL_OSS_H_
+
+#include <sys/ioccom.h>
+
+#define VIRTUAL_OSS_NAME_MAX 32
+#define VIRTUAL_OSS_VERSION 0x00010008
+#define VIRTUAL_OSS_OPTIONS_MAX 1024 /* bytes */
+#define VIRTUAL_OSS_FILTER_MAX 65536 /* samples */
+
+#define VIRTUAL_OSS_GET_VERSION _IOR('O', 0, int)
+
+struct virtual_oss_io_info {
+ int number; /* must be first */
+ int channel;
+ char name[VIRTUAL_OSS_NAME_MAX];
+ int bits;
+ int rx_amp;
+ int tx_amp;
+ int rx_chan;
+ int tx_chan;
+ int rx_mute;
+ int tx_mute;
+ int rx_pol;
+ int tx_pol;
+ int rx_delay; /* in samples */
+ int rx_delay_limit; /* in samples */
+};
+
+#define VIRTUAL_OSS_GET_DEV_INFO _IOWR('O', 1, struct virtual_oss_io_info)
+#define VIRTUAL_OSS_SET_DEV_INFO _IOW('O', 2, struct virtual_oss_io_info)
+
+#define VIRTUAL_OSS_GET_LOOP_INFO _IOWR('O', 3, struct virtual_oss_io_info)
+#define VIRTUAL_OSS_SET_LOOP_INFO _IOW('O', 4, struct virtual_oss_io_info)
+
+struct virtual_oss_mon_info {
+ int number;
+ int bits;
+ int src_chan;
+ int dst_chan;
+ int pol;
+ int mute;
+ int amp;
+};
+
+#define VIRTUAL_OSS_GET_INPUT_MON_INFO _IOWR('O', 5, struct virtual_oss_mon_info)
+#define VIRTUAL_OSS_SET_INPUT_MON_INFO _IOW('O', 6, struct virtual_oss_mon_info)
+
+#define VIRTUAL_OSS_GET_OUTPUT_MON_INFO _IOWR('O', 7, struct virtual_oss_mon_info)
+#define VIRTUAL_OSS_SET_OUTPUT_MON_INFO _IOW('O', 8, struct virtual_oss_mon_info)
+
+#define VIRTUAL_OSS_GET_LOCAL_MON_INFO _IOWR('O', 43, struct virtual_oss_mon_info)
+#define VIRTUAL_OSS_SET_LOCAL_MON_INFO _IOW('O', 44, struct virtual_oss_mon_info)
+
+struct virtual_oss_io_peak {
+ int number; /* must be first */
+ int channel;
+ char name[VIRTUAL_OSS_NAME_MAX];
+ int bits;
+ long long rx_peak_value;
+ long long tx_peak_value;
+};
+
+#define VIRTUAL_OSS_GET_DEV_PEAK _IOWR('O', 9, struct virtual_oss_io_peak)
+#define VIRTUAL_OSS_GET_LOOP_PEAK _IOWR('O', 10, struct virtual_oss_io_peak)
+
+struct virtual_oss_mon_peak {
+ int number;
+ int bits;
+ long long peak_value;
+};
+
+#define VIRTUAL_OSS_GET_INPUT_MON_PEAK _IOWR('O', 11, struct virtual_oss_mon_peak)
+#define VIRTUAL_OSS_GET_OUTPUT_MON_PEAK _IOWR('O', 12, struct virtual_oss_mon_peak)
+#define VIRTUAL_OSS_GET_LOCAL_MON_PEAK _IOWR('O', 45, struct virtual_oss_mon_peak)
+
+#define VIRTUAL_OSS_ADD_INPUT_MON _IOR('O', 13, int)
+#define VIRTUAL_OSS_ADD_OUTPUT_MON _IOR('O', 14, int)
+#define VIRTUAL_OSS_ADD_LOCAL_MON _IOR('O', 46, int)
+
+struct virtual_oss_compressor {
+ int enabled;
+ int knee;
+#define VIRTUAL_OSS_KNEE_MAX 255 /* inclusive */
+#define VIRTUAL_OSS_KNEE_MIN 0
+ int attack;
+#define VIRTUAL_OSS_ATTACK_MAX 62 /* inclusive */
+#define VIRTUAL_OSS_ATTACK_MIN 0
+ int decay;
+#define VIRTUAL_OSS_DECAY_MAX 62 /* inclusive */
+#define VIRTUAL_OSS_DECAY_MIN 0
+ int gain; /* read only */
+#define VIRTUAL_OSS_GAIN_MAX 1000 /* inclusive */
+#define VIRTUAL_OSS_GAIN_MIN 0
+};
+
+#define VIRTUAL_OSS_SET_OUTPUT_LIMIT _IOW('O', 17, struct virtual_oss_compressor)
+#define VIRTUAL_OSS_GET_OUTPUT_LIMIT _IOWR('O', 18, struct virtual_oss_compressor)
+
+struct virtual_oss_io_limit {
+ int number; /* must be first */
+ struct virtual_oss_compressor param;
+};
+
+#define VIRTUAL_OSS_SET_DEV_LIMIT _IOW('O', 19, struct virtual_oss_io_limit)
+#define VIRTUAL_OSS_GET_DEV_LIMIT _IOWR('O', 20, struct virtual_oss_io_limit)
+
+#define VIRTUAL_OSS_SET_LOOP_LIMIT _IOW('O', 21, struct virtual_oss_io_limit)
+#define VIRTUAL_OSS_GET_LOOP_LIMIT _IOWR('O', 22, struct virtual_oss_io_limit)
+
+struct virtual_oss_master_peak {
+ int channel;
+ int bits;
+ long long peak_value;
+};
+
+#define VIRTUAL_OSS_GET_OUTPUT_PEAK _IOWR('O', 23, struct virtual_oss_master_peak)
+#define VIRTUAL_OSS_GET_INPUT_PEAK _IOWR('O', 24, struct virtual_oss_master_peak)
+
+#define VIRTUAL_OSS_SET_RECORDING _IOW('O', 25, int)
+#define VIRTUAL_OSS_GET_RECORDING _IOR('O', 26, int)
+
+struct virtual_oss_audio_delay_locator {
+ int channel_output;
+ int channel_input;
+ int channel_last;
+ int signal_output_level; /* 2**n */
+ int signal_input_delay; /* in samples, roundtrip */
+ int signal_delay_hz; /* in samples, HZ */
+ int locator_enabled;
+};
+
+#define VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR _IOW('O', 27, struct virtual_oss_audio_delay_locator)
+#define VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR _IOR('O', 28, struct virtual_oss_audio_delay_locator)
+#define VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR _IO('O', 29)
+
+struct virtual_oss_midi_delay_locator {
+ int channel_output;
+ int channel_input;
+ int signal_delay;
+ int signal_delay_hz; /* in samples, HZ */
+ int locator_enabled;
+};
+
+#define VIRTUAL_OSS_SET_MIDI_DELAY_LOCATOR _IOW('O', 30, struct virtual_oss_midi_delay_locator)
+#define VIRTUAL_OSS_GET_MIDI_DELAY_LOCATOR _IOR('O', 31, struct virtual_oss_midi_delay_locator)
+#define VIRTUAL_OSS_RST_MIDI_DELAY_LOCATOR _IO('O', 32)
+
+#define VIRTUAL_OSS_ADD_OPTIONS _IOWR('O', 33, char [VIRTUAL_OSS_OPTIONS_MAX])
+
+struct virtual_oss_fir_filter {
+ int number; /* must be first */
+ int channel;
+ int filter_size;
+ double *filter_data;
+};
+
+#define VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER _IOWR('O', 34, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER _IOWR('O', 35, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER _IOWR('O', 36, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER _IOWR('O', 37, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER _IOWR('O', 38, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER _IOWR('O', 39, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER _IOWR('O', 40, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER _IOWR('O', 41, struct virtual_oss_fir_filter)
+
+#define VIRTUAL_OSS_GET_SAMPLE_RATE _IOR('O', 42, int)
+
+struct virtual_oss_system_info {
+ unsigned tx_jitter_up;
+ unsigned tx_jitter_down;
+ unsigned sample_rate;
+ unsigned sample_bits;
+ unsigned sample_channels;
+ char rx_device_name[64];
+ char tx_device_name[64];
+};
+
+#define VIRTUAL_OSS_GET_SYSTEM_INFO _IOR('O', 43, struct virtual_oss_system_info)
+
+#endif /* _VIRTUAL_OSS_H_ */
diff --git a/voss_devctl.c b/voss_devctl.c
new file mode 100644
index 0000000..6e9a51a
--- /dev/null
+++ b/voss_devctl.c
@@ -0,0 +1,573 @@
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/sysctl.h>
+
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/Label.h>
+#include <Xm/ComboBox.h>
+#include <Xm/TextF.h>
+#include <Xm/List.h>
+#include <Xm/MessageB.h>
+#include <Xm/ToggleB.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "virtual_oss.h"
+
+#define OSS_DEV "/dev/dsp0"
+#define VOSS_DEV "/dev/dsp.ctl"
+
+#define XM_ROW_HEIGHT 50
+#define XM_BG_COLOR 0xF0F0F0
+#define XM_MSGBUF_LEN 256
+
+#define OSS_DESCR_SIZE (OSS_DEVNODE_SIZE + OSS_LONGNAME_SIZE + 4)
+
+struct oss_dev {
+ int devnum;
+ char devnode[OSS_DEVNODE_SIZE];
+ char descr[OSS_DESCR_SIZE];
+ int caps;
+};
+
+struct oss_devlist {
+ int len;
+ int ct;
+ struct oss_dev *devs;
+};
+
+/* global variables */
+static int oss_dsp_fd, voss_dsp_fd;
+
+struct oss_devlist devs_play, devs_rec;
+
+static struct oss_sysinfo si;
+static struct virtual_oss_system_info vsi;
+
+
+/* dynamic list functions */
+void oss_devlist_init(struct oss_devlist *list)
+{
+ list->len = 10;
+ list->ct = 0;
+ list->devs = (struct oss_dev *)calloc(
+ list->len, sizeof(struct oss_dev)
+ );
+}
+
+void oss_devlist_add(struct oss_devlist *list, struct oss_audioinfo *ai)
+{
+ assert((list->ct < list->len) && (list->devs != NULL));
+
+ struct oss_dev *dev = &(list->devs[list->ct]);
+
+ char descrbuf[OSS_DESCR_SIZE];
+ snprintf(descrbuf, OSS_DESCR_SIZE, "%s: %s",
+ ai->devnode, ai->name);
+ dev->caps = ai->caps;
+ dev->devnum = ai->dev;
+ strncpy(dev->devnode, ai->devnode, OSS_DEVNODE_SIZE);
+ strncpy(dev->descr, descrbuf, OSS_DESCR_SIZE);
+
+ list->ct++;
+
+ /* extend the list if needed */
+ if ((list->len - list->ct) <= 1) {
+ list->len += 10;
+ list->devs = (struct oss_dev *)realloc(
+ list->devs, list->len * sizeof(struct oss_dev)
+ );
+ }
+}
+
+void oss_devlist_free(struct oss_devlist *list)
+{
+ if (list->devs != NULL) {
+ free(list->devs);
+ }
+}
+
+
+/* lookup functions */
+char *oss_devnode_from_descr(const char *descr, struct oss_devlist *list)
+{
+ for (int i = 0; i < list->ct; i++) {
+ if (strncmp(descr, list->devs[i].descr, OSS_DESCR_SIZE) == 0) {
+ return list->devs[i].devnode;
+ }
+ }
+ __builtin_unreachable();
+ return NULL;
+}
+
+int oss_devnum_from_descr(const char *descr, struct oss_devlist *list)
+{
+ for (int i = 0; i < list->ct; i++) {
+ if (strncmp(descr, list->devs[i].descr, OSS_DESCR_SIZE) == 0) {
+ return list->devs[i].devnum;
+ }
+ }
+ __builtin_unreachable();
+ return 0;
+}
+
+char *oss_descr_from_devnode(const char *devnode, struct oss_devlist *list)
+{
+ for (int i = 0; i < list->ct; i++) {
+ if (strncmp(devnode, list->devs[i].devnode, OSS_DEVNODE_SIZE) == 0) {
+ return list->devs[i].descr;
+ }
+ }
+ return NULL;
+}
+
+/* populate list of devices */
+void oss_enum_devs(int fd, int oss_numaudios)
+{
+ devs_play.ct = devs_rec.ct = 0;
+
+ for (int i = 0; i < oss_numaudios; i++) {
+ struct oss_audioinfo ai = { .dev = i };
+
+ if (ioctl(fd, SNDCTL_AUDIOINFO, &ai) < 0) {
+ printf("OSS: could not get device %d info: %s\n",
+ i, strerror(errno));
+ continue;
+ }
+
+ if (!ai.enabled) {
+ continue;
+ }
+
+ printf("%d: %s, %s\n", i, ai.name, ai.devnode);
+
+ if (ai.caps & PCM_CAP_OUTPUT) {
+ oss_devlist_add(&devs_play, &ai);
+ }
+ if (ai.caps & PCM_CAP_INPUT) {
+ oss_devlist_add(&devs_rec, &ai);
+ }
+ }
+}
+
+/* query device capabilities */
+int oss_get_caps(const char *path)
+{
+ int fd, dev_caps;
+
+ if ((fd = open(path, O_WRONLY)) == -1) {
+ printf("Failed to open device %s: %s\n",
+ path, strerror(errno));
+ return 0;
+ }
+
+ if (ioctl(fd, SNDCTL_DSP_GETCAPS, &dev_caps) == -1) {
+ printf("Could not query capabilities for %s: %s\n",
+ path, strerror(errno));
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+ return dev_caps;
+}
+
+/* center an Xt widget on the current display */
+void xt_move_to_center(Widget widget)
+{
+ Display *display = XtDisplay(widget);
+ int screen_number = 0;
+ for (int i = 0; i < ScreenCount(display); i++) {
+ if (ScreenOfDisplay(display, i) == XtScreen(widget)) {
+ screen_number = i;
+ break;
+ }
+ }
+
+ Position screen_width = DisplayWidth(display, screen_number);
+ Position screen_height = DisplayHeight(display, screen_number);
+
+ Dimension window_width, window_height;
+ XtVaGetValues(widget, XmNwidth, &window_width, XmNheight, &window_height, NULL);
+
+ Position pos_x = (screen_width - window_width) / 2;
+ Position pos_y = (screen_height - window_height) / 2;
+
+ XtVaSetValues(widget, XmNx, pos_x, XmNy, pos_y, NULL);
+}
+
+void xm_show_dialog(Widget caller, char *message)
+{
+ Widget dialog;
+
+ dialog = XmCreateInformationDialog(caller, "Message", NULL, 0);
+
+ XmString xm_msg_str = XmStringCreateLocalized(message);
+ XtVaSetValues(
+ dialog, XmNmessageString, xm_msg_str,
+ XmNbackground, XM_BG_COLOR,
+ XmNwidth, 400, XmNheight, 200, NULL
+ );
+
+ XtVaSetValues(XtParent(dialog), XmNtitle, "Message", NULL);
+
+ XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
+ XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
+
+ xt_move_to_center(dialog);
+
+ XtManageChild(dialog);
+
+ XmStringFree(xm_msg_str);
+}
+
+/* simply print out the current ComboBox selection */
+void xm_dev_select_callback(Widget caller,
+ XtPointer client_data __attribute__ ((unused)),
+ XtPointer call_data)
+{
+ XmComboBoxCallbackStruct *cb = (XmComboBoxCallbackStruct *)call_data;
+
+ const char *caller_name = XtName(caller);
+ char *choice = (char *)XmStringUnparse(
+ cb->item_or_text,
+ XmFONTLIST_DEFAULT_TAG,
+ XmCHARSET_TEXT, XmCHARSET_TEXT,
+ NULL, 0, XmOUTPUT_ALL
+ );
+
+ char msg[XM_MSGBUF_LEN], options[VIRTUAL_OSS_OPTIONS_MAX];
+ char flag;
+ char *devnode_name;
+
+ int devnum_sel = 0;
+ size_t len = sizeof(int);
+
+ if (strncmp(caller_name, "menu_rec", 8) == 0) {
+ flag = 'R';
+ devnode_name = oss_devnode_from_descr(choice, &devs_rec);
+ } else {
+ flag = 'P';
+ devnode_name = oss_devnode_from_descr(choice, &devs_play);
+ devnum_sel = oss_devnum_from_descr(choice, &devs_play);
+ sysctlbyname("hw.snd.default_unit", NULL, &len, &devnum_sel, len);
+ }
+
+ if (cb->event != NULL) {
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX,
+ "-%c %s", flag, devnode_name);
+ if (ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options) < 0) {
+ snprintf(msg, XM_MSGBUF_LEN,
+ "Cannot set Virtual OSS device: %s",
+ strerror(errno));
+ xm_show_dialog(caller, msg);
+ }
+ }
+
+ XtFree(choice);
+}
+
+/* enable or disable the widget, toggle VOSS device to /dev/null */
+void xm_toggle_menu_callback(Widget caller,
+ XtPointer client_data,
+ XtPointer call_data)
+{
+ Widget target_widget = (Widget)client_data;
+
+ XmToggleButtonCallbackStruct *toggle_data = \
+ (XmToggleButtonCallbackStruct *)call_data;
+
+ const char *caller_name = XtName(caller);
+
+ char flag = strncmp(caller_name, "toggle_rec", 10) ? 'P' : 'R';
+
+ char msg[XM_MSGBUF_LEN], options[VIRTUAL_OSS_OPTIONS_MAX];
+ char *devnode_name;
+
+ if (toggle_data->set) {
+ XtSetSensitive(target_widget, False);
+ devnode_name = "/dev/null";
+ } else {
+ XtSetSensitive(target_widget, True);
+
+ Widget menu_text_field = XtNameToWidget(target_widget, "*Text");
+ char *menu_text = XmTextFieldGetString(menu_text_field);
+
+ if (strncmp(caller_name, "menu_rec", 10) == 0) {
+ devnode_name = oss_devnode_from_descr(menu_text, &devs_rec);
+ } else {
+ devnode_name = oss_devnode_from_descr(menu_text, &devs_play);
+ }
+
+ XtFree(menu_text);
+ }
+
+ /* pass the new device options to virtual OSS */
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX,
+ "-%c %s", flag, devnode_name);
+ if (ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options) < 0) {
+ snprintf(msg, XM_MSGBUF_LEN,
+ "Cannot set Virtual OSS device: %s",
+ strerror(errno));
+ xm_show_dialog(caller, msg);
+ }
+}
+
+void xm_gui_init(Widget toplevel)
+{
+ Widget container;
+ Widget label_rec, label_play;
+ Widget menu_rec, menu_play;
+ Widget toggle_rec, toggle_play;
+ XmStringTable str_list;
+ int i;
+
+ XtVaSetValues(toplevel, XmNtitle, "Virtual OSS Device Selector", NULL);
+
+ /* top-level container */
+ container = XtVaCreateManagedWidget(
+ "container", xmFormWidgetClass, toplevel,
+ XmNmarginWidth, 20, XmNmarginHeight, 20,
+ XmNbackground, XM_BG_COLOR, NULL
+ );
+
+ /* label for output selection */
+ label_play = XtVaCreateManagedWidget(
+ "label_play", xmLabelWidgetClass, container,
+ XmNlabelString, XmStringCreateLocalized("Playback Device"),
+ XmNheight, 50, XmNbackground, XM_BG_COLOR,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_FORM, NULL
+ );
+
+ /* populate list of output devices, if any found */
+ str_list = (XmStringTable)XtMalloc(devs_play.ct * sizeof(XmString *));
+ if (devs_play.ct) {
+ for (i = 0; i < devs_play.ct; i++) {
+ str_list[i] = XmStringCreateLocalized(devs_play.devs[i].descr);
+ }
+ }
+
+
+ /* output device selection menu */
+ menu_play = XtVaCreateManagedWidget(
+ "menu_play", xmComboBoxWidgetClass, container,
+ XmNcomboBoxType, XmDROP_DOWN_LIST,
+ XmNheight, 50, XmNwidth, 600, XmNbackground, XM_BG_COLOR,
+ XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, label_play,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNitemCount, devs_play.ct, XmNvisibleItemCount, devs_play.ct,
+ XmNitems, str_list, NULL
+ );
+ XtAddCallback(
+ menu_play, XmNselectionCallback,
+ xm_dev_select_callback, NULL
+ );
+
+ /* output device toggle */
+ toggle_play = XtVaCreateManagedWidget(
+ "toggle_play", xmToggleButtonWidgetClass, container,
+ XmNlabelString, XmStringCreateLocalized("Disabled"),
+ XmNheight, 50, XmNbackground, XM_BG_COLOR,
+ XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, label_play,
+ XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, menu_play,
+ XmNleftOffset, 10, NULL
+ );
+ XtAddCallback(
+ toggle_play, XmNvalueChangedCallback,
+ xm_toggle_menu_callback, (XtPointer)menu_play
+ );
+
+ /* combo box clean up */
+ if (devs_play.ct) {
+ for (i = 0; i < devs_play.ct; i++) {
+ XmStringFree(str_list[i]);
+ }
+ } else {
+ XtSetSensitive(menu_play, False);
+ XtSetSensitive(toggle_play, False);
+ }
+ XtFree((XtPointer)str_list);
+
+ /* label for input device section */
+ label_rec = XtVaCreateManagedWidget(
+ "label_rec", xmLabelWidgetClass, container,
+ XmNlabelString, XmStringCreateLocalized("Recording Device"),
+ XmNheight, 50, XmNbackground, XM_BG_COLOR,
+ XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, menu_play,
+ XmNtopOffset, 20,
+ XmNleftAttachment, XmATTACH_FORM, NULL
+ );
+
+ /* populate list of input devices, if any found */
+ str_list = (XmStringTable)XtMalloc(devs_rec.ct * sizeof(XmString *));
+ if (devs_rec.ct) {
+ for (i = 0; i < devs_rec.ct; i++) {
+ str_list[i] = XmStringCreateLocalized(devs_rec.devs[i].descr);
+ }
+ }
+
+ /* input device selection menu */
+ menu_rec = XtVaCreateManagedWidget(
+ "menu_rec", xmComboBoxWidgetClass, container,
+ XmNcomboBoxType, XmDROP_DOWN_LIST,
+ XmNheight, 50, XmNwidth, 600, XmNbackground, XM_BG_COLOR,
+ XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, label_rec,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNitemCount, devs_rec.ct, XmNvisibleItemCount, devs_rec.ct,
+ XmNitems, str_list, NULL
+ );
+ XtAddCallback(
+ menu_rec, XmNselectionCallback,
+ xm_dev_select_callback, NULL
+ );
+
+ /* input device toggle */
+ toggle_rec = XtVaCreateManagedWidget(
+ "toggle_rec", xmToggleButtonWidgetClass, container,
+ XmNlabelString, XmStringCreateLocalized("Disabled"),
+ XmNheight, 50, XmNbackground, XM_BG_COLOR,
+ XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, label_rec,
+ XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, menu_rec,
+ XmNleftOffset, 10, NULL
+ );
+ XtAddCallback(
+ toggle_rec, XmNvalueChangedCallback,
+ xm_toggle_menu_callback, (XtPointer)menu_rec
+ );
+
+ /* combo box clean up */
+ if (devs_rec.ct) {
+ for (i = 0; i < devs_rec.ct; i++) {
+ XmStringFree(str_list[i]);
+ }
+ } else {
+ XtSetSensitive(menu_rec, False);
+ XtSetSensitive(toggle_rec, False);
+ }
+ XtFree((XtPointer)str_list);
+
+ /* pre-select the Virtual OSS playback device */
+ if (strncmp("/dev/null", vsi.tx_device_name, 64) != 0) {
+ if ((oss_get_caps(vsi.tx_device_name) & (PCM_CAP_OUTPUT | PCM_CAP_VIRTUAL))
+ && (oss_descr_from_devnode(vsi.tx_device_name, &devs_play) != NULL)) {
+ XmString opt_string = XmStringCreateLocalized(
+ oss_descr_from_devnode(vsi.tx_device_name, &devs_play)
+ );
+ XmComboBoxSelectItem(menu_play, opt_string);
+ XmStringFree(opt_string);
+ } else {
+ Widget menu_text_field = XtNameToWidget(menu_play, "*Text");
+ char *menu_text = XmTextFieldGetString(menu_text_field);
+
+ char options[VIRTUAL_OSS_OPTIONS_MAX];
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX,
+ "-P %s", oss_devnode_from_descr(menu_text, &devs_play));
+ ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options);
+
+ XtFree(menu_text);
+ }
+ } else {
+ XmToggleButtonSetState(toggle_play, True, False);
+ XtSetSensitive(menu_play, False);
+ }
+
+ /* pre-select the Virtual OSS recording device */
+ if (strncmp("/dev/null", vsi.rx_device_name, 64) != 0) {
+ if ((oss_get_caps(vsi.rx_device_name) & (PCM_CAP_INPUT | PCM_CAP_VIRTUAL))
+ && (oss_descr_from_devnode(vsi.tx_device_name, &devs_rec) != NULL)) {
+ XmString opt_string = XmStringCreateLocalized(
+ oss_descr_from_devnode(vsi.rx_device_name, &devs_rec)
+ );
+ XmComboBoxSelectItem(menu_rec, opt_string);
+ XmStringFree(opt_string);
+ } else {
+ Widget menu_text_field = XtNameToWidget(menu_rec, "*Text");
+ char *menu_text = XmTextFieldGetString(menu_text_field);
+
+ char options[VIRTUAL_OSS_OPTIONS_MAX];
+ snprintf(options, VIRTUAL_OSS_OPTIONS_MAX, "-R %s",
+ oss_devnode_from_descr(menu_text, &devs_rec));
+ ioctl(voss_dsp_fd, VIRTUAL_OSS_ADD_OPTIONS, &options);
+
+ XtFree(menu_text);
+ }
+ } else {
+ XmToggleButtonSetState(toggle_rec, True, False);
+ XtSetSensitive(menu_rec, False);
+ }
+
+ XtRealizeWidget(toplevel);
+ xt_move_to_center(toplevel);
+}
+
+int main(int argc, char **argv)
+{
+ XtAppContext app;
+ Widget toplevel;
+
+ /* 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));
+ return -1;
+ }
+ if (ioctl(oss_dsp_fd, SNDCTL_SYSINFO, &si) < 0) {
+ printf("Cannot get OSS system info: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ /* 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));
+ return -1;
+ }
+ if (ioctl(voss_dsp_fd, VIRTUAL_OSS_GET_SYSTEM_INFO, &vsi) < 0) {
+ printf("Cannot get Virtual OSS system info: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ printf("OSS: Using %s version %s\n", si.product, si.version);
+ printf("OSS: %d device(s) available\n", si.numaudios - 1);
+
+ oss_devlist_init(&devs_play);
+ oss_devlist_init(&devs_rec);
+
+ oss_enum_devs(oss_dsp_fd, si.numaudios);
+
+ XtSetLanguageProc(NULL, NULL, NULL);
+
+ toplevel = XtVaOpenApplication(
+ &app, "Virtual OSS Device Selector",
+ NULL, 0, &argc, argv, NULL, sessionShellWidgetClass,
+ XmNwidth, 800, XmNheight, 280, NULL
+ );
+
+ xm_gui_init(toplevel);
+
+ xt_move_to_center(toplevel);
+
+ XtAppMainLoop(app);
+
+ oss_devlist_free(&devs_play);
+ oss_devlist_free(&devs_rec);
+
+ close(voss_dsp_fd);
+ close(oss_dsp_fd);
+
+ return 0;
+}