1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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.
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.
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/>.
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.
29 #include <drm_fourcc.h>
38 #include <sys/ioctl.h>
40 #include <systemd/sd-bus.h>
41 #include <systemd/sd-event.h>
42 #include <systemd/sd-login.h>
47 #include "event-util.h"
49 #include "grdev-internal.h"
54 typedef struct Modeset Modeset;
61 sd_event_source *exit_src;
62 sysview_context *sysview;
64 grdev_session *grdev_session;
67 bool r_up, g_up, b_up;
73 static int modeset_exit_fn(sd_event_source *source, void *userdata) {
74 Modeset *m = userdata;
77 grdev_session_restore(m->grdev_session);
82 static Modeset *modeset_free(Modeset *m) {
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);
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free);
101 static bool is_my_tty(const char *session) {
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. */
122 r = sd_session_get_vt(session, &vtnr);
123 if (r < 0 || vtnr < 1 || vtnr > 63)
127 r = ioctl(1, KDGETMODE, &mode);
128 if (r < 0 || mode != KD_TEXT)
132 if (r < 0 || minor(st.st_rdev) != vtnr)
138 static int modeset_new(Modeset **out) {
139 _cleanup_(modeset_freep) Modeset *m = NULL;
144 m = new0(Modeset, 1);
148 r = sd_pid_get_session(getpid(), &m->session);
150 return log_error_errno(r, "Cannot retrieve logind session: %m");
152 r = sd_session_get_seat(m->session, &m->seat);
154 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
156 m->my_tty = is_my_tty(m->session);
157 m->managed = m->my_tty && geteuid() > 0;
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;
164 r = sd_event_default(&m->event);
168 r = sd_bus_open_system(&m->bus);
172 r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
176 r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
180 r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
184 r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
188 r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
192 /* schedule before sd-bus close */
193 r = sd_event_source_set_priority(m->exit_src, -10);
197 r = sysview_context_new(&m->sysview,
198 SYSVIEW_CONTEXT_SCAN_LOGIND |
199 SYSVIEW_CONTEXT_SCAN_DRM,
206 r = grdev_context_new(&m->grdev, m->event, m->bus);
215 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
218 /* generate smoothly morphing colors */
220 next = cur + (*up ? 1 : -1) * (rand() % mod);
221 if ((*up && next < cur) || (!*up && next > cur)) {
229 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
233 assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
237 l = t->back->maps[0];
238 for (j = 0; j < t->height; ++j) {
239 for (k = 0; k < t->width; ++k) {
241 b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
244 l += t->back->strides[0];
248 static void modeset_render(Modeset *m, grdev_display *d) {
249 const grdev_display_target *t;
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);
255 GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
257 grdev_display_flip_target(d, t);
260 grdev_session_commit(m->grdev_session);
263 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
264 Modeset *m = userdata;
267 case GRDEV_EVENT_DISPLAY_ADD:
268 grdev_display_enable(ev->display_add.display);
270 case GRDEV_EVENT_DISPLAY_REMOVE:
272 case GRDEV_EVENT_DISPLAY_CHANGE:
274 case GRDEV_EVENT_DISPLAY_FRAME:
275 modeset_render(m, ev->display_frame.display);
280 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
281 unsigned int flags, type;
282 Modeset *m = userdata;
288 case SYSVIEW_EVENT_SESSION_FILTER:
289 if (streq_ptr(m->session, ev->session_filter.id))
293 case SYSVIEW_EVENT_SESSION_ADD:
294 assert(!m->grdev_session);
296 name = sysview_session_get_name(ev->session_add.session);
300 flags |= GRDEV_SESSION_MANAGED;
302 r = grdev_session_new(&m->grdev_session,
309 return log_error_errno(r, "Cannot create grdev session: %m");
312 r = sysview_session_take_control(ev->session_add.session);
314 return log_error_errno(r, "Cannot request session control: %m");
317 grdev_session_enable(m->grdev_session);
320 case SYSVIEW_EVENT_SESSION_REMOVE:
321 if (!m->grdev_session)
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);
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));
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));
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);
351 case SYSVIEW_EVENT_SESSION_CONTROL:
352 r = ev->session_control.error;
354 return log_error_errno(r, "Cannot acquire session control: %m");
356 r = ioctl(1, KDSKBMODE, K_UNICODE);
358 return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
366 static int modeset_run(Modeset *m) {
367 struct termios in_attr, saved_attr;
373 log_warning("You need to run this program on a free VT");
377 if (!m->managed && geteuid() > 0)
378 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
380 printf("modeset - Show test pattern on selected graphics devices\n"
381 " Running on seat '%s' in user-session '%s'\n"
382 " Exit by pressing ^C\n\n",
383 m->seat ? : "seat0", m->session ? : "<none>");
385 r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
389 r = tcgetattr(0, &in_attr);
395 saved_attr = in_attr;
396 in_attr.c_lflag &= ~ECHO;
398 r = tcsetattr(0, TCSANOW, &in_attr);
404 r = sd_event_loop(m->event);
405 tcsetattr(0, TCSANOW, &saved_attr);
406 printf("exiting..\n");
409 sysview_context_stop(m->sysview);
413 static int help(void) {
414 printf("%s [OPTIONS...]\n\n"
415 "Show test pattern on all selected graphics devices.\n\n"
416 " -h --help Show this help\n"
417 " --version Show package version\n"
418 , program_invocation_short_name);
423 static int parse_argv(int argc, char *argv[]) {
427 static const struct option options[] = {
428 { "help", no_argument, NULL, 'h' },
429 { "version", no_argument, NULL, ARG_VERSION },
437 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
444 puts(PACKAGE_STRING);
445 puts(SYSTEMD_FEATURES);
452 assert_not_reached("Unhandled option");
456 log_error("Too many arguments");
463 int main(int argc, char *argv[]) {
464 _cleanup_(modeset_freep) Modeset *m = NULL;
467 log_set_target(LOG_TARGET_AUTO);
468 log_parse_environment();
473 r = parse_argv(argc, argv);
484 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;