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