xref: /qemu/ui/input-barrier.c (revision 8063396b)
1 /*
2  * SPDX-License-Identifier: GPL-2.0-or-later
3  *
4  * This work is licensed under the terms of the GNU GPL, version 2 or later.
5  * See the COPYING file in the top-level directory.
6  */
7 
8 #include "qemu/osdep.h"
9 #include "sysemu/sysemu.h"
10 #include "qemu/main-loop.h"
11 #include "qemu/sockets.h"
12 #include "qapi/error.h"
13 #include "qom/object_interfaces.h"
14 #include "io/channel-socket.h"
15 #include "ui/input.h"
16 #include "qom/object.h"
17 #include "ui/vnc_keysym.h" /* use name2keysym from VNC as we use X11 values */
18 #include "qemu/cutils.h"
19 #include "qapi/qmp/qerror.h"
20 #include "input-barrier.h"
21 
22 #define TYPE_INPUT_BARRIER "input-barrier"
23 OBJECT_DECLARE_SIMPLE_TYPE(InputBarrier,
24                            INPUT_BARRIER)
25 
26 
27 #define MAX_HELLO_LENGTH 1024
28 
29 struct InputBarrier {
30     Object parent;
31 
32     QIOChannelSocket *sioc;
33     guint ioc_tag;
34 
35     /* display properties */
36     gchar *name;
37     int16_t x_origin, y_origin;
38     int16_t width, height;
39 
40     /* keyboard/mouse server */
41 
42     SocketAddress saddr;
43 
44     char buffer[MAX_HELLO_LENGTH];
45 };
46 
47 
48 static const char *cmd_names[] = {
49     [barrierCmdCNoop]          = "CNOP",
50     [barrierCmdCClose]         = "CBYE",
51     [barrierCmdCEnter]         = "CINN",
52     [barrierCmdCLeave]         = "COUT",
53     [barrierCmdCClipboard]     = "CCLP",
54     [barrierCmdCScreenSaver]   = "CSEC",
55     [barrierCmdCResetOptions]  = "CROP",
56     [barrierCmdCInfoAck]       = "CIAK",
57     [barrierCmdCKeepAlive]     = "CALV",
58     [barrierCmdDKeyDown]       = "DKDN",
59     [barrierCmdDKeyRepeat]     = "DKRP",
60     [barrierCmdDKeyUp]         = "DKUP",
61     [barrierCmdDMouseDown]     = "DMDN",
62     [barrierCmdDMouseUp]       = "DMUP",
63     [barrierCmdDMouseMove]     = "DMMV",
64     [barrierCmdDMouseRelMove]  = "DMRM",
65     [barrierCmdDMouseWheel]    = "DMWM",
66     [barrierCmdDClipboard]     = "DCLP",
67     [barrierCmdDInfo]          = "DINF",
68     [barrierCmdDSetOptions]    = "DSOP",
69     [barrierCmdDFileTransfer]  = "DFTR",
70     [barrierCmdDDragInfo]      = "DDRG",
71     [barrierCmdQInfo]          = "QINF",
72     [barrierCmdEIncompatible]  = "EICV",
73     [barrierCmdEBusy]          = "EBSY",
74     [barrierCmdEUnknown]       = "EUNK",
75     [barrierCmdEBad]           = "EBAD",
76     [barrierCmdHello]          = "Barrier",
77     [barrierCmdHelloBack]      = "Barrier",
78 };
79 
80 static kbd_layout_t *kbd_layout;
81 
82 static int input_barrier_to_qcode(uint16_t keyid, uint16_t keycode)
83 {
84     /* keycode is optional, if it is not provided use keyid */
85     if (keycode && keycode <= qemu_input_map_xorgkbd_to_qcode_len) {
86         return qemu_input_map_xorgkbd_to_qcode[keycode];
87     }
88 
89     if (keyid >= 0xE000 && keyid <= 0xEFFF) {
90         keyid += 0x1000;
91     }
92 
93     /* keyid is the X11 key id */
94     if (kbd_layout) {
95         keycode = keysym2scancode(kbd_layout, keyid, NULL, false);
96 
97         return qemu_input_key_number_to_qcode(keycode);
98     }
99 
100     return qemu_input_map_x11_to_qcode[keyid];
101 }
102 
103 static int input_barrier_to_mouse(uint8_t buttonid)
104 {
105     switch (buttonid) {
106     case barrierButtonLeft:
107         return INPUT_BUTTON_LEFT;
108     case barrierButtonMiddle:
109         return INPUT_BUTTON_MIDDLE;
110     case barrierButtonRight:
111         return INPUT_BUTTON_RIGHT;
112     case barrierButtonExtra0:
113         return INPUT_BUTTON_SIDE;
114     }
115     return buttonid;
116 }
117 
118 #define read_char(x, p, l)           \
119 do {                                 \
120     int size = sizeof(char);         \
121     if (l < size) {                  \
122         return G_SOURCE_REMOVE;      \
123     }                                \
124     x = *(char *)p;                  \
125     p += size;                       \
126     l -= size;                       \
127 } while (0)
128 
129 #define read_short(x, p, l)          \
130 do {                                 \
131     int size = sizeof(short);        \
132     if (l < size) {                  \
133         return G_SOURCE_REMOVE;      \
134     }                                \
135     x = ntohs(*(short *)p);          \
136     p += size;                       \
137     l -= size;                       \
138 } while (0)
139 
140 #define write_short(p, x, l)         \
141 do {                                 \
142     int size = sizeof(short);        \
143     if (l < size) {                  \
144         return G_SOURCE_REMOVE;      \
145     }                                \
146     *(short *)p = htons(x);          \
147     p += size;                       \
148     l -= size;                       \
149 } while (0)
150 
151 #define read_int(x, p, l)            \
152 do {                                 \
153     int size = sizeof(int);          \
154     if (l < size) {                  \
155         return G_SOURCE_REMOVE;      \
156     }                                \
157     x = ntohl(*(int *)p);            \
158     p += size;                       \
159     l -= size;                       \
160 } while (0)
161 
162 #define write_int(p, x, l)           \
163 do {                                 \
164     int size = sizeof(int);          \
165     if (l < size) {                  \
166         return G_SOURCE_REMOVE;      \
167     }                                \
168     *(int *)p = htonl(x);            \
169     p += size;                       \
170     l -= size;                       \
171 } while (0)
172 
173 #define write_cmd(p, c, l)           \
174 do {                                 \
175     int size = strlen(cmd_names[c]); \
176     if (l < size) {                  \
177         return G_SOURCE_REMOVE;      \
178     }                                \
179     memcpy(p, cmd_names[c], size);   \
180     p += size;                       \
181     l -= size;                       \
182 } while (0)
183 
184 #define write_string(p, s, l)        \
185 do {                                 \
186     int size = strlen(s);            \
187     if (l < size + sizeof(int)) {    \
188         return G_SOURCE_REMOVE;      \
189     }                                \
190     *(int *)p = htonl(size);         \
191     p += sizeof(size);               \
192     l -= sizeof(size);               \
193     memcpy(p, s, size);              \
194     p += size;                       \
195     l -= size;                       \
196 } while (0)
197 
198 static gboolean readcmd(InputBarrier *ib, struct barrierMsg *msg)
199 {
200     int ret, len, i;
201     enum barrierCmd cmd;
202     char *p;
203 
204     ret = qio_channel_read(QIO_CHANNEL(ib->sioc), (char *)&len, sizeof(len),
205                            NULL);
206     if (ret < 0) {
207         return G_SOURCE_REMOVE;
208     }
209 
210     len = ntohl(len);
211     if (len > MAX_HELLO_LENGTH) {
212         return G_SOURCE_REMOVE;
213     }
214 
215     ret = qio_channel_read(QIO_CHANNEL(ib->sioc), ib->buffer, len, NULL);
216     if (ret < 0) {
217         return G_SOURCE_REMOVE;
218     }
219 
220     p = ib->buffer;
221     if (len >= strlen(cmd_names[barrierCmdHello]) &&
222         memcmp(p, cmd_names[barrierCmdHello],
223                strlen(cmd_names[barrierCmdHello])) == 0) {
224         cmd = barrierCmdHello;
225         p += strlen(cmd_names[barrierCmdHello]);
226         len -= strlen(cmd_names[barrierCmdHello]);
227     } else {
228         for (cmd = 0; cmd < barrierCmdHello; cmd++) {
229             if (memcmp(ib->buffer, cmd_names[cmd], 4) == 0) {
230                 break;
231             }
232         }
233 
234         if (cmd == barrierCmdHello) {
235             return G_SOURCE_REMOVE;
236         }
237         p += 4;
238         len -= 4;
239     }
240 
241     msg->cmd = cmd;
242     switch (cmd) {
243     /* connection */
244     case barrierCmdHello:
245         read_short(msg->version.major, p, len);
246         read_short(msg->version.minor, p, len);
247         break;
248     case barrierCmdDSetOptions:
249         read_int(msg->set.nb, p, len);
250         msg->set.nb /= 2;
251         if (msg->set.nb > BARRIER_MAX_OPTIONS) {
252             msg->set.nb = BARRIER_MAX_OPTIONS;
253         }
254         i = 0;
255         while (len && i < msg->set.nb) {
256             read_int(msg->set.option[i].id, p, len);
257             /* it's a string, restore endianness */
258             msg->set.option[i].id = htonl(msg->set.option[i].id);
259             msg->set.option[i].nul = 0;
260             read_int(msg->set.option[i].value, p, len);
261             i++;
262         }
263         break;
264     case barrierCmdQInfo:
265         break;
266 
267     /* mouse */
268     case barrierCmdDMouseMove:
269     case barrierCmdDMouseRelMove:
270         read_short(msg->mousepos.x, p, len);
271         read_short(msg->mousepos.y, p, len);
272         break;
273     case barrierCmdDMouseDown:
274     case barrierCmdDMouseUp:
275         read_char(msg->mousebutton.buttonid, p, len);
276         break;
277     case barrierCmdDMouseWheel:
278         read_short(msg->mousepos.y, p, len);
279         msg->mousepos.x = 0;
280         if (len) {
281             msg->mousepos.x = msg->mousepos.y;
282             read_short(msg->mousepos.y, p, len);
283         }
284         break;
285 
286     /* keyboard */
287     case barrierCmdDKeyDown:
288     case barrierCmdDKeyUp:
289         read_short(msg->key.keyid, p, len);
290         read_short(msg->key.modifier, p, len);
291         msg->key.button = 0;
292         if (len) {
293             read_short(msg->key.button, p, len);
294         }
295         break;
296     case barrierCmdDKeyRepeat:
297         read_short(msg->repeat.keyid, p, len);
298         read_short(msg->repeat.modifier, p, len);
299         read_short(msg->repeat.repeat, p, len);
300         msg->repeat.button = 0;
301         if (len) {
302             read_short(msg->repeat.button, p, len);
303         }
304         break;
305     case barrierCmdCInfoAck:
306     case barrierCmdCResetOptions:
307     case barrierCmdCEnter:
308     case barrierCmdDClipboard:
309     case barrierCmdCKeepAlive:
310     case barrierCmdCLeave:
311     case barrierCmdCClose:
312         break;
313 
314     /* Invalid from the server */
315     case barrierCmdHelloBack:
316     case barrierCmdCNoop:
317     case barrierCmdDInfo:
318         break;
319 
320     /* Error codes */
321     case barrierCmdEIncompatible:
322         read_short(msg->version.major, p, len);
323         read_short(msg->version.minor, p, len);
324         break;
325     case barrierCmdEBusy:
326     case barrierCmdEUnknown:
327     case barrierCmdEBad:
328         break;
329     default:
330         return G_SOURCE_REMOVE;
331     }
332 
333     return G_SOURCE_CONTINUE;
334 }
335 
336 static gboolean writecmd(InputBarrier *ib, struct barrierMsg *msg)
337 {
338     char *p;
339     int ret, i;
340     int avail, len;
341 
342     p = ib->buffer;
343     avail = MAX_HELLO_LENGTH;
344 
345     /* reserve space to store the length */
346     p += sizeof(int);
347     avail -= sizeof(int);
348 
349     switch (msg->cmd) {
350     case barrierCmdHello:
351         if (msg->version.major < BARRIER_VERSION_MAJOR ||
352             (msg->version.major == BARRIER_VERSION_MAJOR &&
353              msg->version.minor < BARRIER_VERSION_MINOR)) {
354             ib->ioc_tag = 0;
355             return G_SOURCE_REMOVE;
356         }
357         write_cmd(p, barrierCmdHelloBack, avail);
358         write_short(p, BARRIER_VERSION_MAJOR, avail);
359         write_short(p, BARRIER_VERSION_MINOR, avail);
360         write_string(p, ib->name, avail);
361         break;
362     case barrierCmdCClose:
363         ib->ioc_tag = 0;
364         return G_SOURCE_REMOVE;
365     case barrierCmdQInfo:
366         write_cmd(p, barrierCmdDInfo, avail);
367         write_short(p, ib->x_origin, avail);
368         write_short(p, ib->y_origin, avail);
369         write_short(p, ib->width, avail);
370         write_short(p, ib->height, avail);
371         write_short(p, 0, avail);    /* warpsize (obsolete) */
372         write_short(p, 0, avail);    /* mouse x */
373         write_short(p, 0, avail);    /* mouse y */
374         break;
375     case barrierCmdCInfoAck:
376         break;
377     case barrierCmdCResetOptions:
378         /* TODO: reset options */
379         break;
380     case barrierCmdDSetOptions:
381         /* TODO: set options */
382         break;
383     case barrierCmdCEnter:
384         break;
385     case barrierCmdDClipboard:
386         break;
387     case barrierCmdCKeepAlive:
388         write_cmd(p, barrierCmdCKeepAlive, avail);
389         break;
390     case barrierCmdCLeave:
391         break;
392 
393     /* mouse */
394     case barrierCmdDMouseMove:
395         qemu_input_queue_abs(NULL, INPUT_AXIS_X, msg->mousepos.x,
396                              ib->x_origin, ib->width);
397         qemu_input_queue_abs(NULL, INPUT_AXIS_Y, msg->mousepos.y,
398                              ib->y_origin, ib->height);
399         qemu_input_event_sync();
400         break;
401     case barrierCmdDMouseRelMove:
402         qemu_input_queue_rel(NULL, INPUT_AXIS_X, msg->mousepos.x);
403         qemu_input_queue_rel(NULL, INPUT_AXIS_Y, msg->mousepos.y);
404         qemu_input_event_sync();
405         break;
406     case barrierCmdDMouseDown:
407         qemu_input_queue_btn(NULL,
408                              input_barrier_to_mouse(msg->mousebutton.buttonid),
409                              true);
410         qemu_input_event_sync();
411         break;
412     case barrierCmdDMouseUp:
413         qemu_input_queue_btn(NULL,
414                              input_barrier_to_mouse(msg->mousebutton.buttonid),
415                              false);
416         qemu_input_event_sync();
417         break;
418     case barrierCmdDMouseWheel:
419         qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP
420                              : INPUT_BUTTON_WHEEL_DOWN, true);
421         qemu_input_event_sync();
422         qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP
423                              : INPUT_BUTTON_WHEEL_DOWN, false);
424         qemu_input_event_sync();
425         break;
426 
427     /* keyboard */
428     case barrierCmdDKeyDown:
429         qemu_input_event_send_key_qcode(NULL,
430                         input_barrier_to_qcode(msg->key.keyid, msg->key.button),
431                                         true);
432         break;
433     case barrierCmdDKeyRepeat:
434         for (i = 0; i < msg->repeat.repeat; i++) {
435             qemu_input_event_send_key_qcode(NULL,
436                   input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button),
437                                             false);
438             qemu_input_event_send_key_qcode(NULL,
439                   input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button),
440                                             true);
441         }
442         break;
443     case barrierCmdDKeyUp:
444         qemu_input_event_send_key_qcode(NULL,
445                         input_barrier_to_qcode(msg->key.keyid, msg->key.button),
446                                         false);
447         break;
448     default:
449         write_cmd(p, barrierCmdEUnknown, avail);
450         break;
451     }
452 
453     len = MAX_HELLO_LENGTH - avail - sizeof(int);
454     if (len) {
455         p = ib->buffer;
456         avail = sizeof(len);
457         write_int(p, len, avail);
458         ret = qio_channel_write(QIO_CHANNEL(ib->sioc), ib->buffer,
459                                 len + sizeof(len), NULL);
460         if (ret < 0) {
461             ib->ioc_tag = 0;
462             return G_SOURCE_REMOVE;
463         }
464     }
465 
466     return G_SOURCE_CONTINUE;
467 }
468 
469 static gboolean input_barrier_event(QIOChannel *ioc G_GNUC_UNUSED,
470                                     GIOCondition condition, void *opaque)
471 {
472     InputBarrier *ib = opaque;
473     int ret;
474     struct barrierMsg msg;
475 
476     ret = readcmd(ib, &msg);
477     if (ret == G_SOURCE_REMOVE) {
478         ib->ioc_tag = 0;
479         return G_SOURCE_REMOVE;
480     }
481 
482     return writecmd(ib, &msg);
483 }
484 
485 static void input_barrier_complete(UserCreatable *uc, Error **errp)
486 {
487     InputBarrier *ib = INPUT_BARRIER(uc);
488     Error *local_err = NULL;
489 
490     if (!ib->name) {
491         error_setg(errp, QERR_MISSING_PARAMETER, "name");
492         return;
493     }
494 
495     /*
496      * Connect to the primary
497      * Primary is the server where the keyboard and the mouse
498      * are connected and forwarded to the secondary (the client)
499      */
500 
501     ib->sioc = qio_channel_socket_new();
502     qio_channel_set_name(QIO_CHANNEL(ib->sioc), "barrier-client");
503 
504     qio_channel_socket_connect_sync(ib->sioc, &ib->saddr, &local_err);
505     if (local_err) {
506         error_propagate(errp, local_err);
507         return;
508     }
509 
510     qio_channel_set_delay(QIO_CHANNEL(ib->sioc), false);
511 
512     ib->ioc_tag = qio_channel_add_watch(QIO_CHANNEL(ib->sioc), G_IO_IN,
513                                         input_barrier_event, ib, NULL);
514 }
515 
516 static void input_barrier_instance_finalize(Object *obj)
517 {
518     InputBarrier *ib = INPUT_BARRIER(obj);
519 
520     if (ib->ioc_tag) {
521         g_source_remove(ib->ioc_tag);
522         ib->ioc_tag = 0;
523     }
524 
525     if (ib->sioc) {
526         qio_channel_close(QIO_CHANNEL(ib->sioc), NULL);
527         object_unref(OBJECT(ib->sioc));
528     }
529     g_free(ib->name);
530     g_free(ib->saddr.u.inet.host);
531     g_free(ib->saddr.u.inet.port);
532 }
533 
534 static char *input_barrier_get_name(Object *obj, Error **errp)
535 {
536     InputBarrier *ib = INPUT_BARRIER(obj);
537 
538     return g_strdup(ib->name);
539 }
540 
541 static void input_barrier_set_name(Object *obj, const char *value,
542                                   Error **errp)
543 {
544     InputBarrier *ib = INPUT_BARRIER(obj);
545 
546     if (ib->name) {
547         error_setg(errp, "name property already set");
548         return;
549     }
550     ib->name = g_strdup(value);
551 }
552 
553 static char *input_barrier_get_server(Object *obj, Error **errp)
554 {
555     InputBarrier *ib = INPUT_BARRIER(obj);
556 
557     return g_strdup(ib->saddr.u.inet.host);
558 }
559 
560 static void input_barrier_set_server(Object *obj, const char *value,
561                                      Error **errp)
562 {
563     InputBarrier *ib = INPUT_BARRIER(obj);
564 
565     g_free(ib->saddr.u.inet.host);
566     ib->saddr.u.inet.host = g_strdup(value);
567 }
568 
569 static char *input_barrier_get_port(Object *obj, Error **errp)
570 {
571     InputBarrier *ib = INPUT_BARRIER(obj);
572 
573     return g_strdup(ib->saddr.u.inet.port);
574 }
575 
576 static void input_barrier_set_port(Object *obj, const char *value,
577                                      Error **errp)
578 {
579     InputBarrier *ib = INPUT_BARRIER(obj);
580 
581     g_free(ib->saddr.u.inet.port);
582     ib->saddr.u.inet.port = g_strdup(value);
583 }
584 
585 static void input_barrier_set_x_origin(Object *obj, const char *value,
586                                        Error **errp)
587 {
588     InputBarrier *ib = INPUT_BARRIER(obj);
589     int result, err;
590 
591     err = qemu_strtoi(value, NULL, 0, &result);
592     if (err < 0 || result < 0 || result > SHRT_MAX) {
593         error_setg(errp,
594                    "x-origin property must be in the range [0..%d]", SHRT_MAX);
595         return;
596     }
597     ib->x_origin = result;
598 }
599 
600 static char *input_barrier_get_x_origin(Object *obj, Error **errp)
601 {
602     InputBarrier *ib = INPUT_BARRIER(obj);
603 
604     return g_strdup_printf("%d", ib->x_origin);
605 }
606 
607 static void input_barrier_set_y_origin(Object *obj, const char *value,
608                                        Error **errp)
609 {
610     InputBarrier *ib = INPUT_BARRIER(obj);
611     int result, err;
612 
613     err = qemu_strtoi(value, NULL, 0, &result);
614     if (err < 0 || result < 0 || result > SHRT_MAX) {
615         error_setg(errp,
616                    "y-origin property must be in the range [0..%d]", SHRT_MAX);
617         return;
618     }
619     ib->y_origin = result;
620 }
621 
622 static char *input_barrier_get_y_origin(Object *obj, Error **errp)
623 {
624     InputBarrier *ib = INPUT_BARRIER(obj);
625 
626     return g_strdup_printf("%d", ib->y_origin);
627 }
628 
629 static void input_barrier_set_width(Object *obj, const char *value,
630                                        Error **errp)
631 {
632     InputBarrier *ib = INPUT_BARRIER(obj);
633     int result, err;
634 
635     err = qemu_strtoi(value, NULL, 0, &result);
636     if (err < 0 || result < 0 || result > SHRT_MAX) {
637         error_setg(errp,
638                    "width property must be in the range [0..%d]", SHRT_MAX);
639         return;
640     }
641     ib->width = result;
642 }
643 
644 static char *input_barrier_get_width(Object *obj, Error **errp)
645 {
646     InputBarrier *ib = INPUT_BARRIER(obj);
647 
648     return g_strdup_printf("%d", ib->width);
649 }
650 
651 static void input_barrier_set_height(Object *obj, const char *value,
652                                        Error **errp)
653 {
654     InputBarrier *ib = INPUT_BARRIER(obj);
655     int result, err;
656 
657     err = qemu_strtoi(value, NULL, 0, &result);
658     if (err < 0 || result < 0 || result > SHRT_MAX) {
659         error_setg(errp,
660                    "height property must be in the range [0..%d]", SHRT_MAX);
661         return;
662     }
663     ib->height = result;
664 }
665 
666 static char *input_barrier_get_height(Object *obj, Error **errp)
667 {
668     InputBarrier *ib = INPUT_BARRIER(obj);
669 
670     return g_strdup_printf("%d", ib->height);
671 }
672 
673 static void input_barrier_instance_init(Object *obj)
674 {
675     InputBarrier *ib = INPUT_BARRIER(obj);
676 
677     /* always use generic keymaps */
678     if (keyboard_layout && !kbd_layout) {
679         /* We use X11 key id, so use VNC name2keysym */
680         kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
681                                           &error_fatal);
682     }
683 
684     ib->saddr.type = SOCKET_ADDRESS_TYPE_INET;
685     ib->saddr.u.inet.host = g_strdup("localhost");
686     ib->saddr.u.inet.port = g_strdup("24800");
687 
688     ib->x_origin = 0;
689     ib->y_origin = 0;
690     ib->width = 1920;
691     ib->height = 1080;
692 
693     object_property_add_str(obj, "name",
694                             input_barrier_get_name,
695                             input_barrier_set_name);
696     object_property_add_str(obj, "server",
697                             input_barrier_get_server,
698                             input_barrier_set_server);
699     object_property_add_str(obj, "port",
700                             input_barrier_get_port,
701                             input_barrier_set_port);
702     object_property_add_str(obj, "x-origin",
703                             input_barrier_get_x_origin,
704                             input_barrier_set_x_origin);
705     object_property_add_str(obj, "y-origin",
706                             input_barrier_get_y_origin,
707                             input_barrier_set_y_origin);
708     object_property_add_str(obj, "width",
709                             input_barrier_get_width,
710                             input_barrier_set_width);
711     object_property_add_str(obj, "height",
712                             input_barrier_get_height,
713                             input_barrier_set_height);
714 }
715 
716 static void input_barrier_class_init(ObjectClass *oc, void *data)
717 {
718     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
719 
720     ucc->complete = input_barrier_complete;
721 }
722 
723 static const TypeInfo input_barrier_info = {
724     .name = TYPE_INPUT_BARRIER,
725     .parent = TYPE_OBJECT,
726     .class_init = input_barrier_class_init,
727     .instance_size = sizeof(InputBarrier),
728     .instance_init = input_barrier_instance_init,
729     .instance_finalize = input_barrier_instance_finalize,
730     .interfaces = (InterfaceInfo[]) {
731         { TYPE_USER_CREATABLE },
732         { }
733     }
734 };
735 
736 static void register_types(void)
737 {
738     type_register_static(&input_barrier_info);
739 }
740 
741 type_init(register_types);
742