chiark / gitweb /
ab1c407ecb7a3f40aa5d0c901886499e269cf362
[elogind.git] / src / libsystemd-terminal / grdev.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 #include <inttypes.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <systemd/sd-bus.h>
26 #include <systemd/sd-event.h>
27 #include <systemd/sd-login.h>
28 #include "grdev.h"
29 #include "grdev-internal.h"
30 #include "hashmap.h"
31 #include "login-shared.h"
32 #include "macro.h"
33 #include "util.h"
34
35 static void pipe_enable(grdev_pipe *pipe);
36 static void pipe_disable(grdev_pipe *pipe);
37 static void card_modified(grdev_card *card);
38 static void session_frame(grdev_session *session, grdev_display *display);
39
40 /*
41  * Displays
42  */
43
44 static inline grdev_tile *tile_leftmost(grdev_tile *tile) {
45         if (!tile)
46                 return NULL;
47
48         while (tile->type == GRDEV_TILE_NODE && tile->node.child_list)
49                 tile = tile->node.child_list;
50
51         return tile;
52 }
53
54 #define TILE_FOREACH(_root, _i) \
55         for (_i = tile_leftmost(_root); _i; _i = tile_leftmost(_i->childs_by_node_next) ? : _i->parent)
56
57 #define TILE_FOREACH_SAFE(_root, _i, _next) \
58         for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->childs_by_node_next) ? : _i->parent), true); _i = _next)
59
60 static void tile_link(grdev_tile *tile, grdev_tile *parent) {
61         grdev_display *display;
62         grdev_tile *t;
63
64         assert(tile);
65         assert(!tile->parent);
66         assert(!tile->display);
67         assert(parent);
68         assert(parent->type == GRDEV_TILE_NODE);
69
70         display = parent->display;
71
72         assert(!display || !display->enabled);
73
74         ++parent->node.n_childs;
75         LIST_PREPEND(childs_by_node, parent->node.child_list, tile);
76         tile->parent = parent;
77
78         if (display) {
79                 display->modified = true;
80                 TILE_FOREACH(tile, t) {
81                         t->display = display;
82                         if (t->type == GRDEV_TILE_LEAF) {
83                                 ++display->n_leafs;
84                                 if (display->enabled)
85                                         pipe_enable(t->leaf.pipe);
86                         }
87                 }
88         }
89 }
90
91 static void tile_unlink(grdev_tile *tile) {
92         grdev_tile *parent, *t;
93         grdev_display *display;
94
95         assert(tile);
96
97         display = tile->display;
98         parent = tile->parent;
99         if (!parent) {
100                 assert(!display);
101                 return;
102         }
103
104         assert(parent->type == GRDEV_TILE_NODE);
105         assert(parent->display == display);
106         assert(parent->node.n_childs > 0);
107
108         --parent->node.n_childs;
109         LIST_REMOVE(childs_by_node, parent->node.child_list, tile);
110         tile->parent = NULL;
111
112         if (display) {
113                 display->modified = true;
114                 TILE_FOREACH(tile, t) {
115                         t->display = NULL;
116                         if (t->type == GRDEV_TILE_LEAF) {
117                                 --display->n_leafs;
118                                 t->leaf.pipe->cache = NULL;
119                                 pipe_disable(t->leaf.pipe);
120                         }
121                 }
122         }
123
124         /* Tile trees are driven by leafs. Internal nodes have no owner, thus,
125          * we must take care to not leave them around. Therefore, whenever we
126          * unlink any part of a tree, we also destroy the parent, in case it's
127          * now stale.
128          * Parents are stale if they have no childs and either have no display
129          * or if they are intermediate nodes (i.e, they have a parent).
130          * This means, you can easily create trees, but you can never partially
131          * move or destruct them so far. They're always reduced to minimal form
132          * if you cut them. This might change later, but so far we didn't need
133          * partial destruction or the ability to move whole trees. */
134
135         if (parent->node.n_childs < 1 && (parent->parent || !parent->display))
136                 grdev_tile_free(parent);
137 }
138
139 static int tile_new(grdev_tile **out) {
140         _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
141
142         assert(out);
143
144         tile = new0(grdev_tile, 1);
145         if (!tile)
146                 return -ENOMEM;
147
148         tile->type = (unsigned)-1;
149
150         *out = tile;
151         tile = NULL;
152         return 0;
153 }
154
155 int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe) {
156         _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
157         int r;
158
159         assert_return(pipe, -EINVAL);
160         assert_return(!pipe->tile, -EINVAL);
161
162         r = tile_new(&tile);
163         if (r < 0)
164                 return r;
165
166         tile->type = GRDEV_TILE_LEAF;
167         tile->leaf.pipe = pipe;
168
169         if (out)
170                 *out = tile;
171         tile = NULL;
172         return 0;
173 }
174
175 int grdev_tile_new_node(grdev_tile **out) {
176         _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
177         int r;
178
179         assert_return(out, -EINVAL);
180
181         r = tile_new(&tile);
182         if (r < 0)
183                 return r;
184
185         tile->type = GRDEV_TILE_NODE;
186
187         *out = tile;
188         tile = NULL;
189         return 0;
190 }
191
192 grdev_tile *grdev_tile_free(grdev_tile *tile) {
193         if (!tile)
194                 return NULL;
195
196         tile_unlink(tile);
197
198         switch (tile->type) {
199         case GRDEV_TILE_LEAF:
200                 assert(!tile->parent);
201                 assert(!tile->display);
202                 assert(tile->leaf.pipe);
203
204                 break;
205         case GRDEV_TILE_NODE:
206                 assert(!tile->parent);
207                 assert(!tile->display);
208                 assert(tile->node.n_childs == 0);
209
210                 break;
211         }
212
213         free(tile);
214
215         return NULL;
216 }
217
218 grdev_display *grdev_find_display(grdev_session *session, const char *name) {
219         assert_return(session, NULL);
220         assert_return(name, NULL);
221
222         return hashmap_get(session->display_map, name);
223 }
224
225 int grdev_display_new(grdev_display **out, grdev_session *session, const char *name) {
226         _cleanup_(grdev_display_freep) grdev_display *display = NULL;
227         int r;
228
229         assert(session);
230         assert(name);
231
232         display = new0(grdev_display, 1);
233         if (!display)
234                 return -ENOMEM;
235
236         display->session = session;
237
238         display->name = strdup(name);
239         if (!display->name)
240                 return -ENOMEM;
241
242         r = grdev_tile_new_node(&display->tile);
243         if (r < 0)
244                 return r;
245
246         display->tile->display = display;
247
248         r = hashmap_put(session->display_map, display->name, display);
249         if (r < 0)
250                 return r;
251
252         if (out)
253                 *out = display;
254         display = NULL;
255         return 0;
256 }
257
258 grdev_display *grdev_display_free(grdev_display *display) {
259         if (!display)
260                 return NULL;
261
262         assert(!display->public);
263         assert(!display->enabled);
264         assert(!display->modified);
265         assert(display->n_leafs == 0);
266         assert(display->n_pipes == 0);
267
268         if (display->name)
269                 hashmap_remove_value(display->session->display_map, display->name, display);
270
271         if (display->tile) {
272                 display->tile->display = NULL;
273                 grdev_tile_free(display->tile);
274         }
275
276         free(display->pipes);
277         free(display->name);
278         free(display);
279
280         return NULL;
281 }
282
283 bool grdev_display_is_enabled(grdev_display *display) {
284         return display && display->enabled;
285 }
286
287 void grdev_display_enable(grdev_display *display) {
288         grdev_tile *t;
289
290         assert(display);
291
292         if (!display->enabled) {
293                 display->enabled = true;
294                 TILE_FOREACH(display->tile, t)
295                         if (t->type == GRDEV_TILE_LEAF)
296                                 pipe_enable(t->leaf.pipe);
297         }
298 }
299
300 void grdev_display_disable(grdev_display *display) {
301         grdev_tile *t;
302
303         assert(display);
304
305         if (display->enabled) {
306                 display->enabled = false;
307                 TILE_FOREACH(display->tile, t)
308                         if (t->type == GRDEV_TILE_LEAF)
309                                 pipe_disable(t->leaf.pipe);
310         }
311 }
312
313 const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev, uint64_t minage) {
314         grdev_display_cache *cache;
315         size_t idx;
316
317         assert_return(display, NULL);
318         assert_return(!display->modified, NULL);
319         assert_return(display->enabled, NULL);
320
321         if (prev) {
322                 cache = container_of(prev, grdev_display_cache, target);
323
324                 assert(cache->pipe);
325                 assert(cache->pipe->tile->display == display);
326                 assert(display->pipes >= cache);
327
328                 idx = (cache - display->pipes) / sizeof(*cache) + 1;
329         } else {
330                 idx = 0;
331         }
332
333         for (cache = display->pipes + idx; idx < display->n_pipes; ++idx, ++cache) {
334                 grdev_display_target *target;
335                 grdev_pipe *pipe;
336                 grdev_fb *fb;
337
338                 pipe = cache->pipe;
339                 target = &cache->target;
340
341                 if (!pipe->running || !pipe->enabled)
342                         continue;
343
344                 /* if front-buffer is up-to-date, there's nothing to do */
345                 if (minage > 0 && pipe->front && pipe->front->age >= minage)
346                         continue;
347
348                 /* find suitable back-buffer */
349                 if (!(fb = pipe->back)) {
350                         if (!pipe->vtable->target || !(fb = pipe->vtable->target(pipe)))
351                                 continue;
352                 }
353
354                 /* if back-buffer is up-to-date, schedule flip */
355                 if (minage > 0 && fb->age >= minage) {
356                         grdev_display_flip_target(display, target, fb->age);
357                         continue;
358                 }
359
360                 /* we have an out-of-date back-buffer; return for redraw */
361                 target->fb = fb;
362                 return target;
363         }
364
365         return NULL;
366 }
367
368 void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target, uint64_t age) {
369         grdev_display_cache *cache;
370         size_t i;
371
372         assert(display);
373         assert(!display->modified);
374         assert(display->enabled);
375         assert(target);
376         assert(target->fb);
377
378         cache = container_of(target, grdev_display_cache, target);
379
380         assert(cache->pipe);
381         assert(cache->pipe->tile->display == display);
382
383         /* reset age of all FB on overflow */
384         if (age < target->fb->age)
385                 for (i = 0; i < cache->pipe->max_fbs; ++i)
386                         if (cache->pipe->fbs[i])
387                                 cache->pipe->fbs[i]->age = 0;
388
389         ((grdev_fb*)target->fb)->age = age;
390         cache->pipe->flip = true;
391 }
392
393 static void display_cache_apply(grdev_display_cache *c, grdev_tile *l) {
394         uint32_t x, y, width, height;
395         grdev_display_target *t;
396
397         assert(c);
398         assert(l);
399         assert(l->cache_w >= c->target.width + c->target.x);
400         assert(l->cache_h >= c->target.height + c->target.y);
401
402         t = &c->target;
403
404         /* rotate child */
405
406         t->rotate = (t->rotate + l->rotate) & 0x3;
407
408         x = t->x;
409         y = t->y;
410         width = t->width;
411         height = t->height;
412
413         switch (l->rotate) {
414         case GRDEV_ROTATE_0:
415                 break;
416         case GRDEV_ROTATE_90:
417                 t->x = l->cache_h - (height + y);
418                 t->y = x;
419                 t->width = height;
420                 t->height = width;
421                 break;
422         case GRDEV_ROTATE_180:
423                 t->x = l->cache_w - (width + x);
424                 t->y = l->cache_h - (height + y);
425                 break;
426         case GRDEV_ROTATE_270:
427                 t->x = y;
428                 t->y = l->cache_w - (width + x);
429                 t->width = height;
430                 t->height = width;
431                 break;
432         }
433
434         /* flip child */
435
436         t->flip ^= l->flip;
437
438         if (l->flip & GRDEV_FLIP_HORIZONTAL)
439                 t->x = l->cache_w - (t->width + t->x);
440         if (l->flip & GRDEV_FLIP_VERTICAL)
441                 t->y = l->cache_h - (t->height + t->y);
442
443         /* move child */
444
445         t->x += l->x;
446         t->y += l->y;
447 }
448
449 static void display_cache_targets(grdev_display *display) {
450         grdev_display_cache *c;
451         grdev_tile *tile;
452
453         assert(display);
454
455         /* depth-first with childs before parent */
456         for (tile = tile_leftmost(display->tile);
457              tile;
458              tile = tile_leftmost(tile->childs_by_node_next) ? : tile->parent) {
459                 if (tile->type == GRDEV_TILE_LEAF) {
460                         grdev_pipe *p;
461
462                         /* We're at a leaf and no parent has been cached, yet.
463                          * Copy the pipe information into the target cache and
464                          * update our global pipe-caches if required. */
465
466                         assert(tile->leaf.pipe);
467                         assert(display->n_pipes + 1 <= display->max_pipes);
468
469                         p = tile->leaf.pipe;
470                         c = &display->pipes[display->n_pipes++];
471
472                         zero(*c);
473                         c->pipe = p;
474                         c->pipe->cache = c;
475                         c->target.width = p->width;
476                         c->target.height = p->height;
477                         tile->cache_w = p->width;
478                         tile->cache_h = p->height;
479
480                         /* all new tiles are incomplete due to geometry changes */
481                         c->incomplete = true;
482
483                         display_cache_apply(c, tile);
484                 } else {
485                         grdev_tile *child, *l;
486
487                         /* We're now at a node with all it's childs already
488                          * computed (depth-first, child before parent). We
489                          * first need to know the size of our tile, then we
490                          * recurse into all leafs and update their cache. */
491
492                         tile->cache_w = 0;
493                         tile->cache_h = 0;
494
495                         LIST_FOREACH(childs_by_node, child, tile->node.child_list) {
496                                 if (child->x + child->cache_w > tile->cache_w)
497                                         tile->cache_w = child->x + child->cache_w;
498                                 if (child->y + child->cache_h > tile->cache_h)
499                                         tile->cache_h = child->y + child->cache_h;
500                         }
501
502                         assert(tile->cache_w > 0);
503                         assert(tile->cache_h > 0);
504
505                         TILE_FOREACH(tile, l)
506                                 if (l->type == GRDEV_TILE_LEAF)
507                                         display_cache_apply(l->leaf.pipe->cache, tile);
508                 }
509         }
510 }
511
512 static bool display_cache(grdev_display *display) {
513         grdev_tile *tile;
514         size_t n;
515         void *t;
516         int r;
517
518         assert(display);
519
520         if (!display->modified)
521                 return false;
522
523         display->modified = false;
524         display->framed = false;
525         display->n_pipes = 0;
526         display->width = 0;
527         display->height = 0;
528
529         if (display->n_leafs < 1)
530                 return false;
531
532         TILE_FOREACH(display->tile, tile)
533                 if (tile->type == GRDEV_TILE_LEAF)
534                         tile->leaf.pipe->cache = NULL;
535
536         if (display->n_leafs > display->max_pipes) {
537                 n = ALIGN_POWER2(display->n_leafs);
538                 if (!n) {
539                         r = -ENOMEM;
540                         goto out;
541                 }
542
543                 t = realloc_multiply(display->pipes, sizeof(*display->pipes), n);
544                 if (!t) {
545                         r = -ENOMEM;
546                         goto out;
547                 }
548
549                 display->pipes = t;
550                 display->max_pipes = n;
551         }
552
553         display_cache_targets(display);
554
555         r = 0;
556
557 out:
558         if (r < 0)
559                 log_debug("grdev: %s/%s: cannot cache pipes: %s",
560                           display->session->name, display->name, strerror(-r));
561         return true;
562 }
563
564 /*
565  * Pipes
566  */
567
568 grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name) {
569         assert_return(card, NULL);
570         assert_return(name, NULL);
571
572         return hashmap_get(card->pipe_map, name);
573 }
574
575 int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs) {
576         int r;
577
578         assert_return(pipe, -EINVAL);
579         assert_return(pipe->vtable, -EINVAL);
580         assert_return(pipe->vtable->free, -EINVAL);
581         assert_return(pipe->card, -EINVAL);
582         assert_return(pipe->card->session, -EINVAL);
583         assert_return(!pipe->cache, -EINVAL);
584         assert_return(pipe->width > 0, -EINVAL);
585         assert_return(pipe->height > 0, -EINVAL);
586         assert_return(!pipe->enabled, -EINVAL);
587         assert_return(!pipe->running, -EINVAL);
588         assert_return(name, -EINVAL);
589
590         pipe->name = strdup(name);
591         if (!pipe->name)
592                 return -ENOMEM;
593
594         if (n_fbs > 0) {
595                 pipe->fbs = new0(grdev_fb*, n_fbs);
596                 if (!pipe->fbs)
597                         return -ENOMEM;
598
599                 pipe->max_fbs = n_fbs;
600         }
601
602         r = grdev_tile_new_leaf(&pipe->tile, pipe);
603         if (r < 0)
604                 return r;
605
606         r = hashmap_put(pipe->card->pipe_map, pipe->name, pipe);
607         if (r < 0)
608                 return r;
609
610         card_modified(pipe->card);
611         return 0;
612 }
613
614 grdev_pipe *grdev_pipe_free(grdev_pipe *pipe) {
615         grdev_pipe tmp;
616
617         if (!pipe)
618                 return NULL;
619
620         assert(pipe->card);
621         assert(pipe->vtable);
622         assert(pipe->vtable->free);
623
624         if (pipe->name)
625                 hashmap_remove_value(pipe->card->pipe_map, pipe->name, pipe);
626         if (pipe->tile)
627                 tile_unlink(pipe->tile);
628
629         assert(!pipe->cache);
630
631         tmp = *pipe;
632         pipe->vtable->free(pipe);
633
634         grdev_tile_free(tmp.tile);
635         card_modified(tmp.card);
636         free(tmp.fbs);
637         free(tmp.name);
638
639         return NULL;
640 }
641
642 static void pipe_enable(grdev_pipe *pipe) {
643         assert(pipe);
644
645         if (!pipe->enabled) {
646                 pipe->enabled = true;
647                 if (pipe->vtable->enable)
648                         pipe->vtable->enable(pipe);
649         }
650 }
651
652 static void pipe_disable(grdev_pipe *pipe) {
653         assert(pipe);
654
655         if (pipe->enabled) {
656                 pipe->enabled = false;
657                 if (pipe->vtable->disable)
658                         pipe->vtable->disable(pipe);
659         }
660 }
661
662 void grdev_pipe_ready(grdev_pipe *pipe, bool running) {
663         assert(pipe);
664
665         /* grdev_pipe_ready() is used by backends to notify about pipe state
666          * changed. If a pipe is ready, it can be fully used by us (available,
667          * enabled and accessable). Backends can disable pipes at any time
668          * (like for async revocation), but can only enable them from parent
669          * context. Otherwise, we might call user-callbacks recursively. */
670
671         if (pipe->running == running)
672                 return;
673
674         pipe->running = running;
675
676         /* runtime events for unused pipes are not interesting */
677         if (pipe->cache) {
678                 grdev_display *display = pipe->tile->display;
679
680                 assert(display);
681
682                 if (running) {
683                         if (pipe->enabled)
684                                 session_frame(display->session, display);
685                 } else {
686                         pipe->cache->incomplete = true;
687                 }
688         }
689 }
690
691 void grdev_pipe_frame(grdev_pipe *pipe) {
692         grdev_display *display;
693
694         assert(pipe);
695
696         /* if pipe is unused, ignore any frame events */
697         if (!pipe->cache)
698                 return;
699
700         display = pipe->tile->display;
701         assert(display);
702
703         if (pipe->enabled)
704                 session_frame(display->session, display);
705 }
706
707 /*
708  * Cards
709  */
710
711 grdev_card *grdev_find_card(grdev_session *session, const char *name) {
712         assert_return(session, NULL);
713         assert_return(name, NULL);
714
715         return hashmap_get(session->card_map, name);
716 }
717
718 int grdev_card_add(grdev_card *card, const char *name) {
719         int r;
720
721         assert_return(card, -EINVAL);
722         assert_return(card->vtable, -EINVAL);
723         assert_return(card->vtable->free, -EINVAL);
724         assert_return(card->session, -EINVAL);
725         assert_return(name, -EINVAL);
726
727         card->name = strdup(name);
728         if (!card->name)
729                 return -ENOMEM;
730
731         card->pipe_map = hashmap_new(&string_hash_ops);
732         if (!card->pipe_map)
733                 return -ENOMEM;
734
735         r = hashmap_put(card->session->card_map, card->name, card);
736         if (r < 0)
737                 return r;
738
739         return 0;
740 }
741
742 grdev_card *grdev_card_free(grdev_card *card) {
743         grdev_card tmp;
744
745         if (!card)
746                 return NULL;
747
748         assert(!card->enabled);
749         assert(card->vtable);
750         assert(card->vtable->free);
751
752         if (card->name)
753                 hashmap_remove_value(card->session->card_map, card->name, card);
754
755         tmp = *card;
756         card->vtable->free(card);
757
758         assert(hashmap_size(tmp.pipe_map) == 0);
759
760         hashmap_free(tmp.pipe_map);
761         free(tmp.name);
762
763         return NULL;
764 }
765
766 static void card_modified(grdev_card *card) {
767         assert(card);
768         assert(card->session->n_pins > 0);
769
770         card->modified = true;
771 }
772
773 static void grdev_card_enable(grdev_card *card) {
774         assert(card);
775
776         if (!card->enabled) {
777                 card->enabled = true;
778                 if (card->vtable->enable)
779                         card->vtable->enable(card);
780         }
781 }
782
783 static void grdev_card_disable(grdev_card *card) {
784         assert(card);
785
786         if (card->enabled) {
787                 card->enabled = false;
788                 if (card->vtable->disable)
789                         card->vtable->disable(card);
790         }
791 }
792
793 /*
794  * Sessions
795  */
796
797 static void session_raise(grdev_session *session, grdev_event *event) {
798         session->event_fn(session, session->userdata, event);
799 }
800
801 static void session_raise_display_add(grdev_session *session, grdev_display *display) {
802         grdev_event event = {
803                 .type = GRDEV_EVENT_DISPLAY_ADD,
804                 .display_add = {
805                         .display = display,
806                 },
807         };
808
809         session_raise(session, &event);
810 }
811
812 static void session_raise_display_remove(grdev_session *session, grdev_display *display) {
813         grdev_event event = {
814                 .type = GRDEV_EVENT_DISPLAY_REMOVE,
815                 .display_remove = {
816                         .display = display,
817                 },
818         };
819
820         session_raise(session, &event);
821 }
822
823 static void session_raise_display_change(grdev_session *session, grdev_display *display) {
824         grdev_event event = {
825                 .type = GRDEV_EVENT_DISPLAY_CHANGE,
826                 .display_change = {
827                         .display = display,
828                 },
829         };
830
831         session_raise(session, &event);
832 }
833
834 static void session_raise_display_frame(grdev_session *session, grdev_display *display) {
835         grdev_event event = {
836                 .type = GRDEV_EVENT_DISPLAY_FRAME,
837                 .display_frame = {
838                         .display = display,
839                 },
840         };
841
842         session_raise(session, &event);
843 }
844
845 static void session_add_card(grdev_session *session, grdev_card *card) {
846         assert(session);
847         assert(card);
848
849         log_debug("grdev: %s: add card '%s'", session->name, card->name);
850
851         /* Cards are not exposed to users, but managed internally. Cards are
852          * enabled if the session is enabled, and will track that state. The
853          * backend can probe the card at any time, but only if enabled. It
854          * will then add pipes according to hardware state.
855          * That is, the card may create pipes as soon as we enable it here. */
856
857         if (session->enabled)
858                 grdev_card_enable(card);
859 }
860
861 static void session_remove_card(grdev_session *session, grdev_card *card) {
862         assert(session);
863         assert(card);
864
865         log_debug("grdev: %s: remove card '%s'", session->name, card->name);
866
867         /* As cards are not exposed, it can never be accessed by outside
868          * users and we can simply remove it. Disabling the card does not
869          * necessarily drop all pipes of the card. This is usually deferred
870          * to card destruction (as pipes are cached as long as FDs remain
871          * open). Therefore, the card destruction might cause pipes, and thus
872          * visible displays, to be removed. */
873
874         grdev_card_disable(card);
875         grdev_card_free(card);
876 }
877
878 static void session_add_display(grdev_session *session, grdev_display *display) {
879         assert(session);
880         assert(display);
881         assert(!display->enabled);
882
883         log_debug("grdev: %s: add display '%s'", session->name, display->name);
884
885         /* Displays are the main entity for public API users. We create them
886          * independent of card backends and they wrap any underlying display
887          * architecture. Displays are public at all times, thus, may be entered
888          * by outside users at any time. */
889
890         display->public = true;
891         session_raise_display_add(session, display);
892 }
893
894 static void session_remove_display(grdev_session *session, grdev_display *display) {
895         assert(session);
896         assert(display);
897
898         log_debug("grdev: %s: remove display '%s'", session->name, display->name);
899
900         /* Displays are public, so we have to be careful when removing them.
901          * We first tell users about their removal, disable them and then drop
902          * them. We now, after the notification, no external access will
903          * happen. Therefore, we can release the tiles afterwards safely. */
904
905         if (display->public) {
906                 display->public = false;
907                 session_raise_display_remove(session, display);
908         }
909
910         grdev_display_disable(display);
911         grdev_display_free(display);
912 }
913
914 static void session_change_display(grdev_session *session, grdev_display *display) {
915         bool changed;
916
917         assert(session);
918         assert(display);
919
920         changed = display_cache(display);
921
922         if (display->n_leafs == 0)
923                 session_remove_display(session, display);
924         else if (!display->public)
925                 session_add_display(session, display);
926         else if (changed)
927                 session_raise_display_change(session, display);
928         else if (display->framed)
929                 session_frame(session, display);
930 }
931
932 static void session_frame(grdev_session *session, grdev_display *display) {
933         assert(session);
934         assert(display);
935
936         display->framed = false;
937
938         if (!display->enabled || !session->enabled)
939                 return;
940
941         if (session->n_pins > 0)
942                 display->framed = true;
943         else
944                 session_raise_display_frame(session, display);
945 }
946
947 int grdev_session_new(grdev_session **out,
948                       grdev_context *context,
949                       unsigned int flags,
950                       const char *name,
951                       grdev_event_fn event_fn,
952                       void *userdata) {
953         _cleanup_(grdev_session_freep) grdev_session *session = NULL;
954         int r;
955
956         assert(out);
957         assert(context);
958         assert(name);
959         assert(event_fn);
960         assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL);
961         assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL);
962         assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL);
963
964         session = new0(grdev_session, 1);
965         if (!session)
966                 return -ENOMEM;
967
968         session->context = grdev_context_ref(context);
969         session->custom = flags & GRDEV_SESSION_CUSTOM;
970         session->managed = flags & GRDEV_SESSION_MANAGED;
971         session->event_fn = event_fn;
972         session->userdata = userdata;
973
974         session->name = strdup(name);
975         if (!session->name)
976                 return -ENOMEM;
977
978         if (session->managed) {
979                 r = sd_bus_path_encode("/org/freedesktop/login1/session",
980                                        session->name, &session->path);
981                 if (r < 0)
982                         return r;
983         }
984
985         session->card_map = hashmap_new(&string_hash_ops);
986         if (!session->card_map)
987                 return -ENOMEM;
988
989         session->display_map = hashmap_new(&string_hash_ops);
990         if (!session->display_map)
991                 return -ENOMEM;
992
993         r = hashmap_put(context->session_map, session->name, session);
994         if (r < 0)
995                 return r;
996
997         *out = session;
998         session = NULL;
999         return 0;
1000 }
1001
1002 grdev_session *grdev_session_free(grdev_session *session) {
1003         grdev_card *card;
1004
1005         if (!session)
1006                 return NULL;
1007
1008         grdev_session_disable(session);
1009
1010         while ((card = hashmap_first(session->card_map)))
1011                 session_remove_card(session, card);
1012
1013         assert(hashmap_size(session->display_map) == 0);
1014
1015         if (session->name)
1016                 hashmap_remove_value(session->context->session_map, session->name, session);
1017
1018         hashmap_free(session->display_map);
1019         hashmap_free(session->card_map);
1020         session->context = grdev_context_unref(session->context);
1021         free(session->path);
1022         free(session->name);
1023         free(session);
1024
1025         return NULL;
1026 }
1027
1028 bool grdev_session_is_enabled(grdev_session *session) {
1029         return session && session->enabled;
1030 }
1031
1032 void grdev_session_enable(grdev_session *session) {
1033         grdev_card *card;
1034         Iterator iter;
1035
1036         assert(session);
1037
1038         if (!session->enabled) {
1039                 session->enabled = true;
1040                 HASHMAP_FOREACH(card, session->card_map, iter)
1041                         grdev_card_enable(card);
1042         }
1043 }
1044
1045 void grdev_session_disable(grdev_session *session) {
1046         grdev_card *card;
1047         Iterator iter;
1048
1049         assert(session);
1050
1051         if (session->enabled) {
1052                 session->enabled = false;
1053                 HASHMAP_FOREACH(card, session->card_map, iter)
1054                         grdev_card_disable(card);
1055         }
1056 }
1057
1058 void grdev_session_commit(grdev_session *session) {
1059         grdev_card *card;
1060         Iterator iter;
1061
1062         assert(session);
1063
1064         if (!session->enabled)
1065                 return;
1066
1067         HASHMAP_FOREACH(card, session->card_map, iter)
1068                 if (card->vtable->commit)
1069                         card->vtable->commit(card);
1070 }
1071
1072 void grdev_session_restore(grdev_session *session) {
1073         grdev_card *card;
1074         Iterator iter;
1075
1076         assert(session);
1077
1078         if (!session->enabled)
1079                 return;
1080
1081         HASHMAP_FOREACH(card, session->card_map, iter)
1082                 if (card->vtable->restore)
1083                         card->vtable->restore(card);
1084 }
1085
1086 static void session_configure(grdev_session *session) {
1087         grdev_display *display;
1088         grdev_tile *tile;
1089         grdev_card *card;
1090         grdev_pipe *pipe;
1091         Iterator i, j;
1092         int r;
1093
1094         assert(session);
1095
1096         /*
1097          * Whenever backends add or remove pipes, we set session->modified and
1098          * require them to pin the session while modifying it. On release, we
1099          * reconfigure the device and re-assign displays to all modified pipes.
1100          *
1101          * So far, we configure each pipe as a separate display. We do not
1102          * support user-configuration, nor have we gotten any reports from
1103          * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until
1104          * we get reports, we keep the logic to a minimum.
1105          */
1106
1107         /* create new displays for all unconfigured pipes */
1108         HASHMAP_FOREACH(card, session->card_map, i) {
1109                 if (!card->modified)
1110                         continue;
1111
1112                 card->modified = false;
1113
1114                 HASHMAP_FOREACH(pipe, card->pipe_map, j) {
1115                         tile = pipe->tile;
1116                         if (tile->display)
1117                                 continue;
1118
1119                         assert(!tile->parent);
1120
1121                         display = grdev_find_display(session, pipe->name);
1122                         if (display && display->tile) {
1123                                 log_debug("grdev: %s/%s: occupied display for pipe %s",
1124                                           session->name, card->name, pipe->name);
1125                                 continue;
1126                         } else if (!display) {
1127                                 r = grdev_display_new(&display, session, pipe->name);
1128                                 if (r < 0) {
1129                                         log_debug("grdev: %s/%s: cannot create display for pipe %s: %s",
1130                                                   session->name, card->name, pipe->name, strerror(-r));
1131                                         continue;
1132                                 }
1133                         }
1134
1135                         tile_link(pipe->tile, display->tile);
1136                 }
1137         }
1138
1139         /* update displays */
1140         HASHMAP_FOREACH(display, session->display_map, i)
1141                 session_change_display(session, display);
1142 }
1143
1144 grdev_session *grdev_session_pin(grdev_session *session) {
1145         assert(session);
1146
1147         ++session->n_pins;
1148         return session;
1149 }
1150
1151 grdev_session *grdev_session_unpin(grdev_session *session) {
1152         if (!session)
1153                 return NULL;
1154
1155         assert(session->n_pins > 0);
1156
1157         if (--session->n_pins == 0)
1158                 session_configure(session);
1159
1160         return NULL;
1161 }
1162
1163 /*
1164  * Contexts
1165  */
1166
1167 int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) {
1168         _cleanup_(grdev_context_unrefp) grdev_context *context = NULL;
1169
1170         assert_return(out, -EINVAL);
1171         assert_return(event, -EINVAL);
1172
1173         context = new0(grdev_context, 1);
1174         if (!context)
1175                 return -ENOMEM;
1176
1177         context->ref = 1;
1178         context->event = sd_event_ref(event);
1179
1180         if (sysbus)
1181                 context->sysbus = sd_bus_ref(sysbus);
1182
1183         context->session_map = hashmap_new(&string_hash_ops);
1184         if (!context->session_map)
1185                 return -ENOMEM;
1186
1187         *out = context;
1188         context = NULL;
1189         return 0;
1190 }
1191
1192 static void context_cleanup(grdev_context *context) {
1193         assert(hashmap_size(context->session_map) == 0);
1194
1195         hashmap_free(context->session_map);
1196         context->sysbus = sd_bus_unref(context->sysbus);
1197         context->event = sd_event_unref(context->event);
1198         free(context);
1199 }
1200
1201 grdev_context *grdev_context_ref(grdev_context *context) {
1202         assert_return(context, NULL);
1203         assert_return(context->ref > 0, NULL);
1204
1205         ++context->ref;
1206         return context;
1207 }
1208
1209 grdev_context *grdev_context_unref(grdev_context *context) {
1210         if (!context)
1211                 return NULL;
1212
1213         assert_return(context->ref > 0, NULL);
1214
1215         if (--context->ref == 0)
1216                 context_cleanup(context);
1217
1218         return NULL;
1219 }