From 5f20ab328add9442082277a57c23273a3a2125ed Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Mon, 18 Aug 2014 20:49:17 +0200 Subject: Global update --- backends/oss/Makefile.am | 8 +- backends/oss/oss-backend.c | 367 +++++++++++++-------- backends/oss/oss-backend.h | 8 +- backends/oss/oss-device.c | 665 +++++++++++++++++++++++++++++--------- backends/oss/oss-device.h | 12 +- backends/oss/oss-stream-control.c | 200 +++++++----- backends/oss/oss-stream-control.h | 23 +- backends/oss/oss-stream.c | 251 +++++++++----- backends/oss/oss-stream.h | 32 +- backends/oss/oss-switch-option.c | 72 +++++ backends/oss/oss-switch-option.h | 69 ++++ backends/oss/oss-switch.c | 203 ++++++++++++ backends/oss/oss-switch.h | 70 ++++ backends/oss/oss-types.h | 32 ++ 14 files changed, 1534 insertions(+), 478 deletions(-) create mode 100644 backends/oss/oss-switch-option.c create mode 100644 backends/oss/oss-switch-option.h create mode 100644 backends/oss/oss-switch.c create mode 100644 backends/oss/oss-switch.h create mode 100644 backends/oss/oss-types.h (limited to 'backends/oss') diff --git a/backends/oss/Makefile.am b/backends/oss/Makefile.am index 44caeb8..f535a37 100644 --- a/backends/oss/Makefile.am +++ b/backends/oss/Makefile.am @@ -3,6 +3,7 @@ backenddir = $(libdir)/libmatemixer backend_LTLIBRARIES = libmatemixer-oss.la AM_CPPFLAGS = \ + -Wno-unknown-pragmas \ -I$(top_srcdir) \ -DG_LOG_DOMAIN=\"libmatemixer-oss\" @@ -19,7 +20,12 @@ libmatemixer_oss_la_SOURCES = \ oss-stream.c \ oss-stream.h \ oss-stream-control.c \ - oss-stream-control.h + oss-stream-control.h \ + oss-switch.c \ + oss-switch.h \ + oss-switch-option.c \ + oss-switch-option.h \ + oss-types.h libmatemixer_oss_la_LIBADD = \ $(GLIB_LIBS) \ diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c index 2b5eca7..23d265b 100644 --- a/backends/oss/oss-backend.c +++ b/backends/oss/oss-backend.c @@ -32,7 +32,7 @@ #include "oss-stream.h" #define BACKEND_NAME "OSS" -#define BACKEND_PRIORITY 9 +#define BACKEND_PRIORITY 10 #if !defined(__linux__) && !defined(__NetBSD__) && !defined(__OpenBSD__) /* At least on systems based on FreeBSD we will need to read device names @@ -47,7 +47,9 @@ struct _OssBackendPrivate { gchar *default_device; GSource *timeout_source; - GHashTable *devices; + GList *streams; + GList *devices; + GHashTable *devices_paths; }; static void oss_backend_class_init (OssBackendClass *klass); @@ -61,36 +63,49 @@ static void oss_backend_finalize (GObject *object); G_DEFINE_DYNAMIC_TYPE (OssBackend, oss_backend, MATE_MIXER_TYPE_BACKEND) #pragma clang diagnostic pop -static gboolean oss_backend_open (MateMixerBackend *backend); -static void oss_backend_close (MateMixerBackend *backend); -static GList * oss_backend_list_devices (MateMixerBackend *backend); -static GList * oss_backend_list_streams (MateMixerBackend *backend); +static gboolean oss_backend_open (MateMixerBackend *backend); +static void oss_backend_close (MateMixerBackend *backend); +static const GList *oss_backend_list_devices (MateMixerBackend *backend); +static const GList *oss_backend_list_streams (MateMixerBackend *backend); -static gboolean read_devices (OssBackend *oss); +static gboolean read_devices (OssBackend *oss); -static gboolean read_device (OssBackend *oss, - const gchar *path, - gboolean *added); +static gboolean read_device (OssBackend *oss, + const gchar *path, + gboolean *added); -static gchar * read_device_label (OssBackend *oss, - const gchar *path, - gint fd); +static gchar * read_device_label (OssBackend *oss, + const gchar *path, + gint fd); -static gchar * read_device_label_sndstat (OssBackend *oss, - const gchar *sndstat, - const gchar *path, - guint index) G_GNUC_UNUSED; +static gchar * read_device_label_sndstat (OssBackend *oss, + const gchar *sndstat, + const gchar *path, + guint index) G_GNUC_UNUSED; -static void add_device (OssBackend *oss, - OssDevice *device); -static void remove_device (OssBackend *oss, - OssDevice *device); +static void add_device (OssBackend *oss, + OssDevice *device); +static void remove_device (OssBackend *oss, + OssDevice *device); -static void remove_stream (OssBackend *oss, - const gchar *name); +static void remove_device_by_path (OssBackend *oss, + const gchar *path); -static void select_default_input_stream (OssBackend *oss); -static void select_default_output_stream (OssBackend *oss); +static void remove_device_by_list_item (OssBackend *oss, + GList *item); + +static void remove_stream (OssBackend *oss, + const gchar *name); + +static void select_default_input_stream (OssBackend *oss); +static void select_default_output_stream (OssBackend *oss); + +static void free_stream_list (OssBackend *oss); + +static gint compare_devices (gconstpointer a, + gconstpointer b); +static gint compare_device_path (gconstpointer a, + gconstpointer b); static MateMixerBackendInfo info; @@ -142,10 +157,10 @@ oss_backend_init (OssBackend *oss) OSS_TYPE_BACKEND, OssBackendPrivate); - oss->priv->devices = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); + oss->priv->devices_paths = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); } static void @@ -170,7 +185,7 @@ oss_backend_finalize (GObject *object) oss = OSS_BACKEND (object); - g_hash_table_destroy (oss->priv->devices); + g_hash_table_unref (oss->priv->devices_paths); G_OBJECT_CLASS (oss_backend_parent_class)->finalize (object); } @@ -212,63 +227,65 @@ oss_backend_close (MateMixerBackend *backend) oss = OSS_BACKEND (backend); g_source_destroy (oss->priv->timeout_source); - g_hash_table_remove_all (oss->priv->devices); - g_free (oss->priv->default_device); - oss->priv->default_device = NULL; + if (oss->priv->devices != NULL) { + g_list_free_full (oss->priv->devices, g_object_unref); + oss->priv->devices = NULL; + } + if (oss->priv->default_device != NULL) { + g_free (oss->priv->default_device); + oss->priv->default_device = NULL; + } + + free_stream_list (oss); + + g_hash_table_remove_all (oss->priv->devices_paths); _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); } -static GList * +static const GList * oss_backend_list_devices (MateMixerBackend *backend) { - GList *list; - g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (OSS_BACKEND (backend)->priv->devices); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return OSS_BACKEND (backend)->priv->devices; } -static GList * +static const GList * oss_backend_list_streams (MateMixerBackend *backend) { - OssBackend *oss; - GHashTableIter iter; - gpointer value; - GList *list = NULL; + OssBackend *oss; g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); oss = OSS_BACKEND (backend); - /* We don't keep a list or hash table of all streams here, instead walk - * through the list of devices and create the list manually, each device - * has at most one input and one output stream */ - g_hash_table_iter_init (&iter, oss->priv->devices); + if (oss->priv->streams == NULL) { + GList *list; - while (g_hash_table_iter_next (&iter, NULL, &value)) { - OssDevice *device = OSS_DEVICE (value); - OssStream *stream; + /* Walk through the list of devices and create the stream list, each + * device has at most one input and one output stream */ + list = oss->priv->devices; - stream = oss_device_get_output_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); - stream = oss_device_get_input_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); - } + while (list != NULL) { + OssDevice *device = OSS_DEVICE (list->data); + OssStream *stream; - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + stream = oss_device_get_input_stream (device); + if (stream != NULL) { + oss->priv->streams = + g_list_append (oss->priv->streams, g_object_ref (stream)); + } + stream = oss_device_get_output_stream (device); + if (stream != NULL) { + oss->priv->streams = + g_list_append (oss->priv->streams, g_object_ref (stream)); + } + list = list->next; + } + } + return oss->priv->streams; } static gboolean @@ -278,8 +295,10 @@ read_devices (OssBackend *oss) gboolean added = FALSE; for (i = 0; i < OSS_MAX_DEVICES; i++) { - gboolean added_current; - gchar *path = g_strdup_printf ("/dev/mixer%i", i); + gchar *path; + gboolean added_current = FALSE; + + path = g_strdup_printf ("/dev/mixer%i", i); /* On recent FreeBSD both /dev/mixer and /dev/mixer0 point to the same * mixer device, on NetBSD and OpenBSD /dev/mixer is a symlink to one @@ -289,7 +308,7 @@ read_devices (OssBackend *oss) if (read_device (oss, path, &added_current) == FALSE && i == 0) read_device (oss, "/dev/mixer", &added_current); - if (added_current) + if (added_current == TRUE) added = TRUE; g_free (path); @@ -312,16 +331,12 @@ read_device (OssBackend *oss, const gchar *path, gboolean *added) gchar *bname; gchar *label; - device = g_hash_table_lookup (oss->priv->devices, path); - *added = FALSE; - fd = g_open (path, O_RDWR, 0); if (fd == -1) { if (errno != ENOENT && errno != ENXIO) g_debug ("%s: %s", path, g_strerror (errno)); - if (device != NULL) - remove_device (oss, device); + remove_device_by_path (oss, path); return FALSE; } @@ -329,7 +344,7 @@ read_device (OssBackend *oss, const gchar *path, gboolean *added) * still tested to be absolutely sure that the device is removed it case * it has disappeared, but normally the device's polling facility should * handle this by itself */ - if (device != NULL) { + if (g_hash_table_contains (oss->priv->devices_paths, path) == TRUE) { close (fd); return TRUE; } @@ -345,8 +360,9 @@ read_device (OssBackend *oss, const gchar *path, gboolean *added) if ((*added = oss_device_open (device)) == TRUE) add_device (oss, device); + else + g_object_unref (device); - g_object_unref (device); return *added; } @@ -445,44 +461,104 @@ read_device_label_sndstat (OssBackend *oss, static void add_device (OssBackend *oss, OssDevice *device) { - const gchar *name; + oss->priv->devices = + g_list_insert_sorted_with_data (oss->priv->devices, + device, + (GCompareDataFunc) compare_devices, + NULL); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + /* Keep track of added device paths */ + g_hash_table_add (oss->priv->devices_paths, + g_strdup (oss_device_get_path (device))); - g_hash_table_insert (oss->priv->devices, - g_strdup (oss_device_get_path (device)), - g_object_ref (device)); - - // XXX make device emit it when closed + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (remove_device), + oss); g_signal_connect_swapped (G_OBJECT (device), "stream-removed", G_CALLBACK (remove_stream), oss); - g_signal_emit_by_name (G_OBJECT (oss), "device-added", name); + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (free_stream_list), + oss); + g_signal_connect_swapped (G_OBJECT (device), + "stream-added", + G_CALLBACK (free_stream_list), + oss); + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (free_stream_list), + oss); + g_signal_emit_by_name (G_OBJECT (oss), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + /* Load the device elements after emitting device-added, because the load + * function will most likely emit stream-added on the device and backend */ oss_device_load (device); } static void remove_device (OssBackend *oss, OssDevice *device) { - const gchar *name; + GList *item; + + item = g_list_find (oss->priv->devices, device); + if (item != NULL) + remove_device_by_list_item (oss, item); +} + +static void +remove_device_by_path (OssBackend *oss, const gchar *path) +{ + GList *item; + + item = g_list_find_custom (oss->priv->devices, path, compare_device_path); + if (item != NULL) + remove_device_by_list_item (oss, item); +} + +static void +remove_device_by_list_item (OssBackend *oss, GList *item) +{ + OssDevice *device; const gchar *path; - path = oss_device_get_path (device); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + device = OSS_DEVICE (item->data); + + g_signal_handlers_disconnect_by_func (G_OBJECT (device), + G_CALLBACK (remove_device), + oss); + + if (oss_device_is_open (device) == TRUE) + oss_device_close (device); g_signal_handlers_disconnect_by_func (G_OBJECT (device), G_CALLBACK (remove_stream), oss); - // XXX close the device and make it remove streams - g_hash_table_remove (oss->priv->devices, path); + oss->priv->devices = g_list_delete_link (oss->priv->devices, item); + + path = oss_device_get_path (device); + g_hash_table_remove (oss->priv->devices_paths, path); + + if (g_strcmp0 (oss->priv->default_device, path) == 0) { + g_free (oss->priv->default_device); + oss->priv->default_device = NULL; + } + + /* The list may and may not have been invalidate by device signals */ + free_stream_list (oss); g_signal_emit_by_name (G_OBJECT (oss), "device-removed", - name); + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); } static void @@ -492,7 +568,6 @@ remove_stream (OssBackend *oss, const gchar *name) stream = mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (oss)); - // XXX see if the change happens after stream is removed or before if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) select_default_input_stream (oss); @@ -502,16 +577,31 @@ remove_stream (OssBackend *oss, const gchar *name) select_default_output_stream (oss); } +static OssDevice * +get_default_device (OssBackend *oss) +{ + GList *item; + + if (oss->priv->default_device == NULL) + return NULL; + + item = g_list_find_custom (oss->priv->devices, + oss->priv->default_device, + compare_device_path); + if G_LIKELY (item != NULL) + return OSS_DEVICE (item->data); + + return NULL; +} + static void select_default_input_stream (OssBackend *oss) { - OssDevice *device = NULL; + OssDevice *device; OssStream *stream; - gint i; + GList *list; - /* Always prefer stream in the "default" device */ - if (oss->priv->default_device != NULL) - device = g_hash_table_lookup (oss->priv->devices, oss->priv->default_device); + device = get_default_device (oss); if (device != NULL) { stream = oss_device_get_input_stream (device); if (stream != NULL) { @@ -521,23 +611,17 @@ select_default_input_stream (OssBackend *oss) } } - for (i = 0; i < OSS_MAX_DEVICES; i++) { - gchar *path = g_strdup_printf ("/dev/mixer%i", i); - - device = g_hash_table_lookup (oss->priv->devices, path); - if (device == NULL && i == 0) - device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + list = oss->priv->devices; + while (list != NULL) { + device = OSS_DEVICE (list->data); + stream = oss_device_get_input_stream (device); - if (device != NULL) { - stream = oss_device_get_input_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), - MATE_MIXER_STREAM (stream)); - g_free (path); - return; - } + if (stream != NULL) { + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + return; } - g_free (path); + list = list->next; } /* In the worst case unset the default stream */ @@ -547,13 +631,11 @@ select_default_input_stream (OssBackend *oss) static void select_default_output_stream (OssBackend *oss) { - OssDevice *device = NULL; + OssDevice *device; OssStream *stream; - gint i; + GList *list; - /* Always prefer stream in the "default" device */ - if (oss->priv->default_device != NULL) - device = g_hash_table_lookup (oss->priv->devices, oss->priv->default_device); + device = get_default_device (oss); if (device != NULL) { stream = oss_device_get_output_stream (device); if (stream != NULL) { @@ -563,25 +645,48 @@ select_default_output_stream (OssBackend *oss) } } - for (i = 0; i < OSS_MAX_DEVICES; i++) { - gchar *path = g_strdup_printf ("/dev/mixer%i", i); - - device = g_hash_table_lookup (oss->priv->devices, path); - if (device == NULL && i == 0) - device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + list = oss->priv->devices; + while (list != NULL) { + device = OSS_DEVICE (list->data); + stream = oss_device_get_output_stream (device); - if (device != NULL) { - stream = oss_device_get_output_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), - MATE_MIXER_STREAM (stream)); - g_free (path); - return; - } + if (stream != NULL) { + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + return; } - g_free (path); + list = list->next; } /* In the worst case unset the default stream */ _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), NULL); } + +static void +free_stream_list (OssBackend *oss) +{ + if (oss->priv->streams == NULL) + return; + + g_list_free_full (oss->priv->streams, g_object_unref); + + oss->priv->streams = NULL; +} + +static gint +compare_devices (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *d1 = MATE_MIXER_DEVICE (a); + MateMixerDevice *d2 = MATE_MIXER_DEVICE (b); + + return strcmp (mate_mixer_device_get_name (d1), mate_mixer_device_get_name (d2)); +} + +static gint +compare_device_path (gconstpointer a, gconstpointer b) +{ + OssDevice *device = OSS_DEVICE (a); + const gchar *path = (const gchar *) b; + + return strcmp (oss_device_get_path (device), path); +} diff --git a/backends/oss/oss-backend.h b/backends/oss/oss-backend.h index 325b61c..9617e79 100644 --- a/backends/oss/oss-backend.h +++ b/backends/oss/oss-backend.h @@ -20,9 +20,10 @@ #include #include +#include +#include -#include -#include +#include "oss-types.h" #define OSS_TYPE_BACKEND \ (oss_backend_get_type ()) @@ -37,7 +38,6 @@ #define OSS_BACKEND_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_BACKEND, OssBackendClass)) -typedef struct _OssBackend OssBackend; typedef struct _OssBackendClass OssBackendClass; typedef struct _OssBackendPrivate OssBackendPrivate; @@ -54,7 +54,7 @@ struct _OssBackendClass MateMixerBackendClass parent_class; }; -GType oss_backend_get_type (void) G_GNUC_CONST; +GType oss_backend_get_type (void) G_GNUC_CONST; /* Support function for dynamic loading of the backend module */ void backend_module_init (GTypeModule *module); 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 -#include #include #include #include @@ -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); } diff --git a/backends/oss/oss-device.h b/backends/oss/oss-device.h index 261a884..a723f41 100644 --- a/backends/oss/oss-device.h +++ b/backends/oss/oss-device.h @@ -22,7 +22,7 @@ #include #include -#include "oss-stream.h" +#include "oss-types.h" G_BEGIN_DECLS @@ -39,7 +39,6 @@ G_BEGIN_DECLS #define OSS_DEVICE_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_DEVICE, OssDeviceClass)) -typedef struct _OssDevice OssDevice; typedef struct _OssDeviceClass OssDeviceClass; typedef struct _OssDevicePrivate OssDevicePrivate; @@ -54,6 +53,9 @@ struct _OssDevice struct _OssDeviceClass { MateMixerDeviceClass parent; + + /*< private >*/ + void (*closed) (OssDevice *device); }; GType oss_device_get_type (void) G_GNUC_CONST; @@ -64,9 +66,11 @@ OssDevice * oss_device_new (const gchar *name, gint fd); gboolean oss_device_open (OssDevice *device); -gboolean oss_device_load (OssDevice *device); +gboolean oss_device_is_open (OssDevice *device); +void oss_device_close (OssDevice *device); + +void oss_device_load (OssDevice *device); -gint oss_device_get_fd (OssDevice *device); const gchar *oss_device_get_path (OssDevice *device); OssStream * oss_device_get_input_stream (OssDevice *device); diff --git a/backends/oss/oss-stream-control.c b/backends/oss/oss-stream-control.c index 5161528..0307fc7 100644 --- a/backends/oss/oss-stream-control.c +++ b/backends/oss/oss-stream-control.c @@ -26,27 +26,28 @@ #include "oss-common.h" #include "oss-stream-control.h" +#define OSS_VOLUME_JOIN(left,right) (((left) & 0xFF) | (((right) & 0xFF) << 8)) + +#define OSS_VOLUME_JOIN_SAME(volume) (OSS_VOLUME_JOIN ((volume), (volume))) +#define OSS_VOLUME_JOIN_ARRAY(volume) (OSS_VOLUME_JOIN ((volume[0]), (volume[1]))) + +#define OSS_VOLUME_TAKE_LEFT(volume) ((volume) & 0xFF) +#define OSS_VOLUME_TAKE_RIGHT(volume) (((volume) >> 8) & 0xFF) + struct _OssStreamControlPrivate { - gint fd; - gint devnum; - guint volume[2]; - gfloat balance; - gboolean stereo; - MateMixerStreamControlRole role; - MateMixerStreamControlFlags flags; + gint fd; + gint devnum; + guint8 volume[2]; + gboolean stereo; }; static void oss_stream_control_class_init (OssStreamControlClass *klass); - static void oss_stream_control_init (OssStreamControl *control); static void oss_stream_control_finalize (GObject *object); G_DEFINE_TYPE (OssStreamControl, oss_stream_control, MATE_MIXER_TYPE_STREAM_CONTROL) -static gboolean oss_stream_control_set_mute (MateMixerStreamControl *mmsc, - gboolean mute); - static guint oss_stream_control_get_num_channels (MateMixerStreamControl *mmsc); static guint oss_stream_control_get_volume (MateMixerStreamControl *mmsc); @@ -73,7 +74,9 @@ static guint oss_stream_control_get_max_volume (MateMix static guint oss_stream_control_get_normal_volume (MateMixerStreamControl *mmsc); static guint oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc); -static gboolean write_volume (OssStreamControl *control, +static void read_balance (OssStreamControl *control); + +static gboolean write_and_set_volume (OssStreamControl *control, gint volume); static void @@ -86,14 +89,13 @@ oss_stream_control_class_init (OssStreamControlClass *klass) object_class->finalize = oss_stream_control_finalize; control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); - control_class->set_mute = oss_stream_control_set_mute; control_class->get_num_channels = oss_stream_control_get_num_channels; control_class->get_volume = oss_stream_control_get_volume; control_class->set_volume = oss_stream_control_set_volume; control_class->get_channel_volume = oss_stream_control_get_channel_volume; control_class->set_channel_volume = oss_stream_control_set_channel_volume; - control_class->get_channel_position = oss_stream_control_get_channel_position; control_class->has_channel_position = oss_stream_control_has_channel_position; + control_class->get_channel_position = oss_stream_control_get_channel_position; control_class->set_balance = oss_stream_control_set_balance; control_class->get_min_volume = oss_stream_control_get_min_volume; control_class->get_max_volume = oss_stream_control_get_max_volume; @@ -118,7 +120,8 @@ oss_stream_control_finalize (GObject *object) control = OSS_STREAM_CONTROL (object); - close (control->priv->fd); + if (control->priv->fd != -1) + close (control->priv->fd); G_OBJECT_CLASS (oss_stream_control_parent_class)->finalize (object); } @@ -127,6 +130,7 @@ OssStreamControl * oss_stream_control_new (const gchar *name, const gchar *label, MateMixerStreamControlRole role, + OssStream *stream, gint fd, gint devnum, gboolean stereo) @@ -134,8 +138,11 @@ oss_stream_control_new (const gchar *name, OssStreamControl *control; MateMixerStreamControlFlags flags; - flags = MATE_MIXER_STREAM_CONTROL_HAS_VOLUME | - MATE_MIXER_STREAM_CONTROL_CAN_SET_VOLUME; + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (label != NULL, NULL); + + flags = MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | + MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE; if (stereo == TRUE) flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; @@ -143,6 +150,8 @@ oss_stream_control_new (const gchar *name, "name", name, "label", label, "flags", flags, + "role", role, + "stream", stream, NULL); control->priv->fd = fd; @@ -151,52 +160,50 @@ oss_stream_control_new (const gchar *name, return control; } -gboolean -oss_stream_control_update (OssStreamControl *control) +gint +oss_stream_control_get_devnum (OssStreamControl *control) { - gint v; - gint ret; + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), 0); - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); + return control->priv->devnum; +} - ret = ioctl (control->priv->fd, MIXER_READ (control->priv->devnum), &v); - if (ret < 0) { - g_warning ("Failed to read volume: %s", g_strerror (errno)); - return FALSE; - } +void +oss_stream_control_load (OssStreamControl *control) +{ + gint v, ret; - control->priv->volume[0] = v & 0xFF; + g_return_if_fail (OSS_IS_STREAM_CONTROL (control)); - if (control->priv->stereo == TRUE) { - gfloat balance; - guint left; - guint right; - - control->priv->volume[1] = (v >> 8) & 0xFF; - - /* Calculate balance */ - left = control->priv->volume[0]; - right = control->priv->volume[1]; - if (left == right) - balance = 0.0f; - else if (left > right) - balance = -1.0f + ((gfloat) right / (gfloat) left); - else - balance = +1.0f - ((gfloat) left / (gfloat) right); + if G_UNLIKELY (control->priv->fd == -1) + return; + + ret = ioctl (control->priv->fd, MIXER_READ (control->priv->devnum), &v); + if (ret == -1) + return; + + if (v != OSS_VOLUME_JOIN_ARRAY (control->priv->volume)) { + control->priv->volume[0] = OSS_VOLUME_TAKE_LEFT (v); + if (control->priv->stereo == TRUE) + control->priv->volume[1] = OSS_VOLUME_TAKE_RIGHT (v); - _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), - balance); + g_object_notify (G_OBJECT (control), "volume"); } - return TRUE; + + if (control->priv->stereo == TRUE) + read_balance (control); } -static gboolean -oss_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) +void +oss_stream_control_close (OssStreamControl *control) { - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + g_return_if_fail (OSS_IS_STREAM_CONTROL (control)); - // TODO - return TRUE; + if (control->priv->fd == -1) + return; + + close (control->priv->fd); + control->priv->fd = -1; } static guint @@ -226,17 +233,15 @@ static gboolean oss_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) { OssStreamControl *control; - gint v; g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); control = OSS_STREAM_CONTROL (mmsc); - v = CLAMP (volume, 0, 100); - if (control->priv->stereo == TRUE) - v |= (volume & 0xFF) << 8; + if G_UNLIKELY (control->priv->fd == -1) + return FALSE; - return write_volume (control, v); + return write_and_set_volume (control, OSS_VOLUME_JOIN_SAME (CLAMP (volume, 0, 100))); } static guint @@ -269,27 +274,26 @@ oss_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); control = OSS_STREAM_CONTROL (mmsc); - volume = CLAMP (volume, 0, 100); - if (control->priv->stereo == TRUE) { - if (channel > 1) - return FALSE; + if G_UNLIKELY (control->priv->fd == -1) + return FALSE; + if (channel > 1 || (control->priv->stereo == FALSE && channel > 0)) + return FALSE; - /* Stereo channel volume - left channel is in the lowest 8 bits and + volume = CLAMP (volume, 0, 100); + + if (control->priv->stereo == TRUE) { + /* Stereo channel volume - left channel is in the lower 8 bits and * right channel is in the higher 8 bits */ if (channel == 0) - v = volume | (control->priv->volume[1] << 8); + v = OSS_VOLUME_JOIN (volume, control->priv->volume[1]); else - v = control->priv->volume[0] | (volume << 8); + v = OSS_VOLUME_JOIN (control->priv->volume[0], volume); } else { - if (channel > 0) - return FALSE; - - /* Single channel volume - only lowest 8 bits are used */ + /* Single channel volume - only lower 8 bits are used */ v = volume; } - - return write_volume (control, v); + return write_and_set_volume (control, v); } static MateMixerChannelPosition @@ -334,24 +338,24 @@ oss_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance) { OssStreamControl *control; guint max; - gint v; + gint volume[2]; g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); control = OSS_STREAM_CONTROL (mmsc); + if G_UNLIKELY (control->priv->fd == -1) + return FALSE; + max = MAX (control->priv->volume[0], control->priv->volume[1]); if (balance <= 0) { - control->priv->volume[1] = (balance + 1.0f) * max; - control->priv->volume[0] = max; + volume[1] = (balance + 1.0f) * max; + volume[0] = max; } else { - control->priv->volume[0] = (1.0f - balance) * max; - control->priv->volume[1] = max; + volume[0] = (1.0f - balance) * max; + volume[1] = max; } - - v = control->priv->volume[0] | (control->priv->volume[1] << 8); - - return write_volume (control, v); + return write_and_set_volume (control, OSS_VOLUME_JOIN_ARRAY (volume)); } static guint @@ -378,15 +382,45 @@ oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc) return 100; } +static void +read_balance (OssStreamControl *control) +{ + gfloat balance; + guint left = control->priv->volume[0]; + guint right = control->priv->volume[1]; + + if (left == right) + balance = 0.0f; + else if (left > right) + balance = -1.0f + ((gfloat) right / (gfloat) left); + else + balance = +1.0f - ((gfloat) left / (gfloat) right); + + _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), + balance); +} + static gboolean -write_volume (OssStreamControl *control, gint volume) +write_and_set_volume (OssStreamControl *control, gint volume) { gint ret; + /* Nothing to do? */ + if (volume == OSS_VOLUME_JOIN_ARRAY (control->priv->volume)) + return TRUE; + ret = ioctl (control->priv->fd, MIXER_WRITE (control->priv->devnum), &volume); - if (ret < 0) { - g_warning ("Failed to set volume: %s", g_strerror (errno)); + if (ret == -1) return FALSE; - } + + oss_stream_control_load (control); + return TRUE; + + /* The ioctl may make adjustments to the passed volume, so make sure we have + * the correct value saved */ + control->priv->volume[0] = OSS_VOLUME_TAKE_LEFT (volume); + control->priv->volume[1] = OSS_VOLUME_TAKE_RIGHT (volume); + + g_object_notify (G_OBJECT (control), "volume"); return TRUE; } diff --git a/backends/oss/oss-stream-control.h b/backends/oss/oss-stream-control.h index c839faf..1957088 100644 --- a/backends/oss/oss-stream-control.h +++ b/backends/oss/oss-stream-control.h @@ -22,6 +22,8 @@ #include #include +#include "oss-types.h" + G_BEGIN_DECLS #define OSS_TYPE_STREAM_CONTROL \ @@ -37,7 +39,6 @@ G_BEGIN_DECLS #define OSS_STREAM_CONTROL_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_STREAM_CONTROL, OssStreamControlClass)) -typedef struct _OssStreamControl OssStreamControl; typedef struct _OssStreamControlClass OssStreamControlClass; typedef struct _OssStreamControlPrivate OssStreamControlPrivate; @@ -54,16 +55,20 @@ struct _OssStreamControlClass MateMixerStreamControlClass parent; }; -GType oss_stream_control_get_type (void) G_GNUC_CONST; +GType oss_stream_control_get_type (void) G_GNUC_CONST; + +OssStreamControl *oss_stream_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role, + OssStream *stream, + gint fd, + gint devnum, + gboolean stereo); -OssStreamControl *oss_stream_control_new (const gchar *name, - const gchar *label, - MateMixerStreamControlRole role, - gint fd, - gint devnum, - gboolean stereo); +gint oss_stream_control_get_devnum (OssStreamControl *control); -gboolean oss_stream_control_update (OssStreamControl *control); +void oss_stream_control_load (OssStreamControl *control); +void oss_stream_control_close (OssStreamControl *control); G_END_DECLS diff --git a/backends/oss/oss-stream.c b/backends/oss/oss-stream.c index 5f0c629..227c88b 100644 --- a/backends/oss/oss-stream.c +++ b/backends/oss/oss-stream.c @@ -16,32 +16,32 @@ */ #include +#include #include #include +#include -#include "oss-device.h" #include "oss-stream.h" #include "oss-stream-control.h" +#include "oss-switch.h" + +#define OSS_STREAM_SWITCH_NAME "port" struct _OssStreamPrivate { - GHashTable *controls; - OssStreamControl *control; + OssSwitch *swtch; + GList *switches; + GList *controls; }; -static void oss_stream_class_init (OssStreamClass *klass); - -static void oss_stream_init (OssStream *ostream); -static void oss_stream_dispose (GObject *object); -static void oss_stream_finalize (GObject *object); +static void oss_stream_class_init (OssStreamClass *klass); +static void oss_stream_init (OssStream *stream); +static void oss_stream_dispose (GObject *object); G_DEFINE_TYPE (OssStream, oss_stream, MATE_MIXER_TYPE_STREAM) -static MateMixerStreamControl *oss_stream_get_control (MateMixerStream *stream, - const gchar *name); -static MateMixerStreamControl *oss_stream_get_default_control (MateMixerStream *stream); - -static GList * oss_stream_list_controls (MateMixerStream *stream); +static const GList *oss_stream_list_controls (MateMixerStream *mms); +static const GList *oss_stream_list_switches (MateMixerStream *mms); static void oss_stream_class_init (OssStreamClass *klass) @@ -50,13 +50,11 @@ oss_stream_class_init (OssStreamClass *klass) MateMixerStreamClass *stream_class; object_class = G_OBJECT_CLASS (klass); - object_class->dispose = oss_stream_dispose; - object_class->finalize = oss_stream_finalize; + object_class->dispose = oss_stream_dispose; stream_class = MATE_MIXER_STREAM_CLASS (klass); - stream_class->get_control = oss_stream_get_control; - stream_class->get_default_control = oss_stream_get_default_control; - stream_class->list_controls = oss_stream_list_controls; + stream_class->list_controls = oss_stream_list_controls; + stream_class->list_switches = oss_stream_list_switches; g_type_class_add_private (object_class, sizeof (OssStreamPrivate)); } @@ -67,11 +65,6 @@ oss_stream_init (OssStream *stream) stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, OSS_TYPE_STREAM, OssStreamPrivate); - - stream->priv->controls = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); } static void @@ -81,103 +74,201 @@ oss_stream_dispose (GObject *object) stream = OSS_STREAM (object); - g_clear_object (&stream->priv->control); - g_hash_table_remove_all (stream->priv->controls); + if (stream->priv->controls != NULL) { + g_list_free_full (stream->priv->controls, g_object_unref); + stream->priv->controls = NULL; + } + if (stream->priv->switches != NULL) { + g_list_free_full (stream->priv->switches, g_object_unref); + stream->priv->switches = NULL; + } + + g_clear_object (&stream->priv->swtch); G_OBJECT_CLASS (oss_stream_parent_class)->dispose (object); } -static void -oss_stream_finalize (GObject *object) +OssStream * +oss_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction) { - OssStream *stream; + const gchar *label = mate_mixer_device_get_label (device); + + return g_object_new (OSS_TYPE_STREAM, + "name", name, + "label", label, + "device", device, + "direction", direction, + NULL); +} - stream = OSS_STREAM (object); +void +oss_stream_add_control (OssStream *stream, OssStreamControl *control) +{ + const gchar *name; + + g_return_if_fail (OSS_IS_STREAM (stream)); + g_return_if_fail (OSS_IS_STREAM_CONTROL (control)); - g_hash_table_destroy (stream->priv->controls); + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); + + stream->priv->controls = + g_list_append (stream->priv->controls, g_object_ref (control)); - G_OBJECT_CLASS (oss_stream_parent_class)->finalize (object); + g_signal_emit_by_name (G_OBJECT (stream), + "control-added", + name); } -OssStream * -oss_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags) +void +oss_stream_load (OssStream *stream) { - OssStream *stream; + GList *list; + + g_return_if_fail (OSS_IS_STREAM (stream)); + + list = stream->priv->controls; + while (list != NULL) { + OssStreamControl *control = OSS_STREAM_CONTROL (list->data); - stream = g_object_new (OSS_TYPE_STREAM, - "name", name, - "device", device, - "flags", flags, - NULL); - return stream; + oss_stream_control_load (control); + list = list->next; + } + + if (stream->priv->swtch != NULL) + oss_switch_load (stream->priv->swtch); } gboolean -oss_stream_add_control (OssStream *stream, OssStreamControl *control) +oss_stream_has_controls (OssStream *stream) { - const gchar *name; - g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE); - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); - name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); + if (stream->priv->controls != NULL) + return TRUE; - g_hash_table_insert (stream->priv->controls, - g_strdup (name), - control); - return TRUE; + return FALSE; } gboolean -oss_stream_set_default_control (OssStream *stream, OssStreamControl *control) +oss_stream_has_default_control (OssStream *stream) { g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE); - g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); - - /* This function is only used internally so avoid validating that the control - * belongs to this stream */ - if (stream->priv->control != NULL) - g_object_unref (stream->priv->control); - if G_LIKELY (control != NULL) - stream->priv->control = g_object_ref (control); - else - stream->priv->control = NULL; + if (mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)) != NULL) + return TRUE; - return TRUE; + return FALSE; } -static MateMixerStreamControl * -oss_stream_get_control (MateMixerStream *mms, const gchar *name) +OssStreamControl * +oss_stream_get_default_control (OssStream *stream) { - g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); - g_return_val_if_fail (name != NULL, NULL); + MateMixerStreamControl *control; + + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); + if (control != NULL) + return OSS_STREAM_CONTROL (control); - return g_hash_table_lookup (OSS_STREAM (mms)->priv->controls, name); + return NULL; } -static MateMixerStreamControl * -oss_stream_get_default_control (MateMixerStream *mms) +void +oss_stream_set_default_control (OssStream *stream, OssStreamControl *control) { - g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); + g_return_if_fail (OSS_IS_STREAM (stream)); + g_return_if_fail (control == NULL || OSS_IS_STREAM_CONTROL (control)); + + if (control == NULL) + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), NULL); + else + _mate_mixer_stream_set_default_control (MATE_MIXER_STREAM (stream), + MATE_MIXER_STREAM_CONTROL (control)); +} - return MATE_MIXER_STREAM_CONTROL (OSS_STREAM (mms)->priv->control); +void +oss_stream_set_switch_data (OssStream *stream, gint fd, GList *options) +{ + g_return_if_fail (OSS_IS_STREAM (stream)); + g_return_if_fail (fd != -1); + g_return_if_fail (options != NULL); + + /* Function may only be called once */ + if G_UNLIKELY (stream->priv->swtch != NULL) { + g_warn_if_reached (); + return; + } + + /* Takes ownership of options */ + stream->priv->swtch = oss_switch_new (OSS_STREAM_SWITCH_NAME, + _("Capture Source"), + fd, + options); + + /* Read the active selection */ + oss_switch_load (stream->priv->swtch); + + stream->priv->switches = g_list_prepend (NULL, g_object_ref (stream->priv->swtch)); + g_signal_emit_by_name (G_OBJECT (stream), + "switch-added", + OSS_STREAM_SWITCH_NAME); } -static GList * -oss_stream_list_controls (MateMixerStream *mms) +void +oss_stream_remove_all (OssStream *stream) { GList *list; + g_return_if_fail (OSS_IS_STREAM (stream)); + + list = stream->priv->controls; + while (list != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (list->data); + GList *next = list->next; + + oss_stream_control_close (OSS_STREAM_CONTROL (control)); + + stream->priv->controls = g_list_delete_link (stream->priv->controls, list); + g_signal_emit_by_name (G_OBJECT (stream), + "control-removed", + mate_mixer_stream_control_get_name (control)); + + g_object_unref (control); + list = next; + } + + /* Unset the default stream control */ + oss_stream_set_default_control (stream, NULL); + + if (stream->priv->swtch != NULL) { + oss_switch_close (stream->priv->swtch); + + g_list_free_full (stream->priv->switches, g_object_unref); + stream->priv->switches = NULL; + + g_signal_emit_by_name (G_OBJECT (stream), + "switch-removed", + OSS_STREAM_SWITCH_NAME); + + g_clear_object (&stream->priv->swtch); + } +} + +static const GList * +oss_stream_list_controls (MateMixerStream *mms) +{ g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (OSS_STREAM (mms)->priv->controls); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); + return OSS_STREAM (mms)->priv->controls; +} + +static const GList * +oss_stream_list_switches (MateMixerStream *mms) +{ + g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); - return list; + return OSS_STREAM (mms)->priv->switches; } diff --git a/backends/oss/oss-stream.h b/backends/oss/oss-stream.h index 0470eb7..2ade0d8 100644 --- a/backends/oss/oss-stream.h +++ b/backends/oss/oss-stream.h @@ -22,8 +22,7 @@ #include #include -#include "oss-device.h" -#include "oss-stream-control.h" +#include "oss-types.h" G_BEGIN_DECLS @@ -40,7 +39,6 @@ G_BEGIN_DECLS #define OSS_STREAM_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_STREAM, OssStreamClass)) -typedef struct _OssStream OssStream; typedef struct _OssStreamClass OssStreamClass; typedef struct _OssStreamPrivate OssStreamPrivate; @@ -57,17 +55,29 @@ struct _OssStreamClass MateMixerStreamClass parent; }; -GType oss_stream_get_type (void) G_GNUC_CONST; +GType oss_stream_get_type (void) G_GNUC_CONST; -OssStream *oss_stream_new (const gchar *name, - MateMixerDevice *device, - MateMixerStreamFlags flags); +OssStream * oss_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerDirection direction); -gboolean oss_stream_add_control (OssStream *stream, - OssStreamControl *control); +void oss_stream_add_control (OssStream *stream, + OssStreamControl *control); -gboolean oss_stream_set_default_control (OssStream *stream, - OssStreamControl *control); +void oss_stream_load (OssStream *stream); + +gboolean oss_stream_has_controls (OssStream *stream); +gboolean oss_stream_has_default_control (OssStream *stream); + +OssStreamControl *oss_stream_get_default_control (OssStream *stream); +void oss_stream_set_default_control (OssStream *stream, + OssStreamControl *control); + +void oss_stream_set_switch_data (OssStream *stream, + gint fd, + GList *options); + +void oss_stream_remove_all (OssStream *stream); G_END_DECLS diff --git a/backends/oss/oss-switch-option.c b/backends/oss/oss-switch-option.c new file mode 100644 index 0000000..862133d --- /dev/null +++ b/backends/oss/oss-switch-option.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include + +#include "oss-switch-option.h" + +struct _OssSwitchOptionPrivate +{ + guint devnum; +}; + +static void oss_switch_option_class_init (OssSwitchOptionClass *klass); +static void oss_switch_option_init (OssSwitchOption *option); + +G_DEFINE_TYPE (OssSwitchOption, oss_switch_option, MATE_MIXER_TYPE_SWITCH_OPTION) + +static void +oss_switch_option_class_init (OssSwitchOptionClass *klass) +{ + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (OssSwitchOptionPrivate)); +} + +static void +oss_switch_option_init (OssSwitchOption *option) +{ + option->priv = G_TYPE_INSTANCE_GET_PRIVATE (option, + OSS_TYPE_SWITCH_OPTION, + OssSwitchOptionPrivate); +} + +OssSwitchOption * +oss_switch_option_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint devnum) +{ + OssSwitchOption *option; + + option = g_object_new (OSS_TYPE_SWITCH_OPTION, + "name", name, + "label", label, + "icon", icon, + NULL); + + option->priv->devnum = devnum; + return option; +} + +guint +oss_switch_option_get_devnum (OssSwitchOption *option) +{ + g_return_val_if_fail (OSS_IS_SWITCH_OPTION (option), 0); + + return option->priv->devnum; +} diff --git a/backends/oss/oss-switch-option.h b/backends/oss/oss-switch-option.h new file mode 100644 index 0000000..91c21e3 --- /dev/null +++ b/backends/oss/oss-switch-option.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef OSS_SWITCH_OPTION_H +#define OSS_SWITCH_OPTION_H + +#include +#include +#include + +#include "oss-types.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_SWITCH_OPTION \ + (oss_switch_option_get_type ()) +#define OSS_SWITCH_OPTION(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_SWITCH_OPTION, OssSwitchOption)) +#define OSS_IS_SWITCH_OPTION(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_SWITCH_OPTION)) +#define OSS_SWITCH_OPTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_SWITCH_OPTION, OssSwitchOptionClass)) +#define OSS_IS_SWITCH_OPTION_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_SWITCH_OPTION)) +#define OSS_SWITCH_OPTION_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_SWITCH_OPTION, OssSwitchOptionClass)) + +typedef struct _OssSwitchOptionClass OssSwitchOptionClass; +typedef struct _OssSwitchOptionPrivate OssSwitchOptionPrivate; + +struct _OssSwitchOption +{ + MateMixerSwitchOption parent; + + /*< private >*/ + OssSwitchOptionPrivate *priv; +}; + +struct _OssSwitchOptionClass +{ + MateMixerSwitchOptionClass parent_class; +}; + +GType oss_switch_option_get_type (void) G_GNUC_CONST; + +OssSwitchOption *oss_switch_option_new (const gchar *name, + const gchar *label, + const gchar *icon, + guint devnum); + +guint oss_switch_option_get_devnum (OssSwitchOption *option); + +G_END_DECLS + +#endif /* OSS_SWITCH_OPTION_H */ diff --git a/backends/oss/oss-switch.c b/backends/oss/oss-switch.c new file mode 100644 index 0000000..b138051 --- /dev/null +++ b/backends/oss/oss-switch.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include + +#include "oss-common.h" +#include "oss-switch.h" +#include "oss-switch-option.h" + +struct _OssSwitchPrivate +{ + gint fd; + GList *options; +}; + +static void oss_switch_class_init (OssSwitchClass *klass); +static void oss_switch_init (OssSwitch *swtch); +static void oss_switch_dispose (GObject *object); +static void oss_switch_finalize (GObject *object); + +G_DEFINE_TYPE (OssSwitch, oss_switch, MATE_MIXER_TYPE_SWITCH) + +static gboolean oss_switch_set_active_option (MateMixerSwitch *mms, + MateMixerSwitchOption *mmso); + +static const GList *oss_switch_list_options (MateMixerSwitch *mms); + +static void +oss_switch_class_init (OssSwitchClass *klass) +{ + GObjectClass *object_class; + MateMixerSwitchClass *switch_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_switch_dispose; + object_class->finalize = oss_switch_finalize; + + switch_class = MATE_MIXER_SWITCH_CLASS (klass); + switch_class->set_active_option = oss_switch_set_active_option; + switch_class->list_options = oss_switch_list_options; + + g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (OssSwitchPrivate)); +} + +static void +oss_switch_init (OssSwitch *swtch) +{ + swtch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swtch, + OSS_TYPE_SWITCH, + OssSwitchPrivate); +} + +static void +oss_switch_dispose (GObject *object) +{ + OssSwitch *swtch; + + swtch = OSS_SWITCH (object); + + if (swtch->priv->options != NULL) { + g_list_free_full (swtch->priv->options, g_object_unref); + swtch->priv->options = NULL; + } + + G_OBJECT_CLASS (oss_switch_parent_class)->dispose (object); +} + +static void +oss_switch_finalize (GObject *object) +{ + OssSwitch *swtch; + + swtch = OSS_SWITCH (object); + + if (swtch->priv->fd != -1) + close (swtch->priv->fd); + + G_OBJECT_CLASS (oss_switch_parent_class)->finalize (object); +} + +OssSwitch * +oss_switch_new (const gchar *name, + const gchar *label, + gint fd, + GList *options) +{ + OssSwitch *swtch; + + swtch = g_object_new (OSS_TYPE_SWITCH, + "name", name, + "label", label, + "flags", MATE_MIXER_SWITCH_ALLOWS_NO_ACTIVE_OPTION, + "role", MATE_MIXER_SWITCH_ROLE_PORT, + NULL); + + /* Takes ownership of options */ + swtch->priv->fd = dup (fd); + swtch->priv->options = options; + + return swtch; +} + +void +oss_switch_load (OssSwitch *swtch) +{ + GList *list; + gint recsrc; + gint ret; + + g_return_if_fail (OSS_IS_SWITCH (swtch)); + + if G_UNLIKELY (swtch->priv->fd == -1) + return; + + ret = ioctl (swtch->priv->fd, MIXER_READ (SOUND_MIXER_RECSRC), &recsrc); + if (ret == -1) + return; + if (recsrc == 0) { + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), NULL); + return; + } + + list = swtch->priv->options; + while (list != NULL) { + OssSwitchOption *option = OSS_SWITCH_OPTION (list->data); + gint devnum = oss_switch_option_get_devnum (option); + + /* Mark the selected option when we find it */ + if (recsrc & (1 << devnum)) { + _mate_mixer_switch_set_active_option (MATE_MIXER_SWITCH (swtch), + MATE_MIXER_SWITCH_OPTION (option)); + return; + } + list = list->next; + } + + g_warning ("Unknown active option of switch %s", + mate_mixer_switch_get_name (MATE_MIXER_SWITCH (swtch))); +} + +void +oss_switch_close (OssSwitch *swtch) +{ + g_return_if_fail (OSS_IS_SWITCH (swtch)); + + if (swtch->priv->fd == -1) + return; + + close (swtch->priv->fd); + swtch->priv->fd = -1; +} + +static gboolean +oss_switch_set_active_option (MateMixerSwitch *mms, MateMixerSwitchOption *mmso) +{ + OssSwitch *swtch; + gint ret; + gint recsrc; + gint devnum; + + g_return_val_if_fail (OSS_IS_SWITCH (mms), FALSE); + g_return_val_if_fail (OSS_IS_SWITCH_OPTION (mmso), FALSE); + + swtch = OSS_SWITCH (mms); + + if G_UNLIKELY (swtch->priv->fd == -1) + return FALSE; + + devnum = oss_switch_option_get_devnum (OSS_SWITCH_OPTION (mmso)); + recsrc = 1 << devnum; + + ret = ioctl (swtch->priv->fd, MIXER_WRITE (SOUND_MIXER_RECSRC), &recsrc); + if (ret == -1) + return FALSE; + + return TRUE; +} + +static const GList * +oss_switch_list_options (MateMixerSwitch *mms) +{ + g_return_val_if_fail (OSS_IS_SWITCH (mms), NULL); + + return OSS_SWITCH (mms)->priv->options; +} diff --git a/backends/oss/oss-switch.h b/backends/oss/oss-switch.h new file mode 100644 index 0000000..dc91758 --- /dev/null +++ b/backends/oss/oss-switch.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef OSS_SWITCH_H +#define OSS_SWITCH_H + +#include +#include +#include + +#include "oss-types.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_SWITCH \ + (oss_switch_get_type ()) +#define OSS_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_SWITCH, OssSwitch)) +#define OSS_IS_SWITCH(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_SWITCH)) +#define OSS_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_SWITCH, OssSwitchClass)) +#define OSS_IS_SWITCH_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_SWITCH)) +#define OSS_SWITCH_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_SWITCH, OssSwitchClass)) + +typedef struct _OssSwitchClass OssSwitchClass; +typedef struct _OssSwitchPrivate OssSwitchPrivate; + +struct _OssSwitch +{ + MateMixerSwitch parent; + + /*< private >*/ + OssSwitchPrivate *priv; +}; + +struct _OssSwitchClass +{ + MateMixerSwitchClass parent_class; +}; + +GType oss_switch_get_type (void) G_GNUC_CONST; + +OssSwitch *oss_switch_new (const gchar *name, + const gchar *label, + gint fd, + GList *options); + +void oss_switch_load (OssSwitch *swtch); +void oss_switch_close (OssSwitch *swtch); + +G_END_DECLS + +#endif /* OSS_SWITCH_H */ diff --git a/backends/oss/oss-types.h b/backends/oss/oss-types.h new file mode 100644 index 0000000..afd4f58 --- /dev/null +++ b/backends/oss/oss-types.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 Michal Ratajsky + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef OSS_TYPES_H +#define OSS_TYPES_H + +G_BEGIN_DECLS + +typedef struct _OssBackend OssBackend; +typedef struct _OssDevice OssDevice; +typedef struct _OssStream OssStream; +typedef struct _OssStreamControl OssStreamControl; +typedef struct _OssSwitch OssSwitch; +typedef struct _OssSwitchOption OssSwitchOption; + +G_END_DECLS + +#endif /* OSS_TYPES_H */ -- cgit v1.2.1