From 0bc3aa762cd794da510f03229840d939ee7bc0c9 Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Fri, 18 Jul 2014 16:51:07 +0200 Subject: Add module skeleton and build support --- backends/oss/oss-backend.c | 136 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 backends/oss/oss-backend.c (limited to 'backends/oss/oss-backend.c') diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c new file mode 100644 index 0000000..22d3547 --- /dev/null +++ b/backends/oss/oss-backend.c @@ -0,0 +1,136 @@ +/* + * 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-backend.h" + +#define BACKEND_NAME "OSS" +#define BACKEND_PRIORITY 90 + +enum { + PROP_0, + PROP_STATE, + PROP_DEFAULT_INPUT, + PROP_DEFAULT_OUTPUT, + N_PROPERTIES +}; + +static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); + +static void oss_backend_class_init (OssBackendClass *klass); +static void oss_backend_class_finalize (OssBackendClass *klass); +static void oss_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void oss_backend_init (OssBackend *oss); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (OssBackend, oss_backend, + G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, + mate_mixer_backend_interface_init)) + +static gboolean backend_open (MateMixerBackend *backend); +static MateMixerState backend_get_state (MateMixerBackend *backend); + +static MateMixerBackendInfo info; + +void +backend_module_init (GTypeModule *module) +{ + oss_backend_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = OSS_TYPE_BACKEND; + info.backend_type = MATE_MIXER_BACKEND_OSS; +} + +const MateMixerBackendInfo * +backend_module_get_info (void) +{ + return &info; +} + +static void +mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) +{ + iface->open = backend_open; + iface->get_state = backend_get_state; +} + +static void +oss_backend_class_init (OssBackendClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = oss_backend_get_property; + + g_object_class_override_property (object_class, PROP_STATE, "state"); + g_object_class_override_property (object_class, PROP_DEFAULT_INPUT, "default-input"); + g_object_class_override_property (object_class, PROP_DEFAULT_OUTPUT, "default-output"); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +static void +oss_backend_class_finalize (OssBackendClass *klass) +{ +} + +static void +oss_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + switch (param_id) { + case PROP_STATE: + g_value_set_enum (value, MATE_MIXER_STATE_READY); + break; + case PROP_DEFAULT_INPUT: + case PROP_DEFAULT_OUTPUT: + g_value_set_object (value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +oss_backend_init (OssBackend *oss) +{ +} + +static gboolean +backend_open (MateMixerBackend *backend) +{ + return TRUE; +} + +static MateMixerState +backend_get_state (MateMixerBackend *backend) +{ + return MATE_MIXER_STATE_READY; +} -- cgit v1.2.1 From 59a9aabf7b66e130f68b797c5a3674798fae437b Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Fri, 25 Jul 2014 23:47:46 +0200 Subject: Support OSS --- backends/oss/oss-backend.c | 498 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 483 insertions(+), 15 deletions(-) (limited to 'backends/oss/oss-backend.c') diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c index 22d3547..6e058f8 100644 --- a/backends/oss/oss-backend.c +++ b/backends/oss/oss-backend.c @@ -15,43 +15,95 @@ * License along with this library; if not, see . */ +#include +#include +#include #include #include +#include +#include +#include +#include #include #include #include +#include #include "oss-backend.h" +#include "oss-common.h" +#include "oss-device.h" #define BACKEND_NAME "OSS" -#define BACKEND_PRIORITY 90 +#define BACKEND_PRIORITY 9 + +#define PATH_SNDSTAT "/dev/sndstat" + +struct _OssBackendPrivate +{ + GFile *sndstat; + GFile *dev; + GFileMonitor *dev_monitor; + GHashTable *devices; + GHashTable *streams; + MateMixerStream *default_input; + MateMixerStream *default_output; + MateMixerState state; +}; enum { PROP_0, PROP_STATE, PROP_DEFAULT_INPUT, - PROP_DEFAULT_OUTPUT, - N_PROPERTIES + PROP_DEFAULT_OUTPUT }; static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); static void oss_backend_class_init (OssBackendClass *klass); static void oss_backend_class_finalize (OssBackendClass *klass); + static void oss_backend_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); + static void oss_backend_init (OssBackend *oss); +static void oss_backend_dispose (GObject *object); +static void oss_backend_finalize (GObject *object); G_DEFINE_DYNAMIC_TYPE_EXTENDED (OssBackend, oss_backend, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, mate_mixer_backend_interface_init)) -static gboolean backend_open (MateMixerBackend *backend); -static MateMixerState backend_get_state (MateMixerBackend *backend); +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 void change_state (OssBackend *oss, + MateMixerState state); + +static void on_dev_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + OssBackend *oss); + +static gboolean read_device (OssBackend *oss, + const gchar *path); + +static gchar * read_device_description (OssBackend *oss, + const gchar *path, + gint fd); +static gchar * read_device_sndstat_description (const gchar *path, + GFileInputStream *input); + +static void add_device (OssBackend *oss, + OssDevice *device); +static void remove_device (OssBackend *oss, + OssDevice *device); static MateMixerBackendInfo info; @@ -66,8 +118,7 @@ backend_module_init (GTypeModule *module) info.backend_type = MATE_MIXER_BACKEND_OSS; } -const MateMixerBackendInfo * -backend_module_get_info (void) +const MateMixerBackendInfo *backend_module_get_info (void) { return &info; } @@ -75,8 +126,10 @@ backend_module_get_info (void) static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) { - iface->open = backend_open; - iface->get_state = backend_get_state; + iface->open = oss_backend_open; + iface->close = oss_backend_close; + iface->list_devices = oss_backend_list_devices; + iface->list_streams = oss_backend_list_streams; } static void @@ -85,11 +138,15 @@ oss_backend_class_init (OssBackendClass *klass) GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_backend_dispose; + object_class->finalize = oss_backend_finalize; object_class->get_property = oss_backend_get_property; g_object_class_override_property (object_class, PROP_STATE, "state"); g_object_class_override_property (object_class, PROP_DEFAULT_INPUT, "default-input"); g_object_class_override_property (object_class, PROP_DEFAULT_OUTPUT, "default-output"); + + g_type_class_add_private (object_class, sizeof (OssBackendPrivate)); } /* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ @@ -104,13 +161,20 @@ oss_backend_get_property (GObject *object, GValue *value, GParamSpec *pspec) { + OssBackend *oss; + + oss = OSS_BACKEND (object); + switch (param_id) { case PROP_STATE: - g_value_set_enum (value, MATE_MIXER_STATE_READY); + g_value_set_enum (value, oss->priv->state); break; case PROP_DEFAULT_INPUT: + g_value_set_object (value, oss->priv->default_input); + break; case PROP_DEFAULT_OUTPUT: - g_value_set_object (value, NULL); + g_value_set_object (value, oss->priv->default_output); + break; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); @@ -121,16 +185,420 @@ oss_backend_get_property (GObject *object, static void oss_backend_init (OssBackend *oss) { + oss->priv = G_TYPE_INSTANCE_GET_PRIVATE (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->streams = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + +static void +oss_backend_dispose (GObject *object) +{ + oss_backend_close (MATE_MIXER_BACKEND (object)); +} + +static void +oss_backend_finalize (GObject *object) +{ + OssBackend *oss; + + oss = OSS_BACKEND (object); + + g_hash_table_destroy (oss->priv->devices); + g_hash_table_destroy (oss->priv->streams); } static gboolean -backend_open (MateMixerBackend *backend) +oss_backend_open (MateMixerBackend *backend) { + OssBackend *oss; + GError *error = NULL; + gint i; + + g_return_val_if_fail (OSS_IS_BACKEND (backend), FALSE); + + oss = OSS_BACKEND (backend); + + /* Monitor changes on /dev to catch hot-(un)plugged devices */ + // XXX works on linux, doesn't on freebsd, what to do on netbsd/openbsd? + oss->priv->dev = g_file_new_for_path ("/dev"); + oss->priv->dev_monitor = g_file_monitor_directory (oss->priv->dev, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (oss->priv->dev_monitor != NULL) { + g_signal_connect (G_OBJECT (oss->priv->dev_monitor), + "changed", + G_CALLBACK (on_dev_monitor_changed), + oss); + } else { + g_debug ("Failed to monitor /dev: %s", error->message); + g_error_free (error); + } + +#if !defined(__linux__) && !defined(__NetBSD__) && !defined(__OpenBSD__) + /* At least on systems based on FreeBSD we will need to read devices names + * from the sndstat file, but avoid even trying that on systems where this + * is not needed and the file is not present */ + oss->priv->sndstat = g_file_new_for_path (PATH_SNDSTAT); +#endif + + for (i = 0; i < 5; i++) { + /* According to the OSS documentation the mixer devices are + * /dev/mixer0 - /dev/mixer4, of course some systems create them + * dynamically but this approach should be good enough */ + gchar *path = g_strdup_printf ("/dev/mixer%i", i); + + if (read_device (oss, path) == FALSE && i == 0) { + /* For the first mixer device check also /dev/mixer, but it + * might be a symlink to a real mixer device */ + if (g_file_test ("/dev/mixer", G_FILE_TEST_IS_SYMLINK) == FALSE) + read_device (oss, "/dev/mixer"); + } + + g_free (path); + } + + change_state (oss, MATE_MIXER_STATE_READY); return TRUE; } -static MateMixerState -backend_get_state (MateMixerBackend *backend) +void +oss_backend_close (MateMixerBackend *backend) +{ + OssBackend *oss; + + g_return_if_fail (OSS_IS_BACKEND (backend)); + + oss = OSS_BACKEND (backend); + + g_clear_object (&oss->priv->default_input); + g_clear_object (&oss->priv->default_output); + + g_hash_table_remove_all (oss->priv->streams); + g_hash_table_remove_all (oss->priv->devices); + + g_clear_object (&oss->priv->dev_monitor); + g_clear_object (&oss->priv->dev); + g_clear_object (&oss->priv->sndstat); +} + +static 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 sorted 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 NULL; +} + +static GList * +oss_backend_list_streams (MateMixerBackend *backend) +{ + GList *list; + + g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); + + /* Convert the hash table to a sorted linked list, this list is expected + * to be cached in the main library */ + list = g_hash_table_get_values (OSS_BACKEND (backend)->priv->streams); + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; + } + return NULL; +} + +static void +change_state (OssBackend *oss, MateMixerState state) +{ + if (oss->priv->state == state) + return; + + oss->priv->state = state; + + g_object_notify (G_OBJECT (oss), "state"); +} + +static void +on_dev_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + OssBackend *oss) +{ + gchar *path; + + if (event_type != G_FILE_MONITOR_EVENT_CREATED && + event_type != G_FILE_MONITOR_EVENT_DELETED) + return; + + path = g_file_get_path (file); + + /* Only handle creation and deletion of mixer devices */ + if (g_str_has_prefix (path, "/dev/mixer") == FALSE) { + g_free (path); + return; + } + if (strcmp (path, "/dev/mixer") == 0 && + g_file_test (path, G_FILE_TEST_IS_SYMLINK) == TRUE) { + g_free (path); + return; + } + + if (event_type == G_FILE_MONITOR_EVENT_DELETED) { + OssDevice *device; + + device = g_hash_table_lookup (oss->priv->devices, path); + if (device != NULL) + remove_device (oss, device); + } else + read_device (oss, path); + + g_free (path); +} + +static gboolean +read_device (OssBackend *oss, const gchar *path) +{ + OssDevice *device; + gint fd; + gboolean ret; + const gchar *description; + + /* We assume that the name and capabilities of a device do not change */ + device = g_hash_table_lookup (oss->priv->devices, path); + if (G_UNLIKELY (device != NULL)) { + g_debug ("Attempt to re-read already know device %s", path); + return TRUE; + } + + fd = g_open (path, O_RDWR, 0); + if (fd == -1) { + if (errno != ENOENT && errno != ENXIO) + g_debug ("%s: %s", path, g_strerror (errno)); + return FALSE; + } + + device = oss_device_new (path, fd); + + description = read_device_description (oss, path, fd); + if (description != NULL) + oss_device_set_description (device, description); + else + oss_device_set_description (device, _("Unknown device")); + + /* Close the descriptor as the device should dup it if it intends + * to keep it */ + g_close (fd, NULL); + + ret = oss_device_read (device); + if (ret == TRUE) + add_device (oss, device); + + g_object_unref (device); + return ret; +} + +static gchar * +read_device_description (OssBackend *oss, const gchar *path, gint fd) +{ + struct mixer_info info; + + if (ioctl (fd, SOUND_MIXER_INFO, &info) == 0) { + /* Prefer device name supplied by the system, but this fails on FreeBSD */ + return g_strdup (info.name); + } + + /* Reading the sndstat file is a bit desperate, but seem to be the only + * way to determine the name of the sound card on FreeBSD, it also has an + * advantage in being able to find out the default one */ + if (oss->priv->sndstat != NULL) { + GError *error = NULL; + GFileInputStream *input = g_file_read (oss->priv->sndstat, NULL, &error); + + if (input == NULL) { + /* The file can only be open by one application, otherwise the call + * fails with EBUSY */ + if (error->code == G_IO_ERROR_BUSY) { + g_debug ("Delayed reading %s as it is busy", PATH_SNDSTAT); + // XXX use timeout and try again + } else { + if (error->code == G_IO_ERROR_NOT_FOUND) + g_debug ("Device description is unknown as %s does not exist", + PATH_SNDSTAT); + else + g_debug ("%s: %s", PATH_SNDSTAT, error->message); + + g_clear_object (&oss->priv->sndstat); + } + g_error_free (error); + } else + return read_device_sndstat_description (path, input); + } + + return NULL; +} + +static gchar * +read_device_sndstat_description (const gchar *path, GFileInputStream *input) { - return MATE_MIXER_STATE_READY; + gchar *line; + gchar *prefix; + gchar *description = NULL; + GDataInputStream *stream; + + if (G_UNLIKELY (g_str_has_prefix (path, "/dev/mixer")) == FALSE) { + g_warn_if_reached (); + return NULL; + } + + /* We assume that the mixer device number matches the pcm number in the + * sndstat file, this is a bit desperate, but it seems to do the + * right thing in practice */ + prefix = g_strdup_printf ("pcm%u: ", + (guint) g_ascii_strtoull (path + sizeof ("/dev/mixer") - 1, + NULL, + 10)); + + stream = g_data_input_stream_new (G_INPUT_STREAM (input)); + + while (TRUE) { + GError *error = NULL; + gchar *p; + + line = g_data_input_stream_read_line (stream, NULL, NULL, &error); + if (line == NULL) { + if (error != NULL) { + g_warning ("%s: %s", path, error->message); + g_error_free (error); + } + break; + } + + if (g_str_has_prefix (line, prefix) == FALSE) + continue; + + /* Example line: + * pcm0: (play) default */ + p = strchr (line, '<'); + if (p != NULL && *p && *(++p)) { + gchar *end = strchr (p, '>'); + + if (end != NULL) + description = g_strndup (p, end - p); + } + + // XXX we can also read "default" at the end of the line + // XXX http://ashish.is.lostca.se/2011/05/23/default-sound-device-in-freebsd/ + if (g_str_has_suffix (line, "default") || + g_str_has_suffix (line, "default)")) + ; + + if (description != NULL) + break; + } + + g_object_unref (stream); + g_free (prefix); + + return description; +} + +static void +add_device (OssBackend *oss, OssDevice *device) +{ + MateMixerStream *stream; + + /* Add device, file path is used as the key rather than the name, because + * the name is not known until an OssDevice instance is created */ + g_hash_table_insert (oss->priv->devices, + g_strdup (oss_device_get_path (device)), + g_object_ref (device)); + + g_signal_emit_by_name (G_OBJECT (oss), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + /* Add streams if they exist */ + stream = oss_device_get_input_stream (device); + if (stream != NULL) { + g_hash_table_insert (oss->priv->streams, + g_strdup (mate_mixer_stream_get_name (stream)), + g_object_ref (stream)); + + g_signal_emit_by_name (G_OBJECT (oss), + "stream-added", + mate_mixer_stream_get_name (stream)); + } + + stream = oss_device_get_output_stream (device); + if (stream != NULL) { + g_hash_table_insert (oss->priv->streams, + g_strdup (mate_mixer_stream_get_name (stream)), + g_object_ref (stream)); + + g_signal_emit_by_name (G_OBJECT (oss), + "stream-added", + mate_mixer_stream_get_name (stream)); + } +} + +static void +remove_device (OssBackend *oss, OssDevice *device) +{ + MateMixerStream *stream; + const gchar *path; + + /* Remove the device streams first as they are a part of the device */ + stream = oss_device_get_input_stream (device); + if (stream != NULL) { + const gchar *name = mate_mixer_stream_get_name (stream); + + g_hash_table_remove (oss->priv->streams, name); + g_signal_emit_by_name (G_OBJECT (oss), + "stream-removed", + name); + } + + stream = oss_device_get_output_stream (device); + if (stream != NULL) { + const gchar *name = mate_mixer_stream_get_name (stream); + + g_hash_table_remove (oss->priv->streams, stream); + g_signal_emit_by_name (G_OBJECT (oss), + "stream-removed", + name); + } + + path = oss_device_get_path (device); + + /* Remove the device */ + g_object_ref (device); + + g_hash_table_remove (oss->priv->devices, path); + g_signal_emit_by_name (G_OBJECT (oss), + "device-removed", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); } -- cgit v1.2.1 From 6c6d4239ddc807e922df3874654f99eea291aadb Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Tue, 12 Aug 2014 04:56:55 +0200 Subject: Add ALSA, improve OSS and remove OSS4 --- backends/oss/oss-backend.c | 699 ++++++++++++++++++++++----------------------- 1 file changed, 341 insertions(+), 358 deletions(-) (limited to 'backends/oss/oss-backend.c') diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c index 6e058f8..2b5eca7 100644 --- a/backends/oss/oss-backend.c +++ b/backends/oss/oss-backend.c @@ -19,91 +19,78 @@ #include #include #include -#include #include -#include #include -#include +#include -#include -#include -#include -#include +#include +#include #include "oss-backend.h" #include "oss-common.h" #include "oss-device.h" +#include "oss-stream.h" #define BACKEND_NAME "OSS" #define BACKEND_PRIORITY 9 -#define PATH_SNDSTAT "/dev/sndstat" +#if !defined(__linux__) && !defined(__NetBSD__) && !defined(__OpenBSD__) + /* At least on systems based on FreeBSD we will need to read device names + * from the sndstat file, but avoid even trying that on systems where this + * is not needed and the file is not present */ +#define OSS_PATH_SNDSTAT "/dev/sndstat" +#endif + +#define OSS_MAX_DEVICES 32 struct _OssBackendPrivate { - GFile *sndstat; - GFile *dev; - GFileMonitor *dev_monitor; - GHashTable *devices; - GHashTable *streams; - MateMixerStream *default_input; - MateMixerStream *default_output; - MateMixerState state; -}; - -enum { - PROP_0, - PROP_STATE, - PROP_DEFAULT_INPUT, - PROP_DEFAULT_OUTPUT + gchar *default_device; + GSource *timeout_source; + GHashTable *devices; }; -static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); - static void oss_backend_class_init (OssBackendClass *klass); static void oss_backend_class_finalize (OssBackendClass *klass); - -static void oss_backend_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec); - static void oss_backend_init (OssBackend *oss); static void oss_backend_dispose (GObject *object); static void oss_backend_finalize (GObject *object); -G_DEFINE_DYNAMIC_TYPE_EXTENDED (OssBackend, oss_backend, - G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, - mate_mixer_backend_interface_init)) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +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 GList * oss_backend_list_devices (MateMixerBackend *backend); -static GList * oss_backend_list_streams (MateMixerBackend *backend); +static gboolean read_devices (OssBackend *oss); -static void change_state (OssBackend *oss, - MateMixerState state); +static gboolean read_device (OssBackend *oss, + const gchar *path, + gboolean *added); -static void on_dev_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - OssBackend *oss); +static gchar * read_device_label (OssBackend *oss, + const gchar *path, + gint fd); -static gboolean read_device (OssBackend *oss, - const gchar *path); +static gchar * read_device_label_sndstat (OssBackend *oss, + const gchar *sndstat, + const gchar *path, + guint index) G_GNUC_UNUSED; -static gchar * read_device_description (OssBackend *oss, - const gchar *path, - gint fd); -static gchar * read_device_sndstat_description (const gchar *path, - GFileInputStream *input); +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 select_default_input_stream (OssBackend *oss); +static void select_default_output_stream (OssBackend *oss); static MateMixerBackendInfo info; @@ -123,65 +110,31 @@ const MateMixerBackendInfo *backend_module_get_info (void) return &info; } -static void -mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) -{ - iface->open = oss_backend_open; - iface->close = oss_backend_close; - iface->list_devices = oss_backend_list_devices; - iface->list_streams = oss_backend_list_streams; -} - static void oss_backend_class_init (OssBackendClass *klass) { - GObjectClass *object_class; + GObjectClass *object_class; + MateMixerBackendClass *backend_class; object_class = G_OBJECT_CLASS (klass); - object_class->dispose = oss_backend_dispose; - object_class->finalize = oss_backend_finalize; - object_class->get_property = oss_backend_get_property; + object_class->dispose = oss_backend_dispose; + object_class->finalize = oss_backend_finalize; - g_object_class_override_property (object_class, PROP_STATE, "state"); - g_object_class_override_property (object_class, PROP_DEFAULT_INPUT, "default-input"); - g_object_class_override_property (object_class, PROP_DEFAULT_OUTPUT, "default-output"); + backend_class = MATE_MIXER_BACKEND_CLASS (klass); + backend_class->open = oss_backend_open; + backend_class->close = oss_backend_close; + backend_class->list_devices = oss_backend_list_devices; + backend_class->list_streams = oss_backend_list_streams; g_type_class_add_private (object_class, sizeof (OssBackendPrivate)); } -/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE() */ static void oss_backend_class_finalize (OssBackendClass *klass) { } -static void -oss_backend_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) -{ - OssBackend *oss; - - oss = OSS_BACKEND (object); - - switch (param_id) { - case PROP_STATE: - g_value_set_enum (value, oss->priv->state); - break; - case PROP_DEFAULT_INPUT: - g_value_set_object (value, oss->priv->default_input); - break; - case PROP_DEFAULT_OUTPUT: - g_value_set_object (value, oss->priv->default_output); - break; - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - break; - } -} - static void oss_backend_init (OssBackend *oss) { @@ -193,17 +146,21 @@ oss_backend_init (OssBackend *oss) g_str_equal, g_free, g_object_unref); - - oss->priv->streams = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); } static void oss_backend_dispose (GObject *object) { - oss_backend_close (MATE_MIXER_BACKEND (object)); + MateMixerBackend *backend; + MateMixerState state; + + backend = MATE_MIXER_BACKEND (object); + + state = mate_mixer_backend_get_state (backend); + if (state != MATE_MIXER_STATE_IDLE) + oss_backend_close (backend); + + G_OBJECT_CLASS (oss_backend_parent_class)->dispose (object); } static void @@ -214,61 +171,34 @@ oss_backend_finalize (GObject *object) oss = OSS_BACKEND (object); g_hash_table_destroy (oss->priv->devices); - g_hash_table_destroy (oss->priv->streams); + + G_OBJECT_CLASS (oss_backend_parent_class)->finalize (object); } static gboolean oss_backend_open (MateMixerBackend *backend) { OssBackend *oss; - GError *error = NULL; - gint i; g_return_val_if_fail (OSS_IS_BACKEND (backend), FALSE); oss = OSS_BACKEND (backend); - /* Monitor changes on /dev to catch hot-(un)plugged devices */ - // XXX works on linux, doesn't on freebsd, what to do on netbsd/openbsd? - oss->priv->dev = g_file_new_for_path ("/dev"); - oss->priv->dev_monitor = g_file_monitor_directory (oss->priv->dev, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (oss->priv->dev_monitor != NULL) { - g_signal_connect (G_OBJECT (oss->priv->dev_monitor), - "changed", - G_CALLBACK (on_dev_monitor_changed), - oss); - } else { - g_debug ("Failed to monitor /dev: %s", error->message); - g_error_free (error); - } - -#if !defined(__linux__) && !defined(__NetBSD__) && !defined(__OpenBSD__) - /* At least on systems based on FreeBSD we will need to read devices names - * from the sndstat file, but avoid even trying that on systems where this - * is not needed and the file is not present */ - oss->priv->sndstat = g_file_new_for_path (PATH_SNDSTAT); -#endif - - for (i = 0; i < 5; i++) { - /* According to the OSS documentation the mixer devices are - * /dev/mixer0 - /dev/mixer4, of course some systems create them - * dynamically but this approach should be good enough */ - gchar *path = g_strdup_printf ("/dev/mixer%i", i); - - if (read_device (oss, path) == FALSE && i == 0) { - /* For the first mixer device check also /dev/mixer, but it - * might be a symlink to a real mixer device */ - if (g_file_test ("/dev/mixer", G_FILE_TEST_IS_SYMLINK) == FALSE) - read_device (oss, "/dev/mixer"); - } - - g_free (path); - } - - change_state (oss, MATE_MIXER_STATE_READY); + /* Discover added or removed OSS devices every second */ + oss->priv->timeout_source = g_timeout_source_new_seconds (1); + g_source_set_callback (oss->priv->timeout_source, + (GSourceFunc) read_devices, + oss, + NULL); + g_source_attach (oss->priv->timeout_source, + g_main_context_get_thread_default ()); + + /* Read the initial list of devices so we have some starting point, there + * isn't really a way to detect errors here, failing to add a device may + * be a device-related problem so make the backend always open successfully */ + read_devices (oss); + + _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_READY); return TRUE; } @@ -281,15 +211,13 @@ oss_backend_close (MateMixerBackend *backend) oss = OSS_BACKEND (backend); - g_clear_object (&oss->priv->default_input); - g_clear_object (&oss->priv->default_output); - - g_hash_table_remove_all (oss->priv->streams); + g_source_destroy (oss->priv->timeout_source); g_hash_table_remove_all (oss->priv->devices); - g_clear_object (&oss->priv->dev_monitor); - g_clear_object (&oss->priv->dev); - g_clear_object (&oss->priv->sndstat); + g_free (oss->priv->default_device); + oss->priv->default_device = NULL; + + _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); } static GList * @@ -299,306 +227,361 @@ oss_backend_list_devices (MateMixerBackend *backend) g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); - /* Convert the hash table to a sorted linked list, this list is expected - * to be cached in the main library */ + /* 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) { + if (list != NULL) g_list_foreach (list, (GFunc) g_object_ref, NULL); - return list; - } - return NULL; + return list; } static GList * oss_backend_list_streams (MateMixerBackend *backend) { - GList *list; + OssBackend *oss; + GHashTableIter iter; + gpointer value; + GList *list = NULL; g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); - /* Convert the hash table to a sorted linked list, this list is expected - * to be cached in the main library */ - list = g_hash_table_get_values (OSS_BACKEND (backend)->priv->streams); - if (list != NULL) { - g_list_foreach (list, (GFunc) g_object_ref, NULL); + oss = OSS_BACKEND (backend); - return list; + /* 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); + + while (g_hash_table_iter_next (&iter, NULL, &value)) { + OssDevice *device = OSS_DEVICE (value); + OssStream *stream; + + 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); } - return NULL; -} - -static void -change_state (OssBackend *oss, MateMixerState state) -{ - if (oss->priv->state == state) - return; - oss->priv->state = state; + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); - g_object_notify (G_OBJECT (oss), "state"); + return list; } -static void -on_dev_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - OssBackend *oss) +static gboolean +read_devices (OssBackend *oss) { - gchar *path; + gint i; + gboolean added = FALSE; - if (event_type != G_FILE_MONITOR_EVENT_CREATED && - event_type != G_FILE_MONITOR_EVENT_DELETED) - return; + for (i = 0; i < OSS_MAX_DEVICES; i++) { + gboolean added_current; + gchar *path = g_strdup_printf ("/dev/mixer%i", i); - path = g_file_get_path (file); + /* 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 + * of the real mixer device nodes, on Linux /dev/mixer is the first + * device and /dev/mixer1 is the second device. + * Handle all of these cases by trying /dev/mixer if /dev/mixer0 fails */ + if (read_device (oss, path, &added_current) == FALSE && i == 0) + read_device (oss, "/dev/mixer", &added_current); + + if (added_current) + added = TRUE; - /* Only handle creation and deletion of mixer devices */ - if (g_str_has_prefix (path, "/dev/mixer") == FALSE) { - g_free (path); - return; - } - if (strcmp (path, "/dev/mixer") == 0 && - g_file_test (path, G_FILE_TEST_IS_SYMLINK) == TRUE) { g_free (path); - return; } - if (event_type == G_FILE_MONITOR_EVENT_DELETED) { - OssDevice *device; - - device = g_hash_table_lookup (oss->priv->devices, path); - if (device != NULL) - remove_device (oss, device); - } else - read_device (oss, path); - - g_free (path); + /* If any card has been added, make sure we have the most suitable default + * input and output streams */ + if (added == TRUE) { + select_default_input_stream (oss); + select_default_output_stream (oss); + } + return G_SOURCE_CONTINUE; } static gboolean -read_device (OssBackend *oss, const gchar *path) +read_device (OssBackend *oss, const gchar *path, gboolean *added) { - OssDevice *device; - gint fd; - gboolean ret; - const gchar *description; + OssDevice *device; + gint fd; + gchar *bname; + gchar *label; - /* We assume that the name and capabilities of a device do not change */ device = g_hash_table_lookup (oss->priv->devices, path); - if (G_UNLIKELY (device != NULL)) { - g_debug ("Attempt to re-read already know device %s", path); - return TRUE; - } + *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); return FALSE; } - device = oss_device_new (path, fd); + /* Don't proceed if the device is already known, opening the device was + * 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) { + close (fd); + return TRUE; + } - description = read_device_description (oss, path, fd); - if (description != NULL) - oss_device_set_description (device, description); - else - oss_device_set_description (device, _("Unknown device")); + bname = g_path_get_basename (path); + label = read_device_label (oss, path, fd); - /* Close the descriptor as the device should dup it if it intends - * to keep it */ - g_close (fd, NULL); + device = oss_device_new (bname, label, path, fd); + g_free (bname); + g_free (label); - ret = oss_device_read (device); - if (ret == TRUE) + close (fd); + + if ((*added = oss_device_open (device)) == TRUE) add_device (oss, device); g_object_unref (device); - return ret; + return *added; } static gchar * -read_device_description (OssBackend *oss, const gchar *path, gint fd) +read_device_label (OssBackend *oss, const gchar *path, gint fd) { - struct mixer_info info; + guint index; - if (ioctl (fd, SOUND_MIXER_INFO, &info) == 0) { - /* Prefer device name supplied by the system, but this fails on FreeBSD */ - return g_strdup (info.name); - } +#ifdef SOUND_MIXER_INFO + do { + struct mixer_info info; - /* Reading the sndstat file is a bit desperate, but seem to be the only - * way to determine the name of the sound card on FreeBSD, it also has an - * advantage in being able to find out the default one */ - if (oss->priv->sndstat != NULL) { - GError *error = NULL; - GFileInputStream *input = g_file_read (oss->priv->sndstat, NULL, &error); - - if (input == NULL) { - /* The file can only be open by one application, otherwise the call - * fails with EBUSY */ - if (error->code == G_IO_ERROR_BUSY) { - g_debug ("Delayed reading %s as it is busy", PATH_SNDSTAT); - // XXX use timeout and try again - } else { - if (error->code == G_IO_ERROR_NOT_FOUND) - g_debug ("Device description is unknown as %s does not exist", - PATH_SNDSTAT); - else - g_debug ("%s: %s", PATH_SNDSTAT, error->message); + /* Prefer device name supplied by the system, but this calls fails + * with EINVAL on FreeBSD */ + if (ioctl (fd, SOUND_MIXER_INFO, &info) == 0) + return g_strdup (info.name); + } while (0); +#endif - g_clear_object (&oss->priv->sndstat); - } - g_error_free (error); - } else - return read_device_sndstat_description (path, input); - } + index = (guint) g_ascii_strtoull (path + sizeof ("/dev/mixer") - 1, + NULL, + 10); +#ifdef OSS_PATH_SNDSTAT + /* If the ioctl doesn't succeed, assume that the mixer device number + * matches the pcm number in the sndstat file, this is a bit desperate, but + * it should be correct on FreeBSD */ + do { + gchar *label; + + label = read_device_label_sndstat (oss, OSS_PATH_SNDSTAT, path, index); + if (label != NULL) + return label; + } while (0); +#endif - return NULL; + return g_strdup_printf (_("OSS Mixer %d"), index); } static gchar * -read_device_sndstat_description (const gchar *path, GFileInputStream *input) +read_device_label_sndstat (OssBackend *oss, + const gchar *sndstat, + const gchar *path, + guint index) { - gchar *line; - gchar *prefix; - gchar *description = NULL; - GDataInputStream *stream; - - if (G_UNLIKELY (g_str_has_prefix (path, "/dev/mixer")) == FALSE) { - g_warn_if_reached (); + FILE *fp; + gchar *label = NULL; + gchar *prefix; + gchar line[512]; + + fp = g_fopen (sndstat, "r"); + if (fp == NULL) { + g_debug ("Failed to open %s: %s", sndstat, g_strerror (errno)); return NULL; } - /* We assume that the mixer device number matches the pcm number in the - * sndstat file, this is a bit desperate, but it seems to do the - * right thing in practice */ - prefix = g_strdup_printf ("pcm%u: ", - (guint) g_ascii_strtoull (path + sizeof ("/dev/mixer") - 1, - NULL, - 10)); - - stream = g_data_input_stream_new (G_INPUT_STREAM (input)); - - while (TRUE) { - GError *error = NULL; - gchar *p; - - line = g_data_input_stream_read_line (stream, NULL, NULL, &error); - if (line == NULL) { - if (error != NULL) { - g_warning ("%s: %s", path, error->message); - g_error_free (error); - } - break; - } + /* Example line: + * pcm0: (play) default */ + prefix = g_strdup_printf ("pcm%u: ", index); + + while (fgets (line, sizeof (line), fp) != NULL) { + gchar *p; if (g_str_has_prefix (line, prefix) == FALSE) continue; - /* Example line: - * pcm0: (play) default */ p = strchr (line, '<'); if (p != NULL && *p && *(++p)) { gchar *end = strchr (p, '>'); - if (end != NULL) - description = g_strndup (p, end - p); - } + if (end != NULL) { + label = g_strndup (p, end - p); - // XXX we can also read "default" at the end of the line - // XXX http://ashish.is.lostca.se/2011/05/23/default-sound-device-in-freebsd/ - if (g_str_has_suffix (line, "default") || - g_str_has_suffix (line, "default)")) - ; + /* Normally the default OSS device is /dev/dsp, but on FreeBSD + * /dev/dsp doesn't physically exist on the filesystem, but is + * managed by the kernel according to the user-settable default + * device, in sndstat the default card definition ends with the + * word "default" */ + if (g_str_has_suffix (line, "default")) { + g_free (oss->priv->default_device); - if (description != NULL) + oss->priv->default_device = g_strdup (path); + } + } else { + g_debug ("Failed to read sndstat line: %s", line); + } break; + } } - g_object_unref (stream); g_free (prefix); + fclose (fp); - return description; + return label; } static void add_device (OssBackend *oss, OssDevice *device) { - MateMixerStream *stream; + const gchar *name; + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); - /* Add device, file path is used as the key rather than the name, because - * the name is not known until an OssDevice instance is created */ g_hash_table_insert (oss->priv->devices, g_strdup (oss_device_get_path (device)), g_object_ref (device)); - g_signal_emit_by_name (G_OBJECT (oss), - "device-added", - mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); - - /* Add streams if they exist */ - stream = oss_device_get_input_stream (device); - if (stream != NULL) { - g_hash_table_insert (oss->priv->streams, - g_strdup (mate_mixer_stream_get_name (stream)), - g_object_ref (stream)); - - g_signal_emit_by_name (G_OBJECT (oss), - "stream-added", - mate_mixer_stream_get_name (stream)); - } + // XXX make device emit it when closed + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (remove_stream), + oss); - stream = oss_device_get_output_stream (device); - if (stream != NULL) { - g_hash_table_insert (oss->priv->streams, - g_strdup (mate_mixer_stream_get_name (stream)), - g_object_ref (stream)); + g_signal_emit_by_name (G_OBJECT (oss), "device-added", name); - g_signal_emit_by_name (G_OBJECT (oss), - "stream-added", - mate_mixer_stream_get_name (stream)); - } + oss_device_load (device); } static void remove_device (OssBackend *oss, OssDevice *device) +{ + const gchar *name; + const gchar *path; + + path = oss_device_get_path (device); + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (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); + + g_signal_emit_by_name (G_OBJECT (oss), + "device-removed", + name); +} + +static void +remove_stream (OssBackend *oss, const gchar *name) { MateMixerStream *stream; - const gchar *path; - /* Remove the device streams first as they are a part of the device */ - stream = oss_device_get_input_stream (device); - if (stream != NULL) { - const gchar *name = mate_mixer_stream_get_name (stream); + 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); + + stream = mate_mixer_backend_get_default_output_stream (MATE_MIXER_BACKEND (oss)); + + if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) + select_default_output_stream (oss); +} - g_hash_table_remove (oss->priv->streams, name); - g_signal_emit_by_name (G_OBJECT (oss), - "stream-removed", - name); +static void +select_default_input_stream (OssBackend *oss) +{ + OssDevice *device = NULL; + OssStream *stream; + gint i; + + /* 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); + 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)); + return; + } } - stream = oss_device_get_output_stream (device); - if (stream != NULL) { - const gchar *name = mate_mixer_stream_get_name (stream); + for (i = 0; i < OSS_MAX_DEVICES; i++) { + gchar *path = g_strdup_printf ("/dev/mixer%i", i); - g_hash_table_remove (oss->priv->streams, stream); - g_signal_emit_by_name (G_OBJECT (oss), - "stream-removed", - name); + device = g_hash_table_lookup (oss->priv->devices, path); + if (device == NULL && i == 0) + device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + + 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; + } + } + g_free (path); } - path = oss_device_get_path (device); + /* In the worst case unset the default stream */ + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), NULL); +} - /* Remove the device */ - g_object_ref (device); +static void +select_default_output_stream (OssBackend *oss) +{ + OssDevice *device = NULL; + OssStream *stream; + gint i; + + /* 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); + 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)); + return; + } + } - g_hash_table_remove (oss->priv->devices, path); - g_signal_emit_by_name (G_OBJECT (oss), - "device-removed", - mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + for (i = 0; i < OSS_MAX_DEVICES; i++) { + gchar *path = g_strdup_printf ("/dev/mixer%i", i); - g_object_unref (device); + device = g_hash_table_lookup (oss->priv->devices, path); + if (device == NULL && i == 0) + device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + + 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; + } + } + g_free (path); + } + + /* In the worst case unset the default stream */ + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), NULL); } -- cgit v1.2.1