3 Copyright (C) 2013 Proxmox Server Solutions GmbH
5 Copyright: spiceterm is under GNU GPL, the GNU General Public License.
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.
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.
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
21 Author: Dietmar Maurer <dietmar@proxmox.com>
29 #include <sys/types.h>
32 #include "spiceterm.h"
36 #include <spice/enums.h>
37 #include <spice/macros.h>
38 #include <spice/qxl_dev.h>
40 #include "event_loop.h"
44 #define DPRINTF(x, format, ...) { \
46 printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
51 my_kbd_get_leds(SpiceKbdInstance
*sin
)
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)
61 static int kbd_flags
= 0;
64 my_kbd_push_key(SpiceKbdInstance
*sin
, uint8_t frag
)
66 spiceTerm
*vt
= SPICE_CONTAINEROF(sin
, spiceTerm
, keyboard_sin
);
68 char *esc
= NULL
; // used to send special keys
70 static int e0_mode
= 0;
72 DPRINTF(1, "enter frag=%02x flags=%08x", frag
, kbd_flags
);
77 case 0x1d: // press Control_R
78 kbd_flags
|= KBD_MOD_CONTROL_R_FLAG
;
80 case 0x9d: // release Control_R
81 kbd_flags
&= ~KBD_MOD_CONTROL_R_FLAG
;
83 case 0x47: // press Home
86 case 0x4f: // press END
89 case 0x48: // press UP
92 case 0x50: // press DOWN
95 case 0x4b: // press LEFT
98 case 0x4d: // press RIGHT
101 case 0x52: // press INSERT
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);
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);
120 case 0x1d: // press Control_L
121 kbd_flags
|= KBD_MOD_CONTROL_L_FLAG
;
123 case 0x9d: // release Control_L
124 kbd_flags
&= ~KBD_MOD_CONTROL_L_FLAG
;
126 case 0x2a: // press Shift_L
127 kbd_flags
|= KBD_MOD_SHIFT_L_FLAG
;
129 case 0xaa: // release Shift_L
130 kbd_flags
&= ~KBD_MOD_SHIFT_L_FLAG
;
132 case 0x36: // press Shift_R
133 kbd_flags
|= KBD_MOD_SHIFT_R_FLAG
;
135 case 0xb6: // release Shift_R
136 kbd_flags
&= ~KBD_MOD_SHIFT_R_FLAG
;
138 case 0x52: // press KP_INSERT
141 case 0x53: // press KP_Delete
144 case 0x47: // press KP_Home
147 case 0x4f: // press KP_END
150 case 0x48: // press KP_UP
153 case 0x50: // press KP_DOWN
156 case 0x4b: // press KP_LEFT
159 case 0x4d: // press KP_RIGHT
162 case 0x3b: // press F1
165 case 0x3c: // press F2
168 case 0x3d: // press F3
171 case 0x3e: // press F4
174 case 0x3f: // press F5
177 case 0x40: // press F6
180 case 0x41: // press F7
183 case 0x42: // press F8
186 case 0x43: // press F9
189 case 0x44: // press F10
192 case 0x57: // press F11
195 case 0x58: // press F12
202 DPRINTF(1, "escape=%s", esc
);
203 spiceterm_respond_esc(vt
, esc
);
205 if (vt
->y_displ
!= vt
->y_base
) {
206 vt
->y_displ
= vt
->y_base
;
207 spiceterm_refresh(vt
);
210 spiceterm_update_watch_mask(vt
, TRUE
);
213 DPRINTF(1, "leave frag=%02x flags=%08x", frag
, kbd_flags
);
218 my_kbd_push_utf8(SpiceKbdInstance
*sin
, uint32_t size
, uint8_t *data
)
220 spiceTerm
*vt
= SPICE_CONTAINEROF(sin
, spiceTerm
, keyboard_sin
);
222 DPRINTF(1, " size=%d data[0]=%02x", size
, data
[0]);
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
);
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
);
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~");
239 spiceterm_respond_data(vt
, size
, data
);
243 if (vt
->y_displ
!= vt
->y_base
) {
244 vt
->y_displ
= vt
->y_base
;
245 spiceterm_refresh(vt
);
248 spiceterm_update_watch_mask(vt
, TRUE
);
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
,
264 /* vdagent interface - to get mouse/clipboard support */
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, };
272 vdagent_reply(spiceTerm
*vt
, uint32_t type
, uint32_t error
)
276 size
= sizeof(VDAgentReply
);
278 int msg_size
= sizeof(VDIChunkHeader
) + sizeof(VDAgentMessage
) + size
;
279 g_assert((vdagent_write_buffer_pos
+ msg_size
) < VDAGENT_WBUF_SIZE
);
281 unsigned char *buf
= vdagent_write_buffer
+ vdagent_write_buffer_pos
;
282 vdagent_write_buffer_pos
+= msg_size
;
284 memset(buf
, 0, msg_size
);
286 VDIChunkHeader
*hdr
= (VDIChunkHeader
*)buf
;
287 VDAgentMessage
*msg
= (VDAgentMessage
*)&hdr
[1];
288 VDAgentReply
*reply
= (VDAgentReply
*)&msg
[1];
290 reply
->error
= error
;
292 hdr
->port
= VDP_CLIENT_PORT
;
293 hdr
->size
= sizeof(VDAgentMessage
) + size
;
295 msg
->protocol
= VD_AGENT_PROTOCOL
;
296 msg
->type
= VD_AGENT_REPLY
;
300 spice_server_char_device_wakeup(&vt
->vdagent_sin
);
304 dump_message(unsigned char *buf
, int size
)
308 for (i
= 0; i
< size
; i
++) {
309 printf("%d %02X\n", i
, buf
[i
]);
316 vdagent_send_capabilities(spiceTerm
*vt
, uint32_t request
)
318 VDAgentAnnounceCapabilities
*caps
;
321 size
= sizeof(*caps
) + VD_AGENT_CAPS_BYTES
;
322 caps
= calloc(1, size
);
323 g_assert(caps
!= NULL
);
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
);
334 int msg_size
= sizeof(VDIChunkHeader
) + sizeof(VDAgentMessage
) + size
;
335 g_assert((vdagent_write_buffer_pos
+ msg_size
) < VDAGENT_WBUF_SIZE
);
337 unsigned char *buf
= vdagent_write_buffer
+ vdagent_write_buffer_pos
;
338 vdagent_write_buffer_pos
+= msg_size
;
340 VDIChunkHeader
*hdr
= (VDIChunkHeader
*)buf
;
341 VDAgentMessage
*msg
= (VDAgentMessage
*)&hdr
[1];
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
;
350 memcpy(buf
+ sizeof(VDIChunkHeader
) + sizeof(VDAgentMessage
), (uint8_t *)caps
, size
);
352 if (0) dump_message(buf
, msg_size
);
354 spice_server_char_device_wakeup(&vt
->vdagent_sin
);
360 vdagent_owns_clipboard(spiceTerm
*vt
)
362 return !!agent_owns_clipboard
[VD_AGENT_CLIPBOARD_SELECTION_PRIMARY
];
366 vdagent_grab_clipboard(spiceTerm
*vt
)
370 uint8_t selection
= VD_AGENT_CLIPBOARD_SELECTION_PRIMARY
;
372 agent_owns_clipboard
[selection
] = 1;
376 int msg_size
= sizeof(VDIChunkHeader
) + sizeof(VDAgentMessage
) + size
;
377 g_assert((vdagent_write_buffer_pos
+ msg_size
) < VDAGENT_WBUF_SIZE
);
379 unsigned char *buf
= vdagent_write_buffer
+ vdagent_write_buffer_pos
;
380 vdagent_write_buffer_pos
+= msg_size
;
382 memset(buf
, 0, msg_size
);
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
;
390 hdr
->port
= VDP_CLIENT_PORT
;
391 hdr
->size
= sizeof(VDAgentMessage
) + size
;
393 msg
->protocol
= VD_AGENT_PROTOCOL
;
394 msg
->type
= VD_AGENT_CLIPBOARD_GRAB
;
398 if (0) dump_message(buf
, msg_size
);
400 spice_server_char_device_wakeup(&vt
->vdagent_sin
);
404 vdagent_request_clipboard(spiceTerm
*vt
)
408 uint8_t selection
= VD_AGENT_CLIPBOARD_SELECTION_PRIMARY
;
410 size
= 4 + sizeof(VDAgentClipboardRequest
);
412 int msg_size
= sizeof(VDIChunkHeader
) + sizeof(VDAgentMessage
) + size
;
413 g_assert((vdagent_write_buffer_pos
+ msg_size
) < VDAGENT_WBUF_SIZE
);
415 unsigned char *buf
= vdagent_write_buffer
+ vdagent_write_buffer_pos
;
416 vdagent_write_buffer_pos
+= msg_size
;
418 memset(buf
, 0, msg_size
);
420 VDIChunkHeader
*hdr
= (VDIChunkHeader
*)buf
;
421 VDAgentMessage
*msg
= (VDAgentMessage
*)&hdr
[1];
422 uint8_t *data
= (uint8_t *)&msg
[1];
423 *((uint32_t *)data
) = 0;
425 ((uint32_t *)data
)[1] = VD_AGENT_CLIPBOARD_UTF8_TEXT
;
427 hdr
->port
= VDP_CLIENT_PORT
;
428 hdr
->size
= sizeof(VDAgentMessage
) + size
;
430 msg
->protocol
= VD_AGENT_PROTOCOL
;
431 msg
->type
= VD_AGENT_CLIPBOARD_REQUEST
;
435 if (0) dump_message(buf
, msg_size
);
437 spice_server_char_device_wakeup(&vt
->vdagent_sin
);
441 vdagent_send_clipboard(spiceTerm
*vt
, uint8_t selection
)
445 if (selection
!= VD_AGENT_CLIPBOARD_SELECTION_PRIMARY
) {
446 fprintf(stderr
, "clipboard select %d is not supported\n", selection
);
453 sel_data
= g_utf16_to_utf8(vt
->selection
, vt
->selection_len
, NULL
, &sel_len
, NULL
);
455 sel_len
= vt
->selection_len
;
456 sel_data
= g_malloc(sel_len
);
458 for (i
= 0; i
< sel_len
; i
++) { sel_data
[i
] = (char)vt
->selection
[i
]; }
459 sel_data
[sel_len
] = 0;
464 int msg_size
= sizeof(VDIChunkHeader
) + sizeof(VDAgentMessage
) + size
;
465 g_assert((vdagent_write_buffer_pos
+ msg_size
) < VDAGENT_WBUF_SIZE
);
467 unsigned char *buf
= vdagent_write_buffer
+ vdagent_write_buffer_pos
;
468 vdagent_write_buffer_pos
+= msg_size
;
470 memset(buf
, 0, msg_size
);
472 VDIChunkHeader
*hdr
= (VDIChunkHeader
*)buf
;
473 VDAgentMessage
*msg
= (VDAgentMessage
*)&hdr
[1];
474 uint8_t *data
= (uint8_t *)&msg
[1];
475 *((uint8_t *)data
) = selection
;
477 *((uint32_t *)data
) = VD_AGENT_CLIPBOARD_UTF8_TEXT
;
480 memcpy(data
, sel_data
, sel_len
);
483 hdr
->port
= VDP_CLIENT_PORT
;
484 hdr
->size
= sizeof(VDAgentMessage
) + size
;
486 msg
->protocol
= VD_AGENT_PROTOCOL
;
487 msg
->type
= VD_AGENT_CLIPBOARD
;
491 spice_server_char_device_wakeup(&vt
->vdagent_sin
);
495 vmc_write(SpiceCharDeviceInstance
*sin
, const uint8_t *buf
, int len
)
497 spiceTerm
*vt
= SPICE_CONTAINEROF(sin
, spiceTerm
, vdagent_sin
);
499 VDIChunkHeader
*hdr
= (VDIChunkHeader
*)buf
;
500 VDAgentMessage
*msg
= (VDAgentMessage
*)&hdr
[1];
502 //g_assert(hdr->port == VDP_SERVER_PORT);
503 g_assert(msg
->protocol
== VD_AGENT_PROTOCOL
);
505 DPRINTF(1, "%d %d %d %d", len
, hdr
->port
, msg
->protocol
, 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
);
513 case VD_AGENT_ANNOUNCE_CAPABILITIES
: {
514 VDAgentAnnounceCapabilities
*caps
= (VDAgentAnnounceCapabilities
*)&msg
[1];
515 DPRINTF(1, "VD_AGENT_ANNOUNCE_CAPABILITIES %d", caps
->request
);
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
));
523 vdagent_send_capabilities(vt
, 0);
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
);
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));
539 DPRINTF(1, "VD_AGENT_CLIPBOARD_REQUEST %d %d", selection
, type
);
541 vdagent_send_clipboard(vt
, selection
);
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
);
552 if (type
== VD_AGENT_CLIPBOARD_UTF8_TEXT
) {
553 spiceterm_respond_data(vt
, size
, data
+ 8);
554 spiceterm_update_watch_mask(vt
, TRUE
);
558 case VD_AGENT_CLIPBOARD_RELEASE
: {
559 uint8_t *data
= (uint8_t *)&msg
[1];
560 uint8_t selection
= data
[0];
562 DPRINTF(1, "VD_AGENT_CLIPBOARD_RELEASE %d", selection
);
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
);
572 spiceterm_resize(vt
, list
->monitors
[0].width
, list
->monitors
[0].height
);
574 vdagent_reply(vt
, VD_AGENT_MONITORS_CONFIG
, VD_AGENT_SUCCESS
);
578 DPRINTF(1, "got uknown vdagent message type %d\n", msg
->type
);
585 vmc_read(SpiceCharDeviceInstance
*sin
, uint8_t *buf
, int len
)
587 DPRINTF(1, "%d %d", len
, vdagent_write_buffer_pos
);
590 if (!vdagent_write_buffer_pos
) {
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
);
600 vdagent_write_buffer_pos
-= size
;
602 DPRINTF(1, "RET %d %d", size
, vdagent_write_buffer_pos
);
607 vmc_state(SpiceCharDeviceInstance
*sin
, int connected
)
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
,
623 create_spiceterm(int argc
, char** argv
, uint32_t maxx
, uint32_t maxy
, guint timeout
)
625 SpiceCoreInterface
*core
= basic_event_loop_init();
626 SpiceScreen
*spice_screen
= spice_screen_new(core
, maxx
, maxy
, timeout
);
628 //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
630 spice_screen
->image_cache
= g_hash_table_new(g_int_hash
, g_int_equal
);
632 spiceTerm
*vt
= (spiceTerm
*)calloc (sizeof(spiceTerm
), 1);
634 vt
->keyboard_sin
.base
.sif
= &my_keyboard_sif
.base
;
635 spice_server_add_interface(spice_screen
->server
, &vt
->keyboard_sin
.base
);
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
;
642 init_spiceterm(vt
, maxx
, maxy
);