chiark / gitweb /
sd-rtnl: introduce sd_rtnl_new_from_netlink
[elogind.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 <linux/vt.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/ioctl.h>
39 #include <sys/stat.h>
40 #include <systemd/sd-bus.h>
41 #include <systemd/sd-event.h>
42 #include <systemd/sd-login.h>
43 #include <termios.h>
44 #include <unistd.h>
45 #include "build.h"
46 #include "bus-util.h"
47 #include "event-util.h"
48 #include "grdev.h"
49 #include "grdev-internal.h"
50 #include "macro.h"
51 #include "sysview.h"
52 #include "util.h"
53
54 typedef struct Modeset Modeset;
55
56 struct Modeset {
57         char *session;
58         char *seat;
59         sd_event *event;
60         sd_bus *bus;
61         sd_event_source *exit_src;
62         sysview_context *sysview;
63         grdev_context *grdev;
64         grdev_session *grdev_session;
65
66         uint8_t r, g, b;
67         bool r_up, g_up, b_up;
68
69         bool my_tty : 1;
70         bool managed : 1;
71 };
72
73 static int modeset_exit_fn(sd_event_source *source, void *userdata) {
74         Modeset *m = userdata;
75
76         if (m->grdev_session)
77                 grdev_session_restore(m->grdev_session);
78
79         return 0;
80 }
81
82 static Modeset *modeset_free(Modeset *m) {
83         if (!m)
84                 return NULL;
85
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);
92         free(m->seat);
93         free(m->session);
94         free(m);
95
96         return NULL;
97 }
98
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free);
100
101 static bool is_my_tty(const char *session) {
102         unsigned int vtnr;
103         struct stat st;
104         long mode;
105         int r;
106
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. */
115
116         if (!isatty(1))
117                 return false;
118
119         if (!session)
120                 return false;
121
122         r = sd_session_get_vt(session, &vtnr);
123         if (r < 0 || vtnr < 1 || vtnr > 63)
124                 return false;
125
126         mode = 0;
127         r = ioctl(1, KDGETMODE, &mode);
128         if (r < 0 || mode != KD_TEXT)
129                 return false;
130
131         r = fstat(1, &st);
132         if (r < 0 || minor(st.st_rdev) != vtnr)
133                 return false;
134
135         return true;
136 }
137
138 static int modeset_new(Modeset **out) {
139         _cleanup_(modeset_freep) Modeset *m = NULL;
140         int r;
141
142         assert(out);
143
144         m = new0(Modeset, 1);
145         if (!m)
146                 return log_oom();
147
148         r = sd_pid_get_session(getpid(), &m->session);
149         if (r < 0)
150                 return log_error_errno(r, "Cannot retrieve logind session: %m");
151
152         r = sd_session_get_seat(m->session, &m->seat);
153         if (r < 0)
154                 return log_error_errno(r, "Cannot retrieve seat of logind session: %m");
155
156         m->my_tty = is_my_tty(m->session);
157         m->managed = m->my_tty && geteuid() > 0;
158
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;
163
164         r = sd_event_default(&m->event);
165         if (r < 0)
166                 return r;
167
168         r = sd_bus_open_system(&m->bus);
169         if (r < 0)
170                 return r;
171
172         r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
173         if (r < 0)
174                 return r;
175
176         r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
177         if (r < 0)
178                 return r;
179
180         r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
181         if (r < 0)
182                 return r;
183
184         r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
185         if (r < 0)
186                 return r;
187
188         r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
189         if (r < 0)
190                 return r;
191
192         /* schedule before sd-bus close */
193         r = sd_event_source_set_priority(m->exit_src, -10);
194         if (r < 0)
195                 return r;
196
197         r = sysview_context_new(&m->sysview,
198                                 SYSVIEW_CONTEXT_SCAN_LOGIND |
199                                 SYSVIEW_CONTEXT_SCAN_DRM,
200                                 m->event,
201                                 m->bus,
202                                 NULL);
203         if (r < 0)
204                 return r;
205
206         r = grdev_context_new(&m->grdev, m->event, m->bus);
207         if (r < 0)
208                 return r;
209
210         *out = m;
211         m = NULL;
212         return 0;
213 }
214
215 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
216         uint8_t next;
217
218         /* generate smoothly morphing colors */
219
220         next = cur + (*up ? 1 : -1) * (rand() % mod);
221         if ((*up && next < cur) || (!*up && next > cur)) {
222                 *up = !*up;
223                 next = cur;
224         }
225
226         return next;
227 }
228
229 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
230         uint32_t j, k, *b;
231         uint8_t *l;
232
233         assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
234         assert(!t->rotate);
235         assert(!t->flip);
236
237         l = t->back->maps[0];
238         for (j = 0; j < t->height; ++j) {
239                 for (k = 0; k < t->width; ++k) {
240                         b = (uint32_t*)l;
241                         b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
242                 }
243
244                 l += t->back->strides[0];
245         }
246 }
247
248 static void modeset_render(Modeset *m, grdev_display *d) {
249         const grdev_display_target *t;
250
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);
254
255         GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
256                 modeset_draw(m, t);
257                 grdev_display_flip_target(d, t);
258         }
259
260         grdev_session_commit(m->grdev_session);
261 }
262
263 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
264         Modeset *m = userdata;
265
266         switch (ev->type) {
267         case GRDEV_EVENT_DISPLAY_ADD:
268                 grdev_display_enable(ev->display_add.display);
269                 break;
270         case GRDEV_EVENT_DISPLAY_REMOVE:
271                 break;
272         case GRDEV_EVENT_DISPLAY_CHANGE:
273                 break;
274         case GRDEV_EVENT_DISPLAY_FRAME:
275                 modeset_render(m, ev->display_frame.display);
276                 break;
277         }
278 }
279
280 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
281         unsigned int flags, type;
282         Modeset *m = userdata;
283         sysview_device *d;
284         const char *name;
285         int r;
286
287         switch (ev->type) {
288         case SYSVIEW_EVENT_SESSION_FILTER:
289                 if (streq_ptr(m->session, ev->session_filter.id))
290                         return 1;
291
292                 break;
293         case SYSVIEW_EVENT_SESSION_ADD:
294                 assert(!m->grdev_session);
295
296                 name = sysview_session_get_name(ev->session_add.session);
297                 flags = 0;
298
299                 if (m->managed)
300                         flags |= GRDEV_SESSION_MANAGED;
301
302                 r = grdev_session_new(&m->grdev_session,
303                                       m->grdev,
304                                       flags,
305                                       name,
306                                       modeset_grdev_fn,
307                                       m);
308                 if (r < 0)
309                         return log_error_errno(r, "Cannot create grdev session: %m");
310
311                 if (m->managed) {
312                         r = sysview_session_take_control(ev->session_add.session);
313                         if (r < 0)
314                                 return log_error_errno(r, "Cannot request session control: %m");
315                 }
316
317                 grdev_session_enable(m->grdev_session);
318
319                 break;
320         case SYSVIEW_EVENT_SESSION_REMOVE:
321                 if (!m->grdev_session)
322                         return 0;
323
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);
329                 break;
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));
335
336                 break;
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));
342
343                 break;
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);
349
350                 break;
351         case SYSVIEW_EVENT_SESSION_CONTROL:
352                 r = ev->session_control.error;
353                 if (r < 0)
354                         return log_error_errno(r, "Cannot acquire session control: %m");
355
356                 r = ioctl(1, KDSKBMODE, K_UNICODE);
357                 if (r < 0)
358                         return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m");
359
360                 break;
361         }
362
363         return 0;
364 }
365
366 static int modeset_run(Modeset *m) {
367         struct termios in_attr, saved_attr;
368         int r;
369
370         assert(m);
371
372         if (!m->my_tty) {
373                 log_warning("You need to run this program on a free VT");
374                 return -EACCES;
375         }
376
377         if (!m->managed && geteuid() > 0)
378                 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
379
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>");
384
385         r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
386         if (r < 0)
387                 goto out;
388
389         r = tcgetattr(0, &in_attr);
390         if (r < 0) {
391                 r = -errno;
392                 goto out;
393         }
394
395         saved_attr = in_attr;
396         in_attr.c_lflag &= ~ECHO;
397
398         r = tcsetattr(0, TCSANOW, &in_attr);
399         if (r < 0) {
400                 r = -errno;
401                 goto out;
402         }
403
404         r = sd_event_loop(m->event);
405         tcsetattr(0, TCSANOW, &saved_attr);
406         printf("exiting..\n");
407
408 out:
409         sysview_context_stop(m->sysview);
410         return r;
411 }
412
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);
419
420         return 0;
421 }
422
423 static int parse_argv(int argc, char *argv[]) {
424         enum {
425                 ARG_VERSION = 0x100,
426         };
427         static const struct option options[] = {
428                 { "help",       no_argument,    NULL, 'h'               },
429                 { "version",    no_argument,    NULL, ARG_VERSION       },
430                 {},
431         };
432         int c;
433
434         assert(argc >= 0);
435         assert(argv);
436
437         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
438                 switch (c) {
439                 case 'h':
440                         help();
441                         return 0;
442
443                 case ARG_VERSION:
444                         puts(PACKAGE_STRING);
445                         puts(SYSTEMD_FEATURES);
446                         return 0;
447
448                 case '?':
449                         return -EINVAL;
450
451                 default:
452                         assert_not_reached("Unhandled option");
453                 }
454
455         if (argc > optind) {
456                 log_error("Too many arguments");
457                 return -EINVAL;
458         }
459
460         return 1;
461 }
462
463 int main(int argc, char *argv[]) {
464         _cleanup_(modeset_freep) Modeset *m = NULL;
465         int r;
466
467         log_set_target(LOG_TARGET_AUTO);
468         log_parse_environment();
469         log_open();
470
471         initialize_srand();
472
473         r = parse_argv(argc, argv);
474         if (r <= 0)
475                 goto finish;
476
477         r = modeset_new(&m);
478         if (r < 0)
479                 goto finish;
480
481         r = modeset_run(m);
482
483 finish:
484         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
485 }