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