chiark / gitweb /
terminal: add input interface
[elogind.git] / src / libsystemd-terminal / idev.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 "hashmap.h"
29 #include "idev.h"
30 #include "idev-internal.h"
31 #include "login-shared.h"
32 #include "macro.h"
33 #include "set.h"
34 #include "util.h"
35
36 static void element_open(idev_element *e);
37 static void element_close(idev_element *e);
38
39 /*
40  * Devices
41  */
42
43 idev_device *idev_find_device(idev_session *s, const char *name) {
44         assert_return(s, NULL);
45         assert_return(name, NULL);
46
47         return hashmap_get(s->device_map, name);
48 }
49
50 int idev_device_add(idev_device *d, const char *name) {
51         int r;
52
53         assert_return(d, -EINVAL);
54         assert_return(d->vtable, -EINVAL);
55         assert_return(d->session, -EINVAL);
56         assert_return(name, -EINVAL);
57
58         d->name = strdup(name);
59         if (!d->name)
60                 return -ENOMEM;
61
62         r = hashmap_put(d->session->device_map, d->name, d);
63         if (r < 0)
64                 return r;
65
66         return 0;
67 }
68
69 idev_device *idev_device_free(idev_device *d) {
70         idev_device tmp;
71
72         if (!d)
73                 return NULL;
74
75         assert(!d->enabled);
76         assert(!d->public);
77         assert(!d->links);
78         assert(d->vtable);
79         assert(d->vtable->free);
80
81         if (d->name)
82                 hashmap_remove_value(d->session->device_map, d->name, d);
83
84         tmp = *d;
85         d->vtable->free(d);
86
87         free(tmp.name);
88
89         return NULL;
90 }
91
92 int idev_device_feed(idev_device *d, idev_data *data) {
93         assert(d);
94         assert(data);
95         assert(data->type < IDEV_DATA_CNT);
96
97         if (d->vtable->feed)
98                 return d->vtable->feed(d, data);
99         else
100                 return 0;
101 }
102
103 void idev_device_feedback(idev_device *d, idev_data *data) {
104         idev_link *l;
105
106         assert(d);
107         assert(data);
108         assert(data->type < IDEV_DATA_CNT);
109
110         LIST_FOREACH(links_by_device, l, d->links)
111                 idev_element_feedback(l->element, data);
112 }
113
114 static void device_attach(idev_device *d, idev_link *l) {
115         assert(d);
116         assert(l);
117
118         if (d->vtable->attach)
119                 d->vtable->attach(d, l);
120
121         if (d->enabled)
122                 element_open(l->element);
123 }
124
125 static void device_detach(idev_device *d, idev_link *l) {
126         assert(d);
127         assert(l);
128
129         if (d->enabled)
130                 element_close(l->element);
131
132         if (d->vtable->detach)
133                 d->vtable->detach(d, l);
134 }
135
136 void idev_device_enable(idev_device *d) {
137         idev_link *l;
138
139         assert(d);
140
141         if (!d->enabled) {
142                 d->enabled = true;
143                 LIST_FOREACH(links_by_device, l, d->links)
144                         element_open(l->element);
145         }
146 }
147
148 void idev_device_disable(idev_device *d) {
149         idev_link *l;
150
151         assert(d);
152
153         if (d->enabled) {
154                 d->enabled = false;
155                 LIST_FOREACH(links_by_device, l, d->links)
156                         element_close(l->element);
157         }
158 }
159
160 /*
161  * Elements
162  */
163
164 idev_element *idev_find_element(idev_session *s, const char *name) {
165         assert_return(s, NULL);
166         assert_return(name, NULL);
167
168         return hashmap_get(s->element_map, name);
169 }
170
171 int idev_element_add(idev_element *e, const char *name) {
172         int r;
173
174         assert_return(e, -EINVAL);
175         assert_return(e->vtable, -EINVAL);
176         assert_return(e->session, -EINVAL);
177         assert_return(name, -EINVAL);
178
179         e->name = strdup(name);
180         if (!e->name)
181                 return -ENOMEM;
182
183         r = hashmap_put(e->session->element_map, e->name, e);
184         if (r < 0)
185                 return r;
186
187         return 0;
188 }
189
190 idev_element *idev_element_free(idev_element *e) {
191         idev_element tmp;
192
193         if (!e)
194                 return NULL;
195
196         assert(!e->enabled);
197         assert(!e->links);
198         assert(e->n_open == 0);
199         assert(e->vtable);
200         assert(e->vtable->free);
201
202         if (e->name)
203                 hashmap_remove_value(e->session->element_map, e->name, e);
204
205         tmp = *e;
206         e->vtable->free(e);
207
208         free(tmp.name);
209
210         return NULL;
211 }
212
213 int idev_element_feed(idev_element *e, idev_data *data) {
214         int r, error = 0;
215         idev_link *l;
216
217         assert(e);
218         assert(data);
219         assert(data->type < IDEV_DATA_CNT);
220
221         LIST_FOREACH(links_by_element, l, e->links) {
222                 r = idev_device_feed(l->device, data);
223                 if (r != 0)
224                         error = r;
225         }
226
227         return error;
228 }
229
230 void idev_element_feedback(idev_element *e, idev_data *data) {
231         assert(e);
232         assert(data);
233         assert(data->type < IDEV_DATA_CNT);
234
235         if (e->vtable->feedback)
236                e->vtable->feedback(e, data);
237 }
238
239 static void element_open(idev_element *e) {
240         assert(e);
241
242         if (e->n_open++ == 0 && e->vtable->open)
243                 e->vtable->open(e);
244 }
245
246 static void element_close(idev_element *e) {
247         assert(e);
248         assert(e->n_open > 0);
249
250         if (--e->n_open == 0 && e->vtable->close)
251                 e->vtable->close(e);
252 }
253
254 static void element_enable(idev_element *e) {
255         assert(e);
256
257         if (!e->enabled) {
258                 e->enabled = true;
259                 if (e->vtable->enable)
260                         e->vtable->enable(e);
261         }
262 }
263
264 static void element_disable(idev_element *e) {
265         assert(e);
266
267         if (e->enabled) {
268                 e->enabled = false;
269                 if (e->vtable->disable)
270                         e->vtable->disable(e);
271         }
272 }
273
274 /*
275  * Sessions
276  */
277
278 static int session_raise(idev_session *s, idev_event *ev) {
279         return s->event_fn(s, s->userdata, ev);
280 }
281
282 static int session_raise_device_add(idev_session *s, idev_device *d) {
283         idev_event event = {
284                 .type = IDEV_EVENT_DEVICE_ADD,
285                 .device_add = {
286                         .device = d,
287                 },
288         };
289
290         return session_raise(s, &event);
291 }
292
293 static int session_raise_device_remove(idev_session *s, idev_device *d) {
294         idev_event event = {
295                 .type = IDEV_EVENT_DEVICE_REMOVE,
296                 .device_remove = {
297                         .device = d,
298                 },
299         };
300
301         return session_raise(s, &event);
302 }
303
304 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
305         idev_event event = {
306                 .type = IDEV_EVENT_DEVICE_DATA,
307                 .device_data = {
308                         .device = d,
309                         .data = *data,
310                 },
311         };
312
313         return session_raise(s, &event);
314 }
315
316 static int session_add_device(idev_session *s, idev_device *d) {
317         int r;
318
319         assert(s);
320         assert(d);
321
322         log_debug("idev: %s: add device '%s'", s->name, d->name);
323
324         d->public = true;
325         r = session_raise_device_add(s, d);
326         if (r != 0) {
327                 d->public = false;
328                 goto error;
329         }
330
331         return 0;
332
333 error:
334         if (r < 0)
335                 log_debug("idev: %s: error while adding device '%s': %s",
336                           s->name, d->name, strerror(-r));
337         return r;
338 }
339
340 static int session_remove_device(idev_session *s, idev_device *d) {
341         int r, error = 0;
342
343         assert(s);
344         assert(d);
345
346         log_debug("idev: %s: remove device '%s'", s->name, d->name);
347
348         d->public = false;
349         r = session_raise_device_remove(s, d);
350         if (r != 0)
351                 error = r;
352
353         idev_device_disable(d);
354
355         if (error < 0)
356                 log_debug("idev: %s: error while removing device '%s': %s",
357                           s->name, d->name, strerror(-error));
358         idev_device_free(d);
359         return error;
360 }
361
362 static int session_add_element(idev_session *s, idev_element *e) {
363         assert(s);
364         assert(e);
365
366         log_debug("idev: %s: add element '%s'", s->name, e->name);
367
368         if (s->enabled)
369                 element_enable(e);
370
371         return 0;
372 }
373
374 static int session_remove_element(idev_session *s, idev_element *e) {
375         int r, error = 0;
376         idev_device *d;
377         idev_link *l;
378
379         assert(s);
380         assert(e);
381
382         log_debug("idev: %s: remove element '%s'", s->name, e->name);
383
384         while ((l = e->links)) {
385                 d = l->device;
386                 LIST_REMOVE(links_by_device, d->links, l);
387                 LIST_REMOVE(links_by_element, e->links, l);
388                 device_detach(d, l);
389
390                 if (!d->links) {
391                         r = session_remove_device(s, d);
392                         if (r != 0)
393                                 error = r;
394                 }
395
396                 l->device = NULL;
397                 l->element = NULL;
398                 free(l);
399         }
400
401         element_disable(e);
402
403         if (error < 0)
404                 log_debug("idev: %s: error while removing element '%s': %s",
405                           s->name, e->name, strerror(-r));
406         idev_element_free(e);
407         return error;
408 }
409
410 idev_session *idev_find_session(idev_context *c, const char *name) {
411         assert_return(c, NULL);
412         assert_return(name, NULL);
413
414         return hashmap_get(c->session_map, name);
415 }
416
417 int idev_session_new(idev_session **out,
418                      idev_context *c,
419                      unsigned int flags,
420                      const char *name,
421                      idev_event_fn event_fn,
422                      void *userdata) {
423         _cleanup_(idev_session_freep) idev_session *s = NULL;
424         int r;
425
426         assert_return(out, -EINVAL);
427         assert_return(c, -EINVAL);
428         assert_return(name, -EINVAL);
429         assert_return(event_fn, -EINVAL);
430         assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL);
431         assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL);
432         assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL);
433
434         s = new0(idev_session, 1);
435         if (!s)
436                 return -ENOMEM;
437
438         s->context = idev_context_ref(c);
439         s->custom = flags & IDEV_SESSION_CUSTOM;
440         s->managed = flags & IDEV_SESSION_MANAGED;
441         s->event_fn = event_fn;
442         s->userdata = userdata;
443
444         s->name = strdup(name);
445         if (!s->name)
446                 return -ENOMEM;
447
448         if (s->managed) {
449                 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
450                 if (r < 0)
451                         return r;
452         }
453
454         s->element_map = hashmap_new(string_hash_func, string_compare_func);
455         if (!s->element_map)
456                 return -ENOMEM;
457
458         s->device_map = hashmap_new(string_hash_func, string_compare_func);
459         if (!s->device_map)
460                 return -ENOMEM;
461
462         r = hashmap_put(c->session_map, s->name, s);
463         if (r < 0)
464                 return r;
465
466         *out = s;
467         s = NULL;
468         return 0;
469 }
470
471 idev_session *idev_session_free(idev_session *s) {
472         idev_element *e;
473
474         if (!s)
475                 return NULL;
476
477         while ((e = hashmap_first(s->element_map)))
478                 session_remove_element(s, e);
479
480         assert(hashmap_size(s->device_map) == 0);
481
482         if (s->name)
483                 hashmap_remove_value(s->context->session_map, s->name, s);
484
485         s->context = idev_context_unref(s->context);
486         hashmap_free(s->device_map);
487         hashmap_free(s->element_map);
488         free(s->path);
489         free(s->name);
490         free(s);
491
492         return NULL;
493 }
494
495 bool idev_session_is_enabled(idev_session *s) {
496         return s && s->enabled;
497 }
498
499 void idev_session_enable(idev_session *s) {
500         idev_element *e;
501         Iterator i;
502
503         assert(s);
504
505         if (!s->enabled) {
506                 s->enabled = true;
507                 HASHMAP_FOREACH(e, s->element_map, i)
508                         element_enable(e);
509         }
510 }
511
512 void idev_session_disable(idev_session *s) {
513         idev_element *e;
514         Iterator i;
515
516         assert(s);
517
518         if (s->enabled) {
519                 s->enabled = false;
520                 HASHMAP_FOREACH(e, s->element_map, i)
521                         element_disable(e);
522         }
523 }
524
525 /*
526  * Contexts
527  */
528
529 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
530         _cleanup_(idev_context_unrefp) idev_context *c = NULL;
531
532         assert_return(out, -EINVAL);
533         assert_return(event, -EINVAL);
534
535         c = new0(idev_context, 1);
536         if (!c)
537                 return -ENOMEM;
538
539         c->ref = 1;
540         c->event = sd_event_ref(event);
541
542         if (sysbus)
543                 c->sysbus = sd_bus_ref(sysbus);
544
545         c->session_map = hashmap_new(string_hash_func, string_compare_func);
546         if (!c->session_map)
547                 return -ENOMEM;
548
549         c->data_map = hashmap_new(string_hash_func, string_compare_func);
550         if (!c->data_map)
551                 return -ENOMEM;
552
553         *out = c;
554         c = NULL;
555         return 0;
556 }
557
558 static void context_cleanup(idev_context *c) {
559         assert(hashmap_size(c->data_map) == 0);
560         assert(hashmap_size(c->session_map) == 0);
561
562         hashmap_free(c->data_map);
563         hashmap_free(c->session_map);
564         c->sysbus = sd_bus_unref(c->sysbus);
565         c->event = sd_event_unref(c->event);
566         free(c);
567 }
568
569 idev_context *idev_context_ref(idev_context *c) {
570         assert_return(c, NULL);
571         assert_return(c->ref > 0, NULL);
572
573         ++c->ref;
574         return c;
575 }
576
577 idev_context *idev_context_unref(idev_context *c) {
578         if (!c)
579                 return NULL;
580
581         assert_return(c->ref > 0, NULL);
582
583         if (--c->ref == 0)
584                 context_cleanup(c);
585
586         return NULL;
587 }