| /* |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| * See the COPYING file in the top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "sysemu/sysemu.h" |
| #include "qemu/main-loop.h" |
| #include "qemu/sockets.h" |
| #include "qapi/error.h" |
| #include "qom/object_interfaces.h" |
| #include "io/channel-socket.h" |
| #include "ui/input.h" |
| #include "qom/object.h" |
| #include "ui/vnc_keysym.h" /* use name2keysym from VNC as we use X11 values */ |
| #include "qemu/cutils.h" |
| #include "qapi/qmp/qerror.h" |
| #include "input-barrier.h" |
| |
| #define TYPE_INPUT_BARRIER "input-barrier" |
| OBJECT_DECLARE_SIMPLE_TYPE(InputBarrier, |
| INPUT_BARRIER) |
| |
| |
| #define MAX_HELLO_LENGTH 1024 |
| |
| struct InputBarrier { |
| Object parent; |
| |
| QIOChannelSocket *sioc; |
| guint ioc_tag; |
| |
| /* display properties */ |
| gchar *name; |
| int16_t x_origin, y_origin; |
| int16_t width, height; |
| |
| /* keyboard/mouse server */ |
| |
| SocketAddress saddr; |
| |
| char buffer[MAX_HELLO_LENGTH]; |
| }; |
| |
| |
| static const char *cmd_names[] = { |
| [barrierCmdCNoop] = "CNOP", |
| [barrierCmdCClose] = "CBYE", |
| [barrierCmdCEnter] = "CINN", |
| [barrierCmdCLeave] = "COUT", |
| [barrierCmdCClipboard] = "CCLP", |
| [barrierCmdCScreenSaver] = "CSEC", |
| [barrierCmdCResetOptions] = "CROP", |
| [barrierCmdCInfoAck] = "CIAK", |
| [barrierCmdCKeepAlive] = "CALV", |
| [barrierCmdDKeyDown] = "DKDN", |
| [barrierCmdDKeyRepeat] = "DKRP", |
| [barrierCmdDKeyUp] = "DKUP", |
| [barrierCmdDMouseDown] = "DMDN", |
| [barrierCmdDMouseUp] = "DMUP", |
| [barrierCmdDMouseMove] = "DMMV", |
| [barrierCmdDMouseRelMove] = "DMRM", |
| [barrierCmdDMouseWheel] = "DMWM", |
| [barrierCmdDClipboard] = "DCLP", |
| [barrierCmdDInfo] = "DINF", |
| [barrierCmdDSetOptions] = "DSOP", |
| [barrierCmdDFileTransfer] = "DFTR", |
| [barrierCmdDDragInfo] = "DDRG", |
| [barrierCmdQInfo] = "QINF", |
| [barrierCmdEIncompatible] = "EICV", |
| [barrierCmdEBusy] = "EBSY", |
| [barrierCmdEUnknown] = "EUNK", |
| [barrierCmdEBad] = "EBAD", |
| [barrierCmdHello] = "Barrier", |
| [barrierCmdHelloBack] = "Barrier", |
| }; |
| |
| static kbd_layout_t *kbd_layout; |
| |
| static int input_barrier_to_qcode(uint16_t keyid, uint16_t keycode) |
| { |
| /* keycode is optional, if it is not provided use keyid */ |
| if (keycode && keycode <= qemu_input_map_xorgkbd_to_qcode_len) { |
| return qemu_input_map_xorgkbd_to_qcode[keycode]; |
| } |
| |
| if (keyid >= 0xE000 && keyid <= 0xEFFF) { |
| keyid += 0x1000; |
| } |
| |
| /* keyid is the X11 key id */ |
| if (kbd_layout) { |
| keycode = keysym2scancode(kbd_layout, keyid, NULL, false); |
| |
| return qemu_input_key_number_to_qcode(keycode); |
| } |
| |
| return qemu_input_map_x11_to_qcode[keyid]; |
| } |
| |
| static int input_barrier_to_mouse(uint8_t buttonid) |
| { |
| switch (buttonid) { |
| case barrierButtonLeft: |
| return INPUT_BUTTON_LEFT; |
| case barrierButtonMiddle: |
| return INPUT_BUTTON_MIDDLE; |
| case barrierButtonRight: |
| return INPUT_BUTTON_RIGHT; |
| case barrierButtonExtra0: |
| return INPUT_BUTTON_SIDE; |
| } |
| return buttonid; |
| } |
| |
| #define read_char(x, p, l) \ |
| do { \ |
| int size = sizeof(char); \ |
| if (l < size) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| x = *(char *)p; \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| #define read_short(x, p, l) \ |
| do { \ |
| int size = sizeof(short); \ |
| if (l < size) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| x = ntohs(*(short *)p); \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| #define write_short(p, x, l) \ |
| do { \ |
| int size = sizeof(short); \ |
| if (l < size) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| *(short *)p = htons(x); \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| #define read_int(x, p, l) \ |
| do { \ |
| int size = sizeof(int); \ |
| if (l < size) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| x = ntohl(*(int *)p); \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| #define write_int(p, x, l) \ |
| do { \ |
| int size = sizeof(int); \ |
| if (l < size) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| *(int *)p = htonl(x); \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| #define write_cmd(p, c, l) \ |
| do { \ |
| int size = strlen(cmd_names[c]); \ |
| if (l < size) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| memcpy(p, cmd_names[c], size); \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| #define write_string(p, s, l) \ |
| do { \ |
| int size = strlen(s); \ |
| if (l < size + sizeof(int)) { \ |
| return G_SOURCE_REMOVE; \ |
| } \ |
| *(int *)p = htonl(size); \ |
| p += sizeof(size); \ |
| l -= sizeof(size); \ |
| memcpy(p, s, size); \ |
| p += size; \ |
| l -= size; \ |
| } while (0) |
| |
| static gboolean readcmd(InputBarrier *ib, struct barrierMsg *msg) |
| { |
| int ret, len, i; |
| enum barrierCmd cmd; |
| char *p; |
| |
| ret = qio_channel_read(QIO_CHANNEL(ib->sioc), (char *)&len, sizeof(len), |
| NULL); |
| if (ret < 0) { |
| return G_SOURCE_REMOVE; |
| } |
| |
| len = ntohl(len); |
| if (len > MAX_HELLO_LENGTH) { |
| return G_SOURCE_REMOVE; |
| } |
| |
| ret = qio_channel_read(QIO_CHANNEL(ib->sioc), ib->buffer, len, NULL); |
| if (ret < 0) { |
| return G_SOURCE_REMOVE; |
| } |
| |
| p = ib->buffer; |
| if (len >= strlen(cmd_names[barrierCmdHello]) && |
| memcmp(p, cmd_names[barrierCmdHello], |
| strlen(cmd_names[barrierCmdHello])) == 0) { |
| cmd = barrierCmdHello; |
| p += strlen(cmd_names[barrierCmdHello]); |
| len -= strlen(cmd_names[barrierCmdHello]); |
| } else { |
| for (cmd = 0; cmd < barrierCmdHello; cmd++) { |
| if (memcmp(ib->buffer, cmd_names[cmd], 4) == 0) { |
| break; |
| } |
| } |
| |
| if (cmd == barrierCmdHello) { |
| return G_SOURCE_REMOVE; |
| } |
| p += 4; |
| len -= 4; |
| } |
| |
| msg->cmd = cmd; |
| switch (cmd) { |
| /* connection */ |
| case barrierCmdHello: |
| read_short(msg->version.major, p, len); |
| read_short(msg->version.minor, p, len); |
| break; |
| case barrierCmdDSetOptions: |
| read_int(msg->set.nb, p, len); |
| msg->set.nb /= 2; |
| if (msg->set.nb > BARRIER_MAX_OPTIONS) { |
| msg->set.nb = BARRIER_MAX_OPTIONS; |
| } |
| i = 0; |
| while (len && i < msg->set.nb) { |
| read_int(msg->set.option[i].id, p, len); |
| /* it's a string, restore endianness */ |
| msg->set.option[i].id = htonl(msg->set.option[i].id); |
| msg->set.option[i].nul = 0; |
| read_int(msg->set.option[i].value, p, len); |
| i++; |
| } |
| break; |
| case barrierCmdQInfo: |
| break; |
| |
| /* mouse */ |
| case barrierCmdDMouseMove: |
| case barrierCmdDMouseRelMove: |
| read_short(msg->mousepos.x, p, len); |
| read_short(msg->mousepos.y, p, len); |
| break; |
| case barrierCmdDMouseDown: |
| case barrierCmdDMouseUp: |
| read_char(msg->mousebutton.buttonid, p, len); |
| break; |
| case barrierCmdDMouseWheel: |
| read_short(msg->mousepos.y, p, len); |
| msg->mousepos.x = 0; |
| if (len) { |
| msg->mousepos.x = msg->mousepos.y; |
| read_short(msg->mousepos.y, p, len); |
| } |
| break; |
| |
| /* keyboard */ |
| case barrierCmdDKeyDown: |
| case barrierCmdDKeyUp: |
| read_short(msg->key.keyid, p, len); |
| read_short(msg->key.modifier, p, len); |
| msg->key.button = 0; |
| if (len) { |
| read_short(msg->key.button, p, len); |
| } |
| break; |
| case barrierCmdDKeyRepeat: |
| read_short(msg->repeat.keyid, p, len); |
| read_short(msg->repeat.modifier, p, len); |
| read_short(msg->repeat.repeat, p, len); |
| msg->repeat.button = 0; |
| if (len) { |
| read_short(msg->repeat.button, p, len); |
| } |
| break; |
| case barrierCmdCInfoAck: |
| case barrierCmdCResetOptions: |
| case barrierCmdCEnter: |
| case barrierCmdDClipboard: |
| case barrierCmdCKeepAlive: |
| case barrierCmdCLeave: |
| case barrierCmdCClose: |
| break; |
| |
| /* Invalid from the server */ |
| case barrierCmdHelloBack: |
| case barrierCmdCNoop: |
| case barrierCmdDInfo: |
| break; |
| |
| /* Error codes */ |
| case barrierCmdEIncompatible: |
| read_short(msg->version.major, p, len); |
| read_short(msg->version.minor, p, len); |
| break; |
| case barrierCmdEBusy: |
| case barrierCmdEUnknown: |
| case barrierCmdEBad: |
| break; |
| default: |
| return G_SOURCE_REMOVE; |
| } |
| |
| return G_SOURCE_CONTINUE; |
| } |
| |
| static gboolean writecmd(InputBarrier *ib, struct barrierMsg *msg) |
| { |
| char *p; |
| int ret, i; |
| int avail, len; |
| |
| p = ib->buffer; |
| avail = MAX_HELLO_LENGTH; |
| |
| /* reserve space to store the length */ |
| p += sizeof(int); |
| avail -= sizeof(int); |
| |
| switch (msg->cmd) { |
| case barrierCmdHello: |
| if (msg->version.major < BARRIER_VERSION_MAJOR || |
| (msg->version.major == BARRIER_VERSION_MAJOR && |
| msg->version.minor < BARRIER_VERSION_MINOR)) { |
| ib->ioc_tag = 0; |
| return G_SOURCE_REMOVE; |
| } |
| write_cmd(p, barrierCmdHelloBack, avail); |
| write_short(p, BARRIER_VERSION_MAJOR, avail); |
| write_short(p, BARRIER_VERSION_MINOR, avail); |
| write_string(p, ib->name, avail); |
| break; |
| case barrierCmdCClose: |
| ib->ioc_tag = 0; |
| return G_SOURCE_REMOVE; |
| case barrierCmdQInfo: |
| write_cmd(p, barrierCmdDInfo, avail); |
| write_short(p, ib->x_origin, avail); |
| write_short(p, ib->y_origin, avail); |
| write_short(p, ib->width, avail); |
| write_short(p, ib->height, avail); |
| write_short(p, 0, avail); /* warpsize (obsolete) */ |
| write_short(p, 0, avail); /* mouse x */ |
| write_short(p, 0, avail); /* mouse y */ |
| break; |
| case barrierCmdCInfoAck: |
| break; |
| case barrierCmdCResetOptions: |
| /* TODO: reset options */ |
| break; |
| case barrierCmdDSetOptions: |
| /* TODO: set options */ |
| break; |
| case barrierCmdCEnter: |
| break; |
| case barrierCmdDClipboard: |
| break; |
| case barrierCmdCKeepAlive: |
| write_cmd(p, barrierCmdCKeepAlive, avail); |
| break; |
| case barrierCmdCLeave: |
| break; |
| |
| /* mouse */ |
| case barrierCmdDMouseMove: |
| qemu_input_queue_abs(NULL, INPUT_AXIS_X, msg->mousepos.x, |
| ib->x_origin, ib->width); |
| qemu_input_queue_abs(NULL, INPUT_AXIS_Y, msg->mousepos.y, |
| ib->y_origin, ib->height); |
| qemu_input_event_sync(); |
| break; |
| case barrierCmdDMouseRelMove: |
| qemu_input_queue_rel(NULL, INPUT_AXIS_X, msg->mousepos.x); |
| qemu_input_queue_rel(NULL, INPUT_AXIS_Y, msg->mousepos.y); |
| qemu_input_event_sync(); |
| break; |
| case barrierCmdDMouseDown: |
| qemu_input_queue_btn(NULL, |
| input_barrier_to_mouse(msg->mousebutton.buttonid), |
| true); |
| qemu_input_event_sync(); |
| break; |
| case barrierCmdDMouseUp: |
| qemu_input_queue_btn(NULL, |
| input_barrier_to_mouse(msg->mousebutton.buttonid), |
| false); |
| qemu_input_event_sync(); |
| break; |
| case barrierCmdDMouseWheel: |
| qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP |
| : INPUT_BUTTON_WHEEL_DOWN, true); |
| qemu_input_event_sync(); |
| qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP |
| : INPUT_BUTTON_WHEEL_DOWN, false); |
| qemu_input_event_sync(); |
| break; |
| |
| /* keyboard */ |
| case barrierCmdDKeyDown: |
| qemu_input_event_send_key_qcode(NULL, |
| input_barrier_to_qcode(msg->key.keyid, msg->key.button), |
| true); |
| break; |
| case barrierCmdDKeyRepeat: |
| for (i = 0; i < msg->repeat.repeat; i++) { |
| qemu_input_event_send_key_qcode(NULL, |
| input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button), |
| false); |
| qemu_input_event_send_key_qcode(NULL, |
| input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button), |
| true); |
| } |
| break; |
| case barrierCmdDKeyUp: |
| qemu_input_event_send_key_qcode(NULL, |
| input_barrier_to_qcode(msg->key.keyid, msg->key.button), |
| false); |
| break; |
| default: |
| write_cmd(p, barrierCmdEUnknown, avail); |
| break; |
| } |
| |
| len = MAX_HELLO_LENGTH - avail - sizeof(int); |
| if (len) { |
| p = ib->buffer; |
| avail = sizeof(len); |
| write_int(p, len, avail); |
| ret = qio_channel_write(QIO_CHANNEL(ib->sioc), ib->buffer, |
| len + sizeof(len), NULL); |
| if (ret < 0) { |
| ib->ioc_tag = 0; |
| return G_SOURCE_REMOVE; |
| } |
| } |
| |
| return G_SOURCE_CONTINUE; |
| } |
| |
| static gboolean input_barrier_event(QIOChannel *ioc G_GNUC_UNUSED, |
| GIOCondition condition, void *opaque) |
| { |
| InputBarrier *ib = opaque; |
| int ret; |
| struct barrierMsg msg; |
| |
| ret = readcmd(ib, &msg); |
| if (ret == G_SOURCE_REMOVE) { |
| ib->ioc_tag = 0; |
| return G_SOURCE_REMOVE; |
| } |
| |
| return writecmd(ib, &msg); |
| } |
| |
| static void input_barrier_complete(UserCreatable *uc, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(uc); |
| Error *local_err = NULL; |
| |
| if (!ib->name) { |
| error_setg(errp, QERR_MISSING_PARAMETER, "name"); |
| return; |
| } |
| |
| /* |
| * Connect to the primary |
| * Primary is the server where the keyboard and the mouse |
| * are connected and forwarded to the secondary (the client) |
| */ |
| |
| ib->sioc = qio_channel_socket_new(); |
| qio_channel_set_name(QIO_CHANNEL(ib->sioc), "barrier-client"); |
| |
| qio_channel_socket_connect_sync(ib->sioc, &ib->saddr, &local_err); |
| if (local_err) { |
| error_propagate(errp, local_err); |
| return; |
| } |
| |
| qio_channel_set_delay(QIO_CHANNEL(ib->sioc), false); |
| |
| ib->ioc_tag = qio_channel_add_watch(QIO_CHANNEL(ib->sioc), G_IO_IN, |
| input_barrier_event, ib, NULL); |
| } |
| |
| static void input_barrier_instance_finalize(Object *obj) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| if (ib->ioc_tag) { |
| g_source_remove(ib->ioc_tag); |
| ib->ioc_tag = 0; |
| } |
| |
| if (ib->sioc) { |
| qio_channel_close(QIO_CHANNEL(ib->sioc), NULL); |
| object_unref(OBJECT(ib->sioc)); |
| } |
| g_free(ib->name); |
| g_free(ib->saddr.u.inet.host); |
| g_free(ib->saddr.u.inet.port); |
| } |
| |
| static char *input_barrier_get_name(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup(ib->name); |
| } |
| |
| static void input_barrier_set_name(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| if (ib->name) { |
| error_setg(errp, "name property already set"); |
| return; |
| } |
| ib->name = g_strdup(value); |
| } |
| |
| static char *input_barrier_get_server(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup(ib->saddr.u.inet.host); |
| } |
| |
| static void input_barrier_set_server(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| g_free(ib->saddr.u.inet.host); |
| ib->saddr.u.inet.host = g_strdup(value); |
| } |
| |
| static char *input_barrier_get_port(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup(ib->saddr.u.inet.port); |
| } |
| |
| static void input_barrier_set_port(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| g_free(ib->saddr.u.inet.port); |
| ib->saddr.u.inet.port = g_strdup(value); |
| } |
| |
| static void input_barrier_set_x_origin(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| int result, err; |
| |
| err = qemu_strtoi(value, NULL, 0, &result); |
| if (err < 0 || result < 0 || result > SHRT_MAX) { |
| error_setg(errp, |
| "x-origin property must be in the range [0..%d]", SHRT_MAX); |
| return; |
| } |
| ib->x_origin = result; |
| } |
| |
| static char *input_barrier_get_x_origin(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup_printf("%d", ib->x_origin); |
| } |
| |
| static void input_barrier_set_y_origin(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| int result, err; |
| |
| err = qemu_strtoi(value, NULL, 0, &result); |
| if (err < 0 || result < 0 || result > SHRT_MAX) { |
| error_setg(errp, |
| "y-origin property must be in the range [0..%d]", SHRT_MAX); |
| return; |
| } |
| ib->y_origin = result; |
| } |
| |
| static char *input_barrier_get_y_origin(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup_printf("%d", ib->y_origin); |
| } |
| |
| static void input_barrier_set_width(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| int result, err; |
| |
| err = qemu_strtoi(value, NULL, 0, &result); |
| if (err < 0 || result < 0 || result > SHRT_MAX) { |
| error_setg(errp, |
| "width property must be in the range [0..%d]", SHRT_MAX); |
| return; |
| } |
| ib->width = result; |
| } |
| |
| static char *input_barrier_get_width(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup_printf("%d", ib->width); |
| } |
| |
| static void input_barrier_set_height(Object *obj, const char *value, |
| Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| int result, err; |
| |
| err = qemu_strtoi(value, NULL, 0, &result); |
| if (err < 0 || result < 0 || result > SHRT_MAX) { |
| error_setg(errp, |
| "height property must be in the range [0..%d]", SHRT_MAX); |
| return; |
| } |
| ib->height = result; |
| } |
| |
| static char *input_barrier_get_height(Object *obj, Error **errp) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| return g_strdup_printf("%d", ib->height); |
| } |
| |
| static void input_barrier_instance_init(Object *obj) |
| { |
| InputBarrier *ib = INPUT_BARRIER(obj); |
| |
| /* always use generic keymaps */ |
| if (keyboard_layout && !kbd_layout) { |
| /* We use X11 key id, so use VNC name2keysym */ |
| kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout, |
| &error_fatal); |
| } |
| |
| ib->saddr.type = SOCKET_ADDRESS_TYPE_INET; |
| ib->saddr.u.inet.host = g_strdup("localhost"); |
| ib->saddr.u.inet.port = g_strdup("24800"); |
| |
| ib->x_origin = 0; |
| ib->y_origin = 0; |
| ib->width = 1920; |
| ib->height = 1080; |
| |
| object_property_add_str(obj, "name", |
| input_barrier_get_name, |
| input_barrier_set_name); |
| object_property_add_str(obj, "server", |
| input_barrier_get_server, |
| input_barrier_set_server); |
| object_property_add_str(obj, "port", |
| input_barrier_get_port, |
| input_barrier_set_port); |
| object_property_add_str(obj, "x-origin", |
| input_barrier_get_x_origin, |
| input_barrier_set_x_origin); |
| object_property_add_str(obj, "y-origin", |
| input_barrier_get_y_origin, |
| input_barrier_set_y_origin); |
| object_property_add_str(obj, "width", |
| input_barrier_get_width, |
| input_barrier_set_width); |
| object_property_add_str(obj, "height", |
| input_barrier_get_height, |
| input_barrier_set_height); |
| } |
| |
| static void input_barrier_class_init(ObjectClass *oc, void *data) |
| { |
| UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); |
| |
| ucc->complete = input_barrier_complete; |
| } |
| |
| static const TypeInfo input_barrier_info = { |
| .name = TYPE_INPUT_BARRIER, |
| .parent = TYPE_OBJECT, |
| .class_init = input_barrier_class_init, |
| .instance_size = sizeof(InputBarrier), |
| .instance_init = input_barrier_instance_init, |
| .instance_finalize = input_barrier_instance_finalize, |
| .interfaces = (InterfaceInfo[]) { |
| { TYPE_USER_CREATABLE }, |
| { } |
| } |
| }; |
| |
| static void register_types(void) |
| { |
| type_register_static(&input_barrier_info); |
| } |
| |
| type_init(register_types); |