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>
36 #include <sys/ioctl.h>
38 #include <systemd/sd-bus.h>
39 #include <systemd/sd-event.h>
40 #include <systemd/sd-login.h>
49 typedef struct Modeset Modeset;
56 sd_event_source *exit_src;
57 sysview_context *sysview;
59 grdev_session *grdev_session;
62 bool r_up, g_up, b_up;
68 static int modeset_exit_fn(sd_event_source *source, void *userdata) {
69 Modeset *m = userdata;
72 grdev_session_restore(m->grdev_session);
77 static Modeset *modeset_free(Modeset *m) {
81 m->grdev_session = grdev_session_free(m->grdev_session);
82 m->grdev = grdev_context_unref(m->grdev);
83 m->sysview = sysview_context_free(m->sysview);
84 m->exit_src = sd_event_source_unref(m->exit_src);
85 m->bus = sd_bus_unref(m->bus);
86 m->event = sd_event_unref(m->event);
94 DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free);
96 static bool is_my_tty(const char *session) {
102 /* Using logind's Controller API is highly fragile if there is already
103 * a session controller running. If it is registered as controller
104 * itself, TakeControl will simply fail. But if its a legacy controller
105 * that does not use logind's controller API, we must never register
106 * our own controller. Otherwise, we really mess up the VT. Therefore,
107 * only run in managed mode if there's no-one else. Furthermore, never
108 * try to access graphics devices if there's someone else. Unlike input
109 * devices, graphics devies cannot be shared easily. */
117 r = sd_session_get_vt(session, &vtnr);
118 if (r < 0 || vtnr < 1 || vtnr > 63)
122 r = ioctl(1, KDGETMODE, &mode);
123 if (r < 0 || mode != KD_TEXT)
127 if (r < 0 || minor(st.st_rdev) != vtnr)
133 static int modeset_new(Modeset **out) {
134 _cleanup_(modeset_freep) Modeset *m = NULL;
139 m = new0(Modeset, 1);
143 r = sd_pid_get_session(getpid(), &m->session);
145 return log_error_errno(r, "Cannot retrieve logind session: %m");
147 r = sd_session_get_seat(m->session, &m->seat);
149 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
151 m->my_tty = is_my_tty(m->session);
152 m->managed = m->my_tty && geteuid() > 0;
154 m->r = rand() % 0xff;
155 m->g = rand() % 0xff;
156 m->b = rand() % 0xff;
157 m->r_up = m->g_up = m->b_up = true;
159 r = sd_event_default(&m->event);
163 r = sd_bus_open_system(&m->bus);
167 r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
171 r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
175 r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
179 r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
183 r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
187 /* schedule before sd-bus close */
188 r = sd_event_source_set_priority(m->exit_src, -10);
192 r = sysview_context_new(&m->sysview,
193 SYSVIEW_CONTEXT_SCAN_LOGIND |
194 SYSVIEW_CONTEXT_SCAN_DRM,
201 r = grdev_context_new(&m->grdev, m->event, m->bus);
210 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
213 /* generate smoothly morphing colors */
215 next = cur + (*up ? 1 : -1) * (rand() % mod);
216 if ((*up && next < cur) || (!*up && next > cur)) {
224 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
228 assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
232 l = t->back->maps[0];
233 for (j = 0; j < t->height; ++j) {
234 for (k = 0; k < t->width; ++k) {
236 b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
239 l += t->back->strides[0];
243 static void modeset_render(Modeset *m, grdev_display *d) {
244 const grdev_display_target *t;
246 m->r = next_color(&m->r_up, m->r, 4);
247 m->g = next_color(&m->g_up, m->g, 3);
248 m->b = next_color(&m->b_up, m->b, 2);
250 GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
252 grdev_display_flip_target(d, t);
255 grdev_session_commit(m->grdev_session);
258 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
259 Modeset *m = userdata;
262 case GRDEV_EVENT_DISPLAY_ADD:
263 grdev_display_enable(ev->display_add.display);
265 case GRDEV_EVENT_DISPLAY_REMOVE:
267 case GRDEV_EVENT_DISPLAY_CHANGE:
269 case GRDEV_EVENT_DISPLAY_FRAME:
270 modeset_render(m, ev->display_frame.display);
275 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
276 unsigned int flags, type;
277 Modeset *m = userdata;
283 case SYSVIEW_EVENT_SESSION_FILTER:
284 if (streq_ptr(m->session, ev->session_filter.id))
288 case SYSVIEW_EVENT_SESSION_ADD:
289 assert(!m->grdev_session);
291 name = sysview_session_get_name(ev->session_add.session);
295 flags |= GRDEV_SESSION_MANAGED;
297 r = grdev_session_new(&m->grdev_session,
304 return log_error_errno(r, "Cannot create grdev session: %m");
307 r = sysview_session_take_control(ev->session_add.session);
309 return log_error_errno(r, "Cannot request session control: %m");
312 grdev_session_enable(m->grdev_session);
315 case SYSVIEW_EVENT_SESSION_REMOVE:
316 if (!m->grdev_session)
319 grdev_session_restore(m->grdev_session);
320 grdev_session_disable(m->grdev_session);
321 m->grdev_session = grdev_session_free(m->grdev_session);
322 if (sd_event_get_exit_code(m->event, &r) == -ENODATA)
323 sd_event_exit(m->event, 0);
325 case SYSVIEW_EVENT_SESSION_ATTACH:
326 d = ev->session_attach.device;
327 type = sysview_device_get_type(d);
328 if (type == SYSVIEW_DEVICE_DRM)
329 grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d));
332 case SYSVIEW_EVENT_SESSION_DETACH:
333 d = ev->session_detach.device;
334 type = sysview_device_get_type(d);
335 if (type == SYSVIEW_DEVICE_DRM)
336 grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d));
339 case SYSVIEW_EVENT_SESSION_REFRESH:
340 d = ev->session_refresh.device;
341 type = sysview_device_get_type(d);
342 if (type == SYSVIEW_DEVICE_DRM)
343 grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud);
346 case SYSVIEW_EVENT_SESSION_CONTROL:
347 r = ev->session_control.error;
349 return log_error_errno(r, "Cannot acquire session control: %m");
351 r = ioctl(1, KDSKBMODE, K_UNICODE);
353 return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
361 static int modeset_run(Modeset *m) {
362 struct termios in_attr, saved_attr;
368 log_warning("You need to run this program on a free VT");
372 if (!m->managed && geteuid() > 0)
373 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
375 printf("modeset - Show test pattern on selected graphics devices\n"
376 " Running on seat '%s' in user-session '%s'\n"
377 " Exit by pressing ^C\n\n",
378 m->seat ? : "seat0", m->session ? : "<none>");
380 r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
384 r = tcgetattr(0, &in_attr);
390 saved_attr = in_attr;
391 in_attr.c_lflag &= ~ECHO;
393 r = tcsetattr(0, TCSANOW, &in_attr);
399 r = sd_event_loop(m->event);
400 tcsetattr(0, TCSANOW, &saved_attr);
401 printf("exiting..\n");
404 sysview_context_stop(m->sysview);
408 static int help(void) {
409 printf("%s [OPTIONS...]\n\n"
410 "Show test pattern on all selected graphics devices.\n\n"
411 " -h --help Show this help\n"
412 " --version Show package version\n"
413 , program_invocation_short_name);
418 static int parse_argv(int argc, char *argv[]) {
422 static const struct option options[] = {
423 { "help", no_argument, NULL, 'h' },
424 { "version", no_argument, NULL, ARG_VERSION },
432 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
439 puts(PACKAGE_STRING);
440 puts(SYSTEMD_FEATURES);
447 assert_not_reached("Unhandled option");
451 log_error("Too many arguments");
458 int main(int argc, char *argv[]) {
459 _cleanup_(modeset_freep) Modeset *m = NULL;
462 log_set_target(LOG_TARGET_AUTO);
463 log_parse_environment();
468 r = parse_argv(argc, argv);
479 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;