]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/seat-unity.c
Strip out non-core changes in preperation for merging into trunk
[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     if (condition == G_IO_HUP)
174     {
175         g_debug ("Compositor closed communication channel");
176         return FALSE;
177     }
178
179     /* Work out how much required for a message */
180     if (seat->priv->read_buffer_n_used < 4)
181         n_to_read = 4 - seat->priv->read_buffer_n_used;
182     else
183     {
184         payload_length = seat->priv->read_buffer[2] << 8 | seat->priv->read_buffer[3];
185         n_to_read = 4 + payload_length - seat->priv->read_buffer_n_used;
186     }
187
188     /* Read from compositor */
189     if (n_to_read > 0)
190     {
191         gsize n_total, n_read = 0;
192         GIOStatus status;
193         GError *error = NULL;
194
195         n_total = seat->priv->read_buffer_n_used + n_to_read;
196         if (seat->priv->read_buffer_length < n_total)
197             seat->priv->read_buffer = g_realloc (seat->priv->read_buffer, n_total);
198
199         status = g_io_channel_read_chars (source,
200                                           seat->priv->read_buffer + seat->priv->read_buffer_n_used,
201                                           n_to_read,
202                                           &n_read,
203                                           &error);
204         if (error)
205             g_warning ("Failed to read from compositor: %s", error->message);
206         if (status != G_IO_STATUS_NORMAL)
207             return TRUE;
208         g_clear_error (&error);
209         seat->priv->read_buffer_n_used += n_read;
210     }
211
212     /* Read header */
213     if (seat->priv->read_buffer_n_used < 4)
214          return TRUE;
215     id = seat->priv->read_buffer[0] << 8 | seat->priv->read_buffer[1];
216     payload_length = seat->priv->read_buffer[2] << 8 | seat->priv->read_buffer[3];
217
218     /* Read payload */
219     if (seat->priv->read_buffer_n_used < 4 + payload_length)
220         return TRUE;
221     payload = seat->priv->read_buffer + 4;
222
223     switch (id)
224     {
225     case USC_MESSAGE_PING:
226         g_debug ("PING!");
227         write_message (seat, USC_MESSAGE_PONG, NULL, 0);
228         break;
229     case USC_MESSAGE_PONG:
230         g_debug ("PONG!");
231         break;
232     case USC_MESSAGE_READY:
233         g_debug ("READY");
234         if (!seat->priv->compositor_ready)
235         {
236             seat->priv->compositor_ready = TRUE;
237             g_debug ("Compositor ready");
238             g_source_remove (seat->priv->compositor_timeout);
239             seat->priv->compositor_timeout = 0;
240             SEAT_CLASS (seat_unity_parent_class)->start (SEAT (seat));
241         }
242         break;
243     case USC_MESSAGE_SESSION_CONNECTED:
244         g_debug ("SESSION CONNECTED");
245         break;
246     default:
247         g_warning ("Ingoring unknown message %d with %d octets from system compositor", id, payload_length);
248         break;
249     }
250
251     /* Clear buffer */
252     seat->priv->read_buffer_n_used = 0;
253
254     return TRUE;
255 }
256
257 static gchar *
258 get_absolute_command (const gchar *command)
259 {
260     gchar **tokens;
261     gchar *absolute_binary, *absolute_command = NULL;
262
263     tokens = g_strsplit (command, " ", 2);
264
265     absolute_binary = g_find_program_in_path (tokens[0]);
266     if (absolute_binary)
267     {
268         if (tokens[1])
269             absolute_command = g_strjoin (" ", absolute_binary, tokens[1], NULL);
270         else
271             absolute_command = g_strdup (absolute_binary);
272         g_free (absolute_binary);
273     }
274     else
275         absolute_command = g_strdup (command);
276
277     g_strfreev (tokens);
278
279     return absolute_command;
280 }
281
282 static gboolean
283 compositor_timeout_cb (gpointer data)
284 {
285     SeatUnity *seat = data;
286
287     /* Stop the compositor - it is not working */
288     process_stop (seat->priv->compositor_process);
289
290     return TRUE;
291 }
292
293 static gboolean
294 seat_unity_start (Seat *seat)
295 {
296     const gchar *compositor_command;
297     gchar *command, *absolute_command, *dir;
298     gboolean result;
299     int timeout;
300
301     /* Replace Plymouth if it is running */
302     if (plymouth_get_is_active () && plymouth_has_active_vt ())
303     {
304         gint active_vt = vt_get_active ();
305         if (active_vt >= vt_get_min ())
306         {
307             g_debug ("Compositor will replace Plymouth");
308             SEAT_UNITY (seat)->priv->vt = active_vt;
309             plymouth_deactivate ();
310         }
311         else
312             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 ());
313     }
314     if (SEAT_UNITY (seat)->priv->vt < 0)
315         SEAT_UNITY (seat)->priv->vt = vt_get_unused ();
316     if (SEAT_UNITY (seat)->priv->vt < 0)
317     {
318         g_debug ("Failed to get a VT to run on");
319         return FALSE;
320     }
321     vt_ref (SEAT_UNITY (seat)->priv->vt);
322
323     /* Create pipes to talk to compositor */
324     if (pipe (SEAT_UNITY (seat)->priv->to_compositor_pipe) < 0 || pipe (SEAT_UNITY (seat)->priv->from_compositor_pipe) < 0)
325     {
326         g_debug ("Failed to create compositor pipes: %s", g_strerror (errno));
327         return FALSE;
328     }
329
330     /* Don't allow the daemon end of the pipes to be accessed in the compositor */
331     fcntl (SEAT_UNITY (seat)->priv->to_compositor_pipe[1], F_SETFD, FD_CLOEXEC);
332     fcntl (SEAT_UNITY (seat)->priv->from_compositor_pipe[0], F_SETFD, FD_CLOEXEC);
333
334     /* Listen for messages from the compositor */
335     SEAT_UNITY (seat)->priv->from_compositor_channel = g_io_channel_unix_new (SEAT_UNITY (seat)->priv->from_compositor_pipe[0]);
336     g_io_add_watch (SEAT_UNITY (seat)->priv->from_compositor_channel, G_IO_IN | G_IO_HUP, read_cb, seat);
337
338     /* Setup logging */
339     dir = config_get_string (config_get_instance (), "LightDM", "log-directory");
340     SEAT_UNITY (seat)->priv->log_file = g_build_filename (dir, "unity-system-compositor.log", NULL);
341     g_debug ("Logging to %s", SEAT_UNITY (seat)->priv->log_file);
342     g_free (dir);
343
344     SEAT_UNITY (seat)->priv->mir_socket_filename = g_strdup ("/tmp/mir_socket"); // FIXME: Use this socket by default as XMir is hardcoded to this
345     timeout = seat_get_integer_property (seat, "unity-compositor-timeout");
346     compositor_command = seat_get_string_property (seat, "unity-compositor-command");
347     command = g_strdup_printf ("%s --from-dm-fd %d --to-dm-fd %d --vt %d", compositor_command, SEAT_UNITY (seat)->priv->to_compositor_pipe[0], SEAT_UNITY (seat)->priv->from_compositor_pipe[1], SEAT_UNITY (seat)->priv->vt);
348
349     absolute_command = get_absolute_command (command);
350     g_free (command);
351
352     /* Start the compositor */
353     process_set_command (SEAT_UNITY (seat)->priv->compositor_process, absolute_command);
354     g_free (absolute_command);
355     g_signal_connect (SEAT_UNITY (seat)->priv->compositor_process, "stopped", G_CALLBACK (compositor_stopped_cb), seat);
356     g_signal_connect (SEAT_UNITY (seat)->priv->compositor_process, "run", G_CALLBACK (compositor_run_cb), seat);
357     result = process_start (SEAT_UNITY (seat)->priv->compositor_process, FALSE);
358
359     /* Close compostor ends of the pipes */
360     close (SEAT_UNITY (seat)->priv->to_compositor_pipe[0]);
361     SEAT_UNITY (seat)->priv->to_compositor_pipe[0] = 0;
362     close (SEAT_UNITY (seat)->priv->from_compositor_pipe[1]);
363     SEAT_UNITY (seat)->priv->from_compositor_pipe[1] = 0;
364
365     if (!result)
366         return FALSE;
367
368     /* Connect to the compositor */
369     timeout = seat_get_integer_property (seat, "unity-compositor-timeout");
370     if (timeout <= 0)
371         timeout = 60;
372     g_debug ("Waiting for system compositor for %ds", timeout);
373     SEAT_UNITY (seat)->priv->compositor_timeout = g_timeout_add (timeout * 1000, compositor_timeout_cb, seat);
374
375     return TRUE;
376 }
377
378 static DisplayServer *
379 seat_unity_create_display_server (Seat *seat)
380 {
381     XServerLocal *xserver;
382     const gchar *command = NULL, *layout = NULL, *config_file = NULL, *xdmcp_manager = NULL, *key_name = NULL;
383     gboolean allow_tcp;
384     gint port = 0;
385     gchar *id;
386
387     g_debug ("Starting X server on Unity compositor");
388
389     xserver = xserver_local_new ();
390
391     if (!SEAT_UNITY (seat)->priv->use_vt_switching)
392     {
393         id = g_strdup_printf ("%d", SEAT_UNITY (seat)->priv->next_id);
394         SEAT_UNITY (seat)->priv->next_id++;
395         xserver_local_set_mir_id (xserver, id);
396         xserver_local_set_mir_socket (xserver, SEAT_UNITY (seat)->priv->mir_socket_filename);
397         g_free (id);
398     }
399
400     command = seat_get_string_property (seat, "xserver-command");
401     if (command)
402         xserver_local_set_command (xserver, command);
403
404     layout = seat_get_string_property (seat, "xserver-layout");
405     if (layout)
406         xserver_local_set_layout (xserver, layout);
407
408     config_file = seat_get_string_property (seat, "xserver-config");
409     if (config_file)
410         xserver_local_set_config (xserver, config_file);
411
412     allow_tcp = seat_get_boolean_property (seat, "xserver-allow-tcp");
413     xserver_local_set_allow_tcp (xserver, allow_tcp);
414
415     xdmcp_manager = seat_get_string_property (seat, "xdmcp-manager");
416     if (xdmcp_manager)
417         xserver_local_set_xdmcp_server (xserver, xdmcp_manager);
418
419     port = seat_get_integer_property (seat, "xdmcp-port");
420     if (port > 0)
421         xserver_local_set_xdmcp_port (xserver, port);
422
423     key_name = seat_get_string_property (seat, "xdmcp-key");
424     if (key_name)
425     {
426         gchar *dir, *path;
427         GKeyFile *keys;
428         gboolean result;
429         GError *error = NULL;
430
431         dir = config_get_string (config_get_instance (), "LightDM", "config-directory");
432         path = g_build_filename (dir, "keys.conf", NULL);
433         g_free (dir);
434
435         keys = g_key_file_new ();
436         result = g_key_file_load_from_file (keys, path, G_KEY_FILE_NONE, &error);
437         if (error)
438             g_debug ("Error getting key %s", error->message);
439         g_clear_error (&error);
440
441         if (result)
442         {
443             gchar *key = NULL;
444
445             if (g_key_file_has_key (keys, "keyring", key_name, NULL))
446                 key = g_key_file_get_string (keys, "keyring", key_name, NULL);
447             else
448                 g_debug ("Key %s not defined", key_name);
449
450             if (key)
451                 xserver_local_set_xdmcp_key (xserver, key);
452             g_free (key);
453         }
454
455         g_free (path);
456         g_key_file_free (keys);
457     }
458
459     return DISPLAY_SERVER (xserver);
460 }
461
462 static Session *
463 seat_unity_create_session (Seat *seat, Display *display)
464 {
465     XServerLocal *xserver;
466     XSession *session;
467     int vt_number;
468     gchar *t;
469
470     xserver = XSERVER_LOCAL (display_get_display_server (display));
471
472     if (SEAT_UNITY (seat)->priv->use_vt_switching)
473         vt_number = xserver_local_get_vt (xserver);
474     else
475         vt_number = SEAT_UNITY (seat)->priv->vt;
476
477     session = xsession_new (XSERVER (xserver));
478     t = g_strdup_printf ("/dev/tty%d", vt_number);
479     session_set_tty (SESSION (session), t);
480     g_free (t);
481
482     /* Set variables for logind */
483     session_set_env (SESSION (session), "XDG_SEAT", "seat0");
484     t = g_strdup_printf ("%d", vt_number);
485     session_set_env (SESSION (session), "XDG_VTNR", t);
486     g_free (t);
487
488     return SESSION (session);
489 }
490
491 static void
492 seat_unity_set_active_display (Seat *seat, Display *display)
493 {
494     XServerLocal *xserver;
495     const gchar *id;
496
497     /* If no compositor, have to use VT switching */
498     if (SEAT_UNITY (seat)->priv->use_vt_switching)
499     {
500         gint vt = xserver_local_get_vt (XSERVER_LOCAL (display_get_display_server (display)));
501         if (vt >= 0)
502             vt_set_active (vt);
503
504         SEAT_CLASS (seat_unity_parent_class)->set_active_display (seat, display);
505         return;
506     }
507
508     if (display == SEAT_UNITY (seat)->priv->active_display)
509         return;
510     SEAT_UNITY (seat)->priv->active_display = display;
511
512     xserver = XSERVER_LOCAL (display_get_display_server (display));
513     id = xserver_local_get_mir_id (xserver);
514
515     g_debug ("Switching to Mir session %s", id);
516     write_message (SEAT_UNITY (seat), USC_MESSAGE_SET_ACTIVE_SESSION, id, strlen (id));
517
518     SEAT_CLASS (seat_unity_parent_class)->set_active_display (seat, display);
519 }
520
521 static Display *
522 seat_unity_get_active_display (Seat *seat)
523 {
524     if (SEAT_UNITY (seat)->priv->use_vt_switching)
525     {
526         gint vt;
527         GList *link;
528         vt = vt_get_active ();
529         if (vt < 0)
530             return NULL;
531
532         for (link = seat_get_displays (seat); link; link = link->next)
533         {
534             Display *display = link->data;
535             XServerLocal *xserver;
536
537             xserver = XSERVER_LOCAL (display_get_display_server (display));
538             if (xserver_local_get_vt (xserver) == vt)
539                 return display;
540         }
541
542         return NULL;
543     }
544
545     return SEAT_UNITY (seat)->priv->active_display;
546 }
547
548 static void
549 seat_unity_run_script (Seat *seat, Display *display, Process *script)
550 {
551     const gchar *path;
552     XServerLocal *xserver;
553
554     xserver = XSERVER_LOCAL (display_get_display_server (display));
555     path = xserver_local_get_authority_file_path (xserver);
556     process_set_env (script, "DISPLAY", xserver_get_address (XSERVER (xserver)));
557     process_set_env (script, "XAUTHORITY", path);
558
559     SEAT_CLASS (seat_unity_parent_class)->run_script (seat, display, script);
560 }
561
562 static void
563 seat_unity_stop (Seat *seat)
564 {
565     /* Stop the compositor first */
566     if (process_get_is_running (SEAT_UNITY (seat)->priv->compositor_process))
567     {
568         process_stop (SEAT_UNITY (seat)->priv->compositor_process);
569         return;
570     }
571
572     SEAT_CLASS (seat_unity_parent_class)->stop (seat);
573 }
574
575 static void
576 seat_unity_display_removed (Seat *seat, Display *display)
577 {
578     if (seat_get_is_stopping (seat))
579         return;
580
581     /* If this is the only display and it failed to start then stop this seat */
582     if (g_list_length (seat_get_displays (seat)) == 0 && !display_get_is_ready (display))
583     {
584         g_debug ("Stopping Unity seat, failed to start a display");
585         seat_stop (seat);
586         return;
587     }
588
589     /* Show a new greeter */
590     if (display == seat_get_active_display (seat))
591     {
592         g_debug ("Active display stopped, switching to greeter");
593         seat_switch_to_greeter (seat);
594     }
595 }
596
597 static void
598 seat_unity_init (SeatUnity *seat)
599 {
600     seat->priv = G_TYPE_INSTANCE_GET_PRIVATE (seat, SEAT_UNITY_TYPE, SeatUnityPrivate);
601     seat->priv->vt = -1;
602     seat->priv->compositor_process = process_new ();
603 }
604
605 static void
606 seat_unity_finalize (GObject *object)
607 {
608     SeatUnity *seat = SEAT_UNITY (object);
609
610     if (seat->priv->vt >= 0)
611         vt_unref (seat->priv->vt);
612     g_free (seat->priv->log_file);
613     g_free (seat->priv->mir_socket_filename);
614     close (seat->priv->to_compositor_pipe[0]);
615     close (seat->priv->to_compositor_pipe[1]);
616     close (seat->priv->from_compositor_pipe[0]);
617     close (seat->priv->from_compositor_pipe[1]);
618     g_io_channel_unref (seat->priv->from_compositor_channel);
619     g_free (seat->priv->read_buffer);
620     g_object_unref (seat->priv->compositor_process);
621
622     G_OBJECT_CLASS (seat_unity_parent_class)->finalize (object);
623 }
624
625 static void
626 seat_unity_class_init (SeatUnityClass *klass)
627 {
628     GObjectClass *object_class = G_OBJECT_CLASS (klass);
629     SeatClass *seat_class = SEAT_CLASS (klass);
630
631     object_class->finalize = seat_unity_finalize;
632     seat_class->setup = seat_unity_setup;
633     seat_class->start = seat_unity_start;
634     seat_class->create_display_server = seat_unity_create_display_server;
635     seat_class->create_session = seat_unity_create_session;
636     seat_class->set_active_display = seat_unity_set_active_display;
637     seat_class->get_active_display = seat_unity_get_active_display;
638     seat_class->run_script = seat_unity_run_script;
639     seat_class->stop = seat_unity_stop;
640     seat_class->display_removed = seat_unity_display_removed;
641
642     g_type_class_add_private (klass, sizeof (SeatUnityPrivate));
643 }