summaryrefslogtreecommitdiff
path: root/backends/oss/oss-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'backends/oss/oss-device.c')
-rw-r--r--backends/oss/oss-device.c665
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);
}