diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 14 | ||||
-rw-r--r-- | src/libsystemd-terminal/modeset.c | 491 |
3 files changed, 506 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index f8650870a3..288946029b 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ /systemd-logind /systemd-machine-id-setup /systemd-machined +/systemd-modeset /systemd-modules-load /systemd-multi-seat-x /systemd-networkd diff --git a/Makefile.am b/Makefile.am index be25023c75..f80ffc6749 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2987,6 +2987,7 @@ noinst_LTLIBRARIES += \ noinst_PROGRAMS += \ systemd-evcat \ + systemd-modeset \ systemd-subterm unifontdatadir=$(datadir)/unifont @@ -3045,6 +3046,19 @@ systemd_evcat_LDADD = \ libsystemd-shared.la \ $(TERMINAL_LIBS) +systemd_modeset_CFLAGS = \ + $(AM_CFLAGS) \ + $(TERMINAL_CFLAGS) + +systemd_modeset_SOURCES = \ + src/libsystemd-terminal/modeset.c + +systemd_modeset_LDADD = \ + libsystemd-terminal.la \ + libsystemd-internal.la \ + libsystemd-shared.la \ + $(TERMINAL_LIBS) + systemd_subterm_SOURCES = \ src/libsystemd-terminal/subterm.c diff --git a/src/libsystemd-terminal/modeset.c b/src/libsystemd-terminal/modeset.c new file mode 100644 index 0000000000..02ed1a8987 --- /dev/null +++ b/src/libsystemd-terminal/modeset.c @@ -0,0 +1,491 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* + * Modeset Testing + * The modeset tool attaches to the session of the caller and shows a + * test-pattern on all displays of this session. It is meant as debugging tool + * for the grdev infrastructure. + */ + +#include <drm_fourcc.h> +#include <errno.h> +#include <getopt.h> +#include <linux/kd.h> +#include <linux/vt.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> +#include <systemd/sd-login.h> +#include <termios.h> +#include <unistd.h> +#include "build.h" +#include "bus-util.h" +#include "event-util.h" +#include "grdev.h" +#include "grdev-internal.h" +#include "macro.h" +#include "sysview.h" +#include "util.h" + +typedef struct Modeset Modeset; + +struct Modeset { + char *session; + char *seat; + sd_event *event; + sd_bus *bus; + sd_event_source *exit_src; + sysview_context *sysview; + grdev_context *grdev; + grdev_session *grdev_session; + + uint8_t r, g, b; + bool r_up, g_up, b_up; + + bool my_tty : 1; + bool managed : 1; +}; + +static int modeset_exit_fn(sd_event_source *source, void *userdata) { + Modeset *m = userdata; + + if (m->grdev_session) + grdev_session_restore(m->grdev_session); + + return 0; +} + +static Modeset *modeset_free(Modeset *m) { + if (!m) + return NULL; + + m->grdev_session = grdev_session_free(m->grdev_session); + m->grdev = grdev_context_unref(m->grdev); + m->sysview = sysview_context_free(m->sysview); + m->exit_src = sd_event_source_unref(m->exit_src); + m->bus = sd_bus_unref(m->bus); + m->event = sd_event_unref(m->event); + free(m->seat); + free(m->session); + free(m); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free); + +static bool is_my_tty(const char *session) { + unsigned int vtnr; + struct stat st; + long mode; + int r; + + /* Using logind's Controller API is highly fragile if there is already + * a session controller running. If it is registered as controller + * itself, TakeControl will simply fail. But if its a legacy controller + * that does not use logind's controller API, we must never register + * our own controller. Otherwise, we really mess up the VT. Therefore, + * only run in managed mode if there's no-one else. Furthermore, never + * try to access graphics devices if there's someone else. Unlike input + * devices, graphics devies cannot be shared easily. */ + + if (!isatty(1)) + return false; + + if (!session) + return false; + + r = sd_session_get_vt(session, &vtnr); + if (r < 0 || vtnr < 1 || vtnr > 63) + return false; + + mode = 0; + r = ioctl(1, KDGETMODE, &mode); + if (r < 0 || mode != KD_TEXT) + return false; + + r = fstat(1, &st); + if (r < 0 || minor(st.st_rdev) != vtnr) + return false; + + return true; +} + +static int modeset_new(Modeset **out) { + _cleanup_(modeset_freep) Modeset *m = NULL; + int r; + + assert(out); + + m = new0(Modeset, 1); + if (!m) + return log_oom(); + + r = sd_pid_get_session(getpid(), &m->session); + if (r < 0) { + log_error("Cannot retrieve logind session: %s", strerror(-r)); + return r; + } + + r = sd_session_get_seat(m->session, &m->seat); + if (r < 0) { + log_error("Cannot retrieve seat of logind session: %s", strerror(-r)); + return r; + } + + m->my_tty = is_my_tty(m->session); + m->managed = m->my_tty && geteuid() > 0; + + m->r = rand() % 0xff; + m->g = rand() % 0xff; + m->b = rand() % 0xff; + m->r_up = m->g_up = m->b_up = true; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + r = sd_bus_open_system(&m->bus); + if (r < 0) + return r; + + r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + if (r < 0) + return r; + + r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m); + if (r < 0) + return r; + + /* schedule before sd-bus close */ + r = sd_event_source_set_priority(m->exit_src, -10); + if (r < 0) + return r; + + r = sysview_context_new(&m->sysview, + SYSVIEW_CONTEXT_SCAN_LOGIND | + SYSVIEW_CONTEXT_SCAN_DRM, + m->event, + m->bus, + NULL); + if (r < 0) + return r; + + r = grdev_context_new(&m->grdev, m->event, m->bus); + if (r < 0) + return r; + + *out = m; + m = NULL; + return 0; +} + +static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) { + uint8_t next; + + /* generate smoothly morphing colors */ + + next = cur + (*up ? 1 : -1) * (rand() % mod); + if ((*up && next < cur) || (!*up && next > cur)) { + *up = !*up; + next = cur; + } + + return next; +} + +static void modeset_draw(Modeset *m, const grdev_display_target *t) { + uint32_t j, k, *b; + uint8_t *l; + + assert(t->fb->format == DRM_FORMAT_XRGB8888 || t->fb->format == DRM_FORMAT_ARGB8888); + assert(!t->rotate); + assert(!t->flip); + + l = t->fb->maps[0]; + for (j = 0; j < t->height; ++j) { + for (k = 0; k < t->width; ++k) { + b = (uint32_t*)l; + b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b; + } + + l += t->fb->strides[0]; + } +} + +static void modeset_render(Modeset *m, grdev_display *d) { + const grdev_display_target *t; + + m->r = next_color(&m->r_up, m->r, 20); + m->g = next_color(&m->g_up, m->g, 10); + m->b = next_color(&m->b_up, m->b, 5); + + GRDEV_DISPLAY_FOREACH_TARGET(d, t, 0) { + modeset_draw(m, t); + grdev_display_flip_target(d, t, 1); + } + + grdev_session_commit(m->grdev_session); +} + +static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) { + Modeset *m = userdata; + + switch (ev->type) { + case GRDEV_EVENT_DISPLAY_ADD: + grdev_display_enable(ev->display_add.display); + modeset_render(m, ev->display_add.display); + break; + case GRDEV_EVENT_DISPLAY_REMOVE: + break; + case GRDEV_EVENT_DISPLAY_CHANGE: + modeset_render(m, ev->display_change.display); + break; + case GRDEV_EVENT_DISPLAY_FRAME: + modeset_render(m, ev->display_frame.display); + break; + } +} + +static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { + unsigned int flags, type; + Modeset *m = userdata; + sysview_device *d; + const char *name; + int r; + + switch (ev->type) { + case SYSVIEW_EVENT_SESSION_FILTER: + if (streq_ptr(m->session, ev->session_filter.id)) + return 1; + + break; + case SYSVIEW_EVENT_SESSION_ADD: + assert(!m->grdev_session); + + name = sysview_session_get_name(ev->session_add.session); + flags = 0; + + if (m->managed) + flags |= GRDEV_SESSION_MANAGED; + + r = grdev_session_new(&m->grdev_session, + m->grdev, + flags, + name, + modeset_grdev_fn, + m); + if (r < 0) { + log_error("Cannot create grdev session: %s", strerror(-r)); + return r; + } + + if (m->managed) { + r = sysview_session_take_control(ev->session_add.session); + if (r < 0) { + log_error("Cannot request session control: %s", strerror(-r)); + return r; + } + } + + grdev_session_enable(m->grdev_session); + + break; + case SYSVIEW_EVENT_SESSION_REMOVE: + if (!m->grdev_session) + return 0; + + grdev_session_restore(m->grdev_session); + grdev_session_disable(m->grdev_session); + m->grdev_session = grdev_session_free(m->grdev_session); + sd_event_exit(m->event, 0); + break; + case SYSVIEW_EVENT_SESSION_ATTACH: + d = ev->session_attach.device; + type = sysview_device_get_type(d); + if (type == SYSVIEW_DEVICE_DRM) + grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d)); + + break; + case SYSVIEW_EVENT_SESSION_DETACH: + d = ev->session_detach.device; + type = sysview_device_get_type(d); + if (type == SYSVIEW_DEVICE_DRM) + grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d)); + + break; + case SYSVIEW_EVENT_SESSION_CONTROL: + r = ev->session_control.error; + if (r < 0) { + log_error("Cannot acquire session control: %s", strerror(-r)); + return r; + } + + r = ioctl(1, KDSKBMODE, K_UNICODE); + if (r < 0) { + log_error("Cannot set K_UNICODE on stdout: %m"); + return -errno; + } + + break; + } + + return 0; +} + +static int modeset_run(Modeset *m) { + struct termios in_attr, saved_attr; + int r; + + assert(m); + + if (!m->my_tty) { + log_warning("You need to run this program on a free VT"); + return -EACCES; + } + + if (!m->managed && geteuid() > 0) + log_warning("You run in unmanaged mode without being root. This is likely to fail.."); + + printf("modeset - Show test pattern on selected graphics devices\n" + " Running on seat '%s' in user-session '%s'\n" + " Exit by pressing ^C\n\n", + m->seat ? : "seat0", m->session ? : "<none>"); + + r = sysview_context_start(m->sysview, modeset_sysview_fn, m); + if (r < 0) + goto out; + + r = tcgetattr(0, &in_attr); + if (r < 0) { + r = -errno; + goto out; + } + + saved_attr = in_attr; + in_attr.c_lflag &= ~ECHO; + + r = tcsetattr(0, TCSANOW, &in_attr); + if (r < 0) { + r = -errno; + goto out; + } + + r = sd_event_loop(m->event); + tcsetattr(0, TCSANOW, &saved_attr); + printf("exiting..\n"); + +out: + sysview_context_stop(m->sysview); + return r; +} + +static int help(void) { + printf("%s [OPTIONS...]\n\n" + "Show test pattern on all selected graphics devices.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {}, + }; + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind) { + log_error("Too many arguments"); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_(modeset_freep) Modeset *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + srand(time(NULL)); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = modeset_new(&m); + if (r < 0) + goto finish; + + r = modeset_run(m); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} |