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