]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/seat-unity.c
Fall back to VT switching if can't start the system compositor
[sojka/lightdm.git] / src / seat-unity.c
1 /*
2  * Copyright (C) 2012-2013 Robert Ancell.
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 <string.h>
13 #include <fcntl.h>
14 #include <errno.h>
15
16 #include "seat-unity.h"
17 #include "configuration.h"
18 #include "xserver-local.h"
19 #include "xsession.h"
20 #include "vt.h"
21 #include "plymouth.h"
22
23 typedef enum
24 {
25    USC_MESSAGE_PING = 0,
26    USC_MESSAGE_PONG = 1,
27    USC_MESSAGE_READY = 2,
28    USC_MESSAGE_SESSION_CONNECTED = 3,
29    USC_MESSAGE_SET_ACTIVE_SESSION = 4
30 } USCMessageID;
31
32 struct SeatUnityPrivate
33 {
34     /* VT we are running on */
35     gint vt;
36
37     /* TRUE if waiting for X server to start before stopping Plymouth */
38     gboolean stopping_plymouth;
39
40     /* File to log to */
41     gchar *log_file;
42
43     /* Filename of Mir socket */
44     gchar *mir_socket_filename;
45
46     /* Pipes to communicate with compositor */
47     int to_compositor_pipe[2];
48     int from_compositor_pipe[2];
49
50     /* IO channel listening on for messages from the compositor */
51     GIOChannel *from_compositor_channel;
52
53     /* TRUE when the compositor indicates it is ready */
54     gboolean compositor_ready;
55
56     /* Buffer reading from channel */
57     guint8 *read_buffer;
58     gsize read_buffer_length;
59     gsize read_buffer_n_used;
60
61     /* Compositor process */
62     Process *compositor_process;
63
64     /* Timeout when waiting for compositor to start */
65     guint compositor_timeout;
66
67     /* Next Mir ID to use for a compositor client */
68     gint next_id;
69
70     /* TRUE if using VT switching fallback */
71     gboolean use_vt_switching;
72
73     /* The currently visible display */
74     Display *active_display;
75 };
76
77 G_DEFINE_TYPE (SeatUnity, seat_unity, SEAT_TYPE);
78
79 static void
80 seat_unity_setup (Seat *seat)
81 {
82     seat_set_can_switch (seat, TRUE);
83     SEAT_CLASS (seat_unity_parent_class)->setup (seat);
84 }
85
86 static void
87 compositor_stopped_cb (Process *process, SeatUnity *seat)
88 {
89     if (seat->priv->compositor_timeout != 0)
90         g_source_remove (seat->priv->compositor_timeout);
91     seat->priv->compositor_timeout = 0;
92
93     if (seat_get_is_stopping (SEAT (seat)))
94     {
95         SEAT_CLASS (seat_unity_parent_class)->stop (SEAT (seat));
96         return;
97     }
98
99     /* If stopped before it was ready, then revert to VT mode */
100     if (!seat->priv->compositor_ready)
101     {
102         g_debug ("Compositor failed to start, switching to VT mode");
103         seat->priv->use_vt_switching = TRUE;
104         SEAT_CLASS (seat_unity_parent_class)->start (SEAT (seat));
105         return;
106     }
107
108     g_debug ("Stopping Unity seat, compositor terminated");
109
110     if (seat->priv->stopping_plymouth)
111     {
112         g_debug ("Stopping Plymouth, compositor failed to start");
113         plymouth_quit (FALSE);
114         seat->priv->stopping_plymouth = FALSE;
115     }
116
117     seat_stop (SEAT (seat));
118 }
119
120 static void
121 compositor_run_cb (Process *process, SeatUnity *seat)
122 {
123     int fd;
124
125     /* Make input non-blocking */
126     fd = open ("/dev/null", O_RDONLY);
127     dup2 (fd, STDIN_FILENO);
128     close (fd);
129
130     /* Redirect output to logfile */
131     if (seat->priv->log_file)
132     {
133          int fd;
134
135          fd = g_open (seat->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
136          if (fd < 0)
137              g_warning ("Failed to open log file %s: %s", seat->priv->log_file, g_strerror (errno));
138          else
139          {
140              dup2 (fd, STDOUT_FILENO);
141              dup2 (fd, STDERR_FILENO);
142              close (fd);
143          }
144     }
145 }
146
147 static void
148 write_message (SeatUnity *seat, guint16 id, const guint8 *payload, guint16 payload_length)
149 {
150     guint8 *data;
151     gsize data_length = 4 + payload_length;
152
153     data = g_malloc (data_length);
154     data[0] = id >> 8;
155     data[1] = id & 0xFF;
156     data[2] = payload_length >> 8;
157     data[3] = payload_length & 0xFF;
158     memcpy (data + 4, payload, payload_length);
159
160     errno = 0;
161     if (write (seat->priv->to_compositor_pipe[1], data, data_length) != data_length)
162         g_warning ("Failed to write to compositor: %s", strerror (errno));
163 }
164
165 static gboolean
166 read_cb (GIOChannel *source, GIOCondition condition, gpointer data)
167 {
168     SeatUnity *seat = data;
169     gsize n_to_read = 0;
170     guint16 id, payload_length;
171     guint8 *payload;
172
173     /* Work out how much required for a message */
174     if (seat->priv->read_buffer_n_used < 4)
175         n_to_read = 4 - seat->priv->read_buffer_n_used;
176     else
177     {
178         payload_length = seat->priv->read_buffer[2] << 8 | seat->priv->read_buffer[3];
179         n_to_read = 4 + payload_length - seat->priv->read_buffer_n_used;
180     }
181
182     /* Read from compositor */
183     if (n_to_read > 0)
184     {
185         gsize n_total, n_read = 0;
186         GError *error = NULL;
187
188         n_total = seat->priv->read_buffer_n_used + n_to_read;
189         if (seat->priv->read_buffer_length < n_total)
190             seat->priv->read_buffer = g_realloc (seat->priv->read_buffer, n_total);
191
192         g_io_channel_read_chars (source,
193                                  seat->priv->read_buffer + seat->priv->read_buffer_n_used,
194                                  n_to_read,
195                                  &n_read,
196                                  &error);
197         if (error)
198             g_warning ("Failed to read from compositor: %s", error->message);
199         g_clear_error (&error);
200         seat->priv->read_buffer_n_used += n_read;
201     }
202
203     /* Read header */
204     if (seat->priv->read_buffer_n_used < 4)
205          return TRUE;
206     id = seat->priv->read_buffer[0] << 8 | seat->priv->read_buffer[1];
207     payload_length = seat->priv->read_buffer[2] << 8 | seat->priv->read_buffer[3];
208
209     /* Read payload */
210     if (seat->priv->read_buffer_n_used < 4 + payload_length)
211         return TRUE;
212     payload = seat->priv->read_buffer + 4;
213
214     switch (id)
215     {
216     case USC_MESSAGE_PING:
217         g_debug ("PING!");
218         write_message (seat, USC_MESSAGE_PONG, NULL, 0);
219         break;
220     case USC_MESSAGE_PONG:
221         g_debug ("PONG!");
222         break;
223     case USC_MESSAGE_READY:
224         g_debug ("READY");
225         if (!seat->priv->compositor_ready)
226         {
227             seat->priv->compositor_ready = TRUE;
228             g_debug ("Compositor ready");
229             g_source_remove (seat->priv->compositor_timeout);
230             seat->priv->compositor_timeout = 0;
231             SEAT_CLASS (seat_unity_parent_class)->start (SEAT (seat));
232         }
233         break;
234     case USC_MESSAGE_SESSION_CONNECTED:
235         g_debug ("SESSION CONNECTED");
236         break;
237     default:
238         g_warning ("Ingoring unknown message %d with %d octets from system compositor", id, payload_length);
239         break;
240     }
241
242     /* Clear buffer */
243     seat->priv->read_buffer_n_used = 0;
244
245     return TRUE;
246 }
247
248 static gchar *
249 get_absolute_command (const gchar *command)
250 {
251     gchar **tokens;
252     gchar *absolute_binary, *absolute_command = NULL;
253
254     tokens = g_strsplit (command, " ", 2);
255
256     absolute_binary = g_find_program_in_path (tokens[0]);
257     if (absolute_binary)
258     {
259         if (tokens[1])
260             absolute_command = g_strjoin (" ", absolute_binary, tokens[1], NULL);
261         else
262             absolute_command = g_strdup (absolute_binary);
263         g_free (absolute_binary);
264     }
265     else
266         absolute_command = g_strdup (command);
267
268     g_strfreev (tokens);
269
270     return absolute_command;
271 }
272
273 static gboolean
274 compositor_timeout_cb (gpointer data)
275 {
276     Seat *seat = data;
277
278     g_debug ("Compositor failed to start");
279
280     seat_stop (seat);
281
282     return TRUE;
283 }
284
285 static gboolean
286 seat_unity_start (Seat *seat)
287 {
288     gchar *command, *absolute_command, *dir;
289     gboolean result;
290
291     /* Replace Plymouth if it is running */
292     if (plymouth_get_is_active () && plymouth_has_active_vt ())
293     {
294         gint active_vt = vt_get_active ();
295         if (active_vt >= vt_get_min ())
296         {
297             g_debug ("Compositor will replace Plymouth");
298             SEAT_UNITY (seat)->priv->vt = active_vt;
299             plymouth_deactivate ();
300         }
301         else
302             g_debug ("Plymouth is running on VT %d, but this is less than the configured minimum of %d so not replacing it", active_vt, vt_get_min ());
303     }
304     if (SEAT_UNITY (seat)->priv->vt < 0)
305         SEAT_UNITY (seat)->priv->vt = vt_get_unused ();
306     if (SEAT_UNITY (seat)->priv->vt < 0)
307     {
308         g_debug ("Failed to get a VT to run on");
309         return FALSE;
310     }
311     vt_ref (SEAT_UNITY (seat)->priv->vt);
312
313     /* Create pipes to talk to compositor */
314     if (pipe (SEAT_UNITY (seat)->priv->to_compositor_pipe) < 0 || pipe (SEAT_UNITY (seat)->priv->from_compositor_pipe) < 0)
315     {
316         g_debug ("Failed to create compositor pipes: %s", g_strerror (errno));
317         return FALSE;
318     }
319
320     /* Don't allow the daemon end of the pipes to be accessed in the compositor */
321     fcntl (SEAT_UNITY (seat)->priv->to_compositor_pipe[1], F_SETFD, FD_CLOEXEC);
322     fcntl (SEAT_UNITY (seat)->priv->from_compositor_pipe[0], F_SETFD, FD_CLOEXEC);
323
324     /* Listen for messages from the compositor */
325     SEAT_UNITY (seat)->priv->from_compositor_channel = g_io_channel_unix_new (SEAT_UNITY (seat)->priv->from_compositor_pipe[0]);
326     g_io_add_watch (SEAT_UNITY (seat)->priv->from_compositor_channel, G_IO_IN, read_cb, seat);
327
328     /* Setup logging */
329     dir = config_get_string (config_get_instance (), "LightDM", "log-directory");
330     SEAT_UNITY (seat)->priv->log_file = g_build_filename (dir, "unity-system-compositor.log", NULL);
331     g_debug ("Logging to %s", SEAT_UNITY (seat)->priv->log_file);
332     g_free (dir);
333
334     SEAT_UNITY (seat)->priv->mir_socket_filename = g_strdup ("/tmp/mir_socket"); // FIXME: Use this socket by default as XMir is hardcoded to this
335     command = g_strdup_printf ("unity-system-compositor %d %d", SEAT_UNITY (seat)->priv->to_compositor_pipe[0], SEAT_UNITY (seat)->priv->from_compositor_pipe[1]);
336
337     absolute_command = get_absolute_command (command);
338     g_free (command);
339
340     /* Start the compositor */
341     process_set_command (SEAT_UNITY (seat)->priv->compositor_process, absolute_command);
342     g_free (absolute_command);
343     g_signal_connect (SEAT_UNITY (seat)->priv->compositor_process, "stopped", G_CALLBACK (compositor_stopped_cb), seat);
344     g_signal_connect (SEAT_UNITY (seat)->priv->compositor_process, "run", G_CALLBACK (compositor_run_cb), seat);
345     result = process_start (SEAT_UNITY (seat)->priv->compositor_process, FALSE);
346
347     /* Close compostor ends of the pipes */
348     close (SEAT_UNITY (seat)->priv->to_compositor_pipe[0]);
349     SEAT_UNITY (seat)->priv->to_compositor_pipe[0] = 0;
350     close (SEAT_UNITY (seat)->priv->from_compositor_pipe[1]);
351     SEAT_UNITY (seat)->priv->from_compositor_pipe[1] = 0;
352
353     if (!result)
354         return FALSE;
355
356     /* Connect to the compositor */
357     g_debug ("Waiting for system compositor");
358     SEAT_UNITY (seat)->priv->compositor_timeout = g_timeout_add (5000, compositor_timeout_cb, seat);
359
360     return TRUE;
361 }
362
363 static DisplayServer *
364 seat_unity_create_display_server (Seat *seat)
365 {
366     XServerLocal *xserver;
367     const gchar *command = NULL, *layout = NULL, *config_file = NULL, *xdmcp_manager = NULL, *key_name = NULL;
368     gboolean allow_tcp;
369     gint port = 0;
370     gchar *id;
371
372     g_debug ("Starting X server on Unity compositor");
373
374     xserver = xserver_local_new ();
375
376     if (!SEAT_UNITY (seat)->priv->use_vt_switching)
377     {
378         id = g_strdup_printf ("%d", SEAT_UNITY (seat)->priv->next_id);
379         SEAT_UNITY (seat)->priv->next_id++;
380         xserver_local_set_mir_id (xserver, id);
381         xserver_local_set_mir_socket (xserver, SEAT_UNITY (seat)->priv->mir_socket_filename);
382         g_free (id);
383     }
384
385     command = seat_get_string_property (seat, "xserver-command");
386     if (command)
387         xserver_local_set_command (xserver, command);
388
389     layout = seat_get_string_property (seat, "xserver-layout");
390     if (layout)
391         xserver_local_set_layout (xserver, layout);
392
393     config_file = seat_get_string_property (seat, "xserver-config");
394     if (config_file)
395         xserver_local_set_config (xserver, config_file);
396
397     allow_tcp = seat_get_boolean_property (seat, "xserver-allow-tcp");
398     xserver_local_set_allow_tcp (xserver, allow_tcp);
399
400     xdmcp_manager = seat_get_string_property (seat, "xdmcp-manager");
401     if (xdmcp_manager)
402         xserver_local_set_xdmcp_server (xserver, xdmcp_manager);
403
404     port = seat_get_integer_property (seat, "xdmcp-port");
405     if (port > 0)
406         xserver_local_set_xdmcp_port (xserver, port);
407
408     key_name = seat_get_string_property (seat, "xdmcp-key");
409     if (key_name)
410     {
411         gchar *dir, *path;
412         GKeyFile *keys;
413         gboolean result;
414         GError *error = NULL;
415
416         dir = config_get_string (config_get_instance (), "LightDM", "config-directory");
417         path = g_build_filename (dir, "keys.conf", NULL);
418         g_free (dir);
419
420         keys = g_key_file_new ();
421         result = g_key_file_load_from_file (keys, path, G_KEY_FILE_NONE, &error);
422         if (error)
423             g_debug ("Error getting key %s", error->message);
424         g_clear_error (&error);
425
426         if (result)
427         {
428             gchar *key = NULL;
429
430             if (g_key_file_has_key (keys, "keyring", key_name, NULL))
431                 key = g_key_file_get_string (keys, "keyring", key_name, NULL);
432             else
433                 g_debug ("Key %s not defined", key_name);
434
435             if (key)
436                 xserver_local_set_xdmcp_key (xserver, key);
437             g_free (key);
438         }
439
440         g_free (path);
441         g_key_file_free (keys);
442     }
443
444     return DISPLAY_SERVER (xserver);
445 }
446
447 static Session *
448 seat_unity_create_session (Seat *seat, Display *display)
449 {
450     XServerLocal *xserver;
451     XSession *session;
452     gchar *tty;
453
454     xserver = XSERVER_LOCAL (display_get_display_server (display));
455
456     session = xsession_new (XSERVER (xserver));
457     if (SEAT_UNITY (seat)->priv->use_vt_switching)
458         tty = g_strdup_printf ("/dev/tty%d", xserver_local_get_vt (xserver));
459     else
460         tty = g_strdup_printf ("/dev/tty%d", SEAT_UNITY (seat)->priv->vt);
461     session_set_tty (SESSION (session), tty);
462     g_free (tty);
463
464     return SESSION (session);
465 }
466
467 static void
468 seat_unity_set_active_display (Seat *seat, Display *display)
469 {
470     XServerLocal *xserver;
471     const gchar *id;
472
473     /* If no compositor, have to use VT switching */
474     if (SEAT_UNITY (seat)->priv->use_vt_switching)
475     {
476         gint vt = xserver_local_get_vt (XSERVER_LOCAL (display_get_display_server (display)));
477         if (vt >= 0)
478             vt_set_active (vt);
479
480         SEAT_CLASS (seat_unity_parent_class)->set_active_display (seat, display);
481         return;
482     }
483
484     if (display == SEAT_UNITY (seat)->priv->active_display)
485         return;
486     SEAT_UNITY (seat)->priv->active_display = display;
487
488     xserver = XSERVER_LOCAL (display_get_display_server (display));
489     id = xserver_local_get_mir_id (xserver);
490
491     g_debug ("Switching to Mir session %s", id);
492     write_message (SEAT_UNITY (seat), USC_MESSAGE_SET_ACTIVE_SESSION, id, strlen (id));
493
494     SEAT_CLASS (seat_unity_parent_class)->set_active_display (seat, display);
495 }
496
497 static Display *
498 seat_unity_get_active_display (Seat *seat)
499 {
500     if (SEAT_UNITY (seat)->priv->use_vt_switching)
501     {
502         gint vt;
503         GList *link;
504         vt = vt_get_active ();
505         if (vt < 0)
506             return NULL;
507
508         for (link = seat_get_displays (seat); link; link = link->next)
509         {
510             Display *display = link->data;
511             XServerLocal *xserver;
512
513             xserver = XSERVER_LOCAL (display_get_display_server (display));
514             if (xserver_local_get_vt (xserver) == vt)
515                 return display;
516         }
517
518         return NULL;
519     }
520
521     return SEAT_UNITY (seat)->priv->active_display;
522 }
523
524 static void
525 seat_unity_run_script (Seat *seat, Display *display, Process *script)
526 {
527     const gchar *path;
528     XServerLocal *xserver;
529
530     xserver = XSERVER_LOCAL (display_get_display_server (display));
531     path = xserver_local_get_authority_file_path (xserver);
532     process_set_env (script, "DISPLAY", xserver_get_address (XSERVER (xserver)));
533     process_set_env (script, "XAUTHORITY", path);
534
535     SEAT_CLASS (seat_unity_parent_class)->run_script (seat, display, script);
536 }
537
538 static void
539 seat_unity_stop (Seat *seat)
540 {
541     /* Stop the compositor first */
542     if (process_get_is_running (SEAT_UNITY (seat)->priv->compositor_process))
543     {
544         process_stop (SEAT_UNITY (seat)->priv->compositor_process);
545         return;
546     }
547
548     SEAT_CLASS (seat_unity_parent_class)->stop (seat);
549 }
550
551 static void
552 seat_unity_display_removed (Seat *seat, Display *display)
553 {
554     if (seat_get_is_stopping (seat))
555         return;
556
557     /* If this is the only display and it failed to start then stop this seat */
558     if (g_list_length (seat_get_displays (seat)) == 0 && !display_get_is_ready (display))
559     {
560         g_debug ("Stopping Unity seat, failed to start a display");
561         seat_stop (seat);
562         return;
563     }
564
565     /* Show a new greeter */
566     if (display == seat_get_active_display (seat))
567     {
568         g_debug ("Active display stopped, switching to greeter");
569         seat_switch_to_greeter (seat);
570     }
571 }
572
573 static void
574 seat_unity_init (SeatUnity *seat)
575 {
576     seat->priv = G_TYPE_INSTANCE_GET_PRIVATE (seat, SEAT_UNITY_TYPE, SeatUnityPrivate);
577     seat->priv->vt = -1;
578     seat->priv->compositor_process = process_new ();
579 }
580
581 static void
582 seat_unity_finalize (GObject *object)
583 {
584     SeatUnity *seat = SEAT_UNITY (object);
585
586     if (seat->priv->vt >= 0)
587         vt_unref (seat->priv->vt);
588     g_free (seat->priv->log_file);
589     g_free (seat->priv->mir_socket_filename);
590     close (seat->priv->to_compositor_pipe[0]);
591     close (seat->priv->to_compositor_pipe[1]);
592     close (seat->priv->from_compositor_pipe[0]);
593     close (seat->priv->from_compositor_pipe[1]);
594     g_io_channel_unref (seat->priv->from_compositor_channel);
595     g_free (seat->priv->read_buffer);
596     g_object_unref (seat->priv->compositor_process);
597
598     G_OBJECT_CLASS (seat_unity_parent_class)->finalize (object);
599 }
600
601 static void
602 seat_unity_class_init (SeatUnityClass *klass)
603 {
604     GObjectClass *object_class = G_OBJECT_CLASS (klass);
605     SeatClass *seat_class = SEAT_CLASS (klass);
606
607     object_class->finalize = seat_unity_finalize;
608     seat_class->setup = seat_unity_setup;
609     seat_class->start = seat_unity_start;
610     seat_class->create_display_server = seat_unity_create_display_server;
611     seat_class->create_session = seat_unity_create_session;
612     seat_class->set_active_display = seat_unity_set_active_display;
613     seat_class->get_active_display = seat_unity_get_active_display;
614     seat_class->run_script = seat_unity_run_script;
615     seat_class->stop = seat_unity_stop;
616     seat_class->display_removed = seat_unity_display_removed;
617
618     g_type_class_add_private (klass, sizeof (SeatUnityPrivate));
619 }