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
38 PA_MODULE_AUTHOR("Lennart Poettering");
39 PA_MODULE_DESCRIPTION("X11 session management");
40 PA_MODULE_VERSION(PACKAGE_VERSION);
41 PA_MODULE_LOAD_ONCE(false);
42 PA_MODULE_USAGE("session_manager=<session manager string> display=<X11 display>");
43
44 static bool ice_in_use = false;
45
46 static const char* const valid_modargs[] = {
47 "session_manager",
48 "display",
49 "xauthority",
50 NULL
51 };
52
53 struct 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
63 typedef struct {
64 IceConn connection;
65 struct userdata *userdata;
66 } ice_io_callback_data;
67
ice_io_cb_data_new(IceConn connection,struct userdata * userdata)68 static 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
ice_io_cb_data_destroy(pa_mainloop_api * a,pa_io_event * e,void * userdata)77 static 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
x11_kill_cb(pa_x11_wrapper * w,void * userdata)83 static 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
close_xsmp_connection(struct userdata * userdata)110 static 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
die_cb(SmcConn connection,SmPointer client_data)121 static 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
save_complete_cb(SmcConn connection,SmPointer client_data)131 static void save_complete_cb(SmcConn connection, SmPointer client_data) {
132 }
133
shutdown_cancelled_cb(SmcConn connection,SmPointer client_data)134 static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) {
135 SmcSaveYourselfDone(connection, True);
136 }
137
save_yourself_cb(SmcConn connection,SmPointer client_data,int save_type,Bool _shutdown,int interact_style,Bool fast)138 static 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
ice_io_cb(pa_mainloop_api * a,pa_io_event * e,int fd,pa_io_event_flags_t flags,void * userdata)142 static 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
new_ice_connection(IceConn connection,IcePointer client_data,Bool opening,IcePointer * watch_data)157 static 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
175 static 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
ice_io_error_handler(IceConn iceConn)180 static 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
pa__init(pa_module * m)186 int 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
323 fail:
324 if (ma)
325 pa_modargs_free(ma);
326
327 pa__done(m);
328
329 return -1;
330 }
331
pa__done(pa_module * m)332 void 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