]> git.proxmox.com Git - spiceterm.git/blob - input.c
fdd6cfe473685b707b68125c96fad3413221cdfd
[spiceterm.git] / input.c
1 /*
2
3 Copyright (C) 2013 Proxmox Server Solutions GmbH
4
5 Copyright: spiceterm is under GNU GPL, the GNU General Public License.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; version 2 dated June, 1991.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20
21 Author: Dietmar Maurer <dietmar@proxmox.com>
22 */
23
24 #define _GNU_SOURCE
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <string.h>
31
32 #include "spiceterm.h"
33
34 #include <glib.h>
35 #include <spice.h>
36 #include <spice/enums.h>
37 #include <spice/macros.h>
38 #include <spice/qxl_dev.h>
39
40 #include "event_loop.h"
41
42 static int debug = 0;
43
44 #define DPRINTF(x, format, ...) { \
45 if (x <= debug) { \
46 printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
47 } \
48 }
49
50 static uint8_t
51 my_kbd_get_leds(SpiceKbdInstance *sin)
52 {
53 return 0;
54 }
55
56 #define KBD_MOD_CONTROL_L_FLAG (1<<0)
57 #define KBD_MOD_CONTROL_R_FLAG (1<<1)
58 #define KBD_MOD_SHIFT_L_FLAG (1<<2)
59 #define KBD_MOD_SHIFT_R_FLAG (1<<3)
60
61 static int kbd_flags = 0;
62
63 static void
64 my_kbd_push_key(SpiceKbdInstance *sin, uint8_t frag)
65 {
66 spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, keyboard_sin);
67
68 char *esc = NULL; // used to send special keys
69
70 static int e0_mode = 0;
71
72 DPRINTF(1, "enter frag=%02x flags=%08x", frag, kbd_flags);
73
74 if (e0_mode) {
75 e0_mode = 0;
76 switch (frag) {
77 case 0x1d: // press Control_R
78 kbd_flags |= KBD_MOD_CONTROL_R_FLAG;
79 break;
80 case 0x9d: // release Control_R
81 kbd_flags &= ~KBD_MOD_CONTROL_R_FLAG;
82 break;
83 case 0x47: // press Home
84 esc = "OH";
85 break;
86 case 0x4f: // press END
87 esc = "OF";
88 break;
89 case 0x48: // press UP
90 esc = "OA";
91 break;
92 case 0x50: // press DOWN
93 esc = "OB";
94 break;
95 case 0x4b: // press LEFT
96 esc = "OD";
97 break;
98 case 0x4d: // press RIGHT
99 esc = "OC";
100 break;
101 case 0x52: // press INSERT
102 esc = "[2~";
103 break;
104 case 0x49: // press PAGE_UP
105 if (kbd_flags & (KBD_MOD_SHIFT_L_FLAG|KBD_MOD_SHIFT_R_FLAG)) {
106 spiceterm_virtual_scroll(vt, -vt->height/2);
107 }
108 break;
109 case 0x51: // press PAGE_DOWN
110 if (kbd_flags & (KBD_MOD_SHIFT_L_FLAG|KBD_MOD_SHIFT_R_FLAG)) {
111 spiceterm_virtual_scroll(vt, vt->height/2);
112 }
113 break;
114 }
115 } else {
116 switch (frag) {
117 case 0xe0:
118 e0_mode = 1;
119 break;
120 case 0x1d: // press Control_L
121 kbd_flags |= KBD_MOD_CONTROL_L_FLAG;
122 break;
123 case 0x9d: // release Control_L
124 kbd_flags &= ~KBD_MOD_CONTROL_L_FLAG;
125 break;
126 case 0x2a: // press Shift_L
127 kbd_flags |= KBD_MOD_SHIFT_L_FLAG;
128 break;
129 case 0xaa: // release Shift_L
130 kbd_flags &= ~KBD_MOD_SHIFT_L_FLAG;
131 break;
132 case 0x36: // press Shift_R
133 kbd_flags |= KBD_MOD_SHIFT_R_FLAG;
134 break;
135 case 0xb6: // release Shift_R
136 kbd_flags &= ~KBD_MOD_SHIFT_R_FLAG;
137 break;
138 case 0x52: // press KP_INSERT
139 esc = "[2~";
140 break;
141 case 0x53: // press KP_Delete
142 esc = "[3~";
143 break;
144 case 0x47: // press KP_Home
145 esc = "OH";
146 break;
147 case 0x4f: // press KP_END
148 esc = "OF";
149 break;
150 case 0x48: // press KP_UP
151 esc = "OA";
152 break;
153 case 0x50: // press KP_DOWN
154 esc = "OB";
155 break;
156 case 0x4b: // press KP_LEFT
157 esc = "OD";
158 break;
159 case 0x4d: // press KP_RIGHT
160 esc = "OC";
161 break;
162 case 0x3b: // press F1
163 esc = "OP";
164 break;
165 case 0x3c: // press F2
166 esc = "OQ";
167 break;
168 case 0x3d: // press F3
169 esc = "OR";
170 break;
171 case 0x3e: // press F4
172 esc = "OS";
173 break;
174 case 0x3f: // press F5
175 esc = "[15~";
176 break;
177 case 0x40: // press F6
178 esc = "[17~";
179 break;
180 case 0x41: // press F7
181 esc = "[18~";
182 break;
183 case 0x42: // press F8
184 esc = "[19~";
185 break;
186 case 0x43: // press F9
187 esc = "[20~";
188 break;
189 case 0x44: // press F10
190 esc = "[21~";
191 break;
192 case 0x57: // press F11
193 esc = "[23~";
194 break;
195 case 0x58: // press F12
196 esc = "[24~";
197 break;
198 }
199 }
200
201 if (esc) {
202 DPRINTF(1, "escape=%s", esc);
203 spiceterm_respond_esc(vt, esc);
204
205 if (vt->y_displ != vt->y_base) {
206 vt->y_displ = vt->y_base;
207 spiceterm_refresh(vt);
208 }
209
210 spiceterm_update_watch_mask(vt, TRUE);
211 }
212
213 DPRINTF(1, "leave frag=%02x flags=%08x", frag, kbd_flags);
214 return;
215 }
216
217 static void
218 my_kbd_push_utf8(SpiceKbdInstance *sin, uint32_t size, uint8_t *data)
219 {
220 spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, keyboard_sin);
221
222 DPRINTF(1, " size=%d data[0]=%02x", size, data[0]);
223
224 if (kbd_flags & (KBD_MOD_CONTROL_L_FLAG|KBD_MOD_CONTROL_R_FLAG)) {
225 if (size != 1) return;
226 if (data[0] >= 'a' && data[0] <= 'z') {
227 uint8_t buf[1] = { data[0] - 'a' + 1 };
228 spiceterm_respond_data(vt, 1, buf);
229
230 } else if (data[0] >= 'A' && data[0] <= 'Z') {
231 uint8_t buf[1] = { data[0] - 'A' + 1 };
232 spiceterm_respond_data(vt, 1, buf);
233 }
234 } else {
235 if (size == 1 && data[0] == 0x7f) {
236 /* use an escape sequence for DELETE, else it behaves like BACKSPACE */
237 spiceterm_respond_esc(vt, "[3~");
238 } else {
239 spiceterm_respond_data(vt, size, data);
240 }
241 }
242
243 if (vt->y_displ != vt->y_base) {
244 vt->y_displ = vt->y_base;
245 spiceterm_refresh(vt);
246 }
247
248 spiceterm_update_watch_mask(vt, TRUE);
249
250 return;
251 }
252
253 static SpiceKbdInterface my_keyboard_sif = {
254 .base.type = SPICE_INTERFACE_KEYBOARD,
255 .base.description = "spiceterm keyboard device",
256 .base.major_version = SPICE_INTERFACE_KEYBOARD_MAJOR,
257 .base.minor_version = SPICE_INTERFACE_KEYBOARD_MINOR,
258 .push_scan_freg = my_kbd_push_key,
259 .get_leds = my_kbd_get_leds,
260 .push_utf8 = my_kbd_push_utf8,
261 };
262
263
264 /* vdagent interface - to get mouse/clipboard support */
265
266 #define VDAGENT_WBUF_SIZE (1024*50)
267 static unsigned char vdagent_write_buffer[VDAGENT_WBUF_SIZE];
268 static int vdagent_write_buffer_pos = 0;
269 static int agent_owns_clipboard[256] = { 0, };
270
271 static void
272 vdagent_reply(spiceTerm *vt, uint32_t type, uint32_t error)
273 {
274 uint32_t size;
275
276 size = sizeof(VDAgentReply);
277
278 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
279 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
280
281 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
282 vdagent_write_buffer_pos += msg_size;
283
284 memset(buf, 0, msg_size);
285
286 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
287 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
288 VDAgentReply *reply = (VDAgentReply *)&msg[1];
289 reply->type = type;
290 reply->error = error;
291
292 hdr->port = VDP_CLIENT_PORT;
293 hdr->size = sizeof(VDAgentMessage) + size;
294
295 msg->protocol = VD_AGENT_PROTOCOL;
296 msg->type = VD_AGENT_REPLY;
297 msg->opaque = 0;
298 msg->size = size;
299
300 spice_server_char_device_wakeup(&vt->vdagent_sin);
301 }
302
303 static void
304 dump_message(unsigned char *buf, int size)
305 {
306 int i;
307
308 for (i = 0; i < size; i++) {
309 printf("%d %02X\n", i, buf[i]);
310 }
311
312 // exit(0);
313 }
314
315 static void
316 vdagent_send_capabilities(spiceTerm *vt, uint32_t request)
317 {
318 VDAgentAnnounceCapabilities *caps;
319 uint32_t size;
320
321 size = sizeof(*caps) + VD_AGENT_CAPS_BYTES;
322 caps = calloc(1, size);
323 g_assert(caps != NULL);
324
325 caps->request = request;
326 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
327 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
328 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
329 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
330 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
331 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SPARSE_MONITORS_CONFIG);
332 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_GUEST_LINEEND_LF);
333
334 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
335 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
336
337 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
338 vdagent_write_buffer_pos += msg_size;
339
340 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
341 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
342
343 hdr->port = VDP_CLIENT_PORT;
344 hdr->size = sizeof(VDAgentMessage) + size;
345 msg->protocol = VD_AGENT_PROTOCOL;
346 msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
347 msg->opaque = 0;
348 msg->size = size;
349
350 memcpy(buf + sizeof(VDIChunkHeader) + sizeof(VDAgentMessage), (uint8_t *)caps, size);
351
352 if (0) dump_message(buf, msg_size);
353
354 spice_server_char_device_wakeup(&vt->vdagent_sin);
355
356 free(caps);
357 }
358
359 gboolean
360 vdagent_owns_clipboard(spiceTerm *vt)
361 {
362 return !!agent_owns_clipboard[VD_AGENT_CLIPBOARD_SELECTION_PRIMARY];
363 }
364
365 void
366 vdagent_grab_clipboard(spiceTerm *vt)
367 {
368 uint32_t size;
369
370 uint8_t selection = VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
371
372 agent_owns_clipboard[selection] = 1;
373
374 size = 8;
375
376 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
377 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
378
379 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
380 vdagent_write_buffer_pos += msg_size;
381
382 memset(buf, 0, msg_size);
383
384 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
385 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
386 uint8_t *grab = (uint8_t *)&msg[1];
387 *((uint8_t *)grab) = selection;
388 *((uint32_t *)(grab + 4)) = VD_AGENT_CLIPBOARD_UTF8_TEXT;
389
390 hdr->port = VDP_CLIENT_PORT;
391 hdr->size = sizeof(VDAgentMessage) + size;
392
393 msg->protocol = VD_AGENT_PROTOCOL;
394 msg->type = VD_AGENT_CLIPBOARD_GRAB;
395 msg->opaque = 0;
396 msg->size = size;
397
398 if (0) dump_message(buf, msg_size);
399
400 spice_server_char_device_wakeup(&vt->vdagent_sin);
401 }
402
403 void
404 vdagent_request_clipboard(spiceTerm *vt)
405 {
406 uint32_t size;
407
408 uint8_t selection = VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
409
410 size = 4 + sizeof(VDAgentClipboardRequest);
411
412 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
413 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
414
415 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
416 vdagent_write_buffer_pos += msg_size;
417
418 memset(buf, 0, msg_size);
419
420 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
421 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
422 uint8_t *data = (uint8_t *)&msg[1];
423 *((uint32_t *)data) = 0;
424 data[0] = selection;
425 ((uint32_t *)data)[1] = VD_AGENT_CLIPBOARD_UTF8_TEXT;
426
427 hdr->port = VDP_CLIENT_PORT;
428 hdr->size = sizeof(VDAgentMessage) + size;
429
430 msg->protocol = VD_AGENT_PROTOCOL;
431 msg->type = VD_AGENT_CLIPBOARD_REQUEST;
432 msg->opaque = 0;
433 msg->size = size;
434
435 if (0) dump_message(buf, msg_size);
436
437 spice_server_char_device_wakeup(&vt->vdagent_sin);
438 }
439
440 static void
441 vdagent_send_clipboard(spiceTerm *vt, uint8_t selection)
442 {
443 uint32_t size;
444
445 if (selection != VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
446 fprintf(stderr, "clipboard select %d is not supported\n", selection);
447 return;
448 }
449
450 gchar *sel_data;
451 glong sel_len;
452 if (vt->utf8) {
453 sel_data = g_utf16_to_utf8(vt->selection, vt->selection_len, NULL, &sel_len, NULL);
454 } else {
455 sel_len = vt->selection_len;
456 sel_data = g_malloc(sel_len);
457 int i;
458 for (i = 0; i < sel_len; i++) { sel_data[i] = (char)vt->selection[i]; }
459 sel_data[sel_len] = 0;
460 }
461
462 size = 8 + sel_len;
463
464 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
465 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
466
467 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
468 vdagent_write_buffer_pos += msg_size;
469
470 memset(buf, 0, msg_size);
471
472 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
473 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
474 uint8_t *data = (uint8_t *)&msg[1];
475 *((uint8_t *)data) = selection;
476 data += 4;
477 *((uint32_t *)data) = VD_AGENT_CLIPBOARD_UTF8_TEXT;
478 data += 4;
479
480 memcpy(data, sel_data, sel_len);
481 g_free(sel_data);
482
483 hdr->port = VDP_CLIENT_PORT;
484 hdr->size = sizeof(VDAgentMessage) + size;
485
486 msg->protocol = VD_AGENT_PROTOCOL;
487 msg->type = VD_AGENT_CLIPBOARD;
488 msg->opaque = 0;
489 msg->size = size;
490
491 spice_server_char_device_wakeup(&vt->vdagent_sin);
492 }
493
494 static int
495 vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
496 {
497 spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, vdagent_sin);
498
499 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
500 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
501
502 //g_assert(hdr->port == VDP_SERVER_PORT);
503 g_assert(msg->protocol == VD_AGENT_PROTOCOL);
504
505 DPRINTF(1, "%d %d %d %d", len, hdr->port, msg->protocol, msg->type);
506
507 switch (msg->type) {
508 case VD_AGENT_MOUSE_STATE: {
509 VDAgentMouseState *info = (VDAgentMouseState *)&msg[1];
510 spiceterm_motion_event(vt, info->x, info->y, info->buttons);
511 break;
512 }
513 case VD_AGENT_ANNOUNCE_CAPABILITIES: {
514 VDAgentAnnounceCapabilities *caps = (VDAgentAnnounceCapabilities *)&msg[1];
515 DPRINTF(1, "VD_AGENT_ANNOUNCE_CAPABILITIES %d", caps->request);
516 int i;
517
518 int caps_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(hdr->size);
519 for (i = 0; i < VD_AGENT_END_CAP; i++) {
520 DPRINTF(1, "CAPABILITIES %d %d", i, VD_AGENT_HAS_CAPABILITY(caps->caps, caps_size, i));
521 }
522
523 vdagent_send_capabilities(vt, 0);
524 break;
525 }
526 case VD_AGENT_CLIPBOARD_GRAB: {
527 VDAgentClipboardGrab *grab = (VDAgentClipboardGrab *)&msg[1];
528 uint8_t selection = *((uint8_t *)grab);
529 DPRINTF(1, "VD_AGENT_CLIPBOARD_GRAB %d", selection);
530 agent_owns_clipboard[selection] = 0;
531 spiceterm_clear_selection(vt);
532 break;
533 }
534 case VD_AGENT_CLIPBOARD_REQUEST: {
535 uint8_t *req = (uint8_t *)&msg[1];
536 uint8_t selection = *((uint8_t *)req);
537 uint32_t type = *((uint32_t *)(req + 4));
538
539 DPRINTF(1, "VD_AGENT_CLIPBOARD_REQUEST %d %d", selection, type);
540
541 vdagent_send_clipboard(vt, selection);
542
543 break;
544 }
545 case VD_AGENT_CLIPBOARD: {
546 uint8_t *data = (uint8_t *)&msg[1];
547 uint8_t selection = data[0];
548 uint32_t type = *(uint32_t *)(data + 4);
549 int size = msg->size - 8;
550 DPRINTF(1, "VD_AGENT_CLIPBOARD %d %d %d", selection, type, size);
551
552 if (type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
553 spiceterm_respond_data(vt, size, data + 8);
554 spiceterm_update_watch_mask(vt, TRUE);
555 }
556 break;
557 }
558 case VD_AGENT_CLIPBOARD_RELEASE: {
559 uint8_t *data = (uint8_t *)&msg[1];
560 uint8_t selection = data[0];
561
562 DPRINTF(1, "VD_AGENT_CLIPBOARD_RELEASE %d", selection);
563
564 break;
565 }
566 case VD_AGENT_MONITORS_CONFIG: {
567 VDAgentMonitorsConfig *list = (VDAgentMonitorsConfig *)&msg[1];
568 g_assert(list->num_of_monitors > 0);
569 DPRINTF(1, "VD_AGENT_MONITORS_CONFIG %d %d %d", list->num_of_monitors,
570 list->monitors[0].width, list->monitors[0].height);
571
572 spiceterm_resize(vt, list->monitors[0].width, list->monitors[0].height);
573
574 vdagent_reply(vt, VD_AGENT_MONITORS_CONFIG, VD_AGENT_SUCCESS);
575 break;
576 }
577 default:
578 DPRINTF(1, "got uknown vdagent message type %d\n", msg->type);
579 }
580
581 return len;
582 }
583
584 static int
585 vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len)
586 {
587 DPRINTF(1, "%d %d", len, vdagent_write_buffer_pos);
588 g_assert(len >= 8);
589
590 if (!vdagent_write_buffer_pos) {
591 return 0;
592 }
593
594 int size = (len >= vdagent_write_buffer_pos) ? vdagent_write_buffer_pos : len;
595 memcpy(buf, vdagent_write_buffer, size);
596 if (size < vdagent_write_buffer_pos) {
597 memmove(vdagent_write_buffer, vdagent_write_buffer + size,
598 vdagent_write_buffer_pos - size);
599 }
600 vdagent_write_buffer_pos -= size;
601
602 DPRINTF(1, "RET %d %d", size, vdagent_write_buffer_pos);
603 return size;
604 }
605
606 static void
607 vmc_state(SpiceCharDeviceInstance *sin, int connected)
608 {
609 /* IGNORE */
610 }
611
612 static SpiceCharDeviceInterface my_vdagent_sif = {
613 .base.type = SPICE_INTERFACE_CHAR_DEVICE,
614 .base.description = "spice virtual channel char device",
615 .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
616 .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
617 .state = vmc_state,
618 .write = vmc_write,
619 .read = vmc_read,
620 };
621
622 spiceTerm *
623 spiceterm_create(uint32_t width, uint32_t height, SpiceTermOptions *opts)
624 {
625 SpiceCoreInterface *core = basic_event_loop_init();
626 SpiceScreen *spice_screen = spice_screen_new(core, width, height, opts);
627
628 //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
629
630 spice_screen->image_cache = g_hash_table_new(g_int_hash, g_int_equal);
631
632 spiceTerm *vt = (spiceTerm *)calloc (sizeof(spiceTerm), 1);
633
634 vt->keyboard_sin.base.sif = &my_keyboard_sif.base;
635 spice_server_add_interface(spice_screen->server, &vt->keyboard_sin.base);
636
637 vt->vdagent_sin.base.sif = &my_vdagent_sif.base;
638 vt->vdagent_sin.subtype = "vdagent";
639 spice_server_add_interface(spice_screen->server, &vt->vdagent_sin.base);
640 vt->screen = spice_screen;
641
642 init_spiceterm(vt, width, height);
643
644 return vt;
645 }