/*** This file is part of PulseAudio. Copyright 2012 Lennart Poettering 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.1 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, see . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Create a client for each login session of this user"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); static const char* const valid_modargs[] = { NULL }; struct session { char *id; pa_client *client; }; struct userdata { pa_module *module; pa_core *core; pa_hashmap *sessions, *previous_sessions; sd_login_monitor *monitor; pa_io_event *io; }; static int add_session(struct userdata *u, const char *id) { struct session *session; pa_client_new_data data; session = pa_xnew(struct session, 1); session->id = pa_xstrdup(id); pa_client_new_data_init(&data); data.module = u->module; data.driver = __FILE__; pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "Login Session %s", id); pa_proplist_sets(data.proplist, "systemd-login.session", id); session->client = pa_client_new(u->core, &data); pa_client_new_data_done(&data); if (!session->client) { pa_xfree(session->id); pa_xfree(session); return -1; } pa_hashmap_put(u->sessions, session->id, session); pa_log_debug("Added new session %s", id); /* Positive exit_idle_time is only useful when we have no session tracking * capability, so we can set it to 0 now that we have detected a session. * The benefit of setting exit_idle_time to 0 is that pulseaudio will exit * immediately when the session ends. That in turn is useful, because some * systems (those that use pam_systemd but don't use systemd for managing * pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all * services that depend on the files in $XDG_RUNTIME_DIR. The directory * contains our sockets, and if the sockets are removed without terminating * pulseaudio, a quick relogin will likely cause trouble, because a new * instance will be spawned while the old instance is still running. */ if (u->core->exit_idle_time > 0) pa_core_set_exit_idle_time(u->core, 0); return 0; } static void free_session(struct session *session) { pa_assert(session); pa_log_debug("Removing session %s", session->id); pa_client_free(session->client); pa_xfree(session->id); pa_xfree(session); } static int get_session_list(struct userdata *u) { int r; char **sessions; pa_hashmap *h; struct session *o; pa_assert(u); r = sd_uid_get_sessions(getuid(), 0, &sessions); if (r < 0) return -1; /* We copy all sessions that still exist from one hashmap to the * other and then flush the remaining ones */ h = u->previous_sessions; u->previous_sessions = u->sessions; u->sessions = h; if (sessions) { char **s; /* Note that the sessions array is allocated with libc's * malloc()/free() calls, hence do not use pa_xfree() to free * this here. */ for (s = sessions; *s; s++) { o = pa_hashmap_remove(u->previous_sessions, *s); if (o) pa_hashmap_put(u->sessions, o->id, o); else add_session(u, *s); free(*s); } free(sessions); } pa_hashmap_remove_all(u->previous_sessions); return 0; } static void monitor_cb( pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { struct userdata *u = userdata; pa_assert(u); sd_login_monitor_flush(u->monitor); get_session_list(u); } int pa__init(pa_module *m) { struct userdata *u = NULL; pa_modargs *ma; sd_login_monitor *monitor = NULL; int r; pa_assert(m); /* If we are not actually running logind become a NOP */ if (access("/run/systemd/seats/", F_OK) < 0) return 0; ma = pa_modargs_new(m->argument, valid_modargs); if (!ma) { pa_log("Failed to parse module arguments"); goto fail; } r = sd_login_monitor_new("session", &monitor); if (r < 0) { pa_log("Failed to create session monitor: %s", strerror(-r)); goto fail; } m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; u->sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); u->previous_sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); u->monitor = monitor; u->io = u->core->mainloop->io_new(u->core->mainloop, sd_login_monitor_get_fd(monitor), PA_IO_EVENT_INPUT, monitor_cb, u); if (get_session_list(u) < 0) goto fail; pa_modargs_free(ma); return 0; fail: if (ma) pa_modargs_free(ma); pa__done(m); return -1; } void pa__done(pa_module *m) { struct userdata *u; pa_assert(m); u = m->userdata; if (!u) return; if (u->sessions) { pa_hashmap_free(u->sessions); pa_hashmap_free(u->previous_sessions); } if (u->io) m->core->mainloop->io_free(u->io); if (u->monitor) sd_login_monitor_unref(u->monitor); pa_xfree(u); }