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 "sysemu/sysemu.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 Error *local_err = NULL; 494 495 if (!ib->name) { 496 error_setg(errp, QERR_MISSING_PARAMETER, "name"); 497 return; 498 } 499 500 /* 501 * Connect to the primary 502 * Primary is the server where the keyboard and the mouse 503 * are connected and forwarded to the secondary (the client) 504 */ 505 506 ib->sioc = qio_channel_socket_new(); 507 qio_channel_set_name(QIO_CHANNEL(ib->sioc), "barrier-client"); 508 509 qio_channel_socket_connect_sync(ib->sioc, &ib->saddr, &local_err); 510 if (local_err) { 511 error_propagate(errp, local_err); 512 return; 513 } 514 515 qio_channel_set_delay(QIO_CHANNEL(ib->sioc), false); 516 517 ib->ioc_tag = qio_channel_add_watch(QIO_CHANNEL(ib->sioc), G_IO_IN, 518 input_barrier_event, ib, NULL); 519 } 520 521 static void input_barrier_instance_finalize(Object *obj) 522 { 523 InputBarrier *ib = INPUT_BARRIER(obj); 524 525 if (ib->ioc_tag) { 526 g_source_remove(ib->ioc_tag); 527 ib->ioc_tag = 0; 528 } 529 530 if (ib->sioc) { 531 qio_channel_close(QIO_CHANNEL(ib->sioc), NULL); 532 object_unref(OBJECT(ib->sioc)); 533 } 534 g_free(ib->name); 535 g_free(ib->saddr.u.inet.host); 536 g_free(ib->saddr.u.inet.port); 537 } 538 539 static char *input_barrier_get_name(Object *obj, Error **errp) 540 { 541 InputBarrier *ib = INPUT_BARRIER(obj); 542 543 return g_strdup(ib->name); 544 } 545 546 static void input_barrier_set_name(Object *obj, const char *value, 547 Error **errp) 548 { 549 InputBarrier *ib = INPUT_BARRIER(obj); 550 551 if (ib->name) { 552 error_setg(errp, "name property already set"); 553 return; 554 } 555 ib->name = g_strdup(value); 556 } 557 558 static char *input_barrier_get_server(Object *obj, Error **errp) 559 { 560 InputBarrier *ib = INPUT_BARRIER(obj); 561 562 return g_strdup(ib->saddr.u.inet.host); 563 } 564 565 static void input_barrier_set_server(Object *obj, const char *value, 566 Error **errp) 567 { 568 InputBarrier *ib = INPUT_BARRIER(obj); 569 570 g_free(ib->saddr.u.inet.host); 571 ib->saddr.u.inet.host = g_strdup(value); 572 } 573 574 static char *input_barrier_get_port(Object *obj, Error **errp) 575 { 576 InputBarrier *ib = INPUT_BARRIER(obj); 577 578 return g_strdup(ib->saddr.u.inet.port); 579 } 580 581 static void input_barrier_set_port(Object *obj, const char *value, 582 Error **errp) 583 { 584 InputBarrier *ib = INPUT_BARRIER(obj); 585 586 g_free(ib->saddr.u.inet.port); 587 ib->saddr.u.inet.port = g_strdup(value); 588 } 589 590 static void input_barrier_set_x_origin(Object *obj, const char *value, 591 Error **errp) 592 { 593 InputBarrier *ib = INPUT_BARRIER(obj); 594 int result, err; 595 596 err = qemu_strtoi(value, NULL, 0, &result); 597 if (err < 0 || result < 0 || result > SHRT_MAX) { 598 error_setg(errp, 599 "x-origin property must be in the range [0..%d]", SHRT_MAX); 600 return; 601 } 602 ib->x_origin = result; 603 } 604 605 static char *input_barrier_get_x_origin(Object *obj, Error **errp) 606 { 607 InputBarrier *ib = INPUT_BARRIER(obj); 608 609 return g_strdup_printf("%d", ib->x_origin); 610 } 611 612 static void input_barrier_set_y_origin(Object *obj, const char *value, 613 Error **errp) 614 { 615 InputBarrier *ib = INPUT_BARRIER(obj); 616 int result, err; 617 618 err = qemu_strtoi(value, NULL, 0, &result); 619 if (err < 0 || result < 0 || result > SHRT_MAX) { 620 error_setg(errp, 621 "y-origin property must be in the range [0..%d]", SHRT_MAX); 622 return; 623 } 624 ib->y_origin = result; 625 } 626 627 static char *input_barrier_get_y_origin(Object *obj, Error **errp) 628 { 629 InputBarrier *ib = INPUT_BARRIER(obj); 630 631 return g_strdup_printf("%d", ib->y_origin); 632 } 633 634 static void input_barrier_set_width(Object *obj, const char *value, 635 Error **errp) 636 { 637 InputBarrier *ib = INPUT_BARRIER(obj); 638 int result, err; 639 640 err = qemu_strtoi(value, NULL, 0, &result); 641 if (err < 0 || result < 0 || result > SHRT_MAX) { 642 error_setg(errp, 643 "width property must be in the range [0..%d]", SHRT_MAX); 644 return; 645 } 646 ib->width = result; 647 } 648 649 static char *input_barrier_get_width(Object *obj, Error **errp) 650 { 651 InputBarrier *ib = INPUT_BARRIER(obj); 652 653 return g_strdup_printf("%d", ib->width); 654 } 655 656 static void input_barrier_set_height(Object *obj, const char *value, 657 Error **errp) 658 { 659 InputBarrier *ib = INPUT_BARRIER(obj); 660 int result, err; 661 662 err = qemu_strtoi(value, NULL, 0, &result); 663 if (err < 0 || result < 0 || result > SHRT_MAX) { 664 error_setg(errp, 665 "height property must be in the range [0..%d]", SHRT_MAX); 666 return; 667 } 668 ib->height = result; 669 } 670 671 static char *input_barrier_get_height(Object *obj, Error **errp) 672 { 673 InputBarrier *ib = INPUT_BARRIER(obj); 674 675 return g_strdup_printf("%d", ib->height); 676 } 677 678 static void input_barrier_instance_init(Object *obj) 679 { 680 InputBarrier *ib = INPUT_BARRIER(obj); 681 682 /* always use generic keymaps */ 683 if (keyboard_layout && !kbd_layout) { 684 /* We use X11 key id, so use VNC name2keysym */ 685 kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout, 686 &error_fatal); 687 } 688 689 ib->saddr.type = SOCKET_ADDRESS_TYPE_INET; 690 ib->saddr.u.inet.host = g_strdup("localhost"); 691 ib->saddr.u.inet.port = g_strdup("24800"); 692 693 ib->x_origin = 0; 694 ib->y_origin = 0; 695 ib->width = 1920; 696 ib->height = 1080; 697 } 698 699 static void input_barrier_class_init(ObjectClass *oc, void *data) 700 { 701 UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); 702 703 ucc->complete = input_barrier_complete; 704 705 object_class_property_add_str(oc, "name", 706 input_barrier_get_name, 707 input_barrier_set_name); 708 object_class_property_add_str(oc, "server", 709 input_barrier_get_server, 710 input_barrier_set_server); 711 object_class_property_add_str(oc, "port", 712 input_barrier_get_port, 713 input_barrier_set_port); 714 object_class_property_add_str(oc, "x-origin", 715 input_barrier_get_x_origin, 716 input_barrier_set_x_origin); 717 object_class_property_add_str(oc, "y-origin", 718 input_barrier_get_y_origin, 719 input_barrier_set_y_origin); 720 object_class_property_add_str(oc, "width", 721 input_barrier_get_width, 722 input_barrier_set_width); 723 object_class_property_add_str(oc, "height", 724 input_barrier_get_height, 725 input_barrier_set_height); 726 } 727 728 static const TypeInfo input_barrier_info = { 729 .name = TYPE_INPUT_BARRIER, 730 .parent = TYPE_OBJECT, 731 .class_init = input_barrier_class_init, 732 .instance_size = sizeof(InputBarrier), 733 .instance_init = input_barrier_instance_init, 734 .instance_finalize = input_barrier_instance_finalize, 735 .interfaces = (InterfaceInfo[]) { 736 { TYPE_USER_CREATABLE }, 737 { } 738 } 739 }; 740 741 static void register_types(void) 742 { 743 type_register_static(&input_barrier_info); 744 } 745 746 type_init(register_types); 747