chiark / gitweb /
bus-policy: append items rather than prepending them
[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                 log_error("Cannot retrieve logind session: %s", strerror(-r));
151                 return r;
152         }
153
154         r = sd_session_get_seat(m->session, &m->seat);
155         if (r < 0) {
156                 log_error("Cannot retrieve seat of logind session: %s", strerror(-r));
157                 return r;
158         }
159
160         m->my_tty = is_my_tty(m->session);
161         m->managed = m->my_tty && geteuid() > 0;
162
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;
167
168         r = sd_event_default(&m->event);
169         if (r < 0)
170                 return r;
171
172         r = sd_bus_open_system(&m->bus);
173         if (r < 0)
174                 return r;
175
176         r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
177         if (r < 0)
178                 return r;
179
180         r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
181         if (r < 0)
182                 return r;
183
184         r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
185         if (r < 0)
186                 return r;
187
188         r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
189         if (r < 0)
190                 return r;
191
192         r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m);
193         if (r < 0)
194                 return r;
195
196         /* schedule before sd-bus close */
197         r = sd_event_source_set_priority(m->exit_src, -10);
198         if (r < 0)
199                 return r;
200
201         r = sysview_context_new(&m->sysview,
202                                 SYSVIEW_CONTEXT_SCAN_LOGIND |
203                                 SYSVIEW_CONTEXT_SCAN_DRM,
204                                 m->event,
205                                 m->bus,
206                                 NULL);
207         if (r < 0)
208                 return r;
209
210         r = grdev_context_new(&m->grdev, m->event, m->bus);
211         if (r < 0)
212                 return r;
213
214         *out = m;
215         m = NULL;
216         return 0;
217 }
218
219 static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) {
220         uint8_t next;
221
222         /* generate smoothly morphing colors */
223
224         next = cur + (*up ? 1 : -1) * (rand() % mod);
225         if ((*up && next < cur) || (!*up && next > cur)) {
226                 *up = !*up;
227                 next = cur;
228         }
229
230         return next;
231 }
232
233 static void modeset_draw(Modeset *m, const grdev_display_target *t) {
234         uint32_t j, k, *b;
235         uint8_t *l;
236
237         assert(t->fb->format == DRM_FORMAT_XRGB8888 || t->fb->format == DRM_FORMAT_ARGB8888);
238         assert(!t->rotate);
239         assert(!t->flip);
240
241         l = t->fb->maps[0];
242         for (j = 0; j < t->height; ++j) {
243                 for (k = 0; k < t->width; ++k) {
244                         b = (uint32_t*)l;
245                         b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b;
246                 }
247
248                 l += t->fb->strides[0];
249         }
250 }
251
252 static void modeset_render(Modeset *m, grdev_display *d) {
253         const grdev_display_target *t;
254
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);
258
259         GRDEV_DISPLAY_FOREACH_TARGET(d, t, 0) {
260                 modeset_draw(m, t);
261                 grdev_display_flip_target(d, t, 1);
262         }
263
264         grdev_session_commit(m->grdev_session);
265 }
266
267 static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) {
268         Modeset *m = userdata;
269
270         switch (ev->type) {
271         case GRDEV_EVENT_DISPLAY_ADD:
272                 grdev_display_enable(ev->display_add.display);
273                 break;
274         case GRDEV_EVENT_DISPLAY_REMOVE:
275                 break;
276         case GRDEV_EVENT_DISPLAY_CHANGE:
277                 break;
278         case GRDEV_EVENT_DISPLAY_FRAME:
279                 modeset_render(m, ev->display_frame.display);
280                 break;
281         }
282 }
283
284 static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) {
285         unsigned int flags, type;
286         Modeset *m = userdata;
287         sysview_device *d;
288         const char *name;
289         int r;
290
291         switch (ev->type) {
292         case SYSVIEW_EVENT_SESSION_FILTER:
293                 if (streq_ptr(m->session, ev->session_filter.id))
294                         return 1;
295
296                 break;
297         case SYSVIEW_EVENT_SESSION_ADD:
298                 assert(!m->grdev_session);
299
300                 name = sysview_session_get_name(ev->session_add.session);
301                 flags = 0;
302
303                 if (m->managed)
304                         flags |= GRDEV_SESSION_MANAGED;
305
306                 r = grdev_session_new(&m->grdev_session,
307                                       m->grdev,
308                                       flags,
309                                       name,
310                                       modeset_grdev_fn,
311                                       m);
312                 if (r < 0) {
313                         log_error("Cannot create grdev session: %s", strerror(-r));
314                         return r;
315                 }
316
317                 if (m->managed) {
318                         r = sysview_session_take_control(ev->session_add.session);
319                         if (r < 0) {
320                                 log_error("Cannot request session control: %s", strerror(-r));
321                                 return r;
322                         }
323                 }
324
325                 grdev_session_enable(m->grdev_session);
326
327                 break;
328         case SYSVIEW_EVENT_SESSION_REMOVE:
329                 if (!m->grdev_session)
330                         return 0;
331
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);
336                 break;
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));
342
343                 break;
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));
349
350                 break;
351         case SYSVIEW_EVENT_SESSION_CONTROL:
352                 r = ev->session_control.error;
353                 if (r < 0) {
354                         log_error("Cannot acquire session control: %s", strerror(-r));
355                         return r;
356                 }
357
358                 r = ioctl(1, KDSKBMODE, K_UNICODE);
359                 if (r < 0) {
360                         log_error("Cannot set K_UNICODE on stdout: %m");
361                         return -errno;
362                 }
363
364                 break;
365         case SYSVIEW_EVENT_DEVICE_CHANGE:
366                 d = ev->device_change.device;
367                 type = sysview_device_get_type(d);
368                 if (type == SYSVIEW_DEVICE_DRM)
369                         grdev_session_hotplug_drm(m->grdev_session, ev->device_change.ud);
370
371                 break;
372         }
373
374         return 0;
375 }
376
377 static int modeset_run(Modeset *m) {
378         struct termios in_attr, saved_attr;
379         int r;
380
381         assert(m);
382
383         if (!m->my_tty) {
384                 log_warning("You need to run this program on a free VT");
385                 return -EACCES;
386         }
387
388         if (!m->managed && geteuid() > 0)
389                 log_warning("You run in unmanaged mode without being root. This is likely to fail..");
390
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>");
395
396         r = sysview_context_start(m->sysview, modeset_sysview_fn, m);
397         if (r < 0)
398                 goto out;
399
400         r = tcgetattr(0, &in_attr);
401         if (r < 0) {
402                 r = -errno;
403                 goto out;
404         }
405
406         saved_attr = in_attr;
407         in_attr.c_lflag &= ~ECHO;
408
409         r = tcsetattr(0, TCSANOW, &in_attr);
410         if (r < 0) {
411                 r = -errno;
412                 goto out;
413         }
414
415         r = sd_event_loop(m->event);
416         tcsetattr(0, TCSANOW, &saved_attr);
417         printf("exiting..\n");
418
419 out:
420         sysview_context_stop(m->sysview);
421         return r;
422 }
423
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);
430
431         return 0;
432 }
433
434 static int parse_argv(int argc, char *argv[]) {
435         enum {
436                 ARG_VERSION = 0x100,
437         };
438         static const struct option options[] = {
439                 { "help",       no_argument,    NULL, 'h'               },
440                 { "version",    no_argument,    NULL, ARG_VERSION       },
441                 {},
442         };
443         int c;
444
445         assert(argc >= 0);
446         assert(argv);
447
448         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
449                 switch (c) {
450                 case 'h':
451                         help();
452                         return 0;
453
454                 case ARG_VERSION:
455                         puts(PACKAGE_STRING);
456                         puts(SYSTEMD_FEATURES);
457                         return 0;
458
459                 case '?':
460                         return -EINVAL;
461
462                 default:
463                         assert_not_reached("Unhandled option");
464                 }
465
466         if (argc > optind) {
467                 log_error("Too many arguments");
468                 return -EINVAL;
469         }
470
471         return 1;
472 }
473
474 int main(int argc, char *argv[]) {
475         _cleanup_(modeset_freep) Modeset *m = NULL;
476         int r;
477
478         log_set_target(LOG_TARGET_AUTO);
479         log_parse_environment();
480         log_open();
481
482         srand(time(NULL));
483
484         r = parse_argv(argc, argv);
485         if (r <= 0)
486                 goto finish;
487
488         r = modeset_new(&m);
489         if (r < 0)
490                 goto finish;
491
492         r = modeset_run(m);
493
494 finish:
495         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
496 }