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