1/***
2  This file is part of PulseAudio.
3
4  Copyright 2004-2006 Lennart Poettering
5
6  PulseAudio is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published
8  by the Free Software Foundation; either version 2.1 of the License,
9  or (at your option) any later version.
10
11  PulseAudio is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  General Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public License
17  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <stdio.h>
25#include <stdlib.h>
26
27#include <X11/Xlib.h>
28#include <X11/XKBlib.h>
29
30#include <pulse/xmalloc.h>
31
32#include <pulsecore/core-scache.h>
33#include <pulsecore/modargs.h>
34#include <pulsecore/log.h>
35#include <pulsecore/x11wrap.h>
36
37PA_MODULE_AUTHOR("Lennart Poettering");
38PA_MODULE_DESCRIPTION("X11 bell interceptor");
39PA_MODULE_VERSION(PACKAGE_VERSION);
40PA_MODULE_LOAD_ONCE(false);
41PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>");
42
43static const char* const valid_modargs[] = {
44    "sink",
45    "sample",
46    "display",
47    "xauthority",
48    NULL
49};
50
51struct userdata {
52    pa_core *core;
53    pa_module *module;
54
55    int xkb_event_base;
56
57    char *sink_name;
58    char *scache_item;
59
60    pa_x11_wrapper *x11_wrapper;
61    pa_x11_client *x11_client;
62};
63
64static int x11_event_cb(pa_x11_wrapper *w, XEvent *e, void *userdata) {
65    XkbBellNotifyEvent *bne;
66    struct userdata *u = userdata;
67
68    pa_assert(w);
69    pa_assert(e);
70    pa_assert(u);
71    pa_assert(u->x11_wrapper == w);
72
73    if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify)
74        return 0;
75
76    bne = (XkbBellNotifyEvent*) e;
77
78    /* We could use bne->percent to set the volume, but then the "event" role
79     * volume wouldn't have effect. It's better to ignore the volume suggestion
80     * from X11. */
81    if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, PA_VOLUME_INVALID, NULL, NULL) < 0) {
82        pa_log_info("Ringing bell failed, reverting to X11 device bell.");
83        XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent);
84    }
85
86    return 1;
87}
88
89static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
90    struct userdata *u = userdata;
91
92    pa_assert(w);
93    pa_assert(u);
94    pa_assert(u->x11_wrapper == w);
95
96    pa_log_debug("X11 client kill callback called");
97
98    if (u->x11_client)
99        pa_x11_client_free(u->x11_client);
100
101    if (u->x11_wrapper)
102        pa_x11_wrapper_unref(u->x11_wrapper);
103
104    u->x11_client = NULL;
105    u->x11_wrapper = NULL;
106
107    pa_module_unload_request(u->module, true);
108}
109
110int pa__init(pa_module*m) {
111
112    struct userdata *u = NULL;
113    pa_modargs *ma = NULL;
114    int major, minor;
115    unsigned int auto_ctrls, auto_values;
116
117    pa_assert(m);
118
119    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
120        pa_log("Failed to parse module arguments");
121        goto fail;
122    }
123
124    m->userdata = u = pa_xnew(struct userdata, 1);
125    u->core = m->core;
126    u->module = m;
127    u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "bell-window-system"));
128    u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
129    u->x11_client = NULL;
130
131    if (pa_modargs_get_value(ma, "xauthority", NULL)) {
132        if (setenv("XAUTHORITY", pa_modargs_get_value(ma, "xauthority", NULL), 1)) {
133            pa_log("setenv() for $XAUTHORITY failed");
134            goto fail;
135        }
136    }
137
138    if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
139        goto fail;
140
141    major = XkbMajorVersion;
142    minor = XkbMinorVersion;
143
144    if (!XkbLibraryVersion(&major, &minor)) {
145        pa_log("XkbLibraryVersion() failed");
146        goto fail;
147    }
148
149    major = XkbMajorVersion;
150    minor = XkbMinorVersion;
151
152    if (!XkbQueryExtension(pa_x11_wrapper_get_display(u->x11_wrapper), NULL, &u->xkb_event_base, NULL, &major, &minor)) {
153        pa_log("XkbQueryExtension() failed");
154        goto fail;
155    }
156
157    XkbSelectEvents(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask);
158    auto_ctrls = auto_values = XkbAudibleBellMask;
159    XkbSetAutoResetControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbAudibleBellMask, &auto_ctrls, &auto_values);
160    XkbChangeEnabledControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbAudibleBellMask, 0);
161
162    u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_cb, x11_kill_cb, u);
163
164    pa_modargs_free(ma);
165
166    return 0;
167
168fail:
169    if (ma)
170        pa_modargs_free(ma);
171
172    pa__done(m);
173
174    return -1;
175}
176
177void pa__done(pa_module*m) {
178    struct userdata *u;
179
180    pa_assert(m);
181
182    if (!(u = m->userdata))
183        return;
184
185    pa_xfree(u->scache_item);
186    pa_xfree(u->sink_name);
187
188    if (u->x11_client)
189        pa_x11_client_free(u->x11_client);
190
191    if (u->x11_wrapper)
192        pa_x11_wrapper_unref(u->x11_wrapper);
193
194    pa_xfree(u);
195}
196