]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-terminal/modeset.c
c1119c9e0fde6f366df7b569a563bbff2fbe787b
[thirdparty/systemd.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 }