diff options
Diffstat (limited to 'backends/oss4/oss4-backend.c')
-rw-r--r-- | backends/oss4/oss4-backend.c | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/backends/oss4/oss4-backend.c b/backends/oss4/oss4-backend.c new file mode 100644 index 0000000..bd79fdf --- /dev/null +++ b/backends/oss4/oss4-backend.c @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <glib.h> +#include <glib-object.h> +#include <glib/gstdio.h> +#include <glib/gprintf.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <libmatemixer/matemixer-backend.h> +#include <libmatemixer/matemixer-backend-module.h> +#include <libmatemixer/matemixer-enums.h> +#include <libmatemixer/matemixer-stream.h> + +#include "oss4-backend.h" +#include "oss4-common.h" +#include "oss4-device.h" + +#define BACKEND_NAME "OSS4" +#define BACKEND_PRIORITY 8 + +#define PATH_SNDSTAT "/dev/sndstat" + +struct _Oss4BackendPrivate +{ + gint fd; + gchar *sndstat; + GHashTable *devices; + GHashTable *streams; + MateMixerStream *default_input; + MateMixerStream *default_output; + MateMixerState state; +}; + +enum { + PROP_0, + PROP_STATE, + PROP_DEFAULT_INPUT, + PROP_DEFAULT_OUTPUT +}; + +static void mate_mixer_backend_interface_init (MateMixerBackendInterface *iface); + +static void oss4_backend_class_init (Oss4BackendClass *klass); +static void oss4_backend_class_finalize (Oss4BackendClass *klass); + +static void oss4_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void oss4_backend_init (Oss4Backend *oss); +static void oss4_backend_dispose (GObject *object); +static void oss4_backend_finalize (GObject *object); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (Oss4Backend, oss4_backend, + G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (MATE_MIXER_TYPE_BACKEND, + mate_mixer_backend_interface_init)) + +static gboolean oss4_backend_open (MateMixerBackend *backend); +static void oss4_backend_close (MateMixerBackend *backend); +static GList * oss4_backend_list_devices (MateMixerBackend *backend); +static GList * oss4_backend_list_streams (MateMixerBackend *backend); + +static void change_state (Oss4Backend *oss, + MateMixerState state); + +static gboolean read_device (Oss4Backend *oss, gint index); + +static gchar * read_device_sndstat_description (Oss4Backend *oss, + const gchar *prefix); + +static void add_device (Oss4Backend *oss, Oss4Device *device); +static void remove_device (Oss4Backend *oss, Oss4Device *device); + +static MateMixerBackendInfo info; + +void +backend_module_init (GTypeModule *module) +{ + oss4_backend_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = OSS4_TYPE_BACKEND; + info.backend_type = MATE_MIXER_BACKEND_OSS4; +} + +const MateMixerBackendInfo *backend_module_get_info (void) +{ + return &info; +} + +static void +mate_mixer_backend_interface_init (MateMixerBackendInterface *iface) +{ + iface->open = oss4_backend_open; + iface->close = oss4_backend_close; + iface->list_devices = oss4_backend_list_devices; + iface->list_streams = oss4_backend_list_streams; +} + +static void +oss4_backend_class_init (Oss4BackendClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss4_backend_dispose; + object_class->finalize = oss4_backend_finalize; + object_class->get_property = oss4_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 (Oss4BackendPrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE_EXTENDED() */ +static void +oss4_backend_class_finalize (Oss4BackendClass *klass) +{ +} + +static void +oss4_backend_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + Oss4Backend *oss; + + oss = OSS4_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 +oss4_backend_init (Oss4Backend *oss) +{ + oss->priv = G_TYPE_INSTANCE_GET_PRIVATE (oss, + OSS4_TYPE_BACKEND, + Oss4BackendPrivate); + + oss->priv->devices = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + + oss->priv->streams = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + +static void +oss4_backend_dispose (GObject *object) +{ + oss4_backend_close (MATE_MIXER_BACKEND (object)); +} + +static void +oss4_backend_finalize (GObject *object) +{ + Oss4Backend *oss; + + oss = OSS4_BACKEND (object); + + g_hash_table_destroy (oss->priv->devices); + g_hash_table_destroy (oss->priv->streams); +} + +static gboolean +oss4_backend_open (MateMixerBackend *backend) +{ + Oss4Backend *oss; + gint fd; + gint i; + gint ret; + struct oss_sysinfo info; + + g_return_val_if_fail (OSS4_IS_BACKEND (backend), FALSE); + + oss = OSS4_BACKEND (backend); + + fd = g_open ("/dev/mixer", O_RDONLY, 0); + if (fd == -1) + fd = g_open ("/dev/mixer0", O_RDONLY, 0); + if (fd == -1) { + change_state (oss, MATE_MIXER_STATE_FAILED); + return FALSE; + } + + /* Query the number of mixer devices */ + ret = ioctl (fd, OSS_SYSINFO, &info); + if (ret == -1) { + close (fd); + change_state (oss, MATE_MIXER_STATE_FAILED); + return FALSE; + } + + g_debug ("The sound system is %s version %s", + info.product, + info.version); + +#if !defined(__linux__) + /* 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 = PATH_SNDSTAT; +#endif + + oss->priv->fd = fd; + + for (i = 0; i < info.nummixers; i++) + read_device (oss, i); + + change_state (oss, MATE_MIXER_STATE_READY); + return TRUE; +} + +void +oss4_backend_close (MateMixerBackend *backend) +{ + Oss4Backend *oss; + + g_return_if_fail (OSS4_IS_BACKEND (backend)); + + oss = OSS4_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); +} + +static GList * +oss4_backend_list_devices (MateMixerBackend *backend) +{ + GList *list; + + g_return_val_if_fail (OSS4_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 (OSS4_BACKEND (backend)->priv->devices); + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; + } + return NULL; +} + +static GList * +oss4_backend_list_streams (MateMixerBackend *backend) +{ + GList *list; + + g_return_val_if_fail (OSS4_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 (OSS4_BACKEND (backend)->priv->streams); + if (list != NULL) { + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; + } + return NULL; +} + +static void +change_state (Oss4Backend *oss, MateMixerState state) +{ + if (oss->priv->state == state) + return; + + oss->priv->state = state; + + g_object_notify (G_OBJECT (oss), "state"); +} + +static gboolean +read_device (Oss4Backend *oss, gint index) +{ + Oss4Device *device; + gboolean ret; + gchar *description = NULL; + struct oss_mixerinfo info; + + /* We assume that the name and capabilities of a device do not change */ + device = g_hash_table_lookup (oss->priv->devices, GINT_TO_POINTER (index)); + if (G_UNLIKELY (device != NULL)) { + g_debug ("Attempt to re-read already know device with index %d", index); + return TRUE; + } + + info.dev = index; + ret = ioctl (oss->priv->fd, SNDCTL_MIXERINFO, &info); + if (ret == -1) { + g_debug ("Failed to read mixer info: %s", g_strerror (errno)); + return FALSE; + } + + if (info.enabled == 0) + return TRUE; + + /* Use the id as the device name and try to figure out the name of the + * sound card from the sndstat file if it is available, otherwise use + * the name from the mixer info */ + if (oss->priv->sndstat != NULL && + g_str_has_prefix (info.name, "pcm") == TRUE) + description = read_device_sndstat_description (oss, info.name); + + if (description == NULL) + description = g_strdup (info.name); + + device = oss4_device_new (info.id, description, oss->priv->fd, index); + + ret = oss4_device_read (device); + if (ret == TRUE) + add_device (oss, device); + + g_object_unref (device); + g_free (description); + + return ret; +} + +static gchar * +read_device_sndstat_description (Oss4Backend *oss, const gchar *prefix) +{ + FILE *fp; + gchar line[256]; + gchar *description = NULL; + + g_debug ("reading prefix %s", prefix); + + fp = fopen (oss->priv->sndstat, "r"); + if (fp == NULL) { + g_warning ("Failed to read %s: %s", oss->priv->sndstat, g_strerror (errno)); + return FALSE; + } + + while (fgets (line, sizeof (line), fp) != NULL) { + gchar *p; + + if (g_str_has_prefix (line, prefix) == FALSE) + continue; + + /* Example line: + * pcm0: <ATI R6xx (HDMI)> (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; + } + + fclose (fp); + return description; +} + +static void +add_device (Oss4Backend *oss, Oss4Device *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, + GINT_TO_POINTER (oss4_device_get_index (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 = oss4_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 = oss4_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 (Oss4Backend *oss, Oss4Device *device) +{ + MateMixerStream *stream; + + /* Remove the device streams first as they are a part of the device */ + stream = oss4_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 = oss4_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); + } + + /* Remove the device */ + g_object_ref (device); + + g_hash_table_remove (oss->priv->devices, + GINT_TO_POINTER (oss4_device_get_index (device))); + + g_signal_emit_by_name (G_OBJECT (oss), + "device-removed", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); +} |