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 log_error("Cannot retrieve logind session: %s", strerror(-r));
154 r = sd_session_get_seat(m->session, &m->seat);
156 log_error("Cannot retrieve seat of logind session: %s", strerror(-r));
160 m->my_tty = is_my_tty(m->session);
161 m->managed = m->my_tty && geteuid() > 0;
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;
168 r = sd_event_default(&m->event);
172 r = sd_bus_open_system(&m->bus);
176 r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
180 r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
184 r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
188 r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
192 r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
196 /* schedule before sd-bus close */
197 r = sd_event_source_set_priority(m->exit_src, -10);
201 r = sysview_context_new(&m->sysview,
202 SYSVIEW_CONTEXT_SCAN_LOGIND |
203 SYSVIEW_CONTEXT_SCAN_DRM,
210 r = grdev_context_new(&m->grdev, m->event, m->bus);
219 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
222 /* generate smoothly morphing colors */
224 next = cur + (*up ? 1 : -1) * (rand() % mod);
225 if ((*up && next < cur) || (!*up && next > cur)) {
233 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
237 assert(t->fb->format == DRM_FORMAT_XRGB8888 || t->fb->format == DRM_FORMAT_ARGB8888);
242 for (j = 0; j < t->height; ++j) {
243 for (k = 0; k < t->width; ++k) {
245 b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
248 l += t->fb->strides[0];
252 static void modeset_render(Modeset *m, grdev_display *d) {
253 const grdev_display_target *t;
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);
259 GRDEV_DISPLAY_FOREACH_TARGET(d, t, 0) {
261 grdev_display_flip_target(d, t, 1);
264 grdev_session_commit(m->grdev_session);
267 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
268 Modeset *m = userdata;
271 case GRDEV_EVENT_DISPLAY_ADD:
272 grdev_display_enable(ev->display_add.display);
274 case GRDEV_EVENT_DISPLAY_REMOVE:
276 case GRDEV_EVENT_DISPLAY_CHANGE:
278 case GRDEV_EVENT_DISPLAY_FRAME:
279 modeset_render(m, ev->display_frame.display);
284 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
285 unsigned int flags, type;
286 Modeset *m = userdata;
292 case SYSVIEW_EVENT_SESSION_FILTER:
293 if (streq_ptr(m->session, ev->session_filter.id))
297 case SYSVIEW_EVENT_SESSION_ADD:
298 assert(!m->grdev_session);
300 name = sysview_session_get_name(ev->session_add.session);
304 flags |= GRDEV_SESSION_MANAGED;
306 r = grdev_session_new(&m->grdev_session,
313 log_error("Cannot create grdev session: %s", strerror(-r));
318 r = sysview_session_take_control(ev->session_add.session);
320 log_error("Cannot request session control: %s", strerror(-r));
325 grdev_session_enable(m->grdev_session);
328 case SYSVIEW_EVENT_SESSION_REMOVE:
329 if (!m->grdev_session)
332 grdev_session_restore(m->grdev_session);
333 grdev_session_disable(m->grdev_session);
334 m->grdev_session = grdev_session_free(m->grdev_session);
335 sd_event_exit(m->event, 0);
337 case SYSVIEW_EVENT_SESSION_ATTACH:
338 d = ev->session_attach.device;
339 type = sysview_device_get_type(d);
340 if (type == SYSVIEW_DEVICE_DRM)
341 grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d));
344 case SYSVIEW_EVENT_SESSION_DETACH:
345 d = ev->session_detach.device;
346 type = sysview_device_get_type(d);
347 if (type == SYSVIEW_DEVICE_DRM)
348 grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d));
351 case SYSVIEW_EVENT_SESSION_REFRESH:
352 d = ev->session_refresh.device;
353 type = sysview_device_get_type(d);
354 if (type == SYSVIEW_DEVICE_DRM)
355 grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud);
358 case SYSVIEW_EVENT_SESSION_CONTROL:
359 r = ev->session_control.error;
361 log_error("Cannot acquire session control: %s", strerror(-r));
365 r = ioctl(1, KDSKBMODE, K_UNICODE);
367 log_error("Cannot set K_UNICODE on stdout: %m");
377 static int modeset_run(Modeset *m) {
378 struct termios in_attr, saved_attr;
384 log_warning("You need to run this program on a free VT");
388 if (!m->managed && geteuid() > 0)
389 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
391 printf("modeset - Show test pattern on selected graphics devices\n"
392 " Running on seat '%s' in user-session '%s'\n"
393 " Exit by pressing ^C\n\n",
394 m->seat ? : "seat0", m->session ? : "<none>");
396 r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
400 r = tcgetattr(0, &in_attr);
406 saved_attr = in_attr;
407 in_attr.c_lflag &= ~ECHO;
409 r = tcsetattr(0, TCSANOW, &in_attr);
415 r = sd_event_loop(m->event);
416 tcsetattr(0, TCSANOW, &saved_attr);
417 printf("exiting..\n");
420 sysview_context_stop(m->sysview);
424 static int help(void) {
425 printf("%s [OPTIONS...]\n\n"
426 "Show test pattern on all selected graphics devices.\n\n"
427 " -h --help Show this help\n"
428 " --version Show package version\n"
429 , program_invocation_short_name);
434 static int parse_argv(int argc, char *argv[]) {
438 static const struct option options[] = {
439 { "help", no_argument, NULL, 'h' },
440 { "version", no_argument, NULL, ARG_VERSION },
448 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
455 puts(PACKAGE_STRING);
456 puts(SYSTEMD_FEATURES);
463 assert_not_reached("Unhandled option");
467 log_error("Too many arguments");
474 int main(int argc, char *argv[]) {
475 _cleanup_(modeset_freep) Modeset *m = NULL;
478 log_set_target(LOG_TARGET_AUTO);
479 log_parse_environment();
484 r = parse_argv(argc, argv);
495 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;