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#include <string.h>
27
28#include <X11/Xlib.h>
29#include <X11/SM/SMlib.h>
30
31#include <pulse/xmalloc.h>
32#include <pulse/util.h>
33
34#include <pulsecore/modargs.h>
35#include <pulsecore/log.h>
36#include <pulsecore/x11wrap.h>
37
38PA_MODULE_AUTHOR("Lennart Poettering");
39PA_MODULE_DESCRIPTION("X11 session management");
40PA_MODULE_VERSION(PACKAGE_VERSION);
41PA_MODULE_LOAD_ONCE(false);
42PA_MODULE_USAGE("session_manager=<session manager string> display=<X11 display>");
43
44static bool ice_in_use = false;
45
46static const char* const valid_modargs[] = {
47    "session_manager",
48    "display",
49    "xauthority",
50    NULL
51};
52
53struct userdata {
54    pa_core *core;
55    pa_module *module;
56    pa_client *client;
57    SmcConn connection;
58
59    pa_x11_wrapper *x11_wrapper;
60    pa_x11_client *x11_client;
61};
62
63typedef struct {
64    IceConn connection;
65    struct userdata *userdata;
66} ice_io_callback_data;
67
68static void* ice_io_cb_data_new(IceConn connection, struct userdata *userdata) {
69    ice_io_callback_data *data = pa_xnew(ice_io_callback_data, 1);
70
71    data->connection = connection;
72    data->userdata = userdata;
73
74    return data;
75}
76
77static void ice_io_cb_data_destroy(pa_mainloop_api*a, pa_io_event *e, void *userdata) {
78    pa_assert(userdata);
79
80    pa_xfree(userdata);
81}
82
83static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
84    struct userdata *u = userdata;
85
86    pa_assert(w);
87    pa_assert(u);
88    pa_assert(u->x11_wrapper == w);
89
90    pa_log_debug("X11 client kill callback called");
91
92    if (u->connection) {
93        SmcCloseConnection(u->connection, 0, NULL);
94        u->connection = NULL;
95    }
96
97    if (u->x11_client) {
98        pa_x11_client_free(u->x11_client);
99        u->x11_client = NULL;
100    }
101
102    if (u->x11_wrapper) {
103        pa_x11_wrapper_unref(u->x11_wrapper);
104        u->x11_wrapper = NULL;
105    }
106
107    pa_module_unload_request(u->module, true);
108}
109
110static void close_xsmp_connection(struct userdata *userdata) {
111    pa_assert(userdata);
112
113    if (userdata->connection) {
114        SmcCloseConnection(userdata->connection, 0, NULL);
115        userdata->connection = NULL;
116    }
117
118    pa_x11_wrapper_kill_deferred(userdata->x11_wrapper);
119}
120
121static void die_cb(SmcConn connection, SmPointer client_data) {
122    struct userdata *u = client_data;
123
124    pa_assert(u);
125
126    pa_log_debug("Got die message from XSMP.");
127
128    close_xsmp_connection(u);
129}
130
131static void save_complete_cb(SmcConn connection, SmPointer client_data) {
132}
133
134static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) {
135    SmcSaveYourselfDone(connection, True);
136}
137
138static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) {
139    SmcSaveYourselfDone(connection, True);
140}
141
142static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
143    ice_io_callback_data *io_data = userdata;
144
145    pa_assert(io_data);
146
147    if (IceProcessMessages(io_data->connection, NULL, NULL) == IceProcessMessagesIOError) {
148        pa_log_debug("IceProcessMessages: I/O error, closing XSMP.");
149
150        IceSetShutdownNegotiation(io_data->connection, False);
151
152        /* SM owns this connection, close via SmcCloseConnection() */
153        close_xsmp_connection(io_data->userdata);
154    }
155}
156
157static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) {
158    struct userdata *u = client_data;
159
160    pa_assert(u);
161
162    if (opening) {
163        *watch_data = u->core->mainloop->io_new(
164                u->core->mainloop,
165                IceConnectionNumber(connection),
166                PA_IO_EVENT_INPUT,
167                ice_io_cb,
168                ice_io_cb_data_new(connection, u));
169
170        u->core->mainloop->io_set_destroy(*watch_data, ice_io_cb_data_destroy);
171    } else
172        u->core->mainloop->io_free(*watch_data);
173}
174
175static IceIOErrorHandler ice_installed_handler;
176
177/* We call any handler installed before (or after) module is loaded but
178   avoid calling the default libICE handler which does an exit() */
179
180static void ice_io_error_handler(IceConn iceConn) {
181    pa_log_warn("ICE I/O error handler called");
182    if (ice_installed_handler)
183      (*ice_installed_handler) (iceConn);
184}
185
186int pa__init(pa_module*m) {
187
188    pa_modargs *ma = NULL;
189    char t[256], *vendor, *client_id;
190    SmcCallbacks callbacks;
191    SmProp prop_program, prop_user;
192    SmProp *prop_list[2];
193    SmPropValue val_program, val_user;
194    struct userdata *u;
195    const char *e;
196    pa_client_new_data data;
197
198    pa_assert(m);
199
200    if (ice_in_use) {
201        pa_log("module-x11-xsmp may not be loaded twice.");
202        return -1;
203    } else {
204        IceIOErrorHandler default_handler;
205
206        ice_installed_handler = IceSetIOErrorHandler (NULL);
207        default_handler = IceSetIOErrorHandler (ice_io_error_handler);
208
209        if (ice_installed_handler == default_handler)
210            ice_installed_handler = NULL;
211
212        IceSetIOErrorHandler(ice_io_error_handler);
213    }
214
215    m->userdata = u = pa_xnew(struct userdata, 1);
216    u->core = m->core;
217    u->module = m;
218    u->client = NULL;
219    u->connection = NULL;
220    u->x11_wrapper = NULL;
221
222    IceAddConnectionWatch(new_ice_connection, u);
223    ice_in_use = true;
224
225    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
226        pa_log("Failed to parse module arguments");
227        goto fail;
228    }
229
230    if (pa_modargs_get_value(ma, "xauthority", NULL)) {
231        if (setenv("XAUTHORITY", pa_modargs_get_value(ma, "xauthority", NULL), 1)) {
232            pa_log("setenv() for $XAUTHORITY failed");
233            goto fail;
234        }
235    }
236
237    if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
238        goto fail;
239
240    u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u);
241
242    e = pa_modargs_get_value(ma, "session_manager", NULL);
243
244    if (!e && !getenv("SESSION_MANAGER")) {
245        pa_log("X11 session manager not running.");
246        goto fail;
247    }
248
249    memset(&callbacks, 0, sizeof(callbacks));
250    callbacks.die.callback = die_cb;
251    callbacks.die.client_data = u;
252    callbacks.save_yourself.callback = save_yourself_cb;
253    callbacks.save_yourself.client_data = m->core;
254    callbacks.save_complete.callback = save_complete_cb;
255    callbacks.save_complete.client_data = m->core;
256    callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb;
257    callbacks.shutdown_cancelled.client_data = m->core;
258
259    if (!(u->connection = SmcOpenConnection(
260                  (char*) e, m->core,
261                  SmProtoMajor, SmProtoMinor,
262                  SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask,
263                  &callbacks, NULL, &client_id,
264                  sizeof(t), t))) {
265
266        pa_log("Failed to open connection to session manager: %s", t);
267        goto fail;
268    }
269
270    prop_program.name = (char*) SmProgram;
271    prop_program.type = (char*) SmARRAY8;
272    val_program.value = (char*) PACKAGE_NAME;
273    val_program.length = (int) strlen(val_program.value);
274    prop_program.num_vals = 1;
275    prop_program.vals = &val_program;
276    prop_list[0] = &prop_program;
277
278    prop_user.name = (char*) SmUserID;
279    prop_user.type = (char*) SmARRAY8;
280    pa_get_user_name(t, sizeof(t));
281    val_user.value = t;
282    val_user.length = (int) strlen(val_user.value);
283    prop_user.num_vals = 1;
284    prop_user.vals = &val_user;
285    prop_list[1] = &prop_user;
286
287    SmcSetProperties(u->connection, PA_ELEMENTSOF(prop_list), prop_list);
288
289    pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(u->connection), client_id);
290
291    pa_client_new_data_init(&data);
292    data.module = m;
293    data.driver = __FILE__;
294    pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "XSMP Session on %s as %s", vendor, client_id);
295    pa_proplist_sets(data.proplist, "xsmp.vendor", vendor);
296    pa_proplist_sets(data.proplist, "xsmp.client.id", client_id);
297    u->client = pa_client_new(u->core, &data);
298    pa_client_new_data_done(&data);
299
300    free(vendor);
301    free(client_id);
302
303    if (!u->client)
304        goto fail;
305
306    /* Positive exit_idle_time is only useful when we have no session tracking
307     * capability, so we can set it to 0 now that we have detected a session.
308     * The benefit of setting exit_idle_time to 0 is that pulseaudio will exit
309     * immediately when the session ends. That in turn is useful, because some
310     * systems (those that use pam_systemd but don't use systemd for managing
311     * pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all
312     * services that depend on the files in $XDG_RUNTIME_DIR. The directory
313     * contains our sockets, and if the sockets are removed without terminating
314     * pulseaudio, a quick relogin will likely cause trouble, because a new
315     * instance will be spawned while the old instance is still running. */
316    if (u->core->exit_idle_time > 0)
317        pa_core_set_exit_idle_time(u->core, 0);
318
319    pa_modargs_free(ma);
320
321    return 0;
322
323fail:
324    if (ma)
325        pa_modargs_free(ma);
326
327    pa__done(m);
328
329    return -1;
330}
331
332void pa__done(pa_module*m) {
333    struct userdata *u;
334
335    pa_assert(m);
336
337    /* set original ICE I/O error handler and forget it */
338    IceSetIOErrorHandler(ice_installed_handler);
339    ice_installed_handler = NULL;
340
341    if ((u = m->userdata)) {
342
343        if (u->connection)
344            SmcCloseConnection(u->connection, 0, NULL);
345
346        if (u->client)
347            pa_client_free(u->client);
348
349        if (u->x11_client)
350            pa_x11_client_free(u->x11_client);
351
352        if (u->x11_wrapper)
353            pa_x11_wrapper_unref(u->x11_wrapper);
354    }
355
356    if (ice_in_use) {
357        IceRemoveConnectionWatch(new_ice_connection, u);
358        ice_in_use = false;
359    }
360
361    if (u)
362        pa_xfree(u);
363}
364