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