#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 | PCM_CAP_VIRTUAL)) { oss_devlist_add(&devs_play, &ai); } if (ai.caps & (PCM_CAP_INPUT | PCM_CAP_VIRTUAL)) { 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; }