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/Makefile.am | 12 +- backends/oss/oss-backend.c | 498 +++++++++++++++++++++++++++++++++-- backends/oss/oss-backend.h | 8 +- backends/oss/oss-common.h | 38 +++ backends/oss/oss-device.c | 525 +++++++++++++++++++++++++++++++++++++ backends/oss/oss-device.h | 73 ++++++ backends/oss/oss-stream-control.c | 529 ++++++++++++++++++++++++++++++++++++++ backends/oss/oss-stream-control.h | 74 ++++++ backends/oss/oss-stream.c | 315 +++++++++++++++++++++++ backends/oss/oss-stream.h | 75 ++++++ 10 files changed, 2128 insertions(+), 19 deletions(-) create mode 100644 backends/oss/oss-common.h create mode 100644 backends/oss/oss-device.c create mode 100644 backends/oss/oss-device.h create mode 100644 backends/oss/oss-stream-control.c create mode 100644 backends/oss/oss-stream-control.h create mode 100644 backends/oss/oss-stream.c create mode 100644 backends/oss/oss-stream.h (limited to 'backends/oss') diff --git a/backends/oss/Makefile.am b/backends/oss/Makefile.am index 3f0b8ea..44caeb8 100644 --- a/backends/oss/Makefile.am +++ b/backends/oss/Makefile.am @@ -11,11 +11,19 @@ libmatemixer_oss_la_CFLAGS = \ $(OSS_CFLAGS) libmatemixer_oss_la_SOURCES = \ + oss-common.h \ oss-backend.c \ - oss-backend.h + oss-backend.h \ + oss-device.c \ + oss-device.h \ + oss-stream.c \ + oss-stream.h \ + oss-stream-control.c \ + oss-stream-control.h libmatemixer_oss_la_LIBADD = \ - $(GLIB_LIBS) + $(GLIB_LIBS) \ + $(OSS_LIBS) libmatemixer_oss_la_LDFLAGS = \ -avoid-version \ 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); } diff --git a/backends/oss/oss-backend.h b/backends/oss/oss-backend.h index 02567ed..b766799 100644 --- a/backends/oss/oss-backend.h +++ b/backends/oss/oss-backend.h @@ -36,12 +36,16 @@ #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 _OssBackend OssBackend; +typedef struct _OssBackendClass OssBackendClass; +typedef struct _OssBackendPrivate OssBackendPrivate; struct _OssBackend { GObject parent; + + /*< private >*/ + OssBackendPrivate *priv; }; struct _OssBackendClass diff --git a/backends/oss/oss-common.h b/backends/oss/oss-common.h new file mode 100644 index 0000000..7251b86 --- /dev/null +++ b/backends/oss/oss-common.h @@ -0,0 +1,38 @@ +/* + * 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_COMMON_H +#define OSS_COMMON_H + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_SYS_SOUNDCARD_H +# include +#elif HAVE_SOUNDCARD_H +# include +#elif HAVE_MACHINE_SOUNDCARD_H +# include +#else +# error "No OSS header file present" +#endif + +#endif /* OSS_COMMON_H */ diff --git a/backends/oss/oss-device.c b/backends/oss/oss-device.c new file mode 100644 index 0000000..f33ff57 --- /dev/null +++ b/backends/oss/oss-device.c @@ -0,0 +1,525 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include + +#include "oss-common.h" +#include "oss-device.h" +#include "oss-stream.h" +#include "oss-stream-control.h" + +#define OSS_DEVICE_ICON "audio-card" + +typedef struct +{ + gchar *name; + gchar *description; + MateMixerStreamControlRole role; + MateMixerStreamFlags flags; +} OssDeviceName; + +static const OssDeviceName const oss_devices[] = { + { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, MATE_MIXER_STREAM_OUTPUT }, + { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, MATE_MIXER_STREAM_OUTPUT }, + { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, MATE_MIXER_STREAM_OUTPUT }, + { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_INPUT }, + { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, MATE_MIXER_STREAM_OUTPUT }, + { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, MATE_MIXER_STREAM_OUTPUT }, + { "line", N_("Line-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, MATE_MIXER_STREAM_INPUT }, + /* Recording monitor */ + { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_OUTPUT }, + { "pcm2", N_("PCM-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, MATE_MIXER_STREAM_OUTPUT }, + /* Recording level (master input) */ + { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, MATE_MIXER_STREAM_INPUT }, + { "igain", N_("In-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_INPUT }, + { "ogain", N_("Out-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_OUTPUT }, + { "line1", N_("Line-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "line2", N_("Line-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "line3", N_("Line-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "dig1", N_("Digital-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_NO_FLAGS }, + { "dig2", N_("Digital-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_NO_FLAGS }, + { "dig3", N_("Digital-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_NO_FLAGS }, + { "phin", N_("Phone-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "phout", N_("Phone-out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_OUTPUT }, + { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, MATE_MIXER_STREAM_INPUT }, + { "monitor", N_("Monitor"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_OUTPUT }, + { "depth", N_("3D-depth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_OUTPUT }, + { "center", N_("3D-center"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_OUTPUT }, + { "midi", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, MATE_MIXER_STREAM_INPUT } +}; + +struct _OssDevicePrivate +{ + gint fd; + gchar *path; + gchar *name; + gchar *description; + MateMixerStream *input; + MateMixerStream *output; +}; + +enum { + PROP_0, + PROP_NAME, + PROP_DESCRIPTION, + PROP_ICON, + PROP_ACTIVE_PROFILE, + PROP_PATH, + PROP_FD +}; + +static void mate_mixer_device_interface_init (MateMixerDeviceInterface *iface); + +static void oss_device_class_init (OssDeviceClass *klass); + +static void oss_device_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void oss_device_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void oss_device_init (OssDevice *device); +static void oss_device_finalize (GObject *object); + +G_DEFINE_TYPE_WITH_CODE (OssDevice, oss_device, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_DEVICE, + mate_mixer_device_interface_init)) + +static const gchar *oss_device_get_name (MateMixerDevice *device); +static const gchar *oss_device_get_description (MateMixerDevice *device); +static const gchar *oss_device_get_icon (MateMixerDevice *device); + +static gboolean read_mixer_devices (OssDevice *device); + +static void +mate_mixer_device_interface_init (MateMixerDeviceInterface *iface) +{ + iface->get_name = oss_device_get_name; + iface->get_description = oss_device_get_description; + iface->get_icon = oss_device_get_icon; +} + +static void +oss_device_class_init (OssDeviceClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = oss_device_finalize; + object_class->get_property = oss_device_get_property; + object_class->set_property = oss_device_set_property; + + g_object_class_install_property (object_class, + PROP_PATH, + g_param_spec_string ("path", + "Path", + "Path to the device", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_FD, + g_param_spec_int ("fd", + "File descriptor", + "File descriptor of the device", + G_MININT, + G_MAXINT, + -1, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_override_property (object_class, PROP_NAME, "name"); + g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); + g_object_class_override_property (object_class, PROP_ICON, "icon"); + g_object_class_override_property (object_class, PROP_ACTIVE_PROFILE, "active-profile"); + + g_type_class_add_private (object_class, sizeof (OssDevicePrivate)); +} + +static void +oss_device_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + OssDevice *device; + + device = OSS_DEVICE (object); + + switch (param_id) { + case PROP_NAME: + g_value_set_string (value, device->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, device->priv->description); + break; + case PROP_ICON: + g_value_set_string (value, OSS_DEVICE_ICON); + break; + case PROP_ACTIVE_PROFILE: + /* Not supported */ + g_value_set_object (value, NULL); + break; + case PROP_PATH: + g_value_set_string (value, device->priv->path); + break; + case PROP_FD: + g_value_set_int (value, device->priv->fd); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +oss_device_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + OssDevice *device; + + device = OSS_DEVICE (object); + + switch (param_id) { + case PROP_PATH: + /* Construct-only string */ + device->priv->path = g_strdup (g_value_get_string (value)); + break; + case PROP_FD: + device->priv->fd = dup (g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +oss_device_init (OssDevice *device) +{ + device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, + OSS_TYPE_DEVICE, + OssDevicePrivate); +} + +static void +oss_device_finalize (GObject *object) +{ + OssDevice *device; + + device = OSS_DEVICE (object); + + g_free (device->priv->name); + g_free (device->priv->description); + + if (device->priv->fd != -1) + g_close (device->priv->fd, NULL); + + G_OBJECT_CLASS (oss_device_parent_class)->finalize (object); +} + +OssDevice * +oss_device_new (const gchar *path, gint fd) +{ + OssDevice *device; + gchar *basename; + + g_return_val_if_fail (path != NULL, NULL); + + device = g_object_new (OSS_TYPE_DEVICE, + "path", path, + "fd", fd, + NULL); + + basename = g_path_get_basename (path); + + device->priv->name = g_strdup_printf ("oss-%s", basename); + g_free (basename); + + return device; +} + +gboolean +oss_device_read (OssDevice *device) +{ + gchar *name; + gchar *basename; + MateMixerStreamControl *ctl; + + g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE); + + // XXX avoid calling this function repeatedly + + g_debug ("Querying device %s (%s)", + device->priv->path, + device->priv->description); + + basename = g_path_get_basename (device->priv->path); + + name = g_strdup_printf ("oss-input-%s", basename); + device->priv->input = MATE_MIXER_STREAM (oss_stream_new (name, + device->priv->description, + MATE_MIXER_STREAM_INPUT)); + g_free (name); + + name = g_strdup_printf ("oss-output-%s", basename); + device->priv->output = MATE_MIXER_STREAM (oss_stream_new (name, + device->priv->description, + MATE_MIXER_STREAM_OUTPUT | + MATE_MIXER_STREAM_PORTS_FIXED)); + g_free (name); + + if (read_mixer_devices (device) == FALSE) + return FALSE; + + // XXX prefer active ports as default if there is no default + + ctl = mate_mixer_stream_get_default_control (device->priv->input); + if (ctl == NULL) { + const GList *list = mate_mixer_stream_list_controls (device->priv->input); + + if (list != NULL) { + ctl = MATE_MIXER_STREAM_CONTROL (list->data); + oss_stream_set_default_control (OSS_STREAM (device->priv->input), + OSS_STREAM_CONTROL (ctl)); + } else + g_clear_object (&device->priv->input); + } + + if (ctl != NULL) + g_debug ("Default input stream control is %s", + mate_mixer_stream_control_get_description (ctl)); + + ctl = mate_mixer_stream_get_default_control (device->priv->output); + if (ctl == NULL) { + const GList *list = mate_mixer_stream_list_controls (device->priv->output); + + if (list != NULL) { + ctl = MATE_MIXER_STREAM_CONTROL (list->data); + oss_stream_set_default_control (OSS_STREAM (device->priv->output), + OSS_STREAM_CONTROL (ctl)); + } else + g_clear_object (&device->priv->output); + } + + if (ctl != NULL) + g_debug ("Default output stream control is %s", + mate_mixer_stream_control_get_description (ctl)); + + return TRUE; +} + +const gchar * +oss_device_get_path (OssDevice *odevice) +{ + g_return_val_if_fail (OSS_IS_DEVICE (odevice), FALSE); + + return odevice->priv->path; +} + +MateMixerStream * +oss_device_get_input_stream (OssDevice *odevice) +{ + g_return_val_if_fail (OSS_IS_DEVICE (odevice), FALSE); + + return odevice->priv->input; +} + +MateMixerStream * +oss_device_get_output_stream (OssDevice *odevice) +{ + g_return_val_if_fail (OSS_IS_DEVICE (odevice), FALSE); + + return odevice->priv->output; +} + +gboolean +oss_device_set_description (OssDevice *odevice, const gchar *description) +{ + g_return_val_if_fail (OSS_IS_DEVICE (odevice), FALSE); + + if (g_strcmp0 (odevice->priv->description, description) != 0) { + g_free (odevice->priv->description); + + odevice->priv->description = g_strdup (description); + + g_object_notify (G_OBJECT (odevice), "description"); + return TRUE; + } + return FALSE; +} + +static gboolean +read_mixer_devices (OssDevice *device) +{ + gint devmask, + stereomask, + recmask; + gint ret; + gint i; + OssStreamControl *ctl; + + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_DEVMASK), &devmask); + if (ret < 0) + goto fail; + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_STEREODEVS), &stereomask); + if (ret < 0) + goto fail; + ret = ioctl (device->priv->fd, MIXER_READ (SOUND_MIXER_RECMASK), &recmask); + if (ret < 0) + goto fail; + + for (i = 0; i < MIN (G_N_ELEMENTS (oss_devices), SOUND_MIXER_NRDEVICES); i++) { + gboolean stereo; + gboolean input = FALSE; + MateMixerPort *port = NULL; + + /* Skip unavailable controls */ + if ((devmask & (1 << i)) == 0) + continue; + + if ((stereomask & (1 << i)) > 0) + stereo = TRUE; + else + stereo = FALSE; + + if (oss_devices[i].flags == MATE_MIXER_STREAM_NO_FLAGS) { + if ((recmask & (1 << i)) > 0) + input = TRUE; + } + if (oss_devices[i].flags == MATE_MIXER_STREAM_INPUT) { + if ((recmask & (1 << i)) == 0) { + g_debug ("Skipping non-input device %s", oss_devices[i].name); + continue; + } + input = TRUE; + } + + ctl = oss_stream_control_new (device->priv->fd, + i, + oss_devices[i].name, + oss_devices[i].description, + stereo); + + if (oss_devices[i].role == MATE_MIXER_STREAM_CONTROL_ROLE_PORT) + port = _mate_mixer_port_new (oss_devices[i].name, + oss_devices[i].description, + NULL, + 0, + 0); + + if (input == TRUE) { + oss_stream_add_control (OSS_STREAM (device->priv->input), ctl); + + if (i == SOUND_MIXER_RECLEV || i == SOUND_MIXER_IGAIN) { + if (i == SOUND_MIXER_RECLEV) { + oss_stream_set_default_control (OSS_STREAM (device->priv->input), ctl); + } else { + MateMixerStreamControl *defctl; + + defctl = mate_mixer_stream_get_default_control (device->priv->input); + if (defctl == NULL) + oss_stream_set_default_control (OSS_STREAM (device->priv->input), ctl); + } + } + + if (port != NULL) + oss_stream_add_port (OSS_STREAM (device->priv->input), port); + } else { + oss_stream_add_control (OSS_STREAM (device->priv->output), ctl); + + if (i == SOUND_MIXER_VOLUME) { + oss_stream_set_default_control (OSS_STREAM (device->priv->output), ctl); + } + else if (i == SOUND_MIXER_PCM) { + MateMixerStreamControl *defctl; + + defctl = mate_mixer_stream_get_default_control (device->priv->output); + if (defctl == NULL) + oss_stream_set_default_control (OSS_STREAM (device->priv->output), ctl); + } + + if (port != NULL) + oss_stream_add_port (OSS_STREAM (device->priv->output), port); + } + + if (port != NULL) + oss_stream_control_set_port (ctl, port); + + oss_stream_control_set_role (ctl, oss_devices[i].role); + + g_debug ("Added control %s", + mate_mixer_stream_control_get_description (MATE_MIXER_STREAM_CONTROL (ctl))); + + oss_stream_control_update (ctl); + } + return TRUE; + +fail: + g_warning ("Failed to read device %s: %s", + device->priv->path, + g_strerror (errno)); + + return FALSE; +} + +static const gchar * +oss_device_get_name (MateMixerDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), NULL); + + return OSS_DEVICE (device)->priv->name; +} + +static const gchar * +oss_device_get_description (MateMixerDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), NULL); + + return OSS_DEVICE (device)->priv->description; +} + +static const gchar * +oss_device_get_icon (MateMixerDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), NULL); + + return OSS_DEVICE_ICON; +} diff --git a/backends/oss/oss-device.h b/backends/oss/oss-device.h new file mode 100644 index 0000000..fe40eb2 --- /dev/null +++ b/backends/oss/oss-device.h @@ -0,0 +1,73 @@ +/* + * 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_DEVICE_H +#define OSS_DEVICE_H + +#include +#include + +G_BEGIN_DECLS + +#define OSS_TYPE_DEVICE \ + (oss_device_get_type ()) +#define OSS_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_DEVICE, OssDevice)) +#define OSS_IS_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_DEVICE)) +#define OSS_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_DEVICE, OssDeviceClass)) +#define OSS_IS_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_DEVICE)) +#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; + +struct _OssDevice +{ + GObject parent; + + /*< private >*/ + OssDevicePrivate *priv; +}; + +struct _OssDeviceClass +{ + GObjectClass parent; +}; + +GType oss_device_get_type (void) G_GNUC_CONST; + +OssDevice * oss_device_new (const gchar *path, + gint fd); + +gboolean oss_device_read (OssDevice *device); + +const gchar * oss_device_get_path (OssDevice *odevice); + +MateMixerStream *oss_device_get_input_stream (OssDevice *odevice); +MateMixerStream *oss_device_get_output_stream (OssDevice *odevice); + +gboolean oss_device_set_description (OssDevice *odevice, + const gchar *description); + +G_END_DECLS + +#endif /* OSS_DEVICE_H */ diff --git a/backends/oss/oss-stream-control.c b/backends/oss/oss-stream-control.c new file mode 100644 index 0000000..0b1db26 --- /dev/null +++ b/backends/oss/oss-stream-control.c @@ -0,0 +1,529 @@ +/* + * 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 +#include + +#include "oss-common.h" +#include "oss-stream-control.h" + +struct _OssStreamControlPrivate +{ + gint fd; + gint dev_number; + gchar *name; + gchar *description; + guint volume[2]; + gfloat balance; + gboolean stereo; + MateMixerPort *port; + MateMixerStreamControlRole role; + MateMixerStreamControlFlags flags; +}; + +enum { + PROP_0, + PROP_NAME, + PROP_DESCRIPTION, + PROP_FLAGS, + PROP_MUTE, + PROP_VOLUME, + PROP_BALANCE, + PROP_FADE, + PROP_FD, + PROP_DEV_NUMBER, + PROP_STEREO +}; + +static void mate_mixer_stream_control_interface_init (MateMixerStreamControlInterface *iface); + +static void oss_stream_control_class_init (OssStreamControlClass *klass); + +static void oss_stream_control_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void oss_stream_control_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void oss_stream_control_init (OssStreamControl *octl); +static void oss_stream_control_finalize (GObject *object); + +G_DEFINE_TYPE_WITH_CODE (OssStreamControl, oss_stream_control, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM_CONTROL, + mate_mixer_stream_control_interface_init)) + +static const gchar * oss_stream_control_get_name (MateMixerStreamControl *ctl); +static const gchar * oss_stream_control_get_description (MateMixerStreamControl *ctl); + +static guint oss_stream_control_get_num_channels (MateMixerStreamControl *ctl); + +static gboolean oss_stream_control_set_volume (MateMixerStreamControl *ctl, + guint volume); + +static guint oss_stream_control_get_channel_volume (MateMixerStreamControl *ctl, + guint channel); +static gboolean oss_stream_control_set_channel_volume (MateMixerStreamControl *ctl, + guint channel, + guint volume); + +static MateMixerChannelPosition oss_stream_control_get_channel_position (MateMixerStreamControl *ctl, + guint channel); +static gboolean oss_stream_control_has_channel_position (MateMixerStreamControl *ctl, + MateMixerChannelPosition position); + +static gboolean oss_stream_control_set_balance (MateMixerStreamControl *ctl, + gfloat balance); + +static guint oss_stream_control_get_min_volume (MateMixerStreamControl *ctl); +static guint oss_stream_control_get_max_volume (MateMixerStreamControl *ctl); +static guint oss_stream_control_get_normal_volume (MateMixerStreamControl *ctl); +static guint oss_stream_control_get_base_volume (MateMixerStreamControl *ctl); + +static void +mate_mixer_stream_control_interface_init (MateMixerStreamControlInterface *iface) +{ + iface->get_name = oss_stream_control_get_name; + iface->get_description = oss_stream_control_get_description; + iface->get_num_channels = oss_stream_control_get_num_channels; + iface->set_volume = oss_stream_control_set_volume; + iface->get_channel_volume = oss_stream_control_get_channel_volume; + iface->set_channel_volume = oss_stream_control_set_channel_volume; + iface->get_channel_position = oss_stream_control_get_channel_position; + iface->has_channel_position = oss_stream_control_has_channel_position; + iface->set_balance = oss_stream_control_set_balance; + iface->get_min_volume = oss_stream_control_get_min_volume; + iface->get_max_volume = oss_stream_control_get_max_volume; + iface->get_normal_volume = oss_stream_control_get_normal_volume; + iface->get_base_volume = oss_stream_control_get_base_volume; +} + +static void +oss_stream_control_class_init (OssStreamControlClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = oss_stream_control_finalize; + object_class->get_property = oss_stream_control_get_property; + object_class->set_property = oss_stream_control_set_property; + + g_object_class_install_property (object_class, + PROP_FD, + g_param_spec_int ("fd", + "File descriptor", + "File descriptor of the device", + G_MININT, + G_MAXINT, + -1, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_DEV_NUMBER, + g_param_spec_int ("dev-number", + "Dev number", + "OSS device number", + G_MININT, + G_MAXINT, + 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_STEREO, + g_param_spec_boolean ("stereo", + "Stereo", + "Stereo", + FALSE, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_override_property (object_class, PROP_NAME, "name"); + g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); + g_object_class_override_property (object_class, PROP_FLAGS, "flags"); + g_object_class_override_property (object_class, PROP_MUTE, "mute"); + g_object_class_override_property (object_class, PROP_VOLUME, "volume"); + g_object_class_override_property (object_class, PROP_BALANCE, "balance"); + g_object_class_override_property (object_class, PROP_FADE, "fade"); + + g_type_class_add_private (object_class, sizeof (OssStreamControlPrivate)); +} + +static void +oss_stream_control_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + OssStreamControl *octl; + + octl = OSS_STREAM_CONTROL (object); + + switch (param_id) { + case PROP_NAME: + g_value_set_string (value, octl->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, octl->priv->description); + break; + case PROP_FLAGS: + g_value_set_flags (value, octl->priv->flags); + break; + case PROP_MUTE: + /* Not supported */ + g_value_set_boolean (value, FALSE); + break; + case PROP_VOLUME: + g_value_set_uint (value, MAX (octl->priv->volume[0], octl->priv->volume[1])); + break; + case PROP_BALANCE: + g_value_set_float (value, octl->priv->balance); + break; + case PROP_FADE: + /* Not supported */ + g_value_set_float (value, 0.0f); + break; + case PROP_FD: + g_value_set_int (value, octl->priv->fd); + break; + case PROP_DEV_NUMBER: + g_value_set_int (value, octl->priv->dev_number); + break; + case PROP_STEREO: + g_value_set_boolean (value, octl->priv->stereo); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +oss_stream_control_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + OssStreamControl *octl; + + octl = OSS_STREAM_CONTROL (object); + + switch (param_id) { + case PROP_FD: + octl->priv->fd = dup (g_value_get_int (value)); + break; + case PROP_DEV_NUMBER: + octl->priv->dev_number = g_value_get_int (value); + break; + case PROP_STEREO: + octl->priv->stereo = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +oss_stream_control_init (OssStreamControl *octl) +{ + octl->priv = G_TYPE_INSTANCE_GET_PRIVATE (octl, + OSS_TYPE_STREAM_CONTROL, + OssStreamControlPrivate); +} + +static void +oss_stream_control_finalize (GObject *object) +{ + OssStreamControl *octl; + + octl = OSS_STREAM_CONTROL (object); + + g_free (octl->priv->name); + g_free (octl->priv->description); + + if (octl->priv->fd != -1) + g_close (octl->priv->fd, NULL); + + G_OBJECT_CLASS (oss_stream_control_parent_class)->finalize (object); +} + +OssStreamControl * +oss_stream_control_new (gint fd, + gint dev_number, + const gchar *name, + const gchar *description, + gboolean stereo) +{ + OssStreamControl *ctl; + + ctl = g_object_new (OSS_TYPE_STREAM_CONTROL, + "fd", fd, + "dev-number", dev_number, + "stereo", stereo, + NULL); + + ctl->priv->name = g_strdup (name); + ctl->priv->description = g_strdup (description); + + return ctl; +} + +gboolean +oss_stream_control_update (OssStreamControl *octl) +{ + gint v; + gint ret; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (octl), FALSE); + + ret = ioctl (octl->priv->fd, MIXER_READ (octl->priv->dev_number), &v); + if (ret < 0) { + g_warning ("Failed to read volume: %s", g_strerror (errno)); + return FALSE; + } + + octl->priv->volume[0] = v & 0xFF; + + if (octl->priv->stereo == TRUE) + octl->priv->volume[1] = (v >> 8) & 0xFF; + + return TRUE; +} + +gboolean +oss_stream_control_set_port (OssStreamControl *octl, MateMixerPort *port) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (octl), FALSE); + + // XXX provide property + + if (octl->priv->port != NULL) + g_object_unref (octl->priv->port); + + octl->priv->port = g_object_ref (port); + return TRUE; +} + +gboolean +oss_stream_control_set_role (OssStreamControl *octl, MateMixerStreamControlRole role) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (octl), FALSE); + + octl->priv->role = role; + return TRUE; +} + +static const gchar * +oss_stream_control_get_name (MateMixerStreamControl *ctl) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), NULL); + + return OSS_STREAM_CONTROL (ctl)->priv->name; +} + +static const gchar * +oss_stream_control_get_description (MateMixerStreamControl *ctl) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), NULL); + + return OSS_STREAM_CONTROL (ctl)->priv->description; +} + +static guint +oss_stream_control_get_num_channels (MateMixerStreamControl *ctl) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), 0); + + if (OSS_STREAM_CONTROL (ctl)->priv->stereo == TRUE) + return 2; + else + return 1; +} + +static gboolean +oss_stream_control_set_volume (MateMixerStreamControl *ctl, guint volume) +{ + OssStreamControl *octl; + int v; + int ret; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), FALSE); + + octl = OSS_STREAM_CONTROL (ctl); + + /* Some backends may allow setting higher value than maximum, but not here */ + if (volume > 100) + volume = 100; + + v = volume; + if (octl->priv->stereo == TRUE) + v |= (volume & 0xFF) << 8; + + ret = ioctl (octl->priv->fd, MIXER_WRITE (octl->priv->dev_number), &v); + if (ret < 0) { + g_warning ("Failed to set volume: %s", g_strerror (errno)); + return FALSE; + } + return TRUE; +} + +static guint +oss_stream_control_get_channel_volume (MateMixerStreamControl *ctl, guint channel) +{ + OssStreamControl *octl; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), 0); + + octl = OSS_STREAM_CONTROL (ctl); + + if (channel > 1) + return 0; + + /* Right channel on mono stream will always have zero volume */ + return octl->priv->volume[channel]; +} + +static gboolean +oss_stream_control_set_channel_volume (MateMixerStreamControl *ctl, + guint channel, + guint volume) +{ + OssStreamControl *octl; + int ret; + int v; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), FALSE); + + if (channel > 1) + return FALSE; + + octl = OSS_STREAM_CONTROL (ctl); + + /* Some backends may allow setting higher value than maximum, but not here */ + if (volume > 100) + volume = 100; + + if (channel == 0) + v = volume; + else + v = octl->priv->volume[0]; + + if (channel == 1) { + if (octl->priv->stereo == FALSE) + return FALSE; + + v |= volume << 8; + } else + v |= octl->priv->volume[1] << 8; + + ret = ioctl (octl->priv->fd, MIXER_WRITE (octl->priv->dev_number), &v); + if (ret < 0) { + g_warning ("Failed to set channel volume: %s", g_strerror (errno)); + return FALSE; + } + return TRUE; +} + +static MateMixerChannelPosition +oss_stream_control_get_channel_position (MateMixerStreamControl *ctl, guint channel) +{ + OssStreamControl *octl; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), MATE_MIXER_CHANNEL_UNKNOWN); + + octl = OSS_STREAM_CONTROL (ctl); + + if (octl->priv->stereo == TRUE) { + if (channel == 0) + return MATE_MIXER_CHANNEL_FRONT_LEFT; + else if (channel == 1) + return MATE_MIXER_CHANNEL_FRONT_RIGHT; + } else { + if (channel == 0) + return MATE_MIXER_CHANNEL_MONO; + } + return MATE_MIXER_CHANNEL_UNKNOWN; +} + +static gboolean +oss_stream_control_has_channel_position (MateMixerStreamControl *ctl, + MateMixerChannelPosition position) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), FALSE); + + if (position == MATE_MIXER_CHANNEL_MONO) + return OSS_STREAM_CONTROL (ctl)->priv->stereo == FALSE; + + if (position == MATE_MIXER_CHANNEL_FRONT_LEFT || + position == MATE_MIXER_CHANNEL_FRONT_RIGHT) + return OSS_STREAM_CONTROL (ctl)->priv->stereo == TRUE; + + return FALSE; +} + +static gboolean +oss_stream_control_set_balance (MateMixerStreamControl *ctl, gfloat balance) +{ + OssStreamControl *octl; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (ctl), FALSE); + + octl = OSS_STREAM_CONTROL (ctl); + + if (octl->priv->stereo == FALSE) + return FALSE; + + // XXX + return TRUE; +} + +static guint +oss_stream_control_get_min_volume (MateMixerStreamControl *ctl) +{ + return 0; +} + +static guint +oss_stream_control_get_max_volume (MateMixerStreamControl *ctl) +{ + return 100; +} + +static guint +oss_stream_control_get_normal_volume (MateMixerStreamControl *ctl) +{ + return 100; +} + +static guint +oss_stream_control_get_base_volume (MateMixerStreamControl *ctl) +{ + return 100; +} diff --git a/backends/oss/oss-stream-control.h b/backends/oss/oss-stream-control.h new file mode 100644 index 0000000..420af48 --- /dev/null +++ b/backends/oss/oss-stream-control.h @@ -0,0 +1,74 @@ +/* + * 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_STREAM_CONTROL_H +#define OSS_STREAM_CONTROL_H + +#include +#include + +G_BEGIN_DECLS + +#define OSS_TYPE_STREAM_CONTROL \ + (oss_stream_control_get_type ()) +#define OSS_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_STREAM_CONTROL, OssStreamControl)) +#define OSS_IS_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_STREAM_CONTROL)) +#define OSS_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_STREAM_CONTROL, OssStreamControlClass)) +#define OSS_IS_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_STREAM_CONTROL)) +#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; + +struct _OssStreamControl +{ + GObject parent; + + /*< private >*/ + OssStreamControlPrivate *priv; +}; + +struct _OssStreamControlClass +{ + GObjectClass parent; +}; + +GType oss_stream_control_get_type (void) G_GNUC_CONST; + +OssStreamControl *oss_stream_control_new (gint fd, + gint dev_number, + const gchar *name, + const gchar *description, + gboolean stereo); + +gboolean oss_stream_control_update (OssStreamControl *octl); + +gboolean oss_stream_control_set_port (OssStreamControl *octl, + MateMixerPort *port); + +gboolean oss_stream_control_set_role (OssStreamControl *octl, + MateMixerStreamControlRole role); + +G_END_DECLS + +#endif /* OSS_STREAM_CONTROL_H */ diff --git a/backends/oss/oss-stream.c b/backends/oss/oss-stream.c new file mode 100644 index 0000000..69bfd06 --- /dev/null +++ b/backends/oss/oss-stream.c @@ -0,0 +1,315 @@ +/* + * 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 + +#include "oss-stream.h" +#include "oss-stream-control.h" + +struct _OssStreamPrivate +{ + gchar *name; + gchar *description; + MateMixerDevice *device; + MateMixerStreamFlags flags; + MateMixerStreamState state; + GHashTable *ports; + GList *ports_list; + GHashTable *controls; + GList *controls_list; + OssStreamControl *control; +}; + +enum { + PROP_0, + PROP_NAME, + PROP_DESCRIPTION, + PROP_DEVICE, + PROP_FLAGS, + PROP_STATE, + PROP_ACTIVE_PORT, +}; + +static void mate_mixer_stream_interface_init (MateMixerStreamInterface *iface); + +static void oss_stream_class_init (OssStreamClass *klass); + +static void oss_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void oss_stream_init (OssStream *ostream); +static void oss_stream_dispose (GObject *object); +static void oss_stream_finalize (GObject *object); + +G_DEFINE_TYPE_WITH_CODE (OssStream, oss_stream, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (MATE_MIXER_TYPE_STREAM, + mate_mixer_stream_interface_init)) + +static const gchar * oss_stream_get_name (MateMixerStream *stream); +static const gchar * oss_stream_get_description (MateMixerStream *stream); + +static MateMixerStreamControl *oss_stream_get_control (MateMixerStream *stream, + const gchar *name); +static MateMixerStreamControl *oss_stream_get_default_control (MateMixerStream *stream); + +static const GList * oss_stream_list_controls (MateMixerStream *stream); +static const GList * oss_stream_list_ports (MateMixerStream *stream); + +static void +mate_mixer_stream_interface_init (MateMixerStreamInterface *iface) +{ + iface->get_name = oss_stream_get_name; + iface->get_description = oss_stream_get_description; + iface->get_control = oss_stream_get_control; + iface->get_default_control = oss_stream_get_default_control; + iface->list_controls = oss_stream_list_controls; + iface->list_ports = oss_stream_list_ports; +} + +static void +oss_stream_class_init (OssStreamClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_stream_dispose; + object_class->finalize = oss_stream_finalize; + object_class->get_property = oss_stream_get_property; + + g_object_class_override_property (object_class, PROP_NAME, "name"); + g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); + g_object_class_override_property (object_class, PROP_DEVICE, "device"); + g_object_class_override_property (object_class, PROP_FLAGS, "flags"); + g_object_class_override_property (object_class, PROP_STATE, "state"); + g_object_class_override_property (object_class, PROP_ACTIVE_PORT, "active-port"); + + g_type_class_add_private (object_class, sizeof (OssStreamPrivate)); +} + +static void +oss_stream_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + OssStream *ostream; + + ostream = OSS_STREAM (object); + + switch (param_id) { + case PROP_NAME: + g_value_set_string (value, ostream->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, ostream->priv->description); + break; + case PROP_DEVICE: + g_value_set_object (value, ostream->priv->device); + break; + case PROP_FLAGS: + g_value_set_flags (value, ostream->priv->flags); + break; + case PROP_STATE: + /* Not supported */ + g_value_set_enum (value, MATE_MIXER_STREAM_STATE_UNKNOWN); + break; + case PROP_ACTIVE_PORT: + // XXX + g_value_set_object (value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +oss_stream_init (OssStream *ostream) +{ + ostream->priv = G_TYPE_INSTANCE_GET_PRIVATE (ostream, + OSS_TYPE_STREAM, + OssStreamPrivate); + + ostream->priv->controls = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + ostream->priv->ports = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + +static void +oss_stream_dispose (GObject *object) +{ + OssStream *ostream; + + ostream = OSS_STREAM (object); + + g_hash_table_remove_all (ostream->priv->controls); + g_hash_table_remove_all (ostream->priv->ports); + + G_OBJECT_CLASS (oss_stream_parent_class)->finalize (object); +} + +static void +oss_stream_finalize (GObject *object) +{ + OssStream *ostream; + + ostream = OSS_STREAM (object); + + g_free (ostream->priv->name); + g_free (ostream->priv->description); + + g_hash_table_destroy (ostream->priv->controls); + g_hash_table_destroy (ostream->priv->ports); + + G_OBJECT_CLASS (oss_stream_parent_class)->finalize (object); +} + +OssStream * +oss_stream_new (const gchar *name, + const gchar *description, + MateMixerStreamFlags flags) +{ + OssStream *stream; + + stream = g_object_new (OSS_TYPE_STREAM, NULL); + + stream->priv->name = g_strdup (name); + stream->priv->description = g_strdup (description); + stream->priv->flags = flags; + + return stream; +} + +gboolean +oss_stream_add_control (OssStream *ostream, OssStreamControl *octl) +{ + const gchar *name; + + g_return_val_if_fail (OSS_IS_STREAM (ostream), FALSE); + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (octl), FALSE); + + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (octl)); + + g_hash_table_insert (ostream->priv->controls, + g_strdup (name), + octl); + return TRUE; +} + +gboolean +oss_stream_set_default_control (OssStream *ostream, OssStreamControl *octl) +{ + g_return_val_if_fail (OSS_IS_STREAM (ostream), FALSE); + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (octl), FALSE); + + /* This function is only used internally so avoid validating that the control + * belongs to this stream */ + if (ostream->priv->control != NULL) + g_object_unref (ostream->priv->control); + + ostream->priv->control = g_object_ref (octl); + return TRUE; +} + +gboolean +oss_stream_add_port (OssStream *ostream, MateMixerPort *port) +{ + g_return_val_if_fail (OSS_IS_STREAM (ostream), FALSE); + g_return_val_if_fail (MATE_MIXER_IS_PORT (port), FALSE); + + g_hash_table_insert (ostream->priv->ports, + g_strdup (mate_mixer_port_get_name (port)), + port); + return TRUE; +} + +static const gchar * +oss_stream_get_name (MateMixerStream *stream) +{ + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + return OSS_STREAM (stream)->priv->name; +} + +static const gchar * +oss_stream_get_description (MateMixerStream *stream) +{ + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + return OSS_STREAM (stream)->priv->description; +} + +static MateMixerStreamControl * +oss_stream_get_control (MateMixerStream *stream, const gchar *name) +{ + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_hash_table_lookup (OSS_STREAM (stream)->priv->controls, name); +} + +static MateMixerStreamControl * +oss_stream_get_default_control (MateMixerStream *stream) +{ + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + return MATE_MIXER_STREAM_CONTROL (OSS_STREAM (stream)->priv->control); +} + +static const GList * +oss_stream_list_controls (MateMixerStream *stream) +{ + OssStream *ostream; + + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + ostream = OSS_STREAM (stream); + + if (ostream->priv->controls_list == NULL) + ostream->priv->controls_list = g_hash_table_get_values (ostream->priv->controls); + + return ostream->priv->controls_list; +} + +static const GList * +oss_stream_list_ports (MateMixerStream *stream) +{ + OssStream *ostream; + + g_return_val_if_fail (OSS_IS_STREAM (stream), NULL); + + ostream = OSS_STREAM (stream); + + if (ostream->priv->ports_list == NULL) + ostream->priv->ports_list = g_hash_table_get_values (ostream->priv->ports); + + return ostream->priv->ports_list; +} diff --git a/backends/oss/oss-stream.h b/backends/oss/oss-stream.h new file mode 100644 index 0000000..d6c2fb2 --- /dev/null +++ b/backends/oss/oss-stream.h @@ -0,0 +1,75 @@ +/* + * 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_STREAM_H +#define OSS_STREAM_H + +#include +#include + +#include "oss-stream-control.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_STREAM \ + (oss_stream_get_type ()) +#define OSS_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_STREAM, OssStream)) +#define OSS_IS_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_STREAM)) +#define OSS_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_STREAM, OssStreamClass)) +#define OSS_IS_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_STREAM)) +#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; + +struct _OssStream +{ + GObject parent; + + /*< private >*/ + OssStreamPrivate *priv; +}; + +struct _OssStreamClass +{ + GObjectClass parent; +}; + +GType oss_stream_get_type (void) G_GNUC_CONST; + +OssStream * oss_stream_new (const gchar *name, + const gchar *description, + MateMixerStreamFlags flags); + +gboolean oss_stream_add_control (OssStream *stream, + OssStreamControl *ctl); + +gboolean oss_stream_set_default_control (OssStream *stream, + OssStreamControl *ctl); + +gboolean oss_stream_add_port (OssStream *ostream, + MateMixerPort *port); + +G_END_DECLS + +#endif /* OSS_STREAM_H */ -- cgit v1.2.1