diff options
Diffstat (limited to 'backends/oss/oss-device.c')
-rw-r--r-- | backends/oss/oss-device.c | 665 |
1 files changed, 510 insertions, 155 deletions
diff --git a/backends/oss/oss-device.c b/backends/oss/oss-device.c index cf51705..44ed18f 100644 --- a/backends/oss/oss-device.c +++ b/backends/oss/oss-device.c @@ -16,7 +16,6 @@ */ #include <errno.h> -#include <unistd.h> #include <glib.h> #include <glib/gi18n.h> #include <glib/gstdio.h> @@ -29,87 +28,184 @@ #include "oss-device.h" #include "oss-stream.h" #include "oss-stream-control.h" +#include "oss-switch-option.h" + +/* + * NOTES: + * + * OSS has a predefined list of channels (or "devices"), which historically used + * to be mapped to individual sound card pins. At this time, the channels are + * chosen somehow arbitrarily by drivers. + * + * Each of the channels may have a record switch, which toggles between playback + * and capture direction. OSS doesn't have mute switches and we can't really use + * the record switch as one. For this reason all channels are modelled as + * muteless stream controls and the record switch is modelled as a port switch. + * + * Also, we avoid modelling capturable channels as both input and output channels, + * because the ones which allow capture are normally capture-only channels + * (OSS just doesn't have the ability to make the distinction), so each channel in + * the list contains a flag of whether it can be used as a capture source, given + * that it's reported as capturable. Capturable channels are therefore modelled + * as input controls and this approach avoids for example putting PCM in an input + * stream (which makes no sense). + * + * OSS also has an indicator of whether the record switch is exclusive (only + * allows one capture source at a time), to simplify the lives of applications + * we always create a port switch and therefore assume the exclusivity is always + * true. Ideally, we should probably model a bunch of toggles, one for each channel + * with capture capability if they are known not to be exclusive. + */ #define OSS_DEVICE_ICON "audio-card" -typedef enum -{ +#define OSS_POLL_TIMEOUT_NORMAL 500 +#define OSS_POLL_TIMEOUT_RAPID 50 +#define OSS_POLL_TIMEOUT_RESTORE 3000 + +typedef enum { + OSS_POLL_NORMAL, + OSS_POLL_RAPID +} OssPollMode; + +typedef enum { OSS_DEV_ANY, OSS_DEV_INPUT, OSS_DEV_OUTPUT -} OssDevType; +} OssDevChannelType; -typedef struct -{ +typedef struct { gchar *name; gchar *label; MateMixerStreamControlRole role; - OssDevType type; -} OssDev; - -static const OssDev oss_devices[] = { - { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_OUTPUT }, - { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, OSS_DEV_OUTPUT }, - { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, OSS_DEV_OUTPUT }, - { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }, - { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT }, + OssDevChannelType type; + gchar *icon; +} OssDevChannel; + +/* Index of a channel in the array corresponds to the channel number passed to ioctl()s, + * device names are takes from soundcard.h */ +static const OssDevChannel oss_devices[] = { + { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_OUTPUT, NULL }, + { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, OSS_DEV_OUTPUT, NULL }, + { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, OSS_DEV_OUTPUT, NULL }, + { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT, NULL }, + { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT, NULL }, /* OSS manual says this should be the beeper, but Linux OSS seems to assign it to * regular volume control */ - { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, OSS_DEV_OUTPUT }, - { "line", N_("Line-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, OSS_DEV_INPUT }, + { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, OSS_DEV_OUTPUT, NULL }, + { "line", N_("Line In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_MICROPHONE, OSS_DEV_INPUT, "audio-input-microphone" }, + { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, OSS_DEV_INPUT, NULL }, /* Recording monitor */ - { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "pcm2", N_("PCM-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT }, + { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT, NULL }, + { "pcm2", N_("PCM 2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT, NULL }, /* Recording level (master input) */ - { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_INPUT }, - { "igain", N_("In-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }, - { "ogain", N_("Out-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "line1", N_("Line-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "line2", N_("Line-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "line3", N_("Line-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "dig1", N_("Digital-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, - { "dig2", N_("Digital-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, - { "dig3", N_("Digital-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, - { "phin", N_("Phone-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "phout", N_("Phone-out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_OUTPUT }, - { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, - { "monitor", N_("Monitor"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "depth", N_("3D-depth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "center", N_("3D-center"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, - { "midi", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT } + { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_INPUT, NULL }, + { "igain", N_("Input Gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT, NULL }, + { "ogain", N_("Output Gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT, NULL }, + { "line1", N_("Line In 1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "line2", N_("Line In 2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "line3", N_("Line In 3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + /* These 3 can be attached to either digital input or output */ + { "dig1", N_("Digital 1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY, NULL }, + { "dig2", N_("Digital 2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY, NULL }, + { "dig3", N_("Digital 3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY, NULL }, + { "phin", N_("Phone In"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + { "phout", N_("Phone Out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_OUTPUT, NULL }, + { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_VIDEO, OSS_DEV_INPUT, NULL }, + { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT, NULL }, + + /* soundcard.h on some systems include more channels, but different files provide + * different meanings for the remaining ones and the value is doubtful */ }; #define OSS_N_DEVICES MIN (G_N_ELEMENTS (oss_devices), SOUND_MIXER_NRDEVICES) +/* Priorities for selecting default controls */ +static const gint oss_input_priority[] = { + 11, /* rec */ + 12, /* igain */ + 7, /* mic */ + 6, /* line */ + 14, /* line1 */ + 15, /* line2 */ + 16, /* line3 */ + 17, /* dig1 */ + 18, /* dig2 */ + 19, /* dig3 */ + 20, /* phin */ + 8, /* cd */ + 22, /* video */ + 23, /* radio */ + 3, /* synth */ + 27 /* midi */ +}; + +static const gint oss_output_priority[] = { + 0, /* vol */ + 4, /* pcm */ + 10, /* pcm2 */ + 5, /* speaker */ + 17, /* dig1 */ + 18, /* dig2 */ + 19, /* dig3 */ + 25, /* depth */ + 26, /* center */ + 21, /* phone out */ + 13, /* ogain */ + 9, /* mix */ + 24, /* monitor */ + 1, /* bass */ + 2 /* treble */ +}; + struct _OssDevicePrivate { - gint fd; - gchar *path; - gint devmask; - gint stereodevs; - gint recmask; - gint recsrc; - OssStream *input; - OssStream *output; + gint fd; + gchar *path; + gint devmask; + gint stereodevs; + gint recmask; + guint poll_tag; + guint poll_tag_restore; + guint poll_counter; + gboolean poll_use_counter; + OssPollMode poll_mode; + GList *streams; + OssStream *input; + OssStream *output; +}; + +enum { + CLOSED, + N_SIGNALS }; -static void oss_device_class_init (OssDeviceClass *klass); -static void oss_device_init (OssDevice *device); -static void oss_device_dispose (GObject *object); -static void oss_device_finalize (GObject *object); +static guint signals[N_SIGNALS] = { 0, }; + +static void oss_device_class_init (OssDeviceClass *klass); +static void oss_device_init (OssDevice *device); +static void oss_device_dispose (GObject *object); +static void oss_device_finalize (GObject *object); G_DEFINE_TYPE (OssDevice, oss_device, MATE_MIXER_TYPE_DEVICE) -static GList * oss_device_list_streams (MateMixerDevice *device); +static const GList *oss_device_list_streams (MateMixerDevice *mmd); + +static gboolean poll_mixer (OssDevice *device); +static gboolean poll_mixer_restore (OssDevice *device); + +static void read_mixer_devices (OssDevice *device); +static void read_mixer_switch (OssDevice *device); -static gboolean read_mixer_devices (OssDevice *device); +static guint create_poll_source (OssDevice *device, + OssPollMode mode); +static guint create_poll_restore_source (OssDevice *device); -static gboolean set_stream_default_control (OssStream *stream, - OssStreamControl *control, - gboolean force); +static void free_stream_list (OssDevice *device); + +static gint compare_stream_control_devnum (gconstpointer a, + gconstpointer b); static void oss_device_class_init (OssDeviceClass *klass) @@ -124,6 +220,18 @@ oss_device_class_init (OssDeviceClass *klass) device_class = MATE_MIXER_DEVICE_CLASS (klass); device_class->list_streams = oss_device_list_streams; + signals[CLOSED] = + g_signal_new ("closed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (OssDeviceClass, closed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + G_TYPE_NONE); + g_type_class_add_private (object_class, sizeof (OssDevicePrivate)); } @@ -145,6 +253,8 @@ oss_device_dispose (GObject *object) g_clear_object (&device->priv->input); g_clear_object (&device->priv->output); + free_stream_list (device); + G_OBJECT_CLASS (oss_device_parent_class)->dispose (object); } @@ -153,13 +263,19 @@ oss_device_finalize (GObject *object) { OssDevice *device = OSS_DEVICE (object); - close (device->priv->fd); + if (device->priv->fd != -1) + close (device->priv->fd); + + g_free (device->priv->path); G_OBJECT_CLASS (oss_device_parent_class)->finalize (object); } OssDevice * -oss_device_new (const gchar *name, const gchar *label, const gchar *path, gint fd) +oss_device_new (const gchar *name, + const gchar *label, + const gchar *path, + gint fd) { OssDevice *device; gchar *stream_name; @@ -180,13 +296,13 @@ oss_device_new (const gchar *name, const gchar *label, const gchar *path, gint f stream_name = g_strdup_printf ("oss-input-%s", name); device->priv->input = oss_stream_new (stream_name, MATE_MIXER_DEVICE (device), - MATE_MIXER_STREAM_INPUT); + MATE_MIXER_DIRECTION_INPUT); g_free (stream_name); stream_name = g_strdup_printf ("oss-output-%s", name); device->priv->output = oss_stream_new (stream_name, MATE_MIXER_DEVICE (device), - MATE_MIXER_STREAM_OUTPUT); + MATE_MIXER_DIRECTION_OUTPUT); g_free (stream_name); return device; @@ -205,30 +321,19 @@ oss_device_open (OssDevice *device) /* Read the essential information about the device, these values are not * expected to change and will not be queried */ - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_DEVMASK), + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_DEVMASK), &device->priv->devmask); - if (ret != 0) + if (ret == -1) goto fail; - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_STEREODEVS), + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_STEREODEVS), &device->priv->stereodevs); - if (ret < 0) + if (ret == -1) goto fail; - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_RECMASK), + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_RECMASK), &device->priv->recmask); - if (ret < 0) - goto fail; - - /* The recording source mask may change at any time, here we just read - * the initial value */ - ret = ioctl (device->priv->fd, - MIXER_READ (SOUND_MIXER_RECSRC), - &device->priv->recsrc); - if (ret < 0) + if (ret == -1) goto fail; /* NOTE: Linux also supports SOUND_MIXER_OUTSRC and SOUND_MIXER_OUTMASK which @@ -246,41 +351,135 @@ fail: } gboolean -oss_device_load (OssDevice *device) +oss_device_is_open (OssDevice *device) { - MateMixerStreamControl *control; - g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE); - read_mixer_devices (device); + if (device->priv->fd != -1) + return TRUE; + + return FALSE; +} + +void +oss_device_close (OssDevice *device) +{ + g_return_if_fail (OSS_IS_DEVICE (device)); - control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->input)); - if (control == NULL) { - // XXX pick something + if (device->priv->fd == -1) + return; + + /* Make each stream remove its controls and switch */ + if (oss_stream_has_controls (device->priv->input) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->input)); + + oss_stream_remove_all (device->priv->input); + free_stream_list (device); + + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); } - if (control != NULL) - g_debug ("Default input stream control is %s", - mate_mixer_stream_control_get_label (control)); + if (oss_stream_has_controls (device->priv->output) == TRUE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (device->priv->output)); + + oss_stream_remove_all (device->priv->output); + free_stream_list (device); - control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->output)); - if (control == NULL) { - // XXX pick something + g_signal_emit_by_name (G_OBJECT (device), + "stream-removed", + name); } - if (control != NULL) - g_debug ("Default output stream control is %s", - mate_mixer_stream_control_get_label (control)); + if (device->priv->poll_tag != 0) + g_source_remove (device->priv->poll_tag); - return TRUE; + if (device->priv->poll_tag_restore != 0) + g_source_remove (device->priv->poll_tag_restore); + + close (device->priv->fd); + device->priv->fd = -1; + + g_signal_emit (G_OBJECT (device), signals[CLOSED], 0); } -gint -oss_device_get_fd (OssDevice *device) +void +oss_device_load (OssDevice *device) { - g_return_val_if_fail (OSS_IS_DEVICE (device), -1); + const GList *controls; + guint i; + + g_return_if_fail (OSS_IS_DEVICE (device)); + + read_mixer_devices (device); - return device->priv->fd; + /* Set default input control */ + if (oss_stream_has_controls (device->priv->input) == TRUE) { + controls = mate_mixer_stream_list_controls (MATE_MIXER_STREAM (device->priv->input)); + + for (i = 0; i < G_N_ELEMENTS (oss_input_priority); i++) { + GList *item = g_list_find_custom ((GList *) controls, + GINT_TO_POINTER (oss_input_priority[i]), + compare_stream_control_devnum); + if (item == NULL) + continue; + + oss_stream_set_default_control (device->priv->input, + OSS_STREAM_CONTROL (item->data)); + break; + } + } + + /* Set default output control */ + if (oss_stream_has_controls (device->priv->output) == TRUE) { + controls = mate_mixer_stream_list_controls (MATE_MIXER_STREAM (device->priv->output)); + + for (i = 0; i < G_N_ELEMENTS (oss_output_priority); i++) { + GList *item = g_list_find_custom ((GList *) controls, + GINT_TO_POINTER (oss_output_priority[i]), + compare_stream_control_devnum); + if (item == NULL) + continue; + + oss_stream_set_default_control (device->priv->output, + OSS_STREAM_CONTROL (item->data)); + break; + } + } + + if (device->priv->recmask != 0) + read_mixer_switch (device); + + /* See if we can use the modify_counter field to optimize polling */ +#ifdef SOUND_MIXER_INFO + do { + struct mixer_info info; + gint ret; + + ret = ioctl (device->priv->fd, SOUND_MIXER_INFO, &info); + if (ret == 0) { + device->priv->poll_counter = info.modify_counter; + device->priv->poll_use_counter = TRUE; + } + } while (0); +#endif + + /* + * Use a polling strategy inspired by KMix: + * + * Poll for changes with the OSS_POLL_TIMEOUT_NORMAL interval, when we + * encounter a change in modify_counter, decrease the interval to + * OSS_POLL_TIMEOUT_RAPID for a few seconds to allow for smoother + * adjustments, for example when user drags a slider. + * + * This way is not used on systems which don't support the modify_counter + * field, because there is no way to find out whether anything has + * changed and therefore when to start the rapid polling. + */ + device->priv->poll_tag = create_poll_source (device, OSS_POLL_NORMAL); } const gchar * @@ -307,98 +506,254 @@ oss_device_get_output_stream (OssDevice *device) return device->priv->output; } -static GList * +static const GList * oss_device_list_streams (MateMixerDevice *mmd) { OssDevice *device; - GList *list = NULL; g_return_val_if_fail (OSS_IS_DEVICE (mmd), NULL); device = OSS_DEVICE (mmd); - if (device->priv->output != NULL) - list = g_list_prepend (list, g_object_ref (device->priv->output)); - if (device->priv->input != NULL) - list = g_list_prepend (list, g_object_ref (device->priv->input)); - - return list; + if (device->priv->streams == NULL) { + if (device->priv->output != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->output)); + if (device->priv->input != NULL) + device->priv->streams = g_list_prepend (device->priv->streams, + g_object_ref (device->priv->input)); + } + return device->priv->streams; } #define OSS_MASK_HAS_DEVICE(mask,i) ((gboolean) (((mask) & (1 << (i))) > 0)) -static gboolean +static void read_mixer_devices (OssDevice *device) { - gint i; + OssStreamControl *control; + const gchar *name; + guint i; + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); for (i = 0; i < OSS_N_DEVICES; i++) { - OssStreamControl *control; - gboolean input = FALSE; + OssStream *stream; + gboolean stereo; /* Skip unavailable controls */ if (OSS_MASK_HAS_DEVICE (device->priv->devmask, i) == FALSE) continue; - if (oss_devices[i].type == OSS_DEV_ANY) { - input = OSS_MASK_HAS_DEVICE (device->priv->recmask, i); - } - else if (oss_devices[i].type == OSS_DEV_INPUT) { - input = TRUE; - } + if (OSS_MASK_HAS_DEVICE (device->priv->recmask, i) == TRUE) + stream = device->priv->input; + else + stream = device->priv->output; - control = oss_stream_control_new (oss_devices[i].name, - oss_devices[i].label, + stereo = OSS_MASK_HAS_DEVICE (device->priv->stereodevs, i); + control = oss_stream_control_new (oss_devices[i].name, gettext (oss_devices[i].label), oss_devices[i].role, + stream, device->priv->fd, i, - OSS_MASK_HAS_DEVICE (device->priv->stereodevs, i)); - - if (input == TRUE) { - oss_stream_add_control (OSS_STREAM (device->priv->input), control); - - if (i == SOUND_MIXER_RECLEV || i == SOUND_MIXER_IGAIN) { - if (i == SOUND_MIXER_RECLEV) - set_stream_default_control (OSS_STREAM (device->priv->input), - control, - TRUE); - else - set_stream_default_control (OSS_STREAM (device->priv->input), - control, - FALSE); - } + stereo); + + if (oss_stream_has_controls (stream) == FALSE) { + const gchar *name = + mate_mixer_stream_get_name (MATE_MIXER_STREAM (stream)); + + free_stream_list (device); + + /* Pretend the stream has just been created now that we are adding + * the first control */ + g_signal_emit_by_name (G_OBJECT (device), + "stream-added", + name); + } + + g_debug ("Adding device %s control %s", + name, + mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control))); + + oss_stream_add_control (stream, control); + oss_stream_control_load (control); + + g_object_unref (control); + } +} + +static void +read_mixer_switch (OssDevice *device) +{ + GList *options = NULL; + guint i; + + for (i = 0; i < OSS_N_DEVICES; i++) { + OssSwitchOption *option; + + if (OSS_MASK_HAS_DEVICE (device->priv->recmask, i) == FALSE) + continue; + + option = oss_switch_option_new (oss_devices[i].name, + gettext (oss_devices[i].label), + oss_devices[i].icon, + i); + options = g_list_prepend (options, option); + } + + if G_LIKELY (options != NULL) + oss_stream_set_switch_data (device->priv->input, + device->priv->fd, + options); +} + +static gboolean +poll_mixer (OssDevice *device) +{ + gboolean load = TRUE; + + if G_UNLIKELY (device->priv->fd == -1) + return G_SOURCE_REMOVE; + +#ifdef SOUND_MIXER_INFO + if (device->priv->poll_use_counter == TRUE) { + gint ret; + struct mixer_info info; + + /* + * The modify_counter field increases each time a change happens on + * the device. + * + * If this ioctl() works, we use the field to only poll the controls + * if a change actually occured and we can also adjust the poll interval. + * + * This works well at least on Linux, NetBSD and OpenBSD. This call is + * not supported on FreeBSD as of version 10. + * + * The call is also used to detect unplugged devices early, when not + * supported, the unplug is still caught in the backend. + */ + ret = ioctl (device->priv->fd, SOUND_MIXER_INFO, &info); + if (ret == -1) { + if (errno == EINTR) + return G_SOURCE_CONTINUE; + + oss_device_close (device); + return G_SOURCE_REMOVE; + } + + if (device->priv->poll_counter < info.modify_counter) { + device->priv->poll_counter = info.modify_counter; } else { - oss_stream_add_control (OSS_STREAM (device->priv->output), control); - - if (i == SOUND_MIXER_VOLUME || i == SOUND_MIXER_PCM) { - if (i == SOUND_MIXER_VOLUME) - set_stream_default_control (OSS_STREAM (device->priv->output), - control, - TRUE); - else - set_stream_default_control (OSS_STREAM (device->priv->output), - control, - FALSE); - } + load = FALSE; } + } +#endif + + if (load == TRUE) { + oss_stream_load (device->priv->input); + oss_stream_load (device->priv->output); + + if (device->priv->poll_use_counter == TRUE && + device->priv->poll_mode == OSS_POLL_NORMAL) { + /* Create a new rapid source */ + device->priv->poll_tag = create_poll_source (device, OSS_POLL_RAPID); - g_debug ("Added control %s", - mate_mixer_stream_control_get_label (MATE_MIXER_STREAM_CONTROL (control))); + /* Also create another source to restore the poll interval to the + * original state */ + device->priv->poll_tag_restore = create_poll_restore_source (device); - oss_stream_control_update (control); + device->priv->poll_mode = OSS_POLL_RAPID; + return G_SOURCE_REMOVE; + } } - return TRUE; + return G_SOURCE_CONTINUE; } static gboolean -set_stream_default_control (OssStream *stream, OssStreamControl *control, gboolean force) +poll_mixer_restore (OssDevice *device) { - MateMixerStreamControl *current; + if G_LIKELY (device->priv->poll_mode == OSS_POLL_RAPID) { + /* Remove the current rapid source */ + g_source_remove (device->priv->poll_tag); - current = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); - if (current == NULL || force == TRUE) { - oss_stream_set_default_control (stream, OSS_STREAM_CONTROL (control)); - return TRUE; + device->priv->poll_tag = create_poll_source (device, OSS_POLL_NORMAL); + device->priv->poll_mode = OSS_POLL_NORMAL; } - return FALSE; + + /* Remove the tag for this function as it is only called once, the tag + * is only kept so we can remove it in the case the device is closed */ + device->priv->poll_tag_restore = 0; + + return G_SOURCE_REMOVE; +} + +static guint +create_poll_source (OssDevice *device, OssPollMode mode) +{ + GSource *source; + guint timeout; + guint tag; + + switch (mode) { + case OSS_POLL_NORMAL: + timeout = OSS_POLL_TIMEOUT_NORMAL; + break; + case OSS_POLL_RAPID: + timeout = OSS_POLL_TIMEOUT_RAPID; + break; + default: + g_warn_if_reached (); + return 0; + } + + source = g_timeout_source_new (timeout); + g_source_set_callback (source, + (GSourceFunc) poll_mixer, + device, + NULL); + + tag = g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); + + return tag; +} + +static guint +create_poll_restore_source (OssDevice *device) +{ + GSource *source; + guint tag; + + source = g_timeout_source_new (OSS_POLL_TIMEOUT_RESTORE); + g_source_set_callback (source, + (GSourceFunc) poll_mixer_restore, + device, + NULL); + + tag = g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); + + return tag; +} + +static void +free_stream_list (OssDevice *device) +{ + /* This function is called each time the stream list changes */ + if (device->priv->streams == NULL) + return; + + g_list_free_full (device->priv->streams, g_object_unref); + + device->priv->streams = NULL; +} + +static gint +compare_stream_control_devnum (gconstpointer a, gconstpointer b) +{ + OssStreamControl *control = OSS_STREAM_CONTROL (a); + guint devnum = GPOINTER_TO_INT (b); + + return !(oss_stream_control_get_devnum (control) == devnum); } |