1 /*
2  * Copyright (C) 2019 Purism SPC
3  * SPDX-License-Identifier: GPL-3.0+
4  * Author: Guido Günther <agx@sigxcpu.org>
5  */
6 
7 #define G_LOG_DOMAIN "phoc-server"
8 
9 #include "config.h"
10 #include "server.h"
11 
12 #include <errno.h>
13 
14 G_DEFINE_TYPE(PhocServer, phoc_server, G_TYPE_OBJECT);
15 
16 typedef struct {
17   GSource source;
18   struct wl_display *display;
19 } WaylandEventSource;
20 
21 static gboolean
wayland_event_source_prepare(GSource * base,int * timeout)22 wayland_event_source_prepare (GSource *base,
23                               int     *timeout)
24 {
25   WaylandEventSource *source = (WaylandEventSource *)base;
26 
27   *timeout = -1;
28 
29   wl_display_flush_clients (source->display);
30 
31   return FALSE;
32 }
33 
34 static gboolean
wayland_event_source_dispatch(GSource * base,GSourceFunc callback,void * data)35 wayland_event_source_dispatch (GSource     *base,
36                                GSourceFunc callback,
37                                void        *data)
38 {
39   WaylandEventSource *source = (WaylandEventSource *)base;
40   struct wl_event_loop *loop = wl_display_get_event_loop (source->display);
41 
42   wl_event_loop_dispatch (loop, 0);
43 
44   return TRUE;
45 }
46 
47 static GSourceFuncs wayland_event_source_funcs = {
48   wayland_event_source_prepare,
49   NULL,
50   wayland_event_source_dispatch,
51   NULL
52 };
53 
54 static GSource *
wayland_event_source_new(struct wl_display * display)55 wayland_event_source_new (struct wl_display *display)
56 {
57   WaylandEventSource *source;
58   struct wl_event_loop *loop = wl_display_get_event_loop (display);
59 
60   source = (WaylandEventSource *) g_source_new (&wayland_event_source_funcs,
61                                                 sizeof (WaylandEventSource));
62   source->display = display;
63   g_source_add_unix_fd (&source->source,
64                         wl_event_loop_get_fd (loop),
65                         G_IO_IN | G_IO_ERR);
66 
67   return &source->source;
68 }
69 
70 static void
phoc_wayland_init(PhocServer * self)71 phoc_wayland_init (PhocServer *self)
72 {
73   GSource *wayland_event_source;
74 
75   wayland_event_source = wayland_event_source_new (self->wl_display);
76   self->wl_source = g_source_attach (wayland_event_source, NULL);
77 }
78 
79 
80 static void
on_session_exit(GPid pid,gint status,PhocServer * self)81 on_session_exit (GPid pid, gint status, PhocServer *self)
82 {
83   g_autoptr(GError) err = NULL;
84 
85   g_return_if_fail (PHOC_IS_SERVER (self));
86   g_spawn_close_pid (pid);
87   if (g_spawn_check_exit_status (status, &err)) {
88     self->exit_status = 0;
89   } else {
90     if (err->domain ==  G_SPAWN_EXIT_ERROR)
91       self->exit_status = err->code;
92     else
93       g_warning ("Session terminated: %s (%d)", err->message, self->exit_status);
94   }
95   if (!(self->debug_flags & PHOC_SERVER_DEBUG_FLAG_NO_QUIT))
96     g_main_loop_quit (self->mainloop);
97 }
98 
99 
100 static void
on_child_setup(gpointer unused)101 on_child_setup (gpointer unused)
102 {
103   sigset_t mask;
104 
105   /* phoc wants SIGUSR1 blocked due to wlroots/xwayland but we
106      don't want to inherit that to childs */
107   sigemptyset(&mask);
108   sigaddset(&mask, SIGUSR1);
109   sigprocmask(SIG_UNBLOCK, &mask, NULL);
110 }
111 
112 
113 static gboolean
phoc_startup_session_in_idle(PhocServer * self)114 phoc_startup_session_in_idle(PhocServer *self)
115 {
116   GPid pid;
117   g_autoptr(GError) err = NULL;
118   gchar *cmd[] = { "/bin/sh", "-c", self->session, NULL };
119 
120   if (g_spawn_async (NULL, cmd, NULL,
121 		      G_SPAWN_DO_NOT_REAP_CHILD,
122 		      on_child_setup, self, &pid, &err)) {
123     g_child_watch_add (pid, (GChildWatchFunc)on_session_exit, self);
124   } else {
125     g_warning ("Failed to launch session: %s", err->message);
126     g_main_loop_quit (self->mainloop);
127   }
128   return FALSE;
129 }
130 
131 static void
phoc_startup_session(PhocServer * server)132 phoc_startup_session (PhocServer *server)
133 {
134   gint id;
135 
136   id = g_idle_add ((GSourceFunc) phoc_startup_session_in_idle, server);
137   g_source_set_name_by_id (id, "[phoc] phoc_startup_session");
138 }
139 
140 
141 static void
phoc_server_constructed(GObject * object)142 phoc_server_constructed (GObject *object)
143 {
144   PhocServer *self = PHOC_SERVER (object);
145 
146   self->wl_display = wl_display_create();
147   if (self->wl_display == NULL)
148     g_error("Could not create wayland display");
149 
150   self->backend = wlr_backend_autocreate(self->wl_display, NULL);
151   if (self->backend == NULL)
152     g_error("Could not create backend");
153 
154   self->renderer = wlr_backend_get_renderer(self->backend);
155   if (self->renderer == NULL)
156     g_error("Could not create renderer");
157 
158   self->data_device_manager =
159     wlr_data_device_manager_create(self->wl_display);
160   wlr_renderer_init_wl_display(self->renderer, self->wl_display);
161 
162   G_OBJECT_CLASS (phoc_server_parent_class)->constructed (object);
163 }
164 
165 
166 static void
phoc_server_dispose(GObject * object)167 phoc_server_dispose (GObject *object)
168 {
169   PhocServer *self = PHOC_SERVER (object);
170 
171   if (self->backend) {
172     wl_display_destroy_clients (self->wl_display);
173     wlr_backend_destroy(self->backend);
174     self->backend = NULL;
175   }
176 
177   G_OBJECT_CLASS (phoc_server_parent_class)->dispose (object);
178 }
179 
180 static void
phoc_server_finalize(GObject * object)181 phoc_server_finalize (GObject *object)
182 {
183   PhocServer *self = PHOC_SERVER (object);
184 
185   if (self->wl_source) {
186     g_source_remove (self->wl_source);
187     self->wl_source = 0;
188   }
189   g_clear_object (&self->desktop);
190   g_clear_pointer (&self->session, g_free);
191 
192   if (self->inited) {
193     g_unsetenv("WAYLAND_DISPLAY");
194     self->inited = FALSE;
195   }
196 
197   wl_display_destroy (self->wl_display);
198   G_OBJECT_CLASS (phoc_server_parent_class)->finalize (object);
199 }
200 
201 
202 static void
phoc_server_class_init(PhocServerClass * klass)203 phoc_server_class_init (PhocServerClass *klass)
204 {
205   GObjectClass *object_class = G_OBJECT_CLASS (klass);
206 
207   object_class->constructed = phoc_server_constructed;
208   object_class->finalize = phoc_server_finalize;
209   object_class->dispose = phoc_server_dispose;
210 }
211 
212 static void
phoc_server_init(PhocServer * self)213 phoc_server_init (PhocServer *self)
214 {
215 }
216 
217 PhocServer *
phoc_server_get_default(void)218 phoc_server_get_default (void)
219 {
220   static PhocServer *instance;
221 
222   if (G_UNLIKELY (instance == NULL)) {
223     g_debug("Creating server");
224     instance = g_object_new (PHOC_TYPE_SERVER, NULL);
225     g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
226   }
227 
228   return instance;
229 }
230 
231 /**
232  * phoc_server_setup:
233  *
234  * Perform wayland server intialization: parse command line and config,
235  * create the wayland socket, setup env vars.
236  *
237  * Returns: %TRUE on success, %FALSE otherwise
238  */
239 gboolean
phoc_server_setup(PhocServer * self,const char * config_path,const char * session,GMainLoop * mainloop,PhocServerDebugFlags debug_flags)240 phoc_server_setup (PhocServer *self, const char *config_path,
241 		   const char *session, GMainLoop *mainloop,
242 		   PhocServerDebugFlags debug_flags)
243 {
244   g_assert (!self->inited);
245 
246   self->config = roots_config_create(config_path);
247   if (!self->config) {
248     g_warning("Failed to parse config");
249     return FALSE;
250   }
251 
252   self->mainloop = mainloop;
253   self->exit_status = 1;
254   self->desktop = phoc_desktop_new (self->config);
255   self->input = phoc_input_new (self->config);
256   self->session = g_strdup (session);
257   self->mainloop = mainloop;
258   self->debug_flags = debug_flags;
259 
260   const char *socket = wl_display_add_socket_auto(self->wl_display);
261   if (!socket) {
262     g_warning("Unable to open wayland socket: %s", strerror(errno));
263     wlr_backend_destroy(self->backend);
264     return FALSE;
265   }
266 
267   g_print ("Running compositor on wayland display '%s'\n", socket);
268 
269   if (!wlr_backend_start(self->backend)) {
270     g_warning("Failed to start backend");
271     wlr_backend_destroy(self->backend);
272     wl_display_destroy(self->wl_display);
273     return FALSE;
274   }
275 
276   setenv("WAYLAND_DISPLAY", socket, true);
277 #ifdef PHOC_XWAYLAND
278   if (self->desktop->xwayland != NULL) {
279     struct roots_seat *xwayland_seat =
280       phoc_input_get_seat(self->input, ROOTS_CONFIG_DEFAULT_SEAT_NAME);
281     wlr_xwayland_set_seat(self->desktop->xwayland, xwayland_seat->seat);
282   }
283 #endif
284 
285   phoc_wayland_init (self);
286   if (self->session)
287     phoc_startup_session (self);
288 
289   self->inited = TRUE;
290   return TRUE;
291 }
292 
293 /**
294  * phoc_server_get_exit_status:
295  *
296  * Return the session's exit status. This is only meaningful
297  * if the session has ended.
298  *
299  * Returns: The session's exit status.
300  */
301 gint
phoc_server_get_session_exit_status(PhocServer * self)302 phoc_server_get_session_exit_status (PhocServer *self)
303 {
304   return self->exit_status;
305 }
306