]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/unity-system-compositor.c
Change log backup to enabled by default - this is probably more useful. We explicitly...
[sojka/lightdm.git] / src / unity-system-compositor.c
1 /*
2  * Copyright (C) 2013 Canonical Ltd.
3  * Author: Robert Ancell <robert.ancell@canonical.com>
4  *
5  * This program is free software: you can redistribute it and/or modify it under
6  * the terms of the GNU General Public License as published by the Free Software
7  * Foundation, either version 3 of the License, or (at your option) any later
8  * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
9  * license.
10  */
11
12 #include <config.h>
13 #include <string.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 #include <sys/stat.h>
17 #include <errno.h>
18 #include <glib/gstdio.h>
19 #include <stdlib.h>
20
21 #include "unity-system-compositor.h"
22 #include "configuration.h"
23 #include "process.h"
24 #include "greeter.h"
25 #include "vt.h"
26
27 struct UnitySystemCompositorPrivate
28 {
29     /* Compositor process */
30     Process *process;
31
32     /* Command to run the compositor */
33     gchar *command;
34
35     /* Socket to communicate on */
36     gchar *socket;
37
38     /* VT to run on */
39     gint vt;
40     gboolean have_vt_ref;
41
42     /* TRUE if should show hardware cursor */
43     gboolean enable_hardware_cursor;
44
45     /* Pipes to communicate with compositor */
46     int to_compositor_pipe[2];
47     int from_compositor_pipe[2];
48
49     /* IO channel listening on for messages from the compositor */
50     GIOChannel *from_compositor_channel;
51     guint from_compositor_watch;
52
53     /* Buffer reading from channel */
54     guint8 *read_buffer;
55     gsize read_buffer_length;
56     gsize read_buffer_n_used;
57
58     /* Timeout when waiting for compositor to start */
59     gint timeout;
60     guint timeout_source;
61
62     /* TRUE when received ready signal */
63     gboolean is_ready;
64 };
65
66 G_DEFINE_TYPE (UnitySystemCompositor, unity_system_compositor, DISPLAY_SERVER_TYPE);
67
68 typedef enum
69 {
70    USC_MESSAGE_PING = 0,
71    USC_MESSAGE_PONG = 1,
72    USC_MESSAGE_READY = 2,
73    USC_MESSAGE_SESSION_CONNECTED = 3,
74    USC_MESSAGE_SET_ACTIVE_SESSION = 4,
75    USC_MESSAGE_SET_NEXT_SESSION = 5,
76 } USCMessageID;
77
78 UnitySystemCompositor *
79 unity_system_compositor_new (void)
80 {
81     return g_object_new (UNITY_SYSTEM_COMPOSITOR_TYPE, NULL);
82 }
83
84 void
85 unity_system_compositor_set_command (UnitySystemCompositor *compositor, const gchar *command)
86 {
87     g_return_if_fail (compositor != NULL);
88     g_return_if_fail (command != NULL);
89
90     g_free (compositor->priv->command);
91     compositor->priv->command = g_strdup (command);
92 }
93
94 void
95 unity_system_compositor_set_socket (UnitySystemCompositor *compositor, const gchar *socket)
96 {
97     g_return_if_fail (compositor != NULL);
98     g_free (compositor->priv->socket);
99     compositor->priv->socket = g_strdup (socket);
100 }
101
102 const gchar *
103 unity_system_compositor_get_socket (UnitySystemCompositor *compositor)
104 {
105     g_return_val_if_fail (compositor != NULL, NULL);
106     return compositor->priv->socket;
107 }
108
109 void
110 unity_system_compositor_set_vt (UnitySystemCompositor *compositor, gint vt)
111 {
112     g_return_if_fail (compositor != NULL);
113
114     if (compositor->priv->have_vt_ref)
115         vt_unref (compositor->priv->vt);
116     compositor->priv->have_vt_ref = FALSE;
117     compositor->priv->vt = vt;
118     if (vt > 0)
119     {
120         vt_ref (vt);
121         compositor->priv->have_vt_ref = TRUE;
122     }
123 }
124
125 void
126 unity_system_compositor_set_enable_hardware_cursor (UnitySystemCompositor *compositor, gboolean enable_cursor)
127 {
128     g_return_if_fail (compositor != NULL);
129     compositor->priv->enable_hardware_cursor = enable_cursor;
130 }
131
132 void
133 unity_system_compositor_set_timeout (UnitySystemCompositor *compositor, gint timeout)
134 {
135     g_return_if_fail (compositor != NULL);
136     compositor->priv->timeout = timeout;
137 }
138
139 static void
140 write_message (UnitySystemCompositor *compositor, guint16 id, const guint8 *payload, guint16 payload_length)
141 {
142     guint8 *data;
143     gsize data_length = 4 + payload_length;
144
145     data = g_malloc (data_length);
146     data[0] = id >> 8;
147     data[1] = id & 0xFF;
148     data[2] = payload_length >> 8;
149     data[3] = payload_length & 0xFF;
150     if (payload)
151         memcpy (data + 4, payload, payload_length);
152
153     errno = 0;
154     if (write (compositor->priv->to_compositor_pipe[1], data, data_length) != data_length)
155         l_warning (compositor, "Failed to write to compositor: %s", strerror (errno));
156
157     g_free (data);
158 }
159
160 void
161 unity_system_compositor_set_active_session (UnitySystemCompositor *compositor, const gchar *id)
162 {
163     g_return_if_fail (compositor != NULL);
164     write_message (compositor, USC_MESSAGE_SET_ACTIVE_SESSION, (const guint8 *) id, strlen (id));
165 }
166
167 void
168 unity_system_compositor_set_next_session (UnitySystemCompositor *compositor, const gchar *id)
169 {
170     g_return_if_fail (compositor != NULL);
171     write_message (compositor, USC_MESSAGE_SET_NEXT_SESSION, (const guint8 *) id, strlen (id));
172 }
173
174 static gint
175 unity_system_compositor_get_vt (DisplayServer *server)
176 {
177     g_return_val_if_fail (server != NULL, 0);
178     return UNITY_SYSTEM_COMPOSITOR (server)->priv->vt;
179 }
180
181 static void
182 unity_system_compositor_connect_session (DisplayServer *display_server, Session *session)
183 {
184     UnitySystemCompositor *compositor = UNITY_SYSTEM_COMPOSITOR (display_server);
185     const gchar *name;
186
187     session_set_env (session, "XDG_SESSION_TYPE", "mir");
188
189     if (compositor->priv->socket)
190         session_set_env (session, "MIR_SOCKET", compositor->priv->socket);
191     if (IS_GREETER (session))
192         name = "greeter-0";
193     else
194         name = "session-0";
195     session_set_env (session, "MIR_SERVER_NAME", name);
196
197     if (compositor->priv->vt >= 0)
198     {
199         gchar *value = g_strdup_printf ("%d", compositor->priv->vt);
200         session_set_env (session, "XDG_VTNR", value);
201         g_free (value);
202     }
203 }
204
205 static void
206 unity_system_compositor_disconnect_session (DisplayServer *display_server, Session *session)
207 {
208     session_unset_env (session, "XDG_SESSION_TYPE");
209     session_unset_env (session, "MIR_SOCKET");
210     session_unset_env (session, "MIR_SERVER_NAME");
211     session_unset_env (session, "XDG_VTNR");
212 }
213
214 static gchar *
215 get_absolute_command (const gchar *command)
216 {
217     gchar **tokens;
218     gchar *absolute_binary, *absolute_command = NULL;
219
220     tokens = g_strsplit (command, " ", 2);
221
222     absolute_binary = g_find_program_in_path (tokens[0]);
223     if (absolute_binary)
224     {
225         if (tokens[1])
226             absolute_command = g_strjoin (" ", absolute_binary, tokens[1], NULL);
227         else
228             absolute_command = g_strdup (absolute_binary);
229     }
230     g_free (absolute_binary);
231
232     g_strfreev (tokens);
233
234     return absolute_command;
235 }
236
237 static gboolean
238 read_cb (GIOChannel *source, GIOCondition condition, gpointer data)
239 {
240     UnitySystemCompositor *compositor = data;
241     gsize n_to_read = 0;
242     guint16 id, payload_length;
243     /*guint8 *payload;*/
244
245     if (condition == G_IO_HUP)
246     {
247         l_debug (compositor, "Compositor closed communication channel");
248         compositor->priv->from_compositor_watch = 0;
249         return FALSE;
250     }
251
252     /* Work out how much required for a message */
253     if (compositor->priv->read_buffer_n_used < 4)
254         n_to_read = 4 - compositor->priv->read_buffer_n_used;
255     else
256     {
257         payload_length = compositor->priv->read_buffer[2] << 8 | compositor->priv->read_buffer[3];
258         n_to_read = 4 + payload_length - compositor->priv->read_buffer_n_used;
259     }
260
261     /* Read from compositor */
262     if (n_to_read > 0)
263     {
264         gsize n_total, n_read = 0;
265         GIOStatus status;
266         GError *error = NULL;
267
268         n_total = compositor->priv->read_buffer_n_used + n_to_read;
269         if (compositor->priv->read_buffer_length < n_total)
270             compositor->priv->read_buffer = g_realloc (compositor->priv->read_buffer, n_total);
271
272         status = g_io_channel_read_chars (source,
273                                           (gchar *)compositor->priv->read_buffer + compositor->priv->read_buffer_n_used,
274                                           n_to_read,
275                                           &n_read,
276                                           &error);
277         if (error)
278             l_warning (compositor, "Failed to read from compositor: %s", error->message);
279         if (status != G_IO_STATUS_NORMAL)
280             return TRUE;
281         g_clear_error (&error);
282         compositor->priv->read_buffer_n_used += n_read;
283     }
284
285     /* Read header */
286     if (compositor->priv->read_buffer_n_used < 4)
287          return TRUE;
288     id = compositor->priv->read_buffer[0] << 8 | compositor->priv->read_buffer[1];
289     payload_length = compositor->priv->read_buffer[2] << 8 | compositor->priv->read_buffer[3];
290
291     /* Read payload */
292     if (compositor->priv->read_buffer_n_used < 4 + payload_length)
293         return TRUE;
294     /*payload = compositor->priv->read_buffer + 4;*/
295
296     switch (id)
297     {
298     case USC_MESSAGE_PING:
299         l_debug (compositor, "PING!");
300         write_message (compositor, USC_MESSAGE_PONG, NULL, 0);
301         break;
302     case USC_MESSAGE_PONG:
303         l_debug (compositor, "PONG!");
304         break;
305     case USC_MESSAGE_READY:
306         l_debug (compositor, "READY");
307         if (!compositor->priv->is_ready)
308         {
309             compositor->priv->is_ready = TRUE;
310             l_debug (compositor, "Compositor ready");
311             g_source_remove (compositor->priv->timeout_source);
312             compositor->priv->timeout_source = 0;
313             DISPLAY_SERVER_CLASS (unity_system_compositor_parent_class)->start (DISPLAY_SERVER (compositor));
314         }
315         break;
316     case USC_MESSAGE_SESSION_CONNECTED:
317         l_debug (compositor, "SESSION CONNECTED");
318         break;
319     default:
320         l_warning (compositor, "Ignoring unknown message %d with %d octets from system compositor", id, payload_length);
321         break;
322     }
323
324     /* Clear buffer */
325     compositor->priv->read_buffer_n_used = 0;
326
327     return TRUE;
328 }
329
330 static void
331 run_cb (Process *process, gpointer user_data)
332 {
333     int fd;
334
335     /* Make input non-blocking */
336     fd = open ("/dev/null", O_RDONLY);
337     dup2 (fd, STDIN_FILENO);
338     close (fd);
339 }
340
341 static gboolean
342 timeout_cb (gpointer data)
343 {
344     UnitySystemCompositor *compositor = data;
345
346     /* Stop the compositor - it is not working */
347     display_server_stop (DISPLAY_SERVER (compositor));
348
349     compositor->priv->timeout_source = 0;
350
351     return TRUE;
352 }
353
354 static void
355 stopped_cb (Process *process, UnitySystemCompositor *compositor)
356 {
357     l_debug (compositor, "Unity system compositor stopped");
358
359     if (compositor->priv->timeout_source != 0)
360         g_source_remove (compositor->priv->timeout_source);
361     compositor->priv->timeout_source = 0;
362
363     /* Release VT and display number for re-use */
364     if (compositor->priv->have_vt_ref)
365     {
366         vt_unref (compositor->priv->vt);
367         compositor->priv->have_vt_ref = FALSE;
368     }
369
370     DISPLAY_SERVER_CLASS (unity_system_compositor_parent_class)->stop (DISPLAY_SERVER (compositor));
371 }
372
373 static gboolean
374 unity_system_compositor_start (DisplayServer *server)
375 {
376     UnitySystemCompositor *compositor = UNITY_SYSTEM_COMPOSITOR (server);
377     gboolean result, backup_logs;
378     GString *command;
379     gchar *dir, *log_file, *absolute_command, *value;
380
381     g_return_val_if_fail (compositor->priv->process == NULL, FALSE);
382
383     compositor->priv->is_ready = FALSE;
384
385     g_return_val_if_fail (compositor->priv->command != NULL, FALSE);
386
387     /* Create pipes to talk to compositor */
388     if (pipe (compositor->priv->to_compositor_pipe) < 0 || pipe (compositor->priv->from_compositor_pipe) < 0)
389     {
390         l_debug (compositor, "Failed to create compositor pipes: %s", g_strerror (errno));
391         return FALSE;
392     }
393
394     /* Don't allow the daemon end of the pipes to be accessed in the compositor */
395     fcntl (compositor->priv->to_compositor_pipe[1], F_SETFD, FD_CLOEXEC);
396     fcntl (compositor->priv->from_compositor_pipe[0], F_SETFD, FD_CLOEXEC);
397
398     /* Listen for messages from the compositor */
399     compositor->priv->from_compositor_channel = g_io_channel_unix_new (compositor->priv->from_compositor_pipe[0]);
400     compositor->priv->from_compositor_watch = g_io_add_watch (compositor->priv->from_compositor_channel, G_IO_IN | G_IO_HUP, read_cb, compositor);
401
402     /* Setup logging */
403     dir = config_get_string (config_get_instance (), "LightDM", "log-directory");
404     log_file = g_build_filename (dir, "unity-system-compositor.log", NULL);
405     l_debug (compositor, "Logging to %s", log_file);
406     g_free (dir);
407
408     /* Setup environment */
409     compositor->priv->process = process_new (run_cb, compositor);
410     backup_logs = config_get_boolean (config_get_instance (), "LightDM", "backup-logs");
411     process_set_log_file (compositor->priv->process, log_file, TRUE, backup_logs ? LOG_MODE_BACKUP_AND_TRUNCATE : LOG_MODE_APPEND);
412     g_free (log_file);
413     process_set_clear_environment (compositor->priv->process, TRUE);
414     process_set_env (compositor->priv->process, "XDG_SEAT", "seat0");
415     value = g_strdup_printf ("%d", compositor->priv->vt);
416     process_set_env (compositor->priv->process, "XDG_VTNR", value);
417     g_free (value);
418     /* Variable required for regression tests */
419     if (g_getenv ("LIGHTDM_TEST_ROOT"))
420     {
421         process_set_env (compositor->priv->process, "LIGHTDM_TEST_ROOT", g_getenv ("LIGHTDM_TEST_ROOT"));
422         process_set_env (compositor->priv->process, "LD_PRELOAD", g_getenv ("LD_PRELOAD"));
423         process_set_env (compositor->priv->process, "LD_LIBRARY_PATH", g_getenv ("LD_LIBRARY_PATH"));
424     }
425
426     /* Generate command line to run */
427     absolute_command = get_absolute_command (compositor->priv->command);
428     if (!absolute_command)
429     {
430         l_debug (compositor, "Can't launch compositor %s, not found in path", compositor->priv->command);
431         return FALSE;
432     }
433     command = g_string_new (absolute_command);
434     g_free (absolute_command);
435     g_string_append_printf (command, " --file '%s'", compositor->priv->socket);
436     g_string_append_printf (command, " --from-dm-fd %d --to-dm-fd %d", compositor->priv->to_compositor_pipe[0], compositor->priv->from_compositor_pipe[1]);
437     if (compositor->priv->vt > 0)
438         g_string_append_printf (command, " --vt %d", compositor->priv->vt);
439     if (compositor->priv->enable_hardware_cursor)
440         g_string_append (command, " --enable-hardware-cursor=true");
441     process_set_command (compositor->priv->process, command->str);
442     g_string_free (command, TRUE);
443
444     /* Start the compositor */
445     g_signal_connect (compositor->priv->process, PROCESS_SIGNAL_STOPPED, G_CALLBACK (stopped_cb), compositor);
446     result = process_start (compositor->priv->process, FALSE);
447
448     /* Close compostor ends of the pipes */
449     close (compositor->priv->to_compositor_pipe[0]);
450     compositor->priv->to_compositor_pipe[0] = 0;
451     close (compositor->priv->from_compositor_pipe[1]);
452     compositor->priv->from_compositor_pipe[1] = 0;
453
454     if (!result)
455         return FALSE;
456
457     /* Connect to the compositor */
458     if (compositor->priv->timeout > 0)
459     {
460         l_debug (compositor, "Waiting for system compositor for %ds", compositor->priv->timeout);
461         compositor->priv->timeout_source = g_timeout_add (compositor->priv->timeout * 1000, timeout_cb, compositor);
462     }
463
464     return TRUE;
465 }
466
467 static void
468 unity_system_compositor_stop (DisplayServer *server)
469 {
470     process_stop (UNITY_SYSTEM_COMPOSITOR (server)->priv->process);
471 }
472
473 static void
474 unity_system_compositor_init (UnitySystemCompositor *compositor)
475 {
476     compositor->priv = G_TYPE_INSTANCE_GET_PRIVATE (compositor, UNITY_SYSTEM_COMPOSITOR_TYPE, UnitySystemCompositorPrivate);
477     compositor->priv->vt = -1;
478     compositor->priv->command = g_strdup ("unity-system-compositor");
479     compositor->priv->socket = g_strdup ("/run/mir_socket");
480     compositor->priv->timeout = -1;
481 }
482
483 static void
484 unity_system_compositor_finalize (GObject *object)
485 {
486     UnitySystemCompositor *self = UNITY_SYSTEM_COMPOSITOR (object);
487
488     if (self->priv->process)
489     {
490         g_signal_handlers_disconnect_matched (self->priv->process, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
491         g_object_unref (self->priv->process);
492     }
493     g_free (self->priv->command);
494     g_free (self->priv->socket);
495     if (self->priv->have_vt_ref)
496         vt_unref (self->priv->vt);
497     close (self->priv->to_compositor_pipe[0]);
498     close (self->priv->to_compositor_pipe[1]);
499     close (self->priv->from_compositor_pipe[0]);
500     close (self->priv->from_compositor_pipe[1]);
501     g_io_channel_unref (self->priv->from_compositor_channel);
502     if (self->priv->from_compositor_watch)
503         g_source_remove (self->priv->from_compositor_watch);
504     g_free (self->priv->read_buffer);
505     if (self->priv->timeout_source)
506         g_source_remove (self->priv->timeout_source);
507
508     G_OBJECT_CLASS (unity_system_compositor_parent_class)->finalize (object);
509 }
510
511 static void
512 unity_system_compositor_class_init (UnitySystemCompositorClass *klass)
513 {
514     GObjectClass *object_class = G_OBJECT_CLASS (klass);
515     DisplayServerClass *display_server_class = DISPLAY_SERVER_CLASS (klass);
516
517     display_server_class->get_vt = unity_system_compositor_get_vt;
518     display_server_class->connect_session = unity_system_compositor_connect_session;
519     display_server_class->disconnect_session = unity_system_compositor_disconnect_session;
520     display_server_class->start = unity_system_compositor_start;
521     display_server_class->stop = unity_system_compositor_stop;
522     object_class->finalize = unity_system_compositor_finalize;
523
524     g_type_class_add_private (klass, sizeof (UnitySystemCompositorPrivate));
525 }