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