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