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