/* * Copyright (C) 2012 Robert Ancell. * Author: Robert Ancell * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. See http://www.gnu.org/copyleft/gpl.html the full text of the * license. */ #include #include #include #include #include #include #include #include "seat-weston.h" #include "configuration.h" #include "xserver-local.h" #include "xsession.h" #include "wayland-client.h" // FIXME: See https://bugs.freedesktop.org/show_bug.cgi?id=51740 #include "weston-protocol.h" #include "vt.h" #include "plymouth.h" struct SeatWestonPrivate { /* VT we are running on */ gint vt; /* TRUE if waiting for X server to start before stopping Plymouth */ gboolean stopping_plymouth; /* File to log to */ gchar *log_file; /* Compositor process */ Process *compositor_process; /* Connection to the compositor */ struct wl_display *display; GIOChannel *channel; uint32_t mask; /* Display manager interface */ struct wl_display_manager *display_manager; /* Mapping of displays to handles on the compositor */ GHashTable *displays; }; G_DEFINE_TYPE (SeatWeston, seat_weston, SEAT_TYPE); static void seat_weston_setup (Seat *seat) { seat_set_can_switch (seat, TRUE); SEAT_CLASS (seat_weston_parent_class)->setup (seat); } static void compositor_stopped_cb (Process *process, SeatWeston *seat) { g_debug ("Stopping Weston seat, compositor terminated"); if (seat->priv->stopping_plymouth) { g_debug ("Stopping Plymouth, compositor failed to start"); plymouth_quit (FALSE); seat->priv->stopping_plymouth = FALSE; } seat_stop (SEAT (seat)); } static gboolean read_cb (GIOChannel *source, GIOCondition condition, gpointer data) { SeatWeston *seat = data; wl_display_iterate (seat->priv->display, WL_DISPLAY_READABLE); return TRUE; } static gboolean write_cb (GIOChannel *source, GIOCondition condition, gpointer data) { SeatWeston *seat = data; wl_display_iterate (seat->priv->display, WL_DISPLAY_WRITABLE); return (seat->priv->mask & WL_DISPLAY_WRITABLE) != 0; } static int update_mask_cb (uint32_t mask, void *data) { SeatWeston *seat = data; seat->priv->mask = mask; if ((seat->priv->mask & WL_DISPLAY_WRITABLE) != 0) g_io_add_watch (seat->priv->channel, G_IO_OUT, write_cb, seat); return 0; } static void key_cb (void *data, struct wl_display_manager *display_manager, uint32_t cookie) { vt_set_active (cookie); } static void wayland_cb (struct wl_display *display, uint32_t id, const char *interface, uint32_t version, void *data) { SeatWeston *seat = data; if (g_strcmp0 (interface, "wl_display_manager") == 0) { static struct wl_display_manager_listener listener = { key_cb }; g_debug ("Compositor indicates it is ready"); if (seat->priv->stopping_plymouth) { g_debug ("Stopping Plymouth, compisitor is ready"); plymouth_quit (TRUE); seat->priv->stopping_plymouth = FALSE; } seat->priv->display_manager = wl_display_bind (display, id, &wl_display_manager_interface); wl_display_manager_add_listener (seat->priv->display_manager, &listener, seat); /* Handle keys for VT switching */ // FIXME: 1 | 2 is MODIFIER_CTRL | MODIFIER_ALT in weston language wl_display_manager_bind_key (seat->priv->display_manager, KEY_F1, 1 | 2, 1); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F2, 1 | 2, 2); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F3, 1 | 2, 3); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F4, 1 | 2, 4); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F5, 1 | 2, 5); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F6, 1 | 2, 6); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F7, 1 | 2, 7); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F8, 1 | 2, 8); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F9, 1 | 2, 9); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F10, 1 | 2, 10); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F11, 1 | 2, 11); wl_display_manager_bind_key (seat->priv->display_manager, KEY_F11, 1 | 2, 12); SEAT_CLASS (seat_weston_parent_class)->start (SEAT (seat)); } } static void compositor_run_cb (Process *process, SeatWeston *seat) { int fd; /* Make input non-blocking */ fd = open ("/dev/null", O_RDONLY); dup2 (fd, STDIN_FILENO); close (fd); /* Redirect output to logfile */ if (seat->priv->log_file) { int fd; fd = g_open (seat->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) g_warning ("Failed to open log file %s: %s", seat->priv->log_file, g_strerror (errno)); else { dup2 (fd, STDOUT_FILENO); dup2 (fd, STDERR_FILENO); close (fd); } } /* Ensure that XDG_RUNTIME_DIR is defined */ g_setenv ("XDG_RUNTIME_DIR", "/tmp", FALSE); } static gboolean seat_weston_start (Seat *seat) { gchar *command, *dir; int socket_fds[2], child_fd; gboolean result; /* Replace Plymouth if it is running */ if (plymouth_get_is_active () && plymouth_has_active_vt ()) { gint active_vt = vt_get_active (); if (active_vt >= vt_get_min ()) { g_debug ("Compiositor will replace Plymouth"); SEAT_WESTON (seat)->priv->vt = active_vt; plymouth_deactivate (); } else 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 ()); } if (SEAT_WESTON (seat)->priv->vt < 0) SEAT_WESTON (seat)->priv->vt = vt_get_unused (); if (SEAT_WESTON (seat)->priv->vt < 0) { g_debug ("Failed to get a VT to run on"); return FALSE; } vt_ref (SEAT_WESTON (seat)->priv->vt); if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds) == -1) { g_debug ("Failed to create compositor sockets: %s", g_strerror (errno)); return FALSE; } child_fd = dup (socket_fds[0]); if (child_fd < 0) return FALSE; close (socket_fds[0]); /* Setup logging */ dir = config_get_string (config_get_instance (), "LightDM", "log-directory"); SEAT_WESTON (seat)->priv->log_file = g_build_filename (dir, "weston.log", NULL); g_debug ("Logging to %s", SEAT_WESTON (seat)->priv->log_file); g_free (dir); /* Start the compositor */ command = g_strdup_printf ("weston --shell=system-compositor.so --display-manager-fd=%d --tty=%d", child_fd, SEAT_WESTON (seat)->priv->vt); process_set_command (SEAT_WESTON (seat)->priv->compositor_process, command); g_free (command); g_signal_connect (SEAT_WESTON (seat)->priv->compositor_process, "stopped", G_CALLBACK (compositor_stopped_cb), seat); g_signal_connect (SEAT_WESTON (seat)->priv->compositor_process, "run", G_CALLBACK (compositor_run_cb), seat); result = process_start (SEAT_WESTON (seat)->priv->compositor_process); close (child_fd); if (!result) return FALSE; /* Connect to the compositor */ // NOTE: We have to set the environment variable because that's the way the Wayland API works. // It is unset in the function command = g_strdup_printf ("%d", socket_fds[1]); g_setenv ("WAYLAND_SOCKET", command, TRUE); g_free (command); SEAT_WESTON (seat)->priv->display = wl_display_connect (NULL); wl_display_add_global_listener (SEAT_WESTON (seat)->priv->display, wayland_cb, seat); /* Link Wayland socket into GLib main loop */ SEAT_WESTON (seat)->priv->channel = g_io_channel_unix_new (socket_fds[1]); g_io_add_watch (SEAT_WESTON (seat)->priv->channel, G_IO_IN, read_cb, seat); wl_display_get_fd (SEAT_WESTON (seat)->priv->display, update_mask_cb, seat); return TRUE; } static void client_ready_cb (void *data, struct wl_system_client *client) { SeatWeston *seat = data; g_debug ("XWayland ready, switching compositor to it"); wl_display_manager_switch_to_client (seat->priv->display_manager, client); } static void client_disconnected_cb (void *data, struct wl_system_client *client) { } static DisplayServer * seat_weston_create_display_server (Seat *seat) { XServerLocal *xserver; const gchar *command = NULL, *layout = NULL, *config_file = NULL, *xdmcp_manager = NULL, *key_name = NULL; gboolean allow_tcp; gint port = 0; int socket_fds[2]; struct wl_system_client *client; static struct wl_system_client_listener client_listener = { client_ready_cb, client_disconnected_cb }; g_debug ("Starting X server on weston compositor"); xserver = xserver_local_new (); if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds) == -1) { //perror ("Failed to create compositor sockets"); return FALSE; } xserver_local_set_wayland_socket (xserver, socket_fds[0]); command = seat_get_string_property (seat, "xserver-command"); if (command) xserver_local_set_command (xserver, command); layout = seat_get_string_property (seat, "xserver-layout"); if (layout) xserver_local_set_layout (xserver, layout); config_file = seat_get_string_property (seat, "xserver-config"); if (config_file) xserver_local_set_config (xserver, config_file); allow_tcp = seat_get_boolean_property (seat, "xserver-allow-tcp"); xserver_local_set_allow_tcp (xserver, allow_tcp); xdmcp_manager = seat_get_string_property (seat, "xdmcp-manager"); if (xdmcp_manager) xserver_local_set_xdmcp_server (xserver, xdmcp_manager); port = seat_get_integer_property (seat, "xdmcp-port"); if (port > 0) xserver_local_set_xdmcp_port (xserver, port); key_name = seat_get_string_property (seat, "xdmcp-key"); if (key_name) { gchar *dir, *path; GKeyFile *keys; gboolean result; GError *error = NULL; dir = config_get_string (config_get_instance (), "LightDM", "config-directory"); path = g_build_filename (dir, "keys.conf", NULL); g_free (dir); keys = g_key_file_new (); result = g_key_file_load_from_file (keys, path, G_KEY_FILE_NONE, &error); if (error) g_debug ("Error getting key %s", error->message); g_clear_error (&error); if (result) { gchar *key = NULL; if (g_key_file_has_key (keys, "keyring", key_name, NULL)) key = g_key_file_get_string (keys, "keyring", key_name, NULL); else g_debug ("Key %s not defined", key_name); if (key) xserver_local_set_xdmcp_key (xserver, key); g_free (key); } g_free (path); g_key_file_free (keys); } g_debug ("Adding Wayland client"); client = wl_display_manager_add_client (SEAT_WESTON (seat)->priv->display_manager, socket_fds[1]); wl_system_client_add_listener (client, &client_listener, seat); g_hash_table_insert (SEAT_WESTON (seat)->priv->displays, g_object_ref (xserver), client); return DISPLAY_SERVER (xserver); } static Session * seat_weston_create_session (Seat *seat, Display *display) { XServerLocal *xserver; XSession *session; gchar *tty; xserver = XSERVER_LOCAL (display_get_display_server (display)); session = xsession_new (XSERVER (xserver)); tty = g_strdup_printf ("/dev/tty%d", SEAT_WESTON (seat)->priv->vt); session_set_tty (SESSION (session), tty); g_free (tty); return SESSION (session); } static void seat_weston_set_active_display (Seat *seat, Display *display) { struct wl_system_client *client; client = g_hash_table_lookup (SEAT_WESTON (seat)->priv->displays, display_get_display_server (display)); wl_display_manager_switch_to_client (SEAT_WESTON (seat)->priv->display_manager, client); SEAT_CLASS (seat_weston_parent_class)->set_active_display (seat, display); } static void seat_weston_run_script (Seat *seat, Display *display, Process *script) { gchar *path; XServerLocal *xserver; xserver = XSERVER_LOCAL (display_get_display_server (display)); path = xserver_local_get_authority_file_path (xserver); process_set_env (script, "DISPLAY", xserver_get_address (XSERVER (xserver))); process_set_env (script, "XAUTHORITY", path); g_free (path); SEAT_CLASS (seat_weston_parent_class)->run_script (seat, display, script); } static void seat_weston_display_removed (Seat *seat, Display *display) { if (seat_get_is_stopping (seat)) return; /* If this is the only display and it failed to start then stop this seat */ if (g_list_length (seat_get_displays (seat)) == 0 && !display_get_is_ready (display)) { g_debug ("Stopping Weston seat, failed to start a display"); seat_stop (seat); return; } /* Show a new greeter */ if (display == seat_get_active_display (seat)) { g_debug ("Active display stopped, switching to greeter"); seat_switch_to_greeter (seat); } } static void seat_weston_init (SeatWeston *seat) { seat->priv = G_TYPE_INSTANCE_GET_PRIVATE (seat, SEAT_WESTON_TYPE, SeatWestonPrivate); seat->priv->vt = -1; seat->priv->compositor_process = process_new (); seat->priv->displays = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); } static void seat_weston_finalize (GObject *object) { SeatWeston *seat = SEAT_WESTON (object); if (seat->priv->vt >= 0) vt_unref (seat->priv->vt); g_free (seat->priv->log_file); g_object_unref (seat->priv->compositor_process); g_hash_table_unref (seat->priv->displays); g_io_channel_unref (seat->priv->channel); G_OBJECT_CLASS (seat_weston_parent_class)->finalize (object); } static void seat_weston_class_init (SeatWestonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); SeatClass *seat_class = SEAT_CLASS (klass); object_class->finalize = seat_weston_finalize; seat_class->setup = seat_weston_setup; seat_class->start = seat_weston_start; seat_class->create_display_server = seat_weston_create_display_server; seat_class->create_session = seat_weston_create_session; seat_class->set_active_display = seat_weston_set_active_display; seat_class->run_script = seat_weston_run_script; seat_class->display_removed = seat_weston_display_removed; g_type_class_add_private (klass, sizeof (SeatWestonPrivate)); }