summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArun Raghavan <arun.raghavan@collabora.co.uk>2010-08-21 00:39:54 +0530
committerArun Raghavan <arun.raghavan@collabora.co.uk>2010-12-17 16:58:19 +0530
commit60fe6e264b7baf1ef598e047d6da8dc56e3f65e3 (patch)
treeb8e1c9b06179538e458257e4bd47578373421776
parent0317f5e59b4cfeaa56b9a3d356a086a999fb6a1c (diff)
vff: Add a volume-follows-focus modulevff
Work in progress
-rw-r--r--src/Makefile.am8
-rw-r--r--src/modules/module-volume-follows-focus.c576
2 files changed, 584 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 499a5bada..377c732a4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1033,6 +1033,7 @@ modlibexec_LTLIBRARIES += \
module-tunnel-source.la \
module-position-event-sounds.la \
module-augment-properties.la \
+ module-volume-follows-focus.la \
module-cork-music-on-phone.la \
module-loopback.la \
module-virtual-sink.la \
@@ -1306,6 +1307,7 @@ SYMDEF_FILES = \
modules/gconf/module-gconf-symdef.h \
modules/module-position-event-sounds-symdef.h \
modules/module-augment-properties-symdef.h \
+ modules/module-volume-follows-focus-symdef.h \
modules/module-cork-music-on-phone-symdef.h \
modules/module-console-kit-symdef.h \
modules/dbus/module-dbus-protocol-symdef.h \
@@ -1320,6 +1322,12 @@ $(SYMDEF_FILES): modules/module-defs.h.m4
$(MKDIR_P) $(dir $@)
$(M4) -Dfname="$@" $< > $@
+# Volume follows focus
+
+module_volume_follows_focus_la_SOURCES = modules/module-volume-follows-focus.c
+module_volume_follows_focus_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_volume_follows_focus_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la
+
# Simple protocol
module_simple_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
diff --git a/src/modules/module-volume-follows-focus.c b/src/modules/module-volume-follows-focus.c
new file mode 100644
index 000000000..ed8088c25
--- /dev/null
+++ b/src/modules/module-volume-follows-focus.c
@@ -0,0 +1,576 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan
+
+ PulseAudio 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 License,
+ or (at your option) any later version.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/volume.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/llist.h>
+
+#include <pulsecore/x11wrap.h>
+#include <X11/Xatom.h>
+#include <X11/Xproto.h>
+
+#include "module-volume-follows-focus-symdef.h"
+
+PA_MODULE_AUTHOR("Arun Raghavan");
+PA_MODULE_DESCRIPTION("Automatically lower/raise volume of an application when it's window is lowered/raised");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+#define VFF_SCALE_FACTOR 0.05
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_modargs *modargs;
+ pa_hook_slot *sink_input_put_hook_slot;
+ pa_hook_slot *sink_input_unlink_hook_slot;
+ Display *display;
+ Window active_window;
+ /* XXX: does this need to be protected with a lock? */
+ pa_hashmap *maps;
+
+ pa_x11_wrapper *x11_wrapper;
+ pa_x11_client *x11_client;
+};
+
+static const char* const valid_modargs[] = {
+ "display",
+ NULL
+};
+
+struct windows {
+ Window w;
+ PA_LLIST_FIELDS(struct windows);
+};
+
+struct streams {
+ pa_sink_input *si;
+ pa_cvolume old_volume;
+ PA_LLIST_FIELDS(struct streams);
+};
+
+struct entry {
+ char *key;
+ pa_bool_t damped;
+ PA_LLIST_HEAD(struct windows, windows);
+ PA_LLIST_HEAD(struct streams, streams);
+};
+
+static Atom _net_active_window, _net_wm_pid, _wm_client_machine;
+static XErrorHandler _x_old_handler = NULL;
+
+static void dump_map(struct userdata *u)
+{
+ struct entry *e;
+ struct windows *w;
+ struct streams *s;
+ void *state = NULL;
+
+ pa_log("----");
+ while ((e = pa_hashmap_iterate(u->maps, &state, NULL))) {
+ pa_log("%s%s", e->key, e->damped ? "(damped)" : "");
+ PA_LLIST_FOREACH(w, e->windows)
+ pa_log(" w: %lu", w->w);
+ PA_LLIST_FOREACH(s, e->streams)
+ pa_log(" s: %u", s->si->index);
+ }
+ pa_log("----");
+}
+
+static void entry_free_cb(void *e, void *u)
+{
+ struct entry *entry = e;
+ struct windows *w1, *w2;
+ struct streams *s1, *s2;
+
+ PA_LLIST_FOREACH_SAFE(w1, w2, entry->windows)
+ pa_xfree(w1);
+ PA_LLIST_FOREACH_SAFE(s1, s2, entry->streams)
+ pa_xfree(s1);
+
+ pa_xfree(entry->key);
+ pa_xfree(entry);
+}
+
+static void clean_map(struct pa_hashmap *map, struct entry *e)
+{
+ if (e->windows == NULL && e->streams == NULL) {
+ pa_hashmap_remove (map, e->key);
+ pa_xfree(e);
+ }
+}
+
+static char *get_name(pa_proplist *p, const char *prefix) {
+ const char *r;
+
+ if (!p)
+ return NULL;
+
+ if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_ROLE)))
+ return pa_sprintf_malloc("%s-by-media-role:%s", prefix, r);
+ else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_ID)))
+ return pa_sprintf_malloc("%s-by-application-id:%s", prefix, r);
+ else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME)))
+ return pa_sprintf_malloc("%s-by-application-name:%s", prefix, r);
+ else if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_NAME)))
+ return pa_sprintf_malloc("%s-by-media-name:%s", prefix, r);
+
+ return pa_sprintf_malloc("%s-fallback:%s", prefix, r);
+}
+
+static char *pa_get_app_id_for_stream(pa_proplist *p)
+{
+ char *ret;
+
+ ret = pa_sprintf_malloc("%s:%s",
+ pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_MACHINE_ID),
+ pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_ID));
+
+ return ret;
+}
+
+static char *pa_get_app_id_for_window(Display *d, Window w)
+{
+ Atom t;
+ int f;
+ unsigned long n, b;
+ unsigned char *data = NULL;
+ pid_t pid;
+ char *ret = NULL;
+
+ if ((XGetWindowProperty(d, w, _net_wm_pid, 0, sizeof(pid)/4, False, XA_CARDINAL, &t, &f, &n, &b, &data) != Success) || t != XA_CARDINAL || b != 0 || !data || f != 32) {
+ pa_log("Could not get pid");
+ goto done;
+ }
+
+ pid = (pid_t) ((uint32_t*)data)[0];
+ XFree(data);
+ data = NULL;
+
+ if ((XGetWindowProperty(d, w, _wm_client_machine, 0, 64, False, XA_STRING, &t, &f, &n, &b, &data) != Success) || t != XA_STRING || b != 0 || !data || f != 8) {
+ pa_log("Could not get hostname");
+ goto done;
+ }
+
+ ret = pa_sprintf_malloc("%s:%u", data, (int)pid);
+
+done:
+ if (data)
+ XFree(data);
+
+ return ret;
+}
+
+static struct entry* get_entry_for_window(struct userdata *u, Display *d, Window w)
+{
+ char *id = pa_get_app_id_for_window(d, w);
+ struct entry *e = NULL;
+
+ if (id)
+ e = pa_hashmap_get(u->maps, id);
+
+ pa_xfree(id);
+ return e;
+}
+
+static void add_window(struct userdata *u, Display *d, Window w)
+{
+ char *id = pa_get_app_id_for_window(d, w);
+ struct entry *e = NULL;
+ struct windows *window;
+
+ if (!id)
+ return;
+
+ e = pa_hashmap_get(u->maps, id);
+ if (!e) {
+ e = pa_xnew0(struct entry, 1);
+ e->key = id;
+ e->damped = FALSE;
+ pa_hashmap_put(u->maps, e->key, e);
+ } else
+ pa_xfree(id);
+
+ window = pa_xnew0(struct windows, 1);
+ window->w = w;
+ PA_LLIST_PREPEND(struct windows, e->windows, window);
+ pa_log("add window");
+ dump_map(u);
+}
+
+static void damp_stream(struct streams *s)
+{
+ pa_volume_t scale = pa_sw_volume_from_linear(VFF_SCALE_FACTOR);
+ pa_cvolume new_volume;
+
+ pa_sink_input_get_volume(s->si, &s->old_volume, FALSE);
+
+ new_volume = s->old_volume;
+ pa_cvolume_scale(&new_volume, scale);
+
+ pa_sink_input_set_volume(s->si, &new_volume, FALSE, FALSE);
+}
+
+static void do_damp(struct entry *e)
+{
+ struct streams *s;
+
+ /* Don't mark entries which have no streams as damped */
+ if (e->damped || !e->streams)
+ return;
+
+ e->damped = TRUE;
+ PA_LLIST_FOREACH(s, e->streams) {
+ damp_stream(s);
+ }
+}
+
+static void do_undamp(struct entry *e)
+{
+ struct streams *s;
+
+ if (e->damped) {
+ e->damped = FALSE;
+
+ PA_LLIST_FOREACH(s, e->streams) {
+ pa_sink_input_set_volume(s->si, &s->old_volume, FALSE, FALSE);
+ }
+ }
+}
+
+static void vff_adjust_volumes(struct userdata *u, struct entry *new)
+{
+ struct entry *e;
+ void *state;
+
+ PA_HASHMAP_FOREACH(e, u->maps, state) {
+ if (e != new)
+ do_damp(e);
+ }
+
+ do_undamp(new);
+}
+
+static pa_hook_result_t vff_sink_input_put_hook_callback(pa_core *c, pa_sink_input *si, struct userdata *u)
+{
+ char *id;
+ struct entry *e = NULL;
+ struct streams *stream;
+ struct windows *w;
+ pa_assert(si);
+
+ id = pa_get_app_id_for_stream(si->proplist);
+ if (!id)
+ goto done;
+
+ e = pa_hashmap_get(u->maps, id);
+ if (!e) {
+ e = pa_xnew0(struct entry, 1);
+ e->key = id;
+ e->damped = FALSE;
+ pa_hashmap_put(u->maps, e->key, e);
+ } else {
+ pa_xfree(id);
+
+ }
+
+ stream = pa_xnew0(struct streams, 1);;
+ stream->si = si;
+ PA_LLIST_PREPEND(struct streams, e->streams, stream);
+
+ /* damp all other entries in case its not been done yet - this will
+ * happen for the case where a program has a window with focus and
+ * creates a first stream */
+ PA_LLIST_FOREACH(w, e->windows) {
+ if (w->w == u->active_window) {
+ vff_adjust_volumes(u, e);
+ break;
+ }
+ }
+
+ pa_log("add stream");
+ dump_map(u);
+done:
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t vff_sink_input_unlink_hook_callback(pa_core *c, pa_sink_input *si, struct userdata *u)
+{
+ char *id;
+ struct entry *e = NULL;
+ struct streams *s1, *s2;
+ pa_assert(si);
+
+ id = pa_get_app_id_for_stream(si->proplist);
+ if (id)
+ e = pa_hashmap_get(u->maps, id);
+
+ if (e) {
+ PA_LLIST_FOREACH_SAFE(s1, s2, e->streams) {
+ if (s1->si == si) {
+ PA_LLIST_REMOVE(struct streams, e->streams, s1);
+ pa_xfree(s1);
+
+ if (!e->streams) {
+ /* No streams => we're no longer damped */
+ e->damped = FALSE;
+
+ /* XXX: deque and undamp whatever stream was playing last
+ * before we got focus */
+ }
+
+ clean_map(u->maps, e);
+ break;
+ }
+ }
+
+ pa_log("del stream");
+ dump_map(u);
+ } else {
+ char *name = get_name(si->proplist, "sink-input");
+ pa_log("Weird, got a stream (%s) that wasn't in our stream map", name);
+ pa_xfree(name);
+ }
+
+ pa_xfree(id);
+ return PA_HOOK_OK;
+}
+
+static int vff_x11_error_handler(Display *d, XErrorEvent *ev)
+{
+ if (ev->request_code == X_GetProperty)
+ return 0;
+ else
+ return _x_old_handler(d, ev);
+}
+
+static int vff_x11_event_cb(pa_x11_wrapper *w, XEvent *ev, void *userdata)
+{
+ struct userdata *u = userdata;
+ Atom t;
+ int f;
+ unsigned long n, b;
+ unsigned char *data = NULL;
+ Window win;
+
+ pa_assert(w);
+ pa_assert(u);
+ pa_assert(u->x11_wrapper == w);
+
+ switch (ev->type) {
+ case CreateNotify:
+ {
+ add_window(u, ev->xcreatewindow.display, ev->xcreatewindow.window);
+ break;
+ }
+
+ case DestroyNotify:
+ {
+ /* By the time we get here, the properties on the window are lost,
+ * so we have to do a full iteration. This can be optimised with
+ * another hash (FIXME). */
+ struct entry *e;
+ void *state = NULL;
+
+ while ((e = pa_hashmap_iterate(u->maps, &state, NULL))) {
+ struct windows *w1, *w2;
+
+ PA_LLIST_FOREACH_SAFE(w1, w2, e->windows) {
+ if (w1->w == ev->xcreatewindow.window) {
+ PA_LLIST_REMOVE(struct windows, e->windows, w1);
+ pa_xfree(w1);
+ clean_map(u->maps, e);
+ break;
+ }
+ }
+
+ /* XXX: deque and undamp whatever stream was playing
+ * last before we got focus */
+ }
+ pa_log("del window");
+ dump_map(u);
+ break;
+ }
+
+ case PropertyNotify:
+ {
+ if (ev->xproperty.atom != _net_active_window)
+ return 0;
+
+ if ((XGetWindowProperty(u->display, XDefaultRootWindow(u->display), _net_active_window, 0, 1, False, XA_WINDOW, &t, &f, &n, &b, &data) != Success) || t != XA_WINDOW || b != 0 || !data || f != 32) {
+ pa_log("Could not get active window");
+ goto done;
+ }
+
+ win = (Window) ((uint32_t *)data)[0];
+ pa_log("Window %lu (%lu)", win, u->active_window);
+
+ if (win && win != u->active_window) {
+ struct entry *e;
+ u->active_window = win;
+
+ e = get_entry_for_window (u, u->display, u->active_window);
+ if (!e) {
+ add_window(u, u->display, u->active_window);
+ e = get_entry_for_window (u, u->display, u->active_window);
+ } else if (e->streams) {
+ vff_adjust_volumes(u, e);
+ }
+ }
+
+ XFree(data);
+ break;
+ }
+
+ default:
+ return 0;
+ }
+
+done:
+ return 1;
+}
+
+static void vff_x11_kill_cb(pa_x11_wrapper *w, void *userdata)
+{
+ struct userdata *u = userdata;
+
+ pa_assert(w);
+ pa_assert(u);
+ pa_assert(u->x11_wrapper == w);
+
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ if (u->maps)
+ pa_hashmap_free(u->maps, entry_free_cb, NULL);
+
+ u->x11_client = NULL;
+ u->x11_wrapper = NULL;
+ u->maps = NULL;
+
+ pa_module_unload_request(u->module, TRUE);
+}
+
+int pa__init(pa_module *m) {
+ struct userdata *u = NULL;
+ pa_modargs *ma;
+ Window w;
+ XWindowAttributes wattr;
+ pa_assert(m);
+
+ pa_log("Starting vff");
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->modargs = ma;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
+ if (!(w = XDefaultRootWindow(u->display)))
+ goto fail;
+
+ XGetWindowAttributes(u->display, w, &wattr);
+ XSelectInput(u->display, w, wattr.your_event_mask | PropertyChangeMask | SubstructureNotifyMask);
+ _net_active_window = XInternAtom(u->display, "_NET_ACTIVE_WINDOW", False);
+ _net_wm_pid = XInternAtom(u->display, "_NET_WM_PID", False);
+ _wm_client_machine = XInternAtom(u->display, "WM_CLIENT_MACHINE", False);
+
+ XSync(u->display, FALSE);
+ _x_old_handler = XSetErrorHandler(vff_x11_error_handler);
+
+ u->x11_client = pa_x11_client_new(u->x11_wrapper, vff_x11_event_cb, vff_x11_kill_cb, u);
+
+ u->maps = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ u->sink_input_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) vff_sink_input_put_hook_callback, u);
+
+ u->sink_input_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) vff_sink_input_unlink_hook_callback, u);
+
+ return 0;
+
+fail:
+ if (u)
+ pa_xfree(u);
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata* u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink_input_put_hook_slot)
+ pa_hook_slot_free(u->sink_input_put_hook_slot);
+ if (u->sink_input_unlink_hook_slot)
+ pa_hook_slot_free(u->sink_input_unlink_hook_slot);
+
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ if (u->modargs)
+ pa_modargs_free(u->modargs);
+
+ XSync(u->display, FALSE);
+ XSetErrorHandler(_x_old_handler);
+
+ pa_xfree(u);
+}