chiark / gitweb /
importd: add new bus calls for importing local tar and raw images
[elogind.git] / src / libsystemd-terminal / modeset.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 /*
23  * Modeset Testing
24  * The modeset tool attaches to the session of the caller and shows a
25  * test-pattern on all displays of this session. It is meant as debugging tool
26  * for the grdev infrastructure.
27  */
28
29 #include <drm_fourcc.h>
30 #include <errno.h>
31 #include <getopt.h>
32 #include <linux/kd.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <sys/ioctl.h>
37 #include <sys/stat.h>
38 #include <systemd/sd-bus.h>
39 #include <systemd/sd-event.h>
40 #include <systemd/sd-login.h>
41 #include <termios.h>
42 #include <unistd.h>
43 #include "build.h"
44 #include "grdev.h"
45 #include "macro.h"
46 #include "sysview.h"
47 #include "util.h"
48
49 typedef struct Modeset Modeset;
50
51 struct Modeset {
52         char *session;
53         char *seat;
54         sd_event *event;
55         sd_bus *bus;
56         sd_event_source *exit_src;
57         sysview_context *sysview;
58         grdev_context *grdev;
59         grdev_session *grdev_session;
60
61         uint8_t r, g, b;
62         bool r_up, g_up, b_up;
63
64         bool my_tty : 1;
65         bool managed : 1;
66 };
67
68 static int modeset_exit_fn(sd_event_source *source, void *userdata) {
69         Modeset *m = userdata;
70
71         if (m->grdev_session)
72                 grdev_session_restore(m->grdev_session);
73
74         return 0;
75 }
76
77 static Modeset *modeset_free(Modeset *m) {
78         if (!m)
79                 return NULL;
80
81         m->grdev_session = grdev_session_free(m->grdev_session);
82         m->grdev = grdev_context_unref(m->grdev);
83         m->sysview = sysview_context_free(m->sysview);
84         m->exit_src = sd_event_source_unref(m->exit_src);
85         m->bus = sd_bus_unref(m->bus);
86         m->event = sd_event_unref(m->event);
87         free(m->seat);
88         free(m->session);
89         free(m);
90
91         return NULL;
92 }
93
94 DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free);
95
96 static bool is_my_tty(const char *session) {
97         unsigned int vtnr;
98         struct stat st;
99         long mode;
100         int r;
101
102         /* Using logind's Controller API is highly fragile if there is already
103          * a session controller running. If it is registered as controller
104          * itself, TakeControl will simply fail. But if its a legacy controller
105          * that does not use logind's controller API, we must never register
106          * our own controller. Otherwise, we really mess up the VT. Therefore,
107          * only run in managed mode if there's no-one else.  Furthermore, never
108          * try to access graphics devices if there's someone else. Unlike input
109          * devices, graphics devies cannot be shared easily. */
110
111         if (!isatty(1))
112                 return false;
113
114         if (!session)
115                 return false;
116
117         r = sd_session_get_vt(session, &vtnr);
118         if (r < 0 || vtnr < 1 || vtnr > 63)
119                 return false;
120
121         mode = 0;
122         r = ioctl(1, KDGETMODE, &mode);
123         if (r < 0 || mode != KD_TEXT)
124                 return false;
125
126         r = fstat(1, &st);
127         if (r < 0 || minor(st.st_rdev) != vtnr)
128                 return false;
129
130         return true;
131 }
132
133 static int modeset_new(Modeset **out) {
134         _cleanup_(modeset_freep) Modeset *m = NULL;
135         int r;
136
137         assert(out);
138
139         m = new0(Modeset, 1);
140         if (!m)
141                 return log_oom();
142
143         r = sd_pid_get_session(getpid(), &m->session);
144         if (r < 0)
145                 return log_error_errno(r, "Cannot retrieve logind session: %m");
146
147         r = sd_session_get_seat(m->session, &m->seat);
148         if (r < 0)
149                 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
150
151         m->my_tty = is_my_tty(m->session);
152         m->managed = m->my_tty && geteuid() > 0;
153
154         m->r = rand() % 0xff;
155         m->g = rand() % 0xff;
156         m->b = rand() % 0xff;
157         m->r_up = m->g_up = m->b_up = true;
158
159         r = sd_event_default(&m->event);
160         if (r < 0)
161                 return r;
162
163         r = sd_bus_open_system(&m->bus);
164         if (r < 0)
165                 return r;
166
167         r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
168         if (r < 0)
169                 return r;
170
171         r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
172         if (r < 0)
173                 return r;
174
175         r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
176         if (r < 0)
177                 return r;
178
179         r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
180         if (r < 0)
181                 return r;
182
183         r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
184         if (r < 0)
185                 return r;
186
187         /* schedule before sd-bus close */
188         r = sd_event_source_set_priority(m->exit_src, -10);
189         if (r < 0)
190                 return r;
191
192         r = sysview_context_new(&m->sysview,
193                                 SYSVIEW_CONTEXT_SCAN_LOGIND |
194                                 SYSVIEW_CONTEXT_SCAN_DRM,
195                                 m->event,
196                                 m->bus,
197                                 NULL);
198         if (r < 0)
199                 return r;
200
201         r = grdev_context_new(&m->grdev, m->event, m->bus);
202         if (r < 0)
203                 return r;
204
205         *out = m;
206         m = NULL;
207         return 0;
208 }
209
210 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
211         uint8_t next;
212
213         /* generate smoothly morphing colors */
214
215         next = cur + (*up ? 1 : -1) * (rand() % mod);
216         if ((*up && next < cur) || (!*up && next > cur)) {
217                 *up = !*up;
218                 next = cur;
219         }
220
221         return next;
222 }
223
224 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
225         uint32_t j, k, *b;
226         uint8_t *l;
227
228         assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
229         assert(!t->rotate);
230         assert(!t->flip);
231
232         l = t->back->maps[0];
233         for (j = 0; j < t->height; ++j) {
234                 for (k = 0; k < t->width; ++k) {
235                         b = (uint32_t*)l;
236                         b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
237                 }
238
239                 l += t->back->strides[0];
240         }
241 }
242
243 static void modeset_render(Modeset *m, grdev_display *d) {
244         const grdev_display_target *t;
245
246         m->r = next_color(&m->r_up, m->r, 4);
247         m->g = next_color(&m->g_up, m->g, 3);
248         m->b = next_color(&m->b_up, m->b, 2);
249
250         GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
251                 modeset_draw(m, t);
252                 grdev_display_flip_target(d, t);
253         }
254
255         grdev_session_commit(m->grdev_session);
256 }
257
258 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
259         Modeset *m = userdata;
260
261         switch (ev->type) {
262         case GRDEV_EVENT_DISPLAY_ADD:
263                 grdev_display_enable(ev->display_add.display);
264                 break;
265         case GRDEV_EVENT_DISPLAY_REMOVE:
266                 break;
267         case GRDEV_EVENT_DISPLAY_CHANGE:
268                 break;
269         case GRDEV_EVENT_DISPLAY_FRAME:
270                 modeset_render(m, ev->display_frame.display);
271                 break;
272         }
273 }
274
275 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
276         unsigned int flags, type;
277         Modeset *m = userdata;
278         sysview_device *d;
279         const char *name;
280         int r;
281
282         switch (ev->type) {
283         case SYSVIEW_EVENT_SESSION_FILTER:
284                 if (streq_ptr(m->session, ev->session_filter.id))
285                         return 1;
286
287                 break;
288         case SYSVIEW_EVENT_SESSION_ADD:
289                 assert(!m->grdev_session);
290
291                 name = sysview_session_get_name(ev->session_add.session);
292                 flags = 0;
293
294                 if (m->managed)
295                         flags |= GRDEV_SESSION_MANAGED;
296
297                 r = grdev_session_new(&m->grdev_session,
298                                       m->grdev,
299                                       flags,
300                                       name,
301                                       modeset_grdev_fn,
302                                       m);
303                 if (r < 0)
304                         return log_error_errno(r, "Cannot create grdev session: %m");
305
306                 if (m->managed) {
307                         r = sysview_session_take_control(ev->session_add.session);
308                         if (r < 0)
309                                 return log_error_errno(r, "Cannot request session control: %m");
310                 }
311
312                 grdev_session_enable(m->grdev_session);
313
314                 break;
315         case SYSVIEW_EVENT_SESSION_REMOVE:
316                 if (!m->grdev_session)
317                         return 0;
318
319                 grdev_session_restore(m->grdev_session);
320                 grdev_session_disable(m->grdev_session);
321                 m->grdev_session = grdev_session_free(m->grdev_session);
322                 if (sd_event_get_exit_code(m->event, &r) == -ENODATA)
323                         sd_event_exit(m->event, 0);
324                 break;
325         case SYSVIEW_EVENT_SESSION_ATTACH:
326                 d = ev->session_attach.device;
327                 type = sysview_device_get_type(d);
328                 if (type == SYSVIEW_DEVICE_DRM)
329                         grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d));
330
331                 break;
332         case SYSVIEW_EVENT_SESSION_DETACH:
333                 d = ev->session_detach.device;
334                 type = sysview_device_get_type(d);
335                 if (type == SYSVIEW_DEVICE_DRM)
336                         grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d));
337
338                 break;
339         case SYSVIEW_EVENT_SESSION_REFRESH:
340                 d = ev->session_refresh.device;
341                 type = sysview_device_get_type(d);
342                 if (type == SYSVIEW_DEVICE_DRM)
343                         grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud);
344
345                 break;
346         case SYSVIEW_EVENT_SESSION_CONTROL:
347                 r = ev->session_control.error;
348                 if (r < 0)
349                         return log_error_errno(r, "Cannot acquire session control: %m");
350
351                 r = ioctl(1, KDSKBMODE, K_UNICODE);
352                 if (r < 0)
353                         return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
354
355                 break;
356         }
357
358         return 0;
359 }
360
361 static int modeset_run(Modeset *m) {
362         struct termios in_attr, saved_attr;
363         int r;
364
365         assert(m);
366
367         if (!m->my_tty) {
368                 log_warning("You need to run this program on a free VT");
369                 return -EACCES;
370         }
371
372         if (!m->managed && geteuid() > 0)
373                 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
374
375         printf("modeset - Show test pattern on selected graphics devices\n"
376                "          Running on seat '%s' in user-session '%s'\n"
377                "          Exit by pressing ^C\n\n",
378                m->seat ? : "seat0", m->session ? : "<none>");
379
380         r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
381         if (r < 0)
382                 goto out;
383
384         r = tcgetattr(0, &in_attr);
385         if (r < 0) {
386                 r = -errno;
387                 goto out;
388         }
389
390         saved_attr = in_attr;
391         in_attr.c_lflag &= ~ECHO;
392
393         r = tcsetattr(0, TCSANOW, &in_attr);
394         if (r < 0) {
395                 r = -errno;
396                 goto out;
397         }
398
399         r = sd_event_loop(m->event);
400         tcsetattr(0, TCSANOW, &saved_attr);
401         printf("exiting..\n");
402
403 out:
404         sysview_context_stop(m->sysview);
405         return r;
406 }
407
408 static int help(void) {
409         printf("%s [OPTIONS...]\n\n"
410                "Show test pattern on all selected graphics devices.\n\n"
411                "  -h --help               Show this help\n"
412                "     --version            Show package version\n"
413                , program_invocation_short_name);
414
415         return 0;
416 }
417
418 static int parse_argv(int argc, char *argv[]) {
419         enum {
420                 ARG_VERSION = 0x100,
421         };
422         static const struct option options[] = {
423                 { "help",       no_argument,    NULL, 'h'               },
424                 { "version",    no_argument,    NULL, ARG_VERSION       },
425                 {},
426         };
427         int c;
428
429         assert(argc >= 0);
430         assert(argv);
431
432         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
433                 switch (c) {
434                 case 'h':
435                         help();
436                         return 0;
437
438                 case ARG_VERSION:
439                         puts(PACKAGE_STRING);
440                         puts(SYSTEMD_FEATURES);
441                         return 0;
442
443                 case '?':
444                         return -EINVAL;
445
446                 default:
447                         assert_not_reached("Unhandled option");
448                 }
449
450         if (argc > optind) {
451                 log_error("Too many arguments");
452                 return -EINVAL;
453         }
454
455         return 1;
456 }
457
458 int main(int argc, char *argv[]) {
459         _cleanup_(modeset_freep) Modeset *m = NULL;
460         int r;
461
462         log_set_target(LOG_TARGET_AUTO);
463         log_parse_environment();
464         log_open();
465
466         initialize_srand();
467
468         r = parse_argv(argc, argv);
469         if (r <= 0)
470                 goto finish;
471
472         r = modeset_new(&m);
473         if (r < 0)
474                 goto finish;
475
476         r = modeset_run(m);
477
478 finish:
479         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
480 }