]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-terminal/modeset.c
treewide: use log_*_errno whenever %m is in the format string
[thirdparty/systemd.git] / src / libsystemd-terminal / modeset.c
CommitLineData
810626a8
DH
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
54typedef struct Modeset Modeset;
55
56struct 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
73static 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
82static 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
99DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free);
100
101static 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
138static 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);
23bbb0de
MS
149 if (r < 0)
150 return log_error_errno(r, "Cannot retrieve logind session: %m");
810626a8
DH
151
152 r = sd_session_get_seat(m->session, &m->seat);
23bbb0de
MS
153 if (r < 0)
154 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
810626a8
DH
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
215static 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
229static void modeset_draw(Modeset *m, const grdev_display_target *t) {
230 uint32_t j, k, *b;
231 uint8_t *l;
232
51cff8bd 233 assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
810626a8
DH
234 assert(!t->rotate);
235 assert(!t->flip);
236
51cff8bd 237 l = t->back->maps[0];
810626a8
DH
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
51cff8bd 244 l += t->back->strides[0];
810626a8
DH
245 }
246}
247
248static void modeset_render(Modeset *m, grdev_display *d) {
249 const grdev_display_target *t;
250
39cf40e8
DH
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);
810626a8 254
51cff8bd 255 GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
810626a8 256 modeset_draw(m, t);
66695cc3 257 grdev_display_flip_target(d, t);
810626a8
DH
258 }
259
260 grdev_session_commit(m->grdev_session);
261}
262
263static 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);
810626a8
DH
269 break;
270 case GRDEV_EVENT_DISPLAY_REMOVE:
271 break;
272 case GRDEV_EVENT_DISPLAY_CHANGE:
810626a8
DH
273 break;
274 case GRDEV_EVENT_DISPLAY_FRAME:
275 modeset_render(m, ev->display_frame.display);
276 break;
277 }
278}
279
280static 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);
23bbb0de
MS
308 if (r < 0)
309 return log_error_errno(r, "Cannot create grdev session: %m");
810626a8
DH
310
311 if (m->managed) {
312 r = sysview_session_take_control(ev->session_add.session);
23bbb0de
MS
313 if (r < 0)
314 return log_error_errno(r, "Cannot request session control: %m");
810626a8
DH
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);
c17091b7
DH
327 if (sd_event_get_exit_code(m->event, &r) == -ENODATA)
328 sd_event_exit(m->event, 0);
810626a8
DH
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
f6e3ee14
DH
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
810626a8
DH
350 break;
351 case SYSVIEW_EVENT_SESSION_CONTROL:
352 r = ev->session_control.error;
23bbb0de
MS
353 if (r < 0)
354 return log_error_errno(r, "Cannot acquire session control: %m");
810626a8
DH
355
356 r = ioctl(1, KDSKBMODE, K_UNICODE);
357 if (r < 0) {
56f64d95 358 log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
810626a8
DH
359 return -errno;
360 }
361
362 break;
363 }
364
365 return 0;
366}
367
368static 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
410out:
411 sysview_context_stop(m->sysview);
412 return r;
413}
414
415static 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
425static 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
465int 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
ef309a68 473 initialize_srand();
810626a8
DH
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
485finish:
486 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
487}