summaryrefslogtreecommitdiff
path: root/backends/oss
diff options
context:
space:
mode:
Diffstat (limited to 'backends/oss')
-rw-r--r--backends/oss/Makefile.am34
-rw-r--r--backends/oss/oss-backend.c587
-rw-r--r--backends/oss/oss-backend.h63
-rw-r--r--backends/oss/oss-common.h39
-rw-r--r--backends/oss/oss-device.c404
-rw-r--r--backends/oss/oss-device.h77
-rw-r--r--backends/oss/oss-stream-control.c392
-rw-r--r--backends/oss/oss-stream-control.h70
-rw-r--r--backends/oss/oss-stream.c183
-rw-r--r--backends/oss/oss-stream.h74
10 files changed, 1923 insertions, 0 deletions
diff --git a/backends/oss/Makefile.am b/backends/oss/Makefile.am
new file mode 100644
index 0000000..44caeb8
--- /dev/null
+++ b/backends/oss/Makefile.am
@@ -0,0 +1,34 @@
+backenddir = $(libdir)/libmatemixer
+
+backend_LTLIBRARIES = libmatemixer-oss.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -DG_LOG_DOMAIN=\"libmatemixer-oss\"
+
+libmatemixer_oss_la_CFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(OSS_CFLAGS)
+
+libmatemixer_oss_la_SOURCES = \
+ oss-common.h \
+ oss-backend.c \
+ 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) \
+ $(OSS_LIBS)
+
+libmatemixer_oss_la_LDFLAGS = \
+ -avoid-version \
+ -no-undefined \
+ -export-dynamic \
+ -module
+
+-include $(top_srcdir)/git.mk
diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c
new file mode 100644
index 0000000..2b5eca7
--- /dev/null
+++ b/backends/oss/oss-backend.c
@@ -0,0 +1,587 @@
+/*
+ * 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/gstdio.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer.h>
+#include <libmatemixer/matemixer-private.h>
+
+#include "oss-backend.h"
+#include "oss-common.h"
+#include "oss-device.h"
+#include "oss-stream.h"
+
+#define BACKEND_NAME "OSS"
+#define BACKEND_PRIORITY 9
+
+#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
+{
+ gchar *default_device;
+ GSource *timeout_source;
+ GHashTable *devices;
+};
+
+static void oss_backend_class_init (OssBackendClass *klass);
+static void oss_backend_class_finalize (OssBackendClass *klass);
+static void oss_backend_init (OssBackend *oss);
+static void oss_backend_dispose (GObject *object);
+static void oss_backend_finalize (GObject *object);
+
+#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 read_devices (OssBackend *oss);
+
+static gboolean read_device (OssBackend *oss,
+ const gchar *path,
+ gboolean *added);
+
+static gchar * read_device_label (OssBackend *oss,
+ const gchar *path,
+ gint fd);
+
+static gchar * read_device_label_sndstat (OssBackend *oss,
+ const gchar *sndstat,
+ const gchar *path,
+ guint index) G_GNUC_UNUSED;
+
+static void add_device (OssBackend *oss,
+ OssDevice *device);
+static void remove_device (OssBackend *oss,
+ OssDevice *device);
+
+static void 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;
+
+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
+oss_backend_class_init (OssBackendClass *klass)
+{
+ GObjectClass *object_class;
+ MateMixerBackendClass *backend_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = oss_backend_dispose;
+ object_class->finalize = oss_backend_finalize;
+
+ 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() */
+static void
+oss_backend_class_finalize (OssBackendClass *klass)
+{
+}
+
+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);
+}
+
+static void
+oss_backend_dispose (GObject *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
+oss_backend_finalize (GObject *object)
+{
+ OssBackend *oss;
+
+ oss = OSS_BACKEND (object);
+
+ g_hash_table_destroy (oss->priv->devices);
+
+ G_OBJECT_CLASS (oss_backend_parent_class)->finalize (object);
+}
+
+static gboolean
+oss_backend_open (MateMixerBackend *backend)
+{
+ OssBackend *oss;
+
+ g_return_val_if_fail (OSS_IS_BACKEND (backend), FALSE);
+
+ oss = OSS_BACKEND (backend);
+
+ /* 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;
+}
+
+void
+oss_backend_close (MateMixerBackend *backend)
+{
+ OssBackend *oss;
+
+ g_return_if_fail (OSS_IS_BACKEND (backend));
+
+ oss = OSS_BACKEND (backend);
+
+ g_source_destroy (oss->priv->timeout_source);
+ g_hash_table_remove_all (oss->priv->devices);
+
+ g_free (oss->priv->default_device);
+ oss->priv->default_device = NULL;
+
+ _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE);
+}
+
+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 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;
+}
+
+static GList *
+oss_backend_list_streams (MateMixerBackend *backend)
+{
+ OssBackend *oss;
+ GHashTableIter iter;
+ gpointer value;
+ GList *list = NULL;
+
+ g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL);
+
+ oss = OSS_BACKEND (backend);
+
+ /* We don't keep a list or hash table of all streams here, instead walk
+ * through the list of devices and create the list manually, each device
+ * has at most one input and one output stream */
+ g_hash_table_iter_init (&iter, oss->priv->devices);
+
+ 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);
+ }
+
+ if (list != NULL)
+ g_list_foreach (list, (GFunc) g_object_ref, NULL);
+
+ return list;
+}
+
+static gboolean
+read_devices (OssBackend *oss)
+{
+ gint i;
+ gboolean added = FALSE;
+
+ for (i = 0; i < OSS_MAX_DEVICES; i++) {
+ gboolean added_current;
+ gchar *path = g_strdup_printf ("/dev/mixer%i", i);
+
+ /* On recent FreeBSD both /dev/mixer and /dev/mixer0 point to the same
+ * mixer device, on NetBSD and OpenBSD /dev/mixer is a symlink to one
+ * 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;
+
+ 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, gboolean *added)
+{
+ OssDevice *device;
+ gint fd;
+ gchar *bname;
+ gchar *label;
+
+ device = g_hash_table_lookup (oss->priv->devices, path);
+ *added = FALSE;
+
+ fd = g_open (path, O_RDWR, 0);
+ if (fd == -1) {
+ if (errno != ENOENT && errno != ENXIO)
+ g_debug ("%s: %s", path, g_strerror (errno));
+
+ if (device != NULL)
+ remove_device (oss, device);
+ return FALSE;
+ }
+
+ /* 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;
+ }
+
+ bname = g_path_get_basename (path);
+ label = read_device_label (oss, path, fd);
+
+ device = oss_device_new (bname, label, path, fd);
+ g_free (bname);
+ g_free (label);
+
+ close (fd);
+
+ if ((*added = oss_device_open (device)) == TRUE)
+ add_device (oss, device);
+
+ g_object_unref (device);
+ return *added;
+}
+
+static gchar *
+read_device_label (OssBackend *oss, const gchar *path, gint fd)
+{
+ guint index;
+
+#ifdef SOUND_MIXER_INFO
+ do {
+ struct mixer_info info;
+
+ /* 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
+
+ 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 g_strdup_printf (_("OSS Mixer %d"), index);
+}
+
+static gchar *
+read_device_label_sndstat (OssBackend *oss,
+ const gchar *sndstat,
+ const gchar *path,
+ guint index)
+{
+ 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;
+ }
+
+ /* Example line:
+ * pcm0: <ATI R6xx (HDMI)> (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;
+
+ p = strchr (line, '<');
+ if (p != NULL && *p && *(++p)) {
+ gchar *end = strchr (p, '>');
+
+ if (end != NULL) {
+ label = g_strndup (p, end - p);
+
+ /* 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);
+
+ oss->priv->default_device = g_strdup (path);
+ }
+ } else {
+ g_debug ("Failed to read sndstat line: %s", line);
+ }
+ break;
+ }
+ }
+
+ g_free (prefix);
+ fclose (fp);
+
+ return label;
+}
+
+static void
+add_device (OssBackend *oss, OssDevice *device)
+{
+ const gchar *name;
+
+ name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device));
+
+ g_hash_table_insert (oss->priv->devices,
+ g_strdup (oss_device_get_path (device)),
+ g_object_ref (device));
+
+ // XXX make device emit it when closed
+ g_signal_connect_swapped (G_OBJECT (device),
+ "stream-removed",
+ G_CALLBACK (remove_stream),
+ oss);
+
+ g_signal_emit_by_name (G_OBJECT (oss), "device-added", name);
+
+ 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;
+
+ 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);
+}
+
+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;
+ }
+ }
+
+ for (i = 0; i < OSS_MAX_DEVICES; i++) {
+ gchar *path = g_strdup_printf ("/dev/mixer%i", i);
+
+ device = g_hash_table_lookup (oss->priv->devices, path);
+ if (device == NULL && i == 0)
+ device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer");
+
+ 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);
+ }
+
+ /* In the worst case unset the default stream */
+ _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), NULL);
+}
+
+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;
+ }
+ }
+
+ for (i = 0; i < OSS_MAX_DEVICES; i++) {
+ gchar *path = g_strdup_printf ("/dev/mixer%i", i);
+
+ device = g_hash_table_lookup (oss->priv->devices, path);
+ if (device == NULL && i == 0)
+ device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer");
+
+ 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);
+}
diff --git a/backends/oss/oss-backend.h b/backends/oss/oss-backend.h
new file mode 100644
index 0000000..325b61c
--- /dev/null
+++ b/backends/oss/oss-backend.h
@@ -0,0 +1,63 @@
+/*
+ * 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/>.
+ */
+
+#ifndef OSS_BACKEND_H
+#define OSS_BACKEND_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer-backend.h>
+#include <libmatemixer/matemixer-backend-module.h>
+
+#define OSS_TYPE_BACKEND \
+ (oss_backend_get_type ())
+#define OSS_BACKEND(o) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_BACKEND, OssBackend))
+#define OSS_IS_BACKEND(o) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_BACKEND))
+#define OSS_BACKEND_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_BACKEND, OssBackendClass))
+#define OSS_IS_BACKEND_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_BACKEND))
+#define OSS_BACKEND_GET_CLASS(o) \
+ (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_BACKEND, OssBackendClass))
+
+typedef struct _OssBackend OssBackend;
+typedef struct _OssBackendClass OssBackendClass;
+typedef struct _OssBackendPrivate OssBackendPrivate;
+
+struct _OssBackend
+{
+ MateMixerBackend parent;
+
+ /*< private >*/
+ OssBackendPrivate *priv;
+};
+
+struct _OssBackendClass
+{
+ MateMixerBackendClass parent_class;
+};
+
+GType oss_backend_get_type (void) G_GNUC_CONST;
+
+/* Support function for dynamic loading of the backend module */
+void backend_module_init (GTypeModule *module);
+const MateMixerBackendInfo *backend_module_get_info (void);
+
+#endif /* OSS_BACKEND_H */
diff --git a/backends/oss/oss-common.h b/backends/oss/oss-common.h
new file mode 100644
index 0000000..28a138d
--- /dev/null
+++ b/backends/oss/oss-common.h
@@ -0,0 +1,39 @@
+/*
+ * 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/>.
+ */
+
+#ifndef OSS_COMMON_H
+#define OSS_COMMON_H
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_SOUNDCARD_H
+# include <sys/soundcard.h>
+#elif HAVE_SOUNDCARD_H
+# include <soundcard.h>
+#elif HAVE_MACHINE_SOUNDCARD_H
+# include <machine/soundcard.h>
+#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..cf51705
--- /dev/null
+++ b/backends/oss/oss-device.c
@@ -0,0 +1,404 @@
+/*
+ * 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 <errno.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer.h>
+#include <libmatemixer/matemixer-private.h>
+
+#include "oss-common.h"
+#include "oss-device.h"
+#include "oss-stream.h"
+#include "oss-stream-control.h"
+
+#define OSS_DEVICE_ICON "audio-card"
+
+typedef enum
+{
+ OSS_DEV_ANY,
+ OSS_DEV_INPUT,
+ OSS_DEV_OUTPUT
+} OssDevType;
+
+typedef struct
+{
+ gchar *name;
+ gchar *label;
+ MateMixerStreamControlRole role;
+ OssDevType type;
+} OssDev;
+
+static const OssDev oss_devices[] = {
+ { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_OUTPUT },
+ { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, OSS_DEV_OUTPUT },
+ { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, OSS_DEV_OUTPUT },
+ { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT },
+ { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT },
+ /* OSS manual says this should be the beeper, but Linux OSS seems to assign it to
+ * regular volume control */
+ { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, OSS_DEV_OUTPUT },
+ { "line", N_("Line-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, OSS_DEV_INPUT },
+ /* Recording monitor */
+ { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT },
+ { "pcm2", N_("PCM-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT },
+ /* Recording level (master input) */
+ { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_INPUT },
+ { "igain", N_("In-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT },
+ { "ogain", N_("Out-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT },
+ { "line1", N_("Line-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "line2", N_("Line-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "line3", N_("Line-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "dig1", N_("Digital-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY },
+ { "dig2", N_("Digital-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY },
+ { "dig3", N_("Digital-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY },
+ { "phin", N_("Phone-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "phout", N_("Phone-out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_OUTPUT },
+ { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT },
+ { "monitor", N_("Monitor"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT },
+ { "depth", N_("3D-depth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT },
+ { "center", N_("3D-center"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT },
+ { "midi", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }
+};
+
+#define OSS_N_DEVICES MIN (G_N_ELEMENTS (oss_devices), SOUND_MIXER_NRDEVICES)
+
+struct _OssDevicePrivate
+{
+ gint fd;
+ gchar *path;
+ gint devmask;
+ gint stereodevs;
+ gint recmask;
+ gint recsrc;
+ OssStream *input;
+ OssStream *output;
+};
+
+static void oss_device_class_init (OssDeviceClass *klass);
+static void oss_device_init (OssDevice *device);
+static void oss_device_dispose (GObject *object);
+static void oss_device_finalize (GObject *object);
+
+G_DEFINE_TYPE (OssDevice, oss_device, MATE_MIXER_TYPE_DEVICE)
+
+static GList * oss_device_list_streams (MateMixerDevice *device);
+
+static gboolean read_mixer_devices (OssDevice *device);
+
+static gboolean set_stream_default_control (OssStream *stream,
+ OssStreamControl *control,
+ gboolean force);
+
+static void
+oss_device_class_init (OssDeviceClass *klass)
+{
+ GObjectClass *object_class;
+ MateMixerDeviceClass *device_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = oss_device_dispose;
+ object_class->finalize = oss_device_finalize;
+
+ device_class = MATE_MIXER_DEVICE_CLASS (klass);
+ device_class->list_streams = oss_device_list_streams;
+
+ g_type_class_add_private (object_class, sizeof (OssDevicePrivate));
+}
+
+static void
+oss_device_init (OssDevice *device)
+{
+ device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device,
+ OSS_TYPE_DEVICE,
+ OssDevicePrivate);
+}
+
+static void
+oss_device_dispose (GObject *object)
+{
+ OssDevice *device;
+
+ device = OSS_DEVICE (object);
+
+ g_clear_object (&device->priv->input);
+ g_clear_object (&device->priv->output);
+
+ G_OBJECT_CLASS (oss_device_parent_class)->dispose (object);
+}
+
+static void
+oss_device_finalize (GObject *object)
+{
+ OssDevice *device = OSS_DEVICE (object);
+
+ close (device->priv->fd);
+
+ G_OBJECT_CLASS (oss_device_parent_class)->finalize (object);
+}
+
+OssDevice *
+oss_device_new (const gchar *name, const gchar *label, const gchar *path, gint fd)
+{
+ OssDevice *device;
+ gchar *stream_name;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (label != NULL, NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ device = g_object_new (OSS_TYPE_DEVICE,
+ "name", name,
+ "label", label,
+ "icon", OSS_DEVICE_ICON,
+ NULL);
+
+ device->priv->fd = dup (fd);
+ device->priv->path = g_strdup (path);
+
+ stream_name = g_strdup_printf ("oss-input-%s", name);
+ device->priv->input = oss_stream_new (stream_name,
+ MATE_MIXER_DEVICE (device),
+ MATE_MIXER_STREAM_INPUT);
+ g_free (stream_name);
+
+ stream_name = g_strdup_printf ("oss-output-%s", name);
+ device->priv->output = oss_stream_new (stream_name,
+ MATE_MIXER_DEVICE (device),
+ MATE_MIXER_STREAM_OUTPUT);
+ g_free (stream_name);
+
+ return device;
+}
+
+gboolean
+oss_device_open (OssDevice *device)
+{
+ gint ret;
+
+ g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE);
+
+ g_debug ("Opening device %s (%s)",
+ device->priv->path,
+ mate_mixer_device_get_label (MATE_MIXER_DEVICE (device)));
+
+ /* Read the essential information about the device, these values are not
+ * expected to change and will not be queried */
+ ret = ioctl (device->priv->fd,
+ MIXER_READ (SOUND_MIXER_DEVMASK),
+ &device->priv->devmask);
+ if (ret != 0)
+ goto fail;
+
+ ret = ioctl (device->priv->fd,
+ MIXER_READ (SOUND_MIXER_STEREODEVS),
+ &device->priv->stereodevs);
+ if (ret < 0)
+ goto fail;
+
+ ret = ioctl (device->priv->fd,
+ MIXER_READ (SOUND_MIXER_RECMASK),
+ &device->priv->recmask);
+ if (ret < 0)
+ goto fail;
+
+ /* The recording source mask may change at any time, here we just read
+ * the initial value */
+ ret = ioctl (device->priv->fd,
+ MIXER_READ (SOUND_MIXER_RECSRC),
+ &device->priv->recsrc);
+ if (ret < 0)
+ goto fail;
+
+ /* NOTE: Linux also supports SOUND_MIXER_OUTSRC and SOUND_MIXER_OUTMASK which
+ * inform about/enable input->output, we could potentially create toggles
+ * for these, but these constants are not defined on any BSD. */
+
+ return TRUE;
+
+fail:
+ g_warning ("Failed to read device %s: %s",
+ device->priv->path,
+ g_strerror (errno));
+
+ return FALSE;
+}
+
+gboolean
+oss_device_load (OssDevice *device)
+{
+ MateMixerStreamControl *control;
+
+ g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE);
+
+ read_mixer_devices (device);
+
+ control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->input));
+ if (control == NULL) {
+ // XXX pick something
+ }
+
+ if (control != NULL)
+ g_debug ("Default input stream control is %s",
+ mate_mixer_stream_control_get_label (control));
+
+ control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->output));
+ if (control == NULL) {
+ // XXX pick something
+ }
+
+ if (control != NULL)
+ g_debug ("Default output stream control is %s",
+ mate_mixer_stream_control_get_label (control));
+
+ return TRUE;
+}
+
+gint
+oss_device_get_fd (OssDevice *device)
+{
+ g_return_val_if_fail (OSS_IS_DEVICE (device), -1);
+
+ return device->priv->fd;
+}
+
+const gchar *
+oss_device_get_path (OssDevice *device)
+{
+ g_return_val_if_fail (OSS_IS_DEVICE (device), NULL);
+
+ return device->priv->path;
+}
+
+OssStream *
+oss_device_get_input_stream (OssDevice *device)
+{
+ g_return_val_if_fail (OSS_IS_DEVICE (device), NULL);
+
+ return device->priv->input;
+}
+
+OssStream *
+oss_device_get_output_stream (OssDevice *device)
+{
+ g_return_val_if_fail (OSS_IS_DEVICE (device), NULL);
+
+ return device->priv->output;
+}
+
+static GList *
+oss_device_list_streams (MateMixerDevice *mmd)
+{
+ OssDevice *device;
+ GList *list = NULL;
+
+ g_return_val_if_fail (OSS_IS_DEVICE (mmd), NULL);
+
+ device = OSS_DEVICE (mmd);
+
+ if (device->priv->output != NULL)
+ list = g_list_prepend (list, g_object_ref (device->priv->output));
+ if (device->priv->input != NULL)
+ list = g_list_prepend (list, g_object_ref (device->priv->input));
+
+ return list;
+}
+
+#define OSS_MASK_HAS_DEVICE(mask,i) ((gboolean) (((mask) & (1 << (i))) > 0))
+
+static gboolean
+read_mixer_devices (OssDevice *device)
+{
+ gint i;
+
+ for (i = 0; i < OSS_N_DEVICES; i++) {
+ OssStreamControl *control;
+ gboolean input = FALSE;
+
+ /* Skip unavailable controls */
+ if (OSS_MASK_HAS_DEVICE (device->priv->devmask, i) == FALSE)
+ continue;
+
+ if (oss_devices[i].type == OSS_DEV_ANY) {
+ input = OSS_MASK_HAS_DEVICE (device->priv->recmask, i);
+ }
+ else if (oss_devices[i].type == OSS_DEV_INPUT) {
+ input = TRUE;
+ }
+
+ control = oss_stream_control_new (oss_devices[i].name,
+ oss_devices[i].label,
+ oss_devices[i].role,
+ device->priv->fd,
+ i,
+ OSS_MASK_HAS_DEVICE (device->priv->stereodevs, i));
+
+ if (input == TRUE) {
+ oss_stream_add_control (OSS_STREAM (device->priv->input), control);
+
+ if (i == SOUND_MIXER_RECLEV || i == SOUND_MIXER_IGAIN) {
+ if (i == SOUND_MIXER_RECLEV)
+ set_stream_default_control (OSS_STREAM (device->priv->input),
+ control,
+ TRUE);
+ else
+ set_stream_default_control (OSS_STREAM (device->priv->input),
+ control,
+ FALSE);
+ }
+ } else {
+ oss_stream_add_control (OSS_STREAM (device->priv->output), control);
+
+ if (i == SOUND_MIXER_VOLUME || i == SOUND_MIXER_PCM) {
+ if (i == SOUND_MIXER_VOLUME)
+ set_stream_default_control (OSS_STREAM (device->priv->output),
+ control,
+ TRUE);
+ else
+ set_stream_default_control (OSS_STREAM (device->priv->output),
+ control,
+ FALSE);
+ }
+ }
+
+ g_debug ("Added control %s",
+ mate_mixer_stream_control_get_label (MATE_MIXER_STREAM_CONTROL (control)));
+
+ oss_stream_control_update (control);
+ }
+ return TRUE;
+}
+
+static gboolean
+set_stream_default_control (OssStream *stream, OssStreamControl *control, gboolean force)
+{
+ MateMixerStreamControl *current;
+
+ current = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream));
+ if (current == NULL || force == TRUE) {
+ oss_stream_set_default_control (stream, OSS_STREAM_CONTROL (control));
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/backends/oss/oss-device.h b/backends/oss/oss-device.h
new file mode 100644
index 0000000..261a884
--- /dev/null
+++ b/backends/oss/oss-device.h
@@ -0,0 +1,77 @@
+/*
+ * 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/>.
+ */
+
+#ifndef OSS_DEVICE_H
+#define OSS_DEVICE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libmatemixer/matemixer.h>
+
+#include "oss-stream.h"
+
+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
+{
+ MateMixerDevice parent;
+
+ /*< private >*/
+ OssDevicePrivate *priv;
+};
+
+struct _OssDeviceClass
+{
+ MateMixerDeviceClass parent;
+};
+
+GType oss_device_get_type (void) G_GNUC_CONST;
+
+OssDevice * oss_device_new (const gchar *name,
+ const gchar *label,
+ const gchar *path,
+ gint fd);
+
+gboolean oss_device_open (OssDevice *device);
+gboolean oss_device_load (OssDevice *device);
+
+gint oss_device_get_fd (OssDevice *device);
+const gchar *oss_device_get_path (OssDevice *device);
+
+OssStream * oss_device_get_input_stream (OssDevice *device);
+OssStream * oss_device_get_output_stream (OssDevice *device);
+
+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..5161528
--- /dev/null
+++ b/backends/oss/oss-stream-control.c
@@ -0,0 +1,392 @@
+/*
+ * 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 <errno.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+
+#include <libmatemixer/matemixer.h>
+#include <libmatemixer/matemixer-private.h>
+
+#include "oss-common.h"
+#include "oss-stream-control.h"
+
+struct _OssStreamControlPrivate
+{
+ gint fd;
+ gint devnum;
+ guint volume[2];
+ gfloat balance;
+ gboolean stereo;
+ MateMixerStreamControlRole role;
+ MateMixerStreamControlFlags flags;
+};
+
+static void oss_stream_control_class_init (OssStreamControlClass *klass);
+
+static void oss_stream_control_init (OssStreamControl *control);
+static void oss_stream_control_finalize (GObject *object);
+
+G_DEFINE_TYPE (OssStreamControl, oss_stream_control, MATE_MIXER_TYPE_STREAM_CONTROL)
+
+static gboolean oss_stream_control_set_mute (MateMixerStreamControl *mmsc,
+ gboolean mute);
+
+static guint oss_stream_control_get_num_channels (MateMixerStreamControl *mmsc);
+
+static guint oss_stream_control_get_volume (MateMixerStreamControl *mmsc);
+
+static gboolean oss_stream_control_set_volume (MateMixerStreamControl *mmsc,
+ guint volume);
+
+static gboolean oss_stream_control_has_channel_position (MateMixerStreamControl *mmsc,
+ MateMixerChannelPosition position);
+static MateMixerChannelPosition oss_stream_control_get_channel_position (MateMixerStreamControl *mmsc,
+ guint channel);
+
+static guint oss_stream_control_get_channel_volume (MateMixerStreamControl *mmsc,
+ guint channel);
+static gboolean oss_stream_control_set_channel_volume (MateMixerStreamControl *mmsc,
+ guint channel,
+ guint volume);
+
+static gboolean oss_stream_control_set_balance (MateMixerStreamControl *mmsc,
+ gfloat balance);
+
+static guint oss_stream_control_get_min_volume (MateMixerStreamControl *mmsc);
+static guint oss_stream_control_get_max_volume (MateMixerStreamControl *mmsc);
+static guint oss_stream_control_get_normal_volume (MateMixerStreamControl *mmsc);
+static guint oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc);
+
+static gboolean write_volume (OssStreamControl *control,
+ gint volume);
+
+static void
+oss_stream_control_class_init (OssStreamControlClass *klass)
+{
+ GObjectClass *object_class;
+ MateMixerStreamControlClass *control_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = oss_stream_control_finalize;
+
+ control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass);
+ control_class->set_mute = oss_stream_control_set_mute;
+ control_class->get_num_channels = oss_stream_control_get_num_channels;
+ control_class->get_volume = oss_stream_control_get_volume;
+ control_class->set_volume = oss_stream_control_set_volume;
+ control_class->get_channel_volume = oss_stream_control_get_channel_volume;
+ control_class->set_channel_volume = oss_stream_control_set_channel_volume;
+ control_class->get_channel_position = oss_stream_control_get_channel_position;
+ control_class->has_channel_position = oss_stream_control_has_channel_position;
+ control_class->set_balance = oss_stream_control_set_balance;
+ control_class->get_min_volume = oss_stream_control_get_min_volume;
+ control_class->get_max_volume = oss_stream_control_get_max_volume;
+ control_class->get_normal_volume = oss_stream_control_get_normal_volume;
+ control_class->get_base_volume = oss_stream_control_get_base_volume;
+
+ g_type_class_add_private (object_class, sizeof (OssStreamControlPrivate));
+}
+
+static void
+oss_stream_control_init (OssStreamControl *control)
+{
+ control->priv = G_TYPE_INSTANCE_GET_PRIVATE (control,
+ OSS_TYPE_STREAM_CONTROL,
+ OssStreamControlPrivate);
+}
+
+static void
+oss_stream_control_finalize (GObject *object)
+{
+ OssStreamControl *control;
+
+ control = OSS_STREAM_CONTROL (object);
+
+ close (control->priv->fd);
+
+ G_OBJECT_CLASS (oss_stream_control_parent_class)->finalize (object);
+}
+
+OssStreamControl *
+oss_stream_control_new (const gchar *name,
+ const gchar *label,
+ MateMixerStreamControlRole role,
+ gint fd,
+ gint devnum,
+ gboolean stereo)
+{
+ OssStreamControl *control;
+ MateMixerStreamControlFlags flags;
+
+ flags = MATE_MIXER_STREAM_CONTROL_HAS_VOLUME |
+ MATE_MIXER_STREAM_CONTROL_CAN_SET_VOLUME;
+ if (stereo == TRUE)
+ flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE;
+
+ control = g_object_new (OSS_TYPE_STREAM_CONTROL,
+ "name", name,
+ "label", label,
+ "flags", flags,
+ NULL);
+
+ control->priv->fd = fd;
+ control->priv->devnum = devnum;
+ control->priv->stereo = stereo;
+ return control;
+}
+
+gboolean
+oss_stream_control_update (OssStreamControl *control)
+{
+ gint v;
+ gint ret;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE);
+
+ ret = ioctl (control->priv->fd, MIXER_READ (control->priv->devnum), &v);
+ if (ret < 0) {
+ g_warning ("Failed to read volume: %s", g_strerror (errno));
+ return FALSE;
+ }
+
+ control->priv->volume[0] = v & 0xFF;
+
+ if (control->priv->stereo == TRUE) {
+ gfloat balance;
+ guint left;
+ guint right;
+
+ control->priv->volume[1] = (v >> 8) & 0xFF;
+
+ /* Calculate balance */
+ left = control->priv->volume[0];
+ right = control->priv->volume[1];
+ if (left == right)
+ balance = 0.0f;
+ else if (left > right)
+ balance = -1.0f + ((gfloat) right / (gfloat) left);
+ else
+ balance = +1.0f - ((gfloat) left / (gfloat) right);
+
+ _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control),
+ balance);
+ }
+ return TRUE;
+}
+
+static gboolean
+oss_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute)
+{
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE);
+
+ // TODO
+ return TRUE;
+}
+
+static guint
+oss_stream_control_get_num_channels (MateMixerStreamControl *mmsc)
+{
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), 0);
+
+ return (OSS_STREAM_CONTROL (mmsc)->priv->stereo == TRUE) ? 2 : 1;
+}
+
+static guint
+oss_stream_control_get_volume (MateMixerStreamControl *mmsc)
+{
+ OssStreamControl *control;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), 0);
+
+ control = OSS_STREAM_CONTROL (mmsc);
+
+ if (control->priv->stereo == TRUE)
+ return MAX (control->priv->volume[0], control->priv->volume[1]);
+ else
+ return control->priv->volume[0];
+}
+
+static gboolean
+oss_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume)
+{
+ OssStreamControl *control;
+ gint v;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE);
+
+ control = OSS_STREAM_CONTROL (mmsc);
+
+ v = CLAMP (volume, 0, 100);
+ if (control->priv->stereo == TRUE)
+ v |= (volume & 0xFF) << 8;
+
+ return write_volume (control, v);
+}
+
+static guint
+oss_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint channel)
+{
+ OssStreamControl *control;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), 0);
+
+ control = OSS_STREAM_CONTROL (mmsc);
+
+ if (control->priv->stereo == TRUE) {
+ if (channel == 0 || channel == 1)
+ return control->priv->volume[channel];
+ } else {
+ if (channel == 0)
+ return control->priv->volume[0];
+ }
+ return 0;
+}
+
+static gboolean
+oss_stream_control_set_channel_volume (MateMixerStreamControl *mmsc,
+ guint channel,
+ guint volume)
+{
+ OssStreamControl *control;
+ gint v;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE);
+
+ control = OSS_STREAM_CONTROL (mmsc);
+ volume = CLAMP (volume, 0, 100);
+
+ if (control->priv->stereo == TRUE) {
+ if (channel > 1)
+ return FALSE;
+
+ /* Stereo channel volume - left channel is in the lowest 8 bits and
+ * right channel is in the higher 8 bits */
+ if (channel == 0)
+ v = volume | (control->priv->volume[1] << 8);
+ else
+ v = control->priv->volume[0] | (volume << 8);
+ } else {
+ if (channel > 0)
+ return FALSE;
+
+ /* Single channel volume - only lowest 8 bits are used */
+ v = volume;
+ }
+
+ return write_volume (control, v);
+}
+
+static MateMixerChannelPosition
+oss_stream_control_get_channel_position (MateMixerStreamControl *mmsc, guint channel)
+{
+ OssStreamControl *control;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), MATE_MIXER_CHANNEL_UNKNOWN);
+
+ control = OSS_STREAM_CONTROL (mmsc);
+
+ if (control->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 *mmsc,
+ MateMixerChannelPosition position)
+{
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE);
+
+ if (position == MATE_MIXER_CHANNEL_MONO)
+ return OSS_STREAM_CONTROL (mmsc)->priv->stereo == FALSE;
+
+ if (position == MATE_MIXER_CHANNEL_FRONT_LEFT ||
+ position == MATE_MIXER_CHANNEL_FRONT_RIGHT)
+ return OSS_STREAM_CONTROL (mmsc)->priv->stereo == TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+oss_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance)
+{
+ OssStreamControl *control;
+ guint max;
+ gint v;
+
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE);
+
+ control = OSS_STREAM_CONTROL (mmsc);
+
+ max = MAX (control->priv->volume[0], control->priv->volume[1]);
+ if (balance <= 0) {
+ control->priv->volume[1] = (balance + 1.0f) * max;
+ control->priv->volume[0] = max;
+ } else {
+ control->priv->volume[0] = (1.0f - balance) * max;
+ control->priv->volume[1] = max;
+ }
+
+ v = control->priv->volume[0] | (control->priv->volume[1] << 8);
+
+ return write_volume (control, v);
+}
+
+static guint
+oss_stream_control_get_min_volume (MateMixerStreamControl *mmsc)
+{
+ return 0;
+}
+
+static guint
+oss_stream_control_get_max_volume (MateMixerStreamControl *mmsc)
+{
+ return 100;
+}
+
+static guint
+oss_stream_control_get_normal_volume (MateMixerStreamControl *mmsc)
+{
+ return 100;
+}
+
+static guint
+oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc)
+{
+ return 100;
+}
+
+static gboolean
+write_volume (OssStreamControl *control, gint volume)
+{
+ gint ret;
+
+ ret = ioctl (control->priv->fd, MIXER_WRITE (control->priv->devnum), &volume);
+ if (ret < 0) {
+ g_warning ("Failed to set volume: %s", g_strerror (errno));
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/backends/oss/oss-stream-control.h b/backends/oss/oss-stream-control.h
new file mode 100644
index 0000000..c839faf
--- /dev/null
+++ b/backends/oss/oss-stream-control.h
@@ -0,0 +1,70 @@
+/*
+ * 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/>.
+ */
+
+#ifndef OSS_STREAM_CONTROL_H
+#define OSS_STREAM_CONTROL_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libmatemixer/matemixer.h>
+
+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
+{
+ MateMixerStreamControl parent;
+
+ /*< private >*/
+ OssStreamControlPrivate *priv;
+};
+
+struct _OssStreamControlClass
+{
+ MateMixerStreamControlClass parent;
+};
+
+GType oss_stream_control_get_type (void) G_GNUC_CONST;
+
+OssStreamControl *oss_stream_control_new (const gchar *name,
+ const gchar *label,
+ MateMixerStreamControlRole role,
+ gint fd,
+ gint devnum,
+ gboolean stereo);
+
+gboolean oss_stream_control_update (OssStreamControl *control);
+
+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..5f0c629
--- /dev/null
+++ b/backends/oss/oss-stream.c
@@ -0,0 +1,183 @@
+/*
+ * 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 <glib.h>
+#include <glib-object.h>
+#include <libmatemixer/matemixer.h>
+
+#include "oss-device.h"
+#include "oss-stream.h"
+#include "oss-stream-control.h"
+
+struct _OssStreamPrivate
+{
+ GHashTable *controls;
+ OssStreamControl *control;
+};
+
+static void oss_stream_class_init (OssStreamClass *klass);
+
+static void oss_stream_init (OssStream *ostream);
+static void oss_stream_dispose (GObject *object);
+static void oss_stream_finalize (GObject *object);
+
+G_DEFINE_TYPE (OssStream, oss_stream, MATE_MIXER_TYPE_STREAM)
+
+static MateMixerStreamControl *oss_stream_get_control (MateMixerStream *stream,
+ const gchar *name);
+static MateMixerStreamControl *oss_stream_get_default_control (MateMixerStream *stream);
+
+static GList * oss_stream_list_controls (MateMixerStream *stream);
+
+static void
+oss_stream_class_init (OssStreamClass *klass)
+{
+ GObjectClass *object_class;
+ MateMixerStreamClass *stream_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = oss_stream_dispose;
+ object_class->finalize = oss_stream_finalize;
+
+ stream_class = MATE_MIXER_STREAM_CLASS (klass);
+ stream_class->get_control = oss_stream_get_control;
+ stream_class->get_default_control = oss_stream_get_default_control;
+ stream_class->list_controls = oss_stream_list_controls;
+
+ g_type_class_add_private (object_class, sizeof (OssStreamPrivate));
+}
+
+static void
+oss_stream_init (OssStream *stream)
+{
+ stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream,
+ OSS_TYPE_STREAM,
+ OssStreamPrivate);
+
+ stream->priv->controls = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+}
+
+static void
+oss_stream_dispose (GObject *object)
+{
+ OssStream *stream;
+
+ stream = OSS_STREAM (object);
+
+ g_clear_object (&stream->priv->control);
+ g_hash_table_remove_all (stream->priv->controls);
+
+ G_OBJECT_CLASS (oss_stream_parent_class)->dispose (object);
+}
+
+static void
+oss_stream_finalize (GObject *object)
+{
+ OssStream *stream;
+
+ stream = OSS_STREAM (object);
+
+ g_hash_table_destroy (stream->priv->controls);
+
+ G_OBJECT_CLASS (oss_stream_parent_class)->finalize (object);
+}
+
+OssStream *
+oss_stream_new (const gchar *name,
+ MateMixerDevice *device,
+ MateMixerStreamFlags flags)
+{
+ OssStream *stream;
+
+ stream = g_object_new (OSS_TYPE_STREAM,
+ "name", name,
+ "device", device,
+ "flags", flags,
+ NULL);
+ return stream;
+}
+
+gboolean
+oss_stream_add_control (OssStream *stream, OssStreamControl *control)
+{
+ const gchar *name;
+
+ g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE);
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE);
+
+ name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control));
+
+ g_hash_table_insert (stream->priv->controls,
+ g_strdup (name),
+ control);
+ return TRUE;
+}
+
+gboolean
+oss_stream_set_default_control (OssStream *stream, OssStreamControl *control)
+{
+ g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE);
+ g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE);
+
+ /* This function is only used internally so avoid validating that the control
+ * belongs to this stream */
+ if (stream->priv->control != NULL)
+ g_object_unref (stream->priv->control);
+
+ if G_LIKELY (control != NULL)
+ stream->priv->control = g_object_ref (control);
+ else
+ stream->priv->control = NULL;
+
+ return TRUE;
+}
+
+static MateMixerStreamControl *
+oss_stream_get_control (MateMixerStream *mms, const gchar *name)
+{
+ g_return_val_if_fail (OSS_IS_STREAM (mms), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_hash_table_lookup (OSS_STREAM (mms)->priv->controls, name);
+}
+
+static MateMixerStreamControl *
+oss_stream_get_default_control (MateMixerStream *mms)
+{
+ g_return_val_if_fail (OSS_IS_STREAM (mms), NULL);
+
+ return MATE_MIXER_STREAM_CONTROL (OSS_STREAM (mms)->priv->control);
+}
+
+static GList *
+oss_stream_list_controls (MateMixerStream *mms)
+{
+ GList *list;
+
+ g_return_val_if_fail (OSS_IS_STREAM (mms), NULL);
+
+ /* Convert the hash table to a linked list, this list is expected to be
+ * cached in the main library */
+ list = g_hash_table_get_values (OSS_STREAM (mms)->priv->controls);
+ if (list != NULL)
+ g_list_foreach (list, (GFunc) g_object_ref, NULL);
+
+ return list;
+}
diff --git a/backends/oss/oss-stream.h b/backends/oss/oss-stream.h
new file mode 100644
index 0000000..0470eb7
--- /dev/null
+++ b/backends/oss/oss-stream.h
@@ -0,0 +1,74 @@
+/*
+ * 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/>.
+ */
+
+#ifndef OSS_STREAM_H
+#define OSS_STREAM_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libmatemixer/matemixer.h>
+
+#include "oss-device.h"
+#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
+{
+ MateMixerStream parent;
+
+ /*< private >*/
+ OssStreamPrivate *priv;
+};
+
+struct _OssStreamClass
+{
+ MateMixerStreamClass parent;
+};
+
+GType oss_stream_get_type (void) G_GNUC_CONST;
+
+OssStream *oss_stream_new (const gchar *name,
+ MateMixerDevice *device,
+ MateMixerStreamFlags flags);
+
+gboolean oss_stream_add_control (OssStream *stream,
+ OssStreamControl *control);
+
+gboolean oss_stream_set_default_control (OssStream *stream,
+ OssStreamControl *control);
+
+G_END_DECLS
+
+#endif /* OSS_STREAM_H */