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