chiark / gitweb /
treewide: use log_*_errno whenever %m is in the format string
[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                 return log_error_errno(r, "Cannot retrieve logind session: %m");
151
152         r = sd_session_get_seat(m->session, &m->seat);
153         if (r < 0)
154                 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
155
156         m->my_tty = is_my_tty(m->session);
157         m->managed = m->my_tty && geteuid() > 0;
158
159         m->r = rand() % 0xff;
160         m->g = rand() % 0xff;
161         m->b = rand() % 0xff;
162         m->r_up = m->g_up = m->b_up = true;
163
164         r = sd_event_default(&m->event);
165         if (r < 0)
166                 return r;
167
168         r = sd_bus_open_system(&m->bus);
169         if (r < 0)
170                 return r;
171
172         r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
173         if (r < 0)
174                 return r;
175
176         r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
177         if (r < 0)
178                 return r;
179
180         r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
181         if (r < 0)
182                 return r;
183
184         r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
185         if (r < 0)
186                 return r;
187
188         r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
189         if (r < 0)
190                 return r;
191
192         /* schedule before sd-bus close */
193         r = sd_event_source_set_priority(m->exit_src, -10);
194         if (r < 0)
195                 return r;
196
197         r = sysview_context_new(&m->sysview,
198                                 SYSVIEW_CONTEXT_SCAN_LOGIND |
199                                 SYSVIEW_CONTEXT_SCAN_DRM,
200                                 m->event,
201                                 m->bus,
202                                 NULL);
203         if (r < 0)
204                 return r;
205
206         r = grdev_context_new(&m->grdev, m->event, m->bus);
207         if (r < 0)
208                 return r;
209
210         *out = m;
211         m = NULL;
212         return 0;
213 }
214
215 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
216         uint8_t next;
217
218         /* generate smoothly morphing colors */
219
220         next = cur + (*up ? 1 : -1) * (rand() % mod);
221         if ((*up && next < cur) || (!*up && next > cur)) {
222                 *up = !*up;
223                 next = cur;
224         }
225
226         return next;
227 }
228
229 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
230         uint32_t j, k, *b;
231         uint8_t *l;
232
233         assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
234         assert(!t->rotate);
235         assert(!t->flip);
236
237         l = t->back->maps[0];
238         for (j = 0; j < t->height; ++j) {
239                 for (k = 0; k < t->width; ++k) {
240                         b = (uint32_t*)l;
241                         b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
242                 }
243
244                 l += t->back->strides[0];
245         }
246 }
247
248 static void modeset_render(Modeset *m, grdev_display *d) {
249         const grdev_display_target *t;
250
251         m->r = next_color(&m->r_up, m->r, 4);
252         m->g = next_color(&m->g_up, m->g, 3);
253         m->b = next_color(&m->b_up, m->b, 2);
254
255         GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
256                 modeset_draw(m, t);
257                 grdev_display_flip_target(d, t);
258         }
259
260         grdev_session_commit(m->grdev_session);
261 }
262
263 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
264         Modeset *m = userdata;
265
266         switch (ev->type) {
267         case GRDEV_EVENT_DISPLAY_ADD:
268                 grdev_display_enable(ev->display_add.display);
269                 break;
270         case GRDEV_EVENT_DISPLAY_REMOVE:
271                 break;
272         case GRDEV_EVENT_DISPLAY_CHANGE:
273                 break;
274         case GRDEV_EVENT_DISPLAY_FRAME:
275                 modeset_render(m, ev->display_frame.display);
276                 break;
277         }
278 }
279
280 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
281         unsigned int flags, type;
282         Modeset *m = userdata;
283         sysview_device *d;
284         const char *name;
285         int r;
286
287         switch (ev->type) {
288         case SYSVIEW_EVENT_SESSION_FILTER:
289                 if (streq_ptr(m->session, ev->session_filter.id))
290                         return 1;
291
292                 break;
293         case SYSVIEW_EVENT_SESSION_ADD:
294                 assert(!m->grdev_session);
295
296                 name = sysview_session_get_name(ev->session_add.session);
297                 flags = 0;
298
299                 if (m->managed)
300                         flags |= GRDEV_SESSION_MANAGED;
301
302                 r = grdev_session_new(&m->grdev_session,
303                                       m->grdev,
304                                       flags,
305                                       name,
306                                       modeset_grdev_fn,
307                                       m);
308                 if (r < 0)
309                         return log_error_errno(r, "Cannot create grdev session: %m");
310
311                 if (m->managed) {
312                         r = sysview_session_take_control(ev->session_add.session);
313                         if (r < 0)
314                                 return log_error_errno(r, "Cannot request session control: %m");
315                 }
316
317                 grdev_session_enable(m->grdev_session);
318
319                 break;
320         case SYSVIEW_EVENT_SESSION_REMOVE:
321                 if (!m->grdev_session)
322                         return 0;
323
324                 grdev_session_restore(m->grdev_session);
325                 grdev_session_disable(m->grdev_session);
326                 m->grdev_session = grdev_session_free(m->grdev_session);
327                 if (sd_event_get_exit_code(m->event, &r) == -ENODATA)
328                         sd_event_exit(m->event, 0);
329                 break;
330         case SYSVIEW_EVENT_SESSION_ATTACH:
331                 d = ev->session_attach.device;
332                 type = sysview_device_get_type(d);
333                 if (type == SYSVIEW_DEVICE_DRM)
334                         grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d));
335
336                 break;
337         case SYSVIEW_EVENT_SESSION_DETACH:
338                 d = ev->session_detach.device;
339                 type = sysview_device_get_type(d);
340                 if (type == SYSVIEW_DEVICE_DRM)
341                         grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d));
342
343                 break;
344         case SYSVIEW_EVENT_SESSION_REFRESH:
345                 d = ev->session_refresh.device;
346                 type = sysview_device_get_type(d);
347                 if (type == SYSVIEW_DEVICE_DRM)
348                         grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud);
349
350                 break;
351         case SYSVIEW_EVENT_SESSION_CONTROL:
352                 r = ev->session_control.error;
353                 if (r < 0)
354                         return log_error_errno(r, "Cannot acquire session control: %m");
355
356                 r = ioctl(1, KDSKBMODE, K_UNICODE);
357                 if (r < 0) {
358                         log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
359                         return -errno;
360                 }
361
362                 break;
363         }
364
365         return 0;
366 }
367
368 static int modeset_run(Modeset *m) {
369         struct termios in_attr, saved_attr;
370         int r;
371
372         assert(m);
373
374         if (!m->my_tty) {
375                 log_warning("You need to run this program on a free VT");
376                 return -EACCES;
377         }
378
379         if (!m->managed && geteuid() > 0)
380                 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
381
382         printf("modeset - Show test pattern on selected graphics devices\n"
383                "          Running on seat '%s' in user-session '%s'\n"
384                "          Exit by pressing ^C\n\n",
385                m->seat ? : "seat0", m->session ? : "<none>");
386
387         r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
388         if (r < 0)
389                 goto out;
390
391         r = tcgetattr(0, &in_attr);
392         if (r < 0) {
393                 r = -errno;
394                 goto out;
395         }
396
397         saved_attr = in_attr;
398         in_attr.c_lflag &= ~ECHO;
399
400         r = tcsetattr(0, TCSANOW, &in_attr);
401         if (r < 0) {
402                 r = -errno;
403                 goto out;
404         }
405
406         r = sd_event_loop(m->event);
407         tcsetattr(0, TCSANOW, &saved_attr);
408         printf("exiting..\n");
409
410 out:
411         sysview_context_stop(m->sysview);
412         return r;
413 }
414
415 static int help(void) {
416         printf("%s [OPTIONS...]\n\n"
417                "Show test pattern on all selected graphics devices.\n\n"
418                "  -h --help               Show this help\n"
419                "     --version            Show package version\n"
420                , program_invocation_short_name);
421
422         return 0;
423 }
424
425 static int parse_argv(int argc, char *argv[]) {
426         enum {
427                 ARG_VERSION = 0x100,
428         };
429         static const struct option options[] = {
430                 { "help",       no_argument,    NULL, 'h'               },
431                 { "version",    no_argument,    NULL, ARG_VERSION       },
432                 {},
433         };
434         int c;
435
436         assert(argc >= 0);
437         assert(argv);
438
439         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
440                 switch (c) {
441                 case 'h':
442                         help();
443                         return 0;
444
445                 case ARG_VERSION:
446                         puts(PACKAGE_STRING);
447                         puts(SYSTEMD_FEATURES);
448                         return 0;
449
450                 case '?':
451                         return -EINVAL;
452
453                 default:
454                         assert_not_reached("Unhandled option");
455                 }
456
457         if (argc > optind) {
458                 log_error("Too many arguments");
459                 return -EINVAL;
460         }
461
462         return 1;
463 }
464
465 int main(int argc, char *argv[]) {
466         _cleanup_(modeset_freep) Modeset *m = NULL;
467         int r;
468
469         log_set_target(LOG_TARGET_AUTO);
470         log_parse_environment();
471         log_open();
472
473         initialize_srand();
474
475         r = parse_argv(argc, argv);
476         if (r <= 0)
477                 goto finish;
478
479         r = modeset_new(&m);
480         if (r < 0)
481                 goto finish;
482
483         r = modeset_run(m);
484
485 finish:
486         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
487 }