summaryrefslogtreecommitdiff
path: root/voss_devctl_xm.c
diff options
context:
space:
mode:
Diffstat (limited to 'voss_devctl_xm.c')
-rw-r--r--voss_devctl_xm.c573
1 files changed, 573 insertions, 0 deletions
diff --git a/voss_devctl_xm.c b/voss_devctl_xm.c
new file mode 100644
index 0000000..1739b19
--- /dev/null
+++ b/voss_devctl_xm.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 | PCM_CAP_VIRTUAL)) {
+ 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;
+}