From c0b7b323a2f4372214897ca8b4dd349e7e6b3986 Mon Sep 17 00:00:00 2001 From: Walker Thompson Date: Fri, 17 Apr 2026 11:14:52 +0000 Subject: Initial commit --- compile.sh | 6 + virtual_oss.h | 206 +++++++++++++++++++++ voss_devctl.c | 573 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 785 insertions(+) create mode 100755 compile.sh create mode 100644 virtual_oss.h create mode 100644 voss_devctl.c 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 + +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- cgit v1.3