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 pa_x11_wrapper *x11;
59 };
60
die_cb(SmcConn connection,SmPointer client_data)61 static void die_cb(SmcConn connection, SmPointer client_data) {
62 struct userdata *u = client_data;
63 pa_assert(u);
64
65 pa_log_debug("Got die message from XSMP.");
66
67 pa_x11_wrapper_kill(u->x11);
68
69 pa_x11_wrapper_unref(u->x11);
70 u->x11 = NULL;
71
72 pa_module_unload_request(u->module, true);
73 }
74
save_complete_cb(SmcConn connection,SmPointer client_data)75 static void save_complete_cb(SmcConn connection, SmPointer client_data) {
76 }
77
shutdown_cancelled_cb(SmcConn connection,SmPointer client_data)78 static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) {
79 SmcSaveYourselfDone(connection, True);
80 }
81
save_yourself_cb(SmcConn connection,SmPointer client_data,int save_type,Bool _shutdown,int interact_style,Bool fast)82 static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) {
83 SmcSaveYourselfDone(connection, True);
84 }
85
ice_io_cb(pa_mainloop_api * a,pa_io_event * e,int fd,pa_io_event_flags_t flags,void * userdata)86 static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
87 IceConn connection = userdata;
88
89 if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) {
90 IceSetShutdownNegotiation(connection, False);
91 IceCloseConnection(connection);
92 }
93 }
94
new_ice_connection(IceConn connection,IcePointer client_data,Bool opening,IcePointer * watch_data)95 static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) {
96 pa_core *c = client_data;
97
98 if (opening)
99 *watch_data = c->mainloop->io_new(
100 c->mainloop,
101 IceConnectionNumber(connection),
102 PA_IO_EVENT_INPUT,
103 ice_io_cb,
104 connection);
105 else
106 c->mainloop->io_free(*watch_data);
107 }
108
pa__init(pa_module * m)109 int pa__init(pa_module*m) {
110
111 pa_modargs *ma = NULL;
112 char t[256], *vendor, *client_id;
113 SmcCallbacks callbacks;
114 SmProp prop_program, prop_user;
115 SmProp *prop_list[2];
116 SmPropValue val_program, val_user;
117 struct userdata *u;
118 const char *e;
119 pa_client_new_data data;
120
121 pa_assert(m);
122
123 if (ice_in_use) {
124 pa_log("module-x11-xsmp may not be loaded twice.");
125 return -1;
126 }
127
128 IceAddConnectionWatch(new_ice_connection, m->core);
129 ice_in_use = true;
130
131 m->userdata = u = pa_xnew(struct userdata, 1);
132 u->core = m->core;
133 u->module = m;
134 u->client = NULL;
135 u->connection = NULL;
136 u->x11 = NULL;
137
138 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
139 pa_log("Failed to parse module arguments");
140 goto fail;
141 }
142
143 if (pa_modargs_get_value(ma, "xauthority", NULL)) {
144 if (setenv("XAUTHORITY", pa_modargs_get_value(ma, "xauthority", NULL), 1)) {
145 pa_log("setenv() for $XAUTHORITY failed");
146 goto fail;
147 }
148 }
149
150 if (!(u->x11 = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
151 goto fail;
152
153 e = pa_modargs_get_value(ma, "session_manager", NULL);
154
155 if (!e && !getenv("SESSION_MANAGER")) {
156 pa_log("X11 session manager not running.");
157 goto fail;
158 }
159
160 memset(&callbacks, 0, sizeof(callbacks));
161 callbacks.die.callback = die_cb;
162 callbacks.die.client_data = u;
163 callbacks.save_yourself.callback = save_yourself_cb;
164 callbacks.save_yourself.client_data = m->core;
165 callbacks.save_complete.callback = save_complete_cb;
166 callbacks.save_complete.client_data = m->core;
167 callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb;
168 callbacks.shutdown_cancelled.client_data = m->core;
169
170 if (!(u->connection = SmcOpenConnection(
171 (char*) e, m->core,
172 SmProtoMajor, SmProtoMinor,
173 SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask,
174 &callbacks, NULL, &client_id,
175 sizeof(t), t))) {
176
177 pa_log("Failed to open connection to session manager: %s", t);
178 goto fail;
179 }
180
181 prop_program.name = (char*) SmProgram;
182 prop_program.type = (char*) SmARRAY8;
183 val_program.value = (char*) PACKAGE_NAME;
184 val_program.length = (int) strlen(val_program.value);
185 prop_program.num_vals = 1;
186 prop_program.vals = &val_program;
187 prop_list[0] = &prop_program;
188
189 prop_user.name = (char*) SmUserID;
190 prop_user.type = (char*) SmARRAY8;
191 pa_get_user_name(t, sizeof(t));
192 val_user.value = t;
193 val_user.length = (int) strlen(val_user.value);
194 prop_user.num_vals = 1;
195 prop_user.vals = &val_user;
196 prop_list[1] = &prop_user;
197
198 SmcSetProperties(u->connection, PA_ELEMENTSOF(prop_list), prop_list);
199
200 pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(u->connection), client_id);
201
202 pa_client_new_data_init(&data);
203 data.module = m;
204 data.driver = __FILE__;
205 pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "XSMP Session on %s as %s", vendor, client_id);
206 pa_proplist_sets(data.proplist, "xsmp.vendor", vendor);
207 pa_proplist_sets(data.proplist, "xsmp.client.id", client_id);
208 u->client = pa_client_new(u->core, &data);
209 pa_client_new_data_done(&data);
210
211 free(vendor);
212 free(client_id);
213
214 if (!u->client)
215 goto fail;
216
217 /* Positive exit_idle_time is only useful when we have no session tracking
218 * capability, so we can set it to 0 now that we have detected a session.
219 * The benefit of setting exit_idle_time to 0 is that pulseaudio will exit
220 * immediately when the session ends. That in turn is useful, because some
221 * systems (those that use pam_systemd but don't use systemd for managing
222 * pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all
223 * services that depend on the files in $XDG_RUNTIME_DIR. The directory
224 * contains our sockets, and if the sockets are removed without terminating
225 * pulseaudio, a quick relogin will likely cause trouble, because a new
226 * instance will be spawned while the old instance is still running. */
227 if (u->core->exit_idle_time > 0)
228 pa_core_set_exit_idle_time(u->core, 0);
229
230 pa_modargs_free(ma);
231
232 return 0;
233
234 fail:
235 if (ma)
236 pa_modargs_free(ma);
237
238 pa__done(m);
239
240 return -1;
241 }
242
pa__done(pa_module * m)243 void pa__done(pa_module*m) {
244 struct userdata *u;
245
246 pa_assert(m);
247
248 if ((u = m->userdata)) {
249
250 if (u->connection)
251 SmcCloseConnection(u->connection, 0, NULL);
252
253 if (u->client)
254 pa_client_free(u->client);
255
256 if (u->x11)
257 pa_x11_wrapper_unref(u->x11);
258
259 pa_xfree(u);
260 }
261
262 if (ice_in_use) {
263 IceRemoveConnectionWatch(new_ice_connection, m->core);
264 ice_in_use = false;
265 }
266 }
267