1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2009 Red Hat, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <config.h>
19
20 #include <stddef.h> // NULL
21 #include <spice/macros.h>
22 #include <spice/vd_agent.h>
23 #include <spice/protocol.h>
24
25 #include <common/marshaller.h>
26 #include <common/messages.h>
27 #include <common/generated_server_marshallers.h>
28 #include <common/demarshallers.h>
29
30 #include "spice-wrapped.h"
31 #include "red-common.h"
32 #include "reds.h"
33 #include "red-stream.h"
34 #include "red-channel.h"
35 #include "red-channel-client.h"
36 #include "red-client.h"
37 #include "inputs-channel-client.h"
38 #include "main-channel-client.h"
39 #include "inputs-channel.h"
40 #include "migration-protocol.h"
41 #include "utils.h"
42
43 struct SpiceKbdState {
44 uint8_t push_ext_type;
45
46 /* track key press state */
47 bool key[0x80];
48 bool key_ext[0x80];
49 InputsChannel *inputs;
50 };
51
spice_kbd_state_new(InputsChannel * inputs)52 static SpiceKbdState* spice_kbd_state_new(InputsChannel *inputs)
53 {
54 auto st = g_new0(SpiceKbdState, 1);
55 st->inputs = inputs;
56 return st;
57 }
58
59 struct SpiceMouseState {
60 int dummy;
61 };
62
spice_mouse_state_new()63 static SpiceMouseState* spice_mouse_state_new()
64 {
65 return g_new0(SpiceMouseState, 1);
66 }
67
68 struct SpiceTabletState {
69 RedsState *reds;
70 };
71
spice_tablet_state_new(RedsState * reds)72 static SpiceTabletState* spice_tablet_state_new(RedsState* reds)
73 {
74 auto st = g_new0(SpiceTabletState, 1);
75 st->reds = reds;
76 return st;
77 }
78
spice_tablet_state_free(SpiceTabletState * st)79 static void spice_tablet_state_free(SpiceTabletState* st)
80 {
81 g_free(st);
82 }
83
spice_tablet_state_get_server(SpiceTabletState * st)84 RedsState* spice_tablet_state_get_server(SpiceTabletState *st)
85 {
86 return st->reds;
87 }
88
89 struct RedKeyModifiersPipeItem: public RedPipeItemNum<RED_PIPE_ITEM_KEY_MODIFIERS> {
90 explicit RedKeyModifiersPipeItem(uint8_t modifiers);
91 uint8_t modifiers;
92 };
93
94 struct RedInputsInitPipeItem: public RedPipeItemNum<RED_PIPE_ITEM_INPUTS_INIT> {
95 explicit RedInputsInitPipeItem(uint8_t modifiers);
96 uint8_t modifiers;
97 };
98
99
100 #define KEY_MODIFIERS_TTL (MSEC_PER_SEC * 2)
101
102 #define SCAN_CODE_RELEASE 0x80
103 #define SCROLL_LOCK_SCAN_CODE 0x46
104 #define NUM_LOCK_SCAN_CODE 0x45
105 #define CAPS_LOCK_SCAN_CODE 0x3a
106
set_tablet_logical_size(int x_res,int y_res)107 void InputsChannel::set_tablet_logical_size(int x_res, int y_res)
108 {
109 SpiceTabletInterface *sif;
110
111 sif = SPICE_UPCAST(SpiceTabletInterface, tablet->base.sif);
112 sif->set_logical_size(tablet, x_res, y_res);
113 }
114
get_mouse_state()115 const VDAgentMouseState *InputsChannel::get_mouse_state()
116 {
117 return &mouse_state;
118 }
119
120 // middle and right states are inverted
121 // all buttons from SPICE_MOUSE_BUTTON_MASK_SIDE are mapped a bit higher
122 // to avoid conflicting with some internal Qemu bit
123 #define RED_MOUSE_STATE_TO_LOCAL(state) \
124 ((state & SPICE_MOUSE_BUTTON_MASK_LEFT) | \
125 ((state & (SPICE_MOUSE_BUTTON_MASK_MIDDLE|0xffe0)) << 1) | \
126 ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1))
127
128 // mouse button constants are defined to be off-one between agent and SPICE protocol
129 #define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) ((state) << 1)
130
activate_modifiers_watch()131 void InputsChannel::activate_modifiers_watch()
132 {
133 red_timer_start(key_modifiers_timer, KEY_MODIFIERS_TTL);
134 }
135
kbd_push_scan(SpiceKbdInstance * sin,uint8_t scan)136 static void kbd_push_scan(SpiceKbdInstance *sin, uint8_t scan)
137 {
138 SpiceKbdInterface *sif;
139
140 if (!sin) {
141 return;
142 }
143 sif = SPICE_UPCAST(SpiceKbdInterface, sin->base.sif);
144
145 /* track XT scan code set 1 key state */
146 if (scan >= 0xe0 && scan <= 0xe2) {
147 sin->st->push_ext_type = scan;
148 } else {
149 if (sin->st->push_ext_type == 0 || sin->st->push_ext_type == 0xe0) {
150 bool *state = sin->st->push_ext_type ? sin->st->key_ext : sin->st->key;
151 state[scan & 0x7f] = !(scan & SCAN_CODE_RELEASE);
152 }
153 sin->st->push_ext_type = 0;
154 }
155
156 sif->push_scan_freg(sin, scan);
157 }
158
scancode_to_modifier_flag(uint8_t scancode)159 static uint8_t scancode_to_modifier_flag(uint8_t scancode)
160 {
161 switch (scancode & ~SCAN_CODE_RELEASE) {
162 case CAPS_LOCK_SCAN_CODE:
163 return SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK;
164 case NUM_LOCK_SCAN_CODE:
165 return SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK;
166 case SCROLL_LOCK_SCAN_CODE:
167 return SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK;
168 }
169 return 0;
170 }
171
sync_locks(uint8_t scan)172 void InputsChannel::sync_locks(uint8_t scan)
173 {
174 uint8_t change_modifier = scancode_to_modifier_flag(scan);
175
176 if (scan & SCAN_CODE_RELEASE) { /* KEY_UP */
177 modifiers_pressed &= ~change_modifier;
178 } else { /* KEY_DOWN */
179 if (change_modifier && !(modifiers_pressed & change_modifier)) {
180 modifiers ^= change_modifier;
181 modifiers_pressed |= change_modifier;
182 activate_modifiers_watch();
183 }
184 }
185 }
186
kbd_get_leds(SpiceKbdInstance * sin)187 static uint8_t kbd_get_leds(SpiceKbdInstance *sin)
188 {
189 SpiceKbdInterface *sif;
190
191 if (!sin) {
192 return 0;
193 }
194 sif = SPICE_UPCAST(SpiceKbdInterface, sin->base.sif);
195 return sif->get_leds(sin);
196 }
197
RedKeyModifiersPipeItem(uint8_t init_modifiers)198 RedKeyModifiersPipeItem::RedKeyModifiersPipeItem(uint8_t init_modifiers):
199 modifiers(init_modifiers)
200 {
201 }
202
send_item(RedPipeItem * base)203 void InputsChannelClient::send_item(RedPipeItem *base)
204 {
205 SpiceMarshaller *m = get_marshaller();
206
207 switch (base->type) {
208 case RED_PIPE_ITEM_KEY_MODIFIERS:
209 {
210 SpiceMsgInputsKeyModifiers key_modifiers;
211
212 init_send_data(SPICE_MSG_INPUTS_KEY_MODIFIERS);
213 key_modifiers.modifiers =
214 static_cast<RedKeyModifiersPipeItem*>(base)->modifiers;
215 spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers);
216 break;
217 }
218 case RED_PIPE_ITEM_INPUTS_INIT:
219 {
220 SpiceMsgInputsInit inputs_init;
221
222 init_send_data(SPICE_MSG_INPUTS_INIT);
223 inputs_init.keyboard_modifiers =
224 static_cast<RedInputsInitPipeItem*>(base)->modifiers;
225 spice_marshall_msg_inputs_init(m, &inputs_init);
226 break;
227 }
228 case RED_PIPE_ITEM_MOUSE_MOTION_ACK:
229 init_send_data(SPICE_MSG_INPUTS_MOUSE_MOTION_ACK);
230 break;
231 case RED_PIPE_ITEM_MIGRATE_DATA:
232 get_channel()->src_during_migrate = FALSE;
233 send_migrate_data(m, base);
234 break;
235 default:
236 spice_warning("invalid pipe iten %d", base->type);
237 break;
238 }
239 begin_send_message();
240 }
241
handle_message(uint16_t type,uint32_t size,void * message)242 bool InputsChannelClient::handle_message(uint16_t type, uint32_t size, void *message)
243 {
244 InputsChannel *inputs_channel = get_channel();
245 uint32_t i;
246 RedsState *reds = inputs_channel->get_server();
247
248 switch (type) {
249 case SPICE_MSGC_INPUTS_KEY_DOWN: {
250 auto key_down = (SpiceMsgcKeyDown *) message;
251 inputs_channel->sync_locks(key_down->code);
252 }
253 /* fallthrough */
254 case SPICE_MSGC_INPUTS_KEY_UP: {
255 auto key_up = (SpiceMsgcKeyUp *) message;
256 for (i = 0; i < 4; i++) {
257 uint8_t code = (key_up->code >> (i * 8)) & 0xff;
258 if (code == 0) {
259 break;
260 }
261 kbd_push_scan(inputs_channel->keyboard, code);
262 inputs_channel->sync_locks(code);
263 }
264 break;
265 }
266 case SPICE_MSGC_INPUTS_KEY_SCANCODE: {
267 auto code = (uint8_t *) message;
268 for (i = 0; i < size; i++) {
269 kbd_push_scan(inputs_channel->keyboard, code[i]);
270 inputs_channel->sync_locks(code[i]);
271 }
272 break;
273 }
274 case SPICE_MSGC_INPUTS_MOUSE_MOTION: {
275 SpiceMouseInstance *mouse = inputs_channel->mouse;
276 auto mouse_motion = (SpiceMsgcMouseMotion *) message;
277
278 on_mouse_motion();
279 if (mouse && reds_get_mouse_mode(reds) == SPICE_MOUSE_MODE_SERVER) {
280 SpiceMouseInterface *sif;
281 sif = SPICE_UPCAST(SpiceMouseInterface, mouse->base.sif);
282 sif->motion(mouse,
283 mouse_motion->dx, mouse_motion->dy, 0,
284 RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state));
285 }
286 break;
287 }
288 case SPICE_MSGC_INPUTS_MOUSE_POSITION: {
289 auto pos = (SpiceMsgcMousePosition *) message;
290 SpiceTabletInstance *tablet = inputs_channel->tablet;
291
292 on_mouse_motion();
293 if (reds_get_mouse_mode(reds) != SPICE_MOUSE_MODE_CLIENT) {
294 break;
295 }
296 spice_assert((reds_config_get_agent_mouse(reds) && reds_has_vdagent(reds)) || tablet);
297 if (!reds_config_get_agent_mouse(reds) || !reds_has_vdagent(reds)) {
298 SpiceTabletInterface *sif;
299 sif = SPICE_UPCAST(SpiceTabletInterface, tablet->base.sif);
300 sif->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state));
301 break;
302 }
303 VDAgentMouseState *mouse_state = &inputs_channel->mouse_state;
304 mouse_state->x = pos->x;
305 mouse_state->y = pos->y;
306 mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state);
307 mouse_state->display_id = pos->display_id;
308 reds_handle_agent_mouse_event(reds, mouse_state);
309 break;
310 }
311 case SPICE_MSGC_INPUTS_MOUSE_PRESS: {
312 auto mouse_press = (SpiceMsgcMousePress *) message;
313 int dz = 0;
314 if (mouse_press->button == SPICE_MOUSE_BUTTON_UP) {
315 dz = -1;
316 } else if (mouse_press->button == SPICE_MOUSE_BUTTON_DOWN) {
317 dz = 1;
318 }
319 if (reds_get_mouse_mode(reds) == SPICE_MOUSE_MODE_CLIENT) {
320 if (reds_config_get_agent_mouse(reds) && reds_has_vdagent(reds)) {
321 inputs_channel->mouse_state.buttons =
322 RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) |
323 (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) |
324 (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0);
325 reds_handle_agent_mouse_event(reds, &inputs_channel->mouse_state);
326 } else if (inputs_channel->tablet) {
327 SpiceTabletInterface *sif;
328 sif = SPICE_CONTAINEROF(inputs_channel->tablet->base.sif,
329 SpiceTabletInterface, base);
330 sif->wheel(inputs_channel->tablet, dz,
331 RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
332 }
333 } else if (inputs_channel->mouse) {
334 SpiceMouseInterface *sif;
335 sif = SPICE_CONTAINEROF(inputs_channel->mouse->base.sif,
336 SpiceMouseInterface, base);
337 sif->motion(inputs_channel->mouse, 0, 0, dz,
338 RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
339 }
340 break;
341 }
342 case SPICE_MSGC_INPUTS_MOUSE_RELEASE: {
343 auto mouse_release = (SpiceMsgcMouseRelease *) message;
344 if (reds_get_mouse_mode(reds) == SPICE_MOUSE_MODE_CLIENT) {
345 if (reds_config_get_agent_mouse(reds) && reds_has_vdagent(reds)) {
346 inputs_channel->mouse_state.buttons =
347 RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state);
348 reds_handle_agent_mouse_event(reds, &inputs_channel->mouse_state);
349 } else if (inputs_channel->tablet) {
350 SpiceTabletInterface *sif;
351 sif = SPICE_CONTAINEROF(inputs_channel->tablet->base.sif,
352 SpiceTabletInterface, base);
353 sif->buttons(inputs_channel->tablet,
354 RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
355 }
356 } else if (inputs_channel->mouse) {
357 SpiceMouseInterface *sif;
358 sif = SPICE_CONTAINEROF(inputs_channel->mouse->base.sif,
359 SpiceMouseInterface, base);
360 sif->buttons(inputs_channel->mouse,
361 RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
362 }
363 break;
364 }
365 case SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
366 auto modifiers = (SpiceMsgcKeyModifiers *) message;
367 uint8_t leds;
368 SpiceKbdInstance *keyboard = inputs_channel->keyboard;
369
370 if (!keyboard) {
371 break;
372 }
373 leds = inputs_channel->modifiers;
374 if (!(inputs_channel->modifiers_pressed & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) &&
375 ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) !=
376 (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK))) {
377 kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE);
378 kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE | SCAN_CODE_RELEASE);
379 inputs_channel->modifiers ^= SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK;
380 }
381 if (!(inputs_channel->modifiers_pressed & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) &&
382 ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) !=
383 (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK))) {
384 kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE);
385 kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE | SCAN_CODE_RELEASE);
386 inputs_channel->modifiers ^= SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK;
387 }
388 if (!(inputs_channel->modifiers_pressed & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) &&
389 ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) !=
390 (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK))) {
391 kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE);
392 kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE | SCAN_CODE_RELEASE);
393 inputs_channel->modifiers ^= SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK;
394 }
395 inputs_channel->activate_modifiers_watch();
396 break;
397 }
398 default:
399 return RedChannelClient::handle_message(type, size, message);
400 }
401 return TRUE;
402 }
403
release_keys()404 void InputsChannel::release_keys()
405 {
406 int i;
407 SpiceKbdState *st;
408
409 if (!keyboard) {
410 return;
411 }
412 st = keyboard->st;
413
414 for (i = 0; i < SPICE_N_ELEMENTS(st->key); i++) {
415 if (!st->key[i])
416 continue;
417
418 st->key[i] = FALSE;
419 kbd_push_scan(keyboard, i | SCAN_CODE_RELEASE);
420 }
421
422 for (i = 0; i < SPICE_N_ELEMENTS(st->key_ext); i++) {
423 if (!st->key_ext[i])
424 continue;
425
426 st->key_ext[i] = FALSE;
427 kbd_push_scan(keyboard, 0xe0);
428 kbd_push_scan(keyboard, i | SCAN_CODE_RELEASE);
429 }
430 }
431
RedInputsInitPipeItem(uint8_t init_modifiers)432 RedInputsInitPipeItem::RedInputsInitPipeItem(uint8_t init_modifiers):
433 modifiers(init_modifiers)
434 {
435 }
436
pipe_add_init()437 void InputsChannelClient::pipe_add_init()
438 {
439 auto modifiers = kbd_get_leds(get_channel()->keyboard);
440 pipe_add_push(red::make_shared<RedInputsInitPipeItem>(modifiers));
441 }
442
on_connect(RedClient * client,RedStream * stream,int migration,RedChannelCapabilities * caps)443 void InputsChannel::on_connect(RedClient *client, RedStream *stream, int migration,
444 RedChannelCapabilities *caps)
445 {
446 if (!red_stream_is_ssl(stream) && !client->during_migrate_at_target()) {
447 client->get_main()->push_notify("keyboard channel is insecure");
448 }
449
450 inputs_channel_client_create(this, client, stream, caps);
451 }
452
migrate()453 void InputsChannelClient::migrate()
454 {
455 InputsChannel *inputs = get_channel();
456 inputs->src_during_migrate = true;
457 RedChannelClient::migrate();
458 }
459
push_keyboard_modifiers()460 void InputsChannel::push_keyboard_modifiers()
461 {
462 if (!is_connected() || src_during_migrate) {
463 return;
464 }
465 pipes_add(red::make_shared<RedKeyModifiersPipeItem>(modifiers));
466 }
467
spice_server_kbd_leds(SpiceKbdInstance * sin,int leds)468 SPICE_GNUC_VISIBLE int spice_server_kbd_leds(SpiceKbdInstance *sin, int leds)
469 {
470 InputsChannel *inputs_channel = sin->st->inputs;
471 if (inputs_channel) {
472 inputs_channel->modifiers = leds;
473 inputs_channel->push_keyboard_modifiers();
474 }
475 return 0;
476 }
477
key_modifiers_sender(InputsChannel * inputs)478 void InputsChannel::key_modifiers_sender(InputsChannel *inputs)
479 {
480 inputs->push_keyboard_modifiers();
481 }
482
handle_migrate_flush_mark()483 void InputsChannelClient::handle_migrate_flush_mark()
484 {
485 pipe_add_type(RED_PIPE_ITEM_MIGRATE_DATA);
486 }
487
handle_migrate_data(uint32_t size,void * message)488 bool InputsChannelClient::handle_migrate_data(uint32_t size, void *message)
489 {
490 InputsChannel *inputs = get_channel();
491 SpiceMigrateDataHeader *header;
492 SpiceMigrateDataInputs *mig_data;
493
494 if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataInputs)) {
495 spice_warning("bad message size %u", size);
496 return FALSE;
497 }
498
499 header = (SpiceMigrateDataHeader *)message;
500 mig_data = (SpiceMigrateDataInputs *)(header + 1);
501
502 if (!migration_protocol_validate_header(header,
503 SPICE_MIGRATE_DATA_INPUTS_MAGIC,
504 SPICE_MIGRATE_DATA_INPUTS_VERSION)) {
505 spice_error("bad header");
506 return FALSE;
507 }
508 InputsChannel::key_modifiers_sender(inputs);
509 handle_migrate_data(mig_data->motion_count);
510 return TRUE;
511 }
512
inputs_channel_new(RedsState * reds)513 red::shared_ptr<InputsChannel> inputs_channel_new(RedsState *reds)
514 {
515 return red::make_shared<InputsChannel>(reds);
516 }
517
InputsChannel(RedsState * reds)518 InputsChannel::InputsChannel(RedsState *reds):
519 RedChannel(reds, SPICE_CHANNEL_INPUTS, 0, RedChannel::MigrateAll)
520 {
521 SpiceCoreInterfaceInternal *core = get_core_interface();
522
523 set_cap(SPICE_INPUTS_CAP_KEY_SCANCODE);
524 reds_register_channel(reds, this);
525
526 key_modifiers_timer = core->timer_new(key_modifiers_sender, this);
527 if (!key_modifiers_timer) {
528 spice_error("key modifiers timer create failed");
529 }
530 }
531
~InputsChannel()532 InputsChannel::~InputsChannel()
533 {
534 detach_tablet(tablet);
535 red_timer_remove(key_modifiers_timer);
536 }
537
set_keyboard(SpiceKbdInstance * new_keyboard)538 int InputsChannel::set_keyboard(SpiceKbdInstance *new_keyboard)
539 {
540 if (keyboard) {
541 red_channel_warning(this, "already have keyboard");
542 return -1;
543 }
544 keyboard = new_keyboard;
545 keyboard->st = spice_kbd_state_new(this);
546 return 0;
547 }
548
set_mouse(SpiceMouseInstance * new_mouse)549 int InputsChannel::set_mouse(SpiceMouseInstance *new_mouse)
550 {
551 if (mouse) {
552 red_channel_warning(this, "already have mouse");
553 return -1;
554 }
555 mouse = new_mouse;
556 mouse->st = spice_mouse_state_new();
557 return 0;
558 }
559
set_tablet(SpiceTabletInstance * new_tablet)560 int InputsChannel::set_tablet(SpiceTabletInstance *new_tablet)
561 {
562 if (tablet) {
563 red_channel_warning(this, "already have tablet");
564 return -1;
565 }
566 tablet = new_tablet;
567 tablet->st = spice_tablet_state_new(get_server());
568 return 0;
569 }
570
has_tablet() const571 bool InputsChannel::has_tablet() const
572 {
573 return tablet != nullptr;
574 }
575
detach_tablet(SpiceTabletInstance * old_tablet)576 void InputsChannel::detach_tablet(SpiceTabletInstance *old_tablet)
577 {
578 if (old_tablet != nullptr && old_tablet == tablet) {
579 spice_tablet_state_free(old_tablet->st);
580 old_tablet->st = nullptr;
581 }
582 tablet = nullptr;
583 }
584
is_src_during_migrate() const585 bool InputsChannel::is_src_during_migrate() const
586 {
587 return src_during_migrate;
588 }
589