]> git.proxmox.com Git - spiceterm.git/blob - spiceterm.c
afbda2b3c06fa3744459ca2f4f5a9ab3ba7cb53a
[spiceterm.git] / spiceterm.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 Note: most of the code here is copied from vncterm (which is
24 also written by me).
25
26 */
27
28 #define _GNU_SOURCE
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37 #include <pty.h> /* for openpty and forkpty */
38 #include <string.h>
39 #include <errno.h>
40 #include <sys/ioctl.h>
41 #include <sys/wait.h>
42 #include <signal.h>
43 #include <locale.h>
44
45 #include "spiceterm.h"
46
47 #include <glib.h>
48 #include <spice.h>
49 #include <spice/enums.h>
50 #include <spice/macros.h>
51 #include <spice/qxl_dev.h>
52
53 #include <gdk/gdkkeysyms.h>
54
55 #include "event_loop.h"
56 #include "translations.h"
57
58 static int debug = 0;
59
60 #define DPRINTF(x, format, ...) { \
61 if (x <= debug) { \
62 printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
63 } \
64 }
65
66 #define TERM "xterm"
67
68 #define TERMIDCODE "[?1;2c" // vt100 ID
69
70 #define CHECK_ARGC(argc,argv,i) if (i >= argc-1) { \
71 fprintf(stderr, "ERROR: not enough arguments for: %s\n", argv[i]); \
72 print_usage(NULL); \
73 exit(1); \
74 }
75
76 /* these colours are from linux kernel drivers/char/vt.c */
77
78 unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7,
79 8,12,10,14, 9,13,11,15 };
80
81
82 static void vdagent_grab_clipboard(spiceTerm *vt, uint8_t selection);
83 static void vdagent_request_clipboard(spiceTerm *vt, uint8_t selection);
84
85 static void
86 print_usage(const char *msg)
87 {
88 if (msg) { fprintf(stderr, "ERROR: %s\n", msg); }
89 fprintf(stderr, "USAGE: spiceterm [spiceopts] [-c command [args]]\n");
90 }
91
92 static void
93 draw_char_at(spiceTerm *vt, int x, int y, gunichar2 ch, TextAttributes attrib)
94 {
95 if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) {
96 return;
97 }
98
99 spice_screen_draw_char(vt->screen, x, y, ch, attrib);
100 }
101
102 static void
103 spiceterm_update_xy(spiceTerm *vt, int x, int y)
104 {
105 if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; }
106
107 int y1 = (vt->y_base + y) % vt->total_height;
108 int y2 = y1 - vt->y_displ;
109 if (y2 < 0) {
110 y2 += vt->total_height;
111 }
112 if (y2 < vt->height) {
113 TextCell *c = &vt->cells[y1 * vt->width + x];
114 draw_char_at(vt, x, y2, c->ch, c->attrib);
115 }
116 }
117
118 static void
119 spiceterm_clear_xy(spiceTerm *vt, int x, int y)
120 {
121 if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; }
122
123 int y1 = (vt->y_base + y) % vt->total_height;
124 int y2 = y1 - vt->y_displ;
125 if (y2 < 0) {
126 y2 += vt->total_height;
127 }
128 if (y2 < vt->height) {
129 TextCell *c = &vt->cells[y1 * vt->width + x];
130 c->ch = ' ';
131 c->attrib = vt->default_attrib;
132 c->attrib.fgcol = vt->cur_attrib.fgcol;
133 c->attrib.bgcol = vt->cur_attrib.bgcol;
134
135 draw_char_at(vt, x, y, c->ch, c->attrib);
136 }
137 }
138
139 void
140 spiceterm_toggle_marked_cell(spiceTerm *vt, int pos)
141 {
142 int x = (pos%vt->width);
143 int y = (pos/vt->width);
144
145 if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; }
146
147 int y1 = (vt->y_displ + y) % vt->total_height;
148
149 TextCell *c = &vt->cells[y1 * vt->width + x];
150 c->attrib.selected = c->attrib.selected ? 0 : 1;
151
152 if (y < vt->height) {
153 draw_char_at(vt, x, y, c->ch, c->attrib);
154 }
155 }
156
157 static void
158 spiceterm_show_cursor(spiceTerm *vt, int show)
159 {
160 int x = vt->cx;
161 if (x >= vt->width) {
162 x = vt->width - 1;
163 }
164
165 int y1 = (vt->y_base + vt->cy) % vt->total_height;
166 int y = y1 - vt->y_displ;
167 if (y < 0) {
168 y += vt->total_height;
169 }
170
171 if (y < vt->height) {
172
173 TextCell *c = &vt->cells[y1 * vt->width + x];
174
175 if (show) {
176 TextAttributes attrib = vt->default_attrib;
177 attrib.invers = !(attrib.invers); /* invert fg and bg */
178 draw_char_at(vt, x, y, c->ch, attrib);
179 } else {
180 draw_char_at(vt, x, y, c->ch, c->attrib);
181 }
182 }
183 }
184
185 static void
186 spiceterm_refresh(spiceTerm *vt)
187 {
188 int x, y, y1;
189
190 y1 = vt->y_displ;
191 for(y = 0; y < vt->height; y++) {
192 TextCell *c = vt->cells + y1 * vt->width;
193 for(x = 0; x < vt->width; x++) {
194 draw_char_at(vt, x, y, c->ch, c->attrib);
195 c++;
196 }
197 if (++y1 == vt->total_height)
198 y1 = 0;
199 }
200
201 spiceterm_show_cursor(vt, 1);
202 }
203
204 void
205 spiceterm_unselect_all(spiceTerm *vt)
206 {
207 int i;
208
209 for (i = 0; i < vt->width*vt->total_height; i++) {
210 if (vt->cells[i].attrib.selected) {
211 vt->cells[i].attrib.selected = 0;
212 }
213 }
214
215 spiceterm_refresh(vt);
216 }
217
218 static void
219 spiceterm_scroll_down(spiceTerm *vt, int top, int bottom, int lines)
220 {
221 if ((top + lines) >= bottom) {
222 lines = bottom - top -1;
223 }
224
225 if (top < 0 || bottom > vt->height || top >= bottom || lines < 1) {
226 return;
227 }
228
229 int i;
230 for(i = bottom - top - lines - 1; i >= 0; i--) {
231 int src = ((vt->y_base + top + i) % vt->total_height)*vt->width;
232 int dst = ((vt->y_base + top + lines + i) % vt->total_height)*vt->width;
233
234 memmove(vt->cells + dst, vt->cells + src, vt->width*sizeof(TextCell));
235 }
236
237 for (i = 0; i < lines; i++) {
238 int j;
239 TextCell *c = vt->cells + ((vt->y_base + top + i) % vt->total_height)*vt->width;
240 for(j = 0; j < vt->width; j++) {
241 c->attrib = vt->default_attrib;
242 c->ch = ' ';
243 c++;
244 }
245 }
246
247 int h = lines * 16;
248 int y0 = top*16;
249 int y1 = y0 + h;
250 int y2 = bottom*16;
251
252 spice_screen_scroll(vt->screen, 0, y1, vt->screen->primary_width, y2, 0, y0);
253 spice_screen_clear(vt->screen, 0, y0, vt->screen->primary_width, y1);
254 }
255
256 static void
257 spiceterm_scroll_up(spiceTerm *vt, int top, int bottom, int lines, int moveattr)
258 {
259 if ((top + lines) >= bottom) {
260 lines = bottom - top - 1;
261 }
262
263 if (top < 0 || bottom > vt->height || top >= bottom || lines < 1) {
264 return;
265 }
266
267
268 int h = lines * 16;
269 int y0 = top*16;
270 int y1 = (top + lines)*16;
271 int y2 = bottom*16;
272
273 spice_screen_scroll(vt->screen, 0, y0, vt->screen->primary_width, y2 -h, 0, y1);
274 spice_screen_clear(vt->screen, 0, y2 -h, vt->screen->primary_width, y2);
275
276 if (!moveattr) {
277 return;
278 }
279
280 // move attributes
281
282 int i;
283 for(i = 0; i < (bottom - top - lines); i++) {
284 int dst = ((vt->y_base + top + i) % vt->total_height)*vt->width;
285 int src = ((vt->y_base + top + lines + i) % vt->total_height)*vt->width;
286
287 memmove(vt->cells + dst, vt->cells + src, vt->width*sizeof(TextCell));
288 }
289
290 for (i = 1; i <= lines; i++) {
291 int j;
292 TextCell *c = vt->cells + ((vt->y_base + bottom - i) % vt->total_height)*vt->width;
293 for(j = 0; j < vt->width; j++) {
294 c->attrib = vt->default_attrib;
295 c->ch = ' ';
296 c++;
297 }
298 }
299 }
300
301 static void
302 spiceterm_virtual_scroll(spiceTerm *vt, int lines)
303 {
304 if (vt->altbuf || lines == 0) return;
305
306 if (lines < 0) {
307 lines = -lines;
308 int i = vt->scroll_height;
309 if (i > vt->total_height - vt->height)
310 i = vt->total_height - vt->height;
311 int y1 = vt->y_base - i;
312 if (y1 < 0)
313 y1 += vt->total_height;
314 for(i = 0; i < lines; i++) {
315 if (vt->y_displ == y1) break;
316 if (--vt->y_displ < 0) {
317 vt->y_displ = vt->total_height - 1;
318 }
319 }
320 } else {
321 int i;
322 for(i = 0; i < lines; i++) {
323 if (vt->y_displ == vt->y_base) break;
324 if (++vt->y_displ == vt->total_height) {
325 vt->y_displ = 0;
326 }
327 }
328 }
329
330 spiceterm_refresh(vt);
331 }
332
333 static void
334 spiceterm_respond_esc(spiceTerm *vt, const char *esc)
335 {
336 int len = strlen(esc);
337 int i;
338
339 if (vt->ibuf_count < (IBUFSIZE - 1 - len)) {
340 vt->ibuf[vt->ibuf_count++] = 27;
341 for (i = 0; i < len; i++) {
342 vt->ibuf[vt->ibuf_count++] = esc[i];
343 }
344 }
345 }
346
347 static void
348 spiceterm_put_lf(spiceTerm *vt)
349 {
350 if (vt->cy + 1 == vt->region_bottom) {
351
352 if (vt->altbuf || vt->region_top != 0 || vt->region_bottom != vt->height) {
353 spiceterm_scroll_up(vt, vt->region_top, vt->region_bottom, 1, 1);
354 return;
355 }
356
357 if (vt->y_displ == vt->y_base) {
358 spiceterm_scroll_up(vt, vt->region_top, vt->region_bottom, 1, 0);
359 }
360
361 if (vt->y_displ == vt->y_base) {
362 if (++vt->y_displ == vt->total_height) {
363 vt->y_displ = 0;
364 }
365 }
366
367 if (++vt->y_base == vt->total_height) {
368 vt->y_base = 0;
369 }
370
371 if (vt->scroll_height < vt->total_height) {
372 vt->scroll_height++;
373 }
374
375 int y1 = (vt->y_base + vt->height - 1) % vt->total_height;
376 TextCell *c = &vt->cells[y1 * vt->width];
377 int x;
378 for (x = 0; x < vt->width; x++) {
379 c->ch = ' ';
380 c->attrib = vt->default_attrib;
381 c++;
382 }
383
384 // fprintf (stderr, "BASE: %d DISPLAY %d\n", vt->y_base, vt->y_displ);
385
386 } else if (vt->cy < vt->height - 1) {
387 vt->cy += 1;
388 }
389 }
390
391 static void
392 spiceterm_csi_m(spiceTerm *vt)
393 {
394 int i;
395
396 for (i = 0; i < vt->esc_count; i++) {
397 switch (vt->esc_buf[i]) {
398 case 0: /* reset all console attributes to default */
399 vt->cur_attrib = vt->default_attrib;
400 break;
401 case 1:
402 vt->cur_attrib.bold = 1;
403 break;
404 case 4:
405 vt->cur_attrib.uline = 1;
406 break;
407 case 5:
408 vt->cur_attrib.blink = 1;
409 break;
410 case 7:
411 vt->cur_attrib.invers = 1;
412 break;
413 case 8:
414 vt->cur_attrib.unvisible = 1;
415 break;
416 case 10:
417 vt->cur_enc = LAT1_MAP;
418 // fixme: dispaly controls = 0 ?
419 // fixme: toggle meta = 0 ?
420 break;
421 case 11:
422 vt->cur_enc = IBMPC_MAP;
423 // fixme: dispaly controls = 1 ?
424 // fixme: toggle meta = 0 ?
425 break;
426 case 12:
427 vt->cur_enc = IBMPC_MAP;
428 // fixme: dispaly controls = 1 ?
429 // fixme: toggle meta = 1 ?
430 break;
431 case 22:
432 vt->cur_attrib.bold = 0;
433 break;
434 case 24:
435 vt->cur_attrib.uline = 0;
436 break;
437 case 25:
438 vt->cur_attrib.blink = 0;
439 break;
440 case 27:
441 vt->cur_attrib.invers = 0;
442 break;
443 case 28:
444 vt->cur_attrib.unvisible = 0;
445 break;
446 case 30:
447 case 31:
448 case 32:
449 case 33:
450 case 34:
451 case 35:
452 case 36:
453 case 37:
454 /* set foreground color */
455 vt->cur_attrib.fgcol = color_table [vt->esc_buf[i] - 30];
456 break;
457 case 38:
458 /* reset color to default, enable underline */
459 vt->cur_attrib.fgcol = vt->default_attrib.fgcol;
460 vt->cur_attrib.uline = 1;
461 break;
462 case 39:
463 /* reset color to default, disable underline */
464 vt->cur_attrib.fgcol = vt->default_attrib.fgcol;
465 vt->cur_attrib.uline = 0;
466 break;
467 case 40:
468 case 41:
469 case 42:
470 case 43:
471 case 44:
472 case 45:
473 case 46:
474 case 47:
475 /* set background color */
476 vt->cur_attrib.bgcol = color_table [vt->esc_buf[i] - 40];
477 break;
478 case 49:
479 /* reset background color */
480 vt->cur_attrib.bgcol = vt->default_attrib.bgcol;
481 break;
482 default:
483 fprintf(stderr, "unhandled ESC[%d m code\n",vt->esc_buf[i]);
484 //fixme: implement
485 }
486 }
487 }
488
489 static void
490 spiceterm_save_cursor(spiceTerm *vt)
491 {
492 vt->cx_saved = vt->cx;
493 vt->cy_saved = vt->cy;
494 vt->cur_attrib_saved = vt->cur_attrib;
495 vt->charset_saved = vt->charset;
496 vt->g0enc_saved = vt->g0enc;
497 vt->g1enc_saved = vt->g1enc;
498 vt->cur_enc_saved = vt->cur_enc;
499 }
500
501 static void
502 spiceterm_restore_cursor(spiceTerm *vt)
503 {
504 vt->cx = vt->cx_saved;
505 vt->cy = vt->cy_saved;
506 vt->cur_attrib = vt->cur_attrib_saved;
507 vt->charset = vt->charset_saved;
508 vt->g0enc = vt->g0enc_saved;
509 vt->g1enc = vt->g1enc_saved;
510 vt->cur_enc = vt->cur_enc_saved;
511 }
512
513 static void
514 spiceterm_set_alternate_buffer(spiceTerm *vt, int on_off)
515 {
516 int x, y;
517
518 vt->y_displ = vt->y_base;
519
520 if (on_off) {
521
522 if (vt->altbuf) return;
523
524 vt->altbuf = 1;
525
526 /* alternate buffer & cursor */
527
528 spiceterm_save_cursor(vt);
529 /* save screen to altcels */
530 for (y = 0; y < vt->height; y++) {
531 int y1 = (vt->y_base + y) % vt->total_height;
532 for (x = 0; x < vt->width; x++) {
533 vt->altcells[y*vt->width + x] = vt->cells[y1*vt->width + x];
534 }
535 }
536
537 /* clear screen */
538 for (y = 0; y <= vt->height; y++) {
539 for (x = 0; x < vt->width; x++) {
540 spiceterm_clear_xy(vt, x, y);
541 }
542 }
543
544 } else {
545
546 if (vt->altbuf == 0) return;
547
548 vt->altbuf = 0;
549
550 /* restore saved data */
551 for (y = 0; y < vt->height; y++) {
552 int y1 = (vt->y_base + y) % vt->total_height;
553 for (x = 0; x < vt->width; x++) {
554 vt->cells[y1*vt->width + x] = vt->altcells[y*vt->width + x];
555 }
556 }
557
558 spiceterm_restore_cursor(vt);
559 }
560
561 spiceterm_refresh(vt);
562 }
563
564 static void
565 spiceterm_set_mode(spiceTerm *vt, int on_off)
566 {
567 int i;
568
569 for (i = 0; i <= vt->esc_count; i++) {
570 if (vt->esc_ques) { /* DEC private modes set/reset */
571 switch(vt->esc_buf[i]) {
572 case 10: /* X11 mouse reporting on/off */
573 case 1000: /* SET_VT200_MOUSE */
574 case 1002: /* xterm SET_BTN_EVENT_MOUSE */
575 vt->report_mouse = on_off;
576 break;
577 case 1049: /* start/end special app mode (smcup/rmcup) */
578 spiceterm_set_alternate_buffer (vt, on_off);
579 break;
580 case 25: /* Cursor on/off */
581 case 9: /* X10 mouse reporting on/off */
582 case 6: /* Origin relative/absolute */
583 case 1: /* Cursor keys in appl mode*/
584 case 5: /* Inverted screen on/off */
585 case 7: /* Autowrap on/off */
586 case 8: /* Autorepeat on/off */
587 break;
588 }
589 } else { /* ANSI modes set/reset */
590 //g_assert_not_reached();
591
592 /* fixme: implement me */
593 }
594 }
595 }
596
597 static void
598 spiceterm_gotoxy(spiceTerm *vt, int x, int y)
599 {
600 /* verify all boundaries */
601
602 if (x < 0) {
603 x = 0;
604 }
605
606 if (x >= vt->width) {
607 x = vt->width - 1;
608 }
609
610 vt->cx = x;
611
612 if (y < 0) {
613 y = 0;
614 }
615
616 if (y >= vt->height) {
617 y = vt->height - 1;
618 }
619
620 vt->cy = y;
621 }
622
623 static void
624 debug_print_escape_buffer(spiceTerm *vt, const char *func, const char *prefix,
625 const char *qes, gunichar2 ch)
626 {
627 if (debug >=1 ) {
628 if (vt->esc_count == 0) {
629 printf("%s:%s ESC[%s%c\n", func, prefix, qes, ch);
630 } else if (vt->esc_count == 1) {
631 printf("%s:%s ESC[%s%d%c\n", func, prefix, qes, vt->esc_buf[0], ch);
632 } else {
633 int i;
634 printf("%s:%s ESC[%s%d", func, prefix, qes, vt->esc_buf[0]);
635 for (i = 1; i < vt->esc_count; i++) {
636 printf(";%d", vt->esc_buf[i]);
637 }
638 printf("%c\n", ch);
639 }
640 }
641 }
642
643 enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
644 EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
645 ESpalette, ESidquery, ESosc1, ESosc2};
646
647 static void
648 spiceterm_putchar(spiceTerm *vt, gunichar2 ch)
649 {
650 int x, y, i, c;
651
652 if (debug && !vt->tty_state) {
653 DPRINTF(1, "CHAR:%2d: %4x '%c' (cur_enc %d) %d %d",
654 vt->tty_state, ch, ch, vt->cur_enc, vt->cx, vt->cy);
655 }
656
657 switch(vt->tty_state) {
658 case ESesc:
659 vt->tty_state = ESnormal;
660 switch (ch) {
661 case '[':
662 vt->tty_state = ESsquare;
663 break;
664 case ']':
665 vt->tty_state = ESnonstd;
666 break;
667 case '%':
668 vt->tty_state = ESpercent;
669 break;
670 case '7':
671 spiceterm_save_cursor(vt);
672 break;
673 case '8':
674 spiceterm_restore_cursor(vt);
675 break;
676 case '(':
677 vt->tty_state = ESsetG0; // SET G0
678 break;
679 case ')':
680 vt->tty_state = ESsetG1; // SET G1
681 break;
682 case 'M':
683 /* cursor up (ri) */
684 if (vt->cy == vt->region_top)
685 spiceterm_scroll_down(vt, vt->region_top, vt->region_bottom, 1);
686 else if (vt->cy > 0) {
687 vt->cy--;
688 }
689 break;
690 case '>':
691 /* numeric keypad - ignored */
692 break;
693 case '=':
694 /* appl. keypad - ignored */
695 break;
696 default:
697 DPRINTF(1, "got unhandled ESC%c %d", ch, ch);
698 break;
699 }
700 break;
701 case ESnonstd: /* Operating System Controls */
702 vt->tty_state = ESnormal;
703
704 switch (ch) {
705 case 'P': /* palette escape sequence */
706 for(i = 0; i < MAX_ESC_PARAMS; i++) {
707 vt->esc_buf[i] = 0;
708 }
709
710 vt->esc_count = 0;
711 vt->tty_state = ESpalette;
712 break;
713 case 'R': /* reset palette */
714 // fixme: reset_palette(vc);
715 break;
716 case '0':
717 case '1':
718 case '2':
719 case '4':
720 vt->osc_cmd = ch;
721 vt->osc_textbuf[0] = 0;
722 vt->tty_state = ESosc1;
723 break;
724 default:
725 DPRINTF(1, "got unhandled OSC %c", ch);
726 vt->tty_state = ESnormal;
727 break;
728 }
729 break;
730 case ESosc1:
731 vt->tty_state = ESnormal;
732 if (ch == ';') {
733 vt->tty_state = ESosc2;
734 } else {
735 DPRINTF(1, "got illegal OSC sequence");
736 }
737 break;
738 case ESosc2:
739 if (ch != 0x9c && ch != 7) {
740 int i = 0;
741 while (vt->osc_textbuf[i]) i++;
742 vt->osc_textbuf[i++] = ch;
743 vt->osc_textbuf[i] = 0;
744 } else {
745 DPRINTF(1, "OSC:%c:%s", vt->osc_cmd, vt->osc_textbuf);
746 vt->tty_state = ESnormal;
747 }
748 break;
749 case ESpalette:
750 if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
751 || (ch >= 'a' && ch <= 'f')) {
752 vt->esc_buf[vt->esc_count++] = (ch > '9' ? (ch & 0xDF) - 'A' + 10 : ch - '0');
753 if (vt->esc_count == 7) {
754 // fixme: this does not work - please test
755 /*
756 rfbColourMap *cmap =&vt->screen->colourMap;
757
758 int i = color_table[vt->esc_buf[0]] * 3, j = 1;
759 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
760 cmap->data.bytes[i++] += vt->esc_buf[j++];
761 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
762 cmap->data.bytes[i++] += vt->esc_buf[j++];
763 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
764 cmap->data.bytes[i] += vt->esc_buf[j];
765 */
766 //set_palette(vc); ?
767
768 vt->tty_state = ESnormal;
769 }
770 } else
771 vt->tty_state = ESnormal;
772 break;
773 case ESsquare:
774 for(i = 0; i < MAX_ESC_PARAMS; i++) {
775 vt->esc_buf[i] = 0;
776 }
777
778 vt->esc_count = 0;
779 vt->esc_has_par = 0;
780 vt->tty_state = ESgetpars;
781
782 if (ch == '>') {
783 vt->tty_state = ESidquery;
784 break;
785 }
786
787 if ((vt->esc_ques = (ch == '?'))) {
788 break;
789 }
790 case ESgetpars:
791 if (ch >= '0' && ch <= '9') {
792 vt->esc_has_par = 1;
793 if (vt->esc_count < MAX_ESC_PARAMS) {
794 vt->esc_buf[vt->esc_count] = vt->esc_buf[vt->esc_count] * 10 + ch - '0';
795 }
796 break;
797 } else if (ch == ';') {
798 vt->esc_count++;
799 break;
800 } else {
801 if (vt->esc_has_par) {
802 vt->esc_count++;
803 }
804 vt->tty_state = ESgotpars;
805 }
806 case ESgotpars:
807
808 vt->tty_state = ESnormal;
809
810 char *qes = vt->esc_ques ? "?" : "";
811
812 if (debug) {
813 debug_print_escape_buffer(vt, __func__, "", qes, ch);
814 }
815
816 switch (ch) {
817 case 'h':
818 spiceterm_set_mode(vt, 1);
819 break;
820 case 'l':
821 spiceterm_set_mode(vt, 0);
822 break;
823 case 'm':
824 if (!vt->esc_count) {
825 vt->esc_count++; // default parameter 0
826 }
827 spiceterm_csi_m(vt);
828 break;
829 case 'n':
830 /* report cursor position */
831 /* TODO: send ESC[row;colR */
832 break;
833 case 'A':
834 /* move cursor up */
835 if (vt->esc_buf[0] == 0) {
836 vt->esc_buf[0] = 1;
837 }
838 vt->cy -= vt->esc_buf[0];
839 if (vt->cy < 0) {
840 vt->cy = 0;
841 }
842 break;
843 case 'B':
844 case 'e':
845 /* move cursor down */
846 if (vt->esc_buf[0] == 0) {
847 vt->esc_buf[0] = 1;
848 }
849 vt->cy += vt->esc_buf[0];
850 if (vt->cy >= vt->height) {
851 vt->cy = vt->height - 1;
852 }
853 break;
854 case 'C':
855 case 'a':
856 /* move cursor right */
857 if (vt->esc_buf[0] == 0) {
858 vt->esc_buf[0] = 1;
859 }
860 vt->cx += vt->esc_buf[0];
861 if (vt->cx >= vt->width) {
862 vt->cx = vt->width - 1;
863 }
864 break;
865 case 'D':
866 /* move cursor left */
867 if (vt->esc_buf[0] == 0) {
868 vt->esc_buf[0] = 1;
869 }
870 vt->cx -= vt->esc_buf[0];
871 if (vt->cx < 0) {
872 vt->cx = 0;
873 }
874 break;
875 case 'G':
876 case '`':
877 /* move cursor to column */
878 spiceterm_gotoxy(vt, vt->esc_buf[0] - 1, vt->cy);
879 break;
880 case 'd':
881 /* move cursor to row */
882 spiceterm_gotoxy(vt, vt->cx , vt->esc_buf[0] - 1);
883 break;
884 case 'f':
885 case 'H':
886 /* move cursor to row, column */
887 spiceterm_gotoxy(vt, vt->esc_buf[1] - 1, vt->esc_buf[0] - 1);
888 break;
889 case 'J':
890 switch (vt->esc_buf[0]) {
891 case 0:
892 /* clear to end of screen */
893 for (y = vt->cy; y < vt->height; y++) {
894 for (x = 0; x < vt->width; x++) {
895 if (y == vt->cy && x < vt->cx) {
896 continue;
897 }
898 spiceterm_clear_xy (vt, x, y);
899 }
900 }
901 break;
902 case 1:
903 /* clear from beginning of screen */
904 for (y = 0; y <= vt->cy; y++) {
905 for (x = 0; x < vt->width; x++) {
906 if (y == vt->cy && x > vt->cx) {
907 break;
908 }
909 spiceterm_clear_xy(vt, x, y);
910 }
911 }
912 break;
913 case 2:
914 /* clear entire screen */
915 for (y = 0; y <= vt->height; y++) {
916 for (x = 0; x < vt->width; x++) {
917 spiceterm_clear_xy(vt, x, y);
918 }
919 }
920 break;
921 }
922 break;
923 case 'K':
924 switch (vt->esc_buf[0]) {
925 case 0:
926 /* clear to eol */
927 for(x = vt->cx; x < vt->width; x++) {
928 spiceterm_clear_xy(vt, x, vt->cy);
929 }
930 break;
931 case 1:
932 /* clear from beginning of line */
933 for (x = 0; x <= vt->cx; x++) {
934 spiceterm_clear_xy(vt, x, vt->cy);
935 }
936 break;
937 case 2:
938 /* clear entire line */
939 for(x = 0; x < vt->width; x++) {
940 spiceterm_clear_xy(vt, x, vt->cy);
941 }
942 break;
943 }
944 break;
945 case 'L':
946 /* insert line */
947 c = vt->esc_buf[0];
948
949 if (c > vt->height - vt->cy)
950 c = vt->height - vt->cy;
951 else if (!c)
952 c = 1;
953
954 spiceterm_scroll_down(vt, vt->cy, vt->region_bottom, c);
955 break;
956 case 'M':
957 /* delete line */
958 c = vt->esc_buf[0];
959
960 if (c > vt->height - vt->cy)
961 c = vt->height - vt->cy;
962 else if (!c)
963 c = 1;
964
965 spiceterm_scroll_up(vt, vt->cy, vt->region_bottom, c, 1);
966 break;
967 case 'T':
968 /* scroll down */
969 c = vt->esc_buf[0];
970 if (!c) c = 1;
971 spiceterm_scroll_down(vt, vt->region_top, vt->region_bottom, c);
972 break;
973 case 'S':
974 /* scroll up */
975 c = vt->esc_buf[0];
976 if (!c) c = 1;
977 spiceterm_scroll_up(vt, vt->region_top, vt->region_bottom, c, 1);
978 break;
979 case 'P':
980 /* delete c character */
981 c = vt->esc_buf[0];
982
983 if (c > vt->width - vt->cx)
984 c = vt->width - vt->cx;
985 else if (!c)
986 c = 1;
987
988 for (x = vt->cx; x < vt->width - c; x++) {
989 int y1 = (vt->y_base + vt->cy) % vt->total_height;
990 TextCell *dst = &vt->cells[y1 * vt->width + x];
991 TextCell *src = dst + c;
992 *dst = *src;
993 spiceterm_update_xy(vt, x + c, vt->cy);
994 src->ch = ' ';
995 src->attrib = vt->default_attrib;
996 spiceterm_update_xy(vt, x, vt->cy);
997 }
998 break;
999 case 's':
1000 /* save cursor position */
1001 spiceterm_save_cursor(vt);
1002 break;
1003 case 'u':
1004 /* restore cursor position */
1005 spiceterm_restore_cursor(vt);
1006 break;
1007 case 'X':
1008 /* erase c characters */
1009 c = vt->esc_buf[0];
1010 if (!c) c = 1;
1011
1012 if (c > (vt->width - vt->cx)) c = vt->width - vt->cx;
1013
1014 for(i = 0; i < c; i++) {
1015 spiceterm_clear_xy(vt, vt->cx + i, vt->cy);
1016 }
1017 break;
1018 case '@':
1019 /* insert c character */
1020 c = vt->esc_buf[0];
1021 if (c > (vt->width - vt->cx)) {
1022 c = vt->width - vt->cx;
1023 }
1024 if (!c) c = 1;
1025
1026 for (x = vt->width - c; x >= vt->cx; x--) {
1027 int y1 = (vt->y_base + vt->cy) % vt->total_height;
1028 TextCell *src = &vt->cells[y1 * vt->width + x];
1029 TextCell *dst = src + c;
1030 *dst = *src;
1031 spiceterm_update_xy (vt, x + c, vt->cy);
1032 src->ch = ' ';
1033 src->attrib = vt->cur_attrib;
1034 spiceterm_update_xy(vt, x, vt->cy);
1035 }
1036
1037 break;
1038 case 'r':
1039 /* set region */
1040 if (!vt->esc_buf[0])
1041 vt->esc_buf[0]++;
1042 if (!vt->esc_buf[1])
1043 vt->esc_buf[1] = vt->height;
1044 /* Minimum allowed region is 2 lines */
1045 if (vt->esc_buf[0] < vt->esc_buf[1] &&
1046 vt->esc_buf[1] <= vt->height) {
1047 vt->region_top = vt->esc_buf[0] - 1;
1048 vt->region_bottom = vt->esc_buf[1];
1049 vt->cx = 0;
1050 vt->cy = vt->region_top;
1051 DPRINTF(1, "set region %d %d", vt->region_top, vt->region_bottom);
1052 }
1053
1054 break;
1055 default:
1056 if (debug) {
1057 debug_print_escape_buffer(vt, __func__, " unhandled escape", qes, ch);
1058 }
1059 break;
1060 }
1061 vt->esc_ques = 0;
1062 break;
1063 case ESsetG0: // Set G0
1064 vt->tty_state = ESnormal;
1065
1066 if (ch == '0')
1067 vt->g0enc = GRAF_MAP;
1068 else if (ch == 'B')
1069 vt->g0enc = LAT1_MAP;
1070 else if (ch == 'U')
1071 vt->g0enc = IBMPC_MAP;
1072 else if (ch == 'K')
1073 vt->g0enc = USER_MAP;
1074
1075 if (vt->charset == 0)
1076 vt->cur_enc = vt->g0enc;
1077
1078 break;
1079 case ESsetG1: // Set G1
1080 vt->tty_state = ESnormal;
1081
1082 if (ch == '0')
1083 vt->g1enc = GRAF_MAP;
1084 else if (ch == 'B')
1085 vt->g1enc = LAT1_MAP;
1086 else if (ch == 'U')
1087 vt->g1enc = IBMPC_MAP;
1088 else if (ch == 'K')
1089 vt->g1enc = USER_MAP;
1090
1091 if (vt->charset == 1)
1092 vt->cur_enc = vt->g1enc;
1093
1094 break;
1095 case ESidquery: // vt100 query id
1096 vt->tty_state = ESnormal;
1097
1098 if (ch == 'c') {
1099 DPRINTF(1, "ESC[>c Query term ID");
1100 spiceterm_respond_esc(vt, TERMIDCODE);
1101 }
1102 break;
1103 case ESpercent:
1104 vt->tty_state = ESnormal;
1105 switch (ch) {
1106 case '@': /* defined in ISO 2022 */
1107 vt->utf8 = 0;
1108 break;
1109 case 'G': /* prelim official escape code */
1110 case '8': /* retained for compatibility */
1111 vt->utf8 = 1;
1112 break;
1113 }
1114 break;
1115 default: // ESnormal
1116 vt->tty_state = ESnormal;
1117
1118 switch(ch) {
1119 case 0:
1120 break;
1121 case 7: /* alert aka. bell */
1122 // fixme:
1123 //rfbSendBell(vt->screen);
1124 break;
1125 case 8: /* backspace */
1126 if (vt->cx > 0)
1127 vt->cx--;
1128 break;
1129 case 9: /* tabspace */
1130 if (vt->cx + (8 - (vt->cx % 8)) > vt->width) {
1131 vt->cx = 0;
1132 spiceterm_put_lf(vt);
1133 } else {
1134 vt->cx = vt->cx + (8 - (vt->cx % 8));
1135 }
1136 break;
1137 case 10: /* LF,*/
1138 case 11: /* VT */
1139 case 12: /* FF */
1140 spiceterm_put_lf(vt);
1141 break;
1142 case 13: /* carriage return */
1143 vt->cx = 0;
1144 break;
1145 case 14:
1146 /* SI (shift in), select character set 1 */
1147 vt->charset = 1;
1148 vt->cur_enc = vt->g1enc;
1149 /* fixme: display controls = 1 */
1150 break;
1151 case 15:
1152 /* SO (shift out), select character set 0 */
1153 vt->charset = 0;
1154 vt->cur_enc = vt->g0enc;
1155 /* fixme: display controls = 0 */
1156 break;
1157 case 27: /* esc */
1158 vt->tty_state = ESesc;
1159 break;
1160 case 127: /* delete */
1161 /* ignore */
1162 break;
1163 case 128+27: /* csi */
1164 vt->tty_state = ESsquare;
1165 break;
1166 default:
1167 if (vt->cx >= vt->width) {
1168 /* line wrap */
1169 vt->cx = 0;
1170 spiceterm_put_lf(vt);
1171 }
1172
1173 int y1 = (vt->y_base + vt->cy) % vt->total_height;
1174 TextCell *c = &vt->cells[y1*vt->width + vt->cx];
1175 c->attrib = vt->cur_attrib;
1176 c->ch = ch;
1177 spiceterm_update_xy(vt, vt->cx, vt->cy);
1178 vt->cx++;
1179 break;
1180 }
1181 break;
1182 }
1183 }
1184
1185 static int
1186 spiceterm_puts(spiceTerm *vt, const char *buf, int len)
1187 {
1188 gunichar2 tc;
1189
1190 spiceterm_show_cursor(vt, 0);
1191
1192 while (len) {
1193 unsigned char c = *buf;
1194 len--;
1195 buf++;
1196
1197 if (vt->tty_state != ESnormal) {
1198 // never translate escape sequence
1199 tc = c;
1200 } else if (vt->utf8 && !vt->cur_enc) {
1201
1202 if(c & 0x80) { // utf8 multi-byte sequence
1203
1204 if (vt->utf_count > 0 && (c & 0xc0) == 0x80) {
1205 // inside UTF8 sequence
1206 vt->utf_char = (vt->utf_char << 6) | (c & 0x3f);
1207 vt->utf_count--;
1208 if (vt->utf_count == 0) {
1209 tc = vt->utf_char;
1210 } else {
1211 continue;
1212 }
1213 } else {
1214 // first char of a UTF8 sequence
1215 if ((c & 0xe0) == 0xc0) {
1216 vt->utf_count = 1;
1217 vt->utf_char = (c & 0x1f);
1218 } else if ((c & 0xf0) == 0xe0) {
1219 vt->utf_count = 2;
1220 vt->utf_char = (c & 0x0f);
1221 } else if ((c & 0xf8) == 0xf0) {
1222 vt->utf_count = 3;
1223 vt->utf_char = (c & 0x07);
1224 } else if ((c & 0xfc) == 0xf8) {
1225 vt->utf_count = 4;
1226 vt->utf_char = (c & 0x03);
1227 } else if ((c & 0xfe) == 0xfc) {
1228 vt->utf_count = 5;
1229 vt->utf_char = (c & 0x01);
1230 } else
1231 vt->utf_count = 0;
1232
1233 continue;
1234 }
1235 } else {
1236 // utf8 single byte
1237 tc = c;
1238 vt->utf_count = 0;
1239 }
1240
1241 } else {
1242 // never translate controls
1243 if (c >= 32 && c != 127 && c != (128+27)) {
1244 tc = translations[vt->cur_enc][c & 0x0ff];
1245 } else {
1246 tc = c;
1247 }
1248 }
1249
1250 spiceterm_putchar(vt, tc);
1251 }
1252
1253 spiceterm_show_cursor(vt, 1);
1254
1255 return len;
1256 }
1257
1258 /* fixme:
1259 void
1260 spiceterm_set_xcut_text(char* str, int len, struct _rfbClientRec* cl)
1261 {
1262 spiceTerm *vt =(spiceTerm *)cl->screen->screenData;
1263
1264 // seems str is Latin-1 encoded
1265 if (vt->selection) free (vt->selection);
1266 vt->selection = (gunichar2 *)malloc (len*sizeof (gunichar2));
1267 int i;
1268 for (i = 0; i < len; i++) {
1269 vt->selection[i] = str[i] & 0xff;
1270 }
1271 vt->selection_len = len;
1272 }
1273 */
1274
1275 static void
1276 spiceterm_update_watch_mask(spiceTerm *vt, gboolean writable)
1277 {
1278 g_assert(vt != NULL);
1279
1280 int mask = SPICE_WATCH_EVENT_READ;
1281
1282 if (writable) {
1283 mask |= SPICE_WATCH_EVENT_WRITE;
1284 }
1285
1286 vt->screen->core->watch_update_mask(vt->screen->mwatch, mask);
1287 }
1288
1289 static void
1290 mouse_report(spiceTerm *vt, int butt, int mrx, int mry)
1291 {
1292 char buf[8];
1293
1294 sprintf(buf, "[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
1295 (char)('!' + mry));
1296
1297 spiceterm_respond_esc(vt, buf);
1298
1299 spiceterm_update_watch_mask(vt, TRUE);
1300 }
1301
1302 static void
1303 spiceterm_respond_unichar2(spiceTerm *vt, gunichar2 uc)
1304 {
1305 if (vt->utf8) {
1306 gchar buf[10];
1307 gint len = g_unichar_to_utf8(uc, buf);
1308
1309 if (len > 0) {
1310 if ((vt->ibuf_count + len) < IBUFSIZE) {
1311 int i;
1312 for (i = 0; i < len; i++) {
1313 vt->ibuf[vt->ibuf_count++] = buf[i];
1314 }
1315 } else {
1316 fprintf(stderr, "warning: input buffer overflow\n");
1317 }
1318 }
1319 } else {
1320 if ((vt->ibuf_count + 1) < IBUFSIZE) {
1321 vt->ibuf[vt->ibuf_count++] = (char)uc;
1322 } else {
1323 fprintf(stderr, "warning: input buffer overflow\n");
1324 }
1325 }
1326 }
1327
1328 static void
1329 my_kbd_push_key(SpiceKbdInstance *sin, uint8_t frag)
1330 {
1331 // spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, keyboard_sin);
1332
1333 /* we no not need this */
1334
1335 return;
1336 }
1337
1338 static void
1339 my_kbd_push_keyval(SpiceKbdInstance *sin, uint32_t keySym, int flags)
1340 {
1341 spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, keyboard_sin);
1342 static int control = 0;
1343 static int shift = 0;
1344 char *esc = NULL;
1345
1346 gunichar2 uc = 0;
1347
1348 DPRINTF(1, "flags=%d keySym=%08x", flags, keySym);
1349
1350 if (flags & 1) {
1351 if (keySym == GDK_KEY_Shift_L || keySym == GDK_KEY_Shift_R) {
1352 shift = 1;
1353 } if (keySym == GDK_KEY_Control_L || keySym == GDK_KEY_Control_R) {
1354 control = 1;
1355 } else if (vt->ibuf_count < (IBUFSIZE - 32)) {
1356
1357 if (control) {
1358 if(keySym >= 'a' && keySym <= 'z')
1359 uc = keySym - 'a' + 1;
1360 else if (keySym >= 'A' && keySym <= 'Z')
1361 uc = keySym - 'A' + 1;
1362 else
1363 uc = 0;
1364
1365 } else {
1366 switch (keySym) {
1367 case GDK_KEY_Escape:
1368 uc = 27; break;
1369 case GDK_KEY_Return:
1370 uc = '\r'; break;
1371 case GDK_KEY_BackSpace:
1372 uc = 8; break;
1373 case GDK_KEY_Tab:
1374 uc = '\t'; break;
1375 case GDK_KEY_Delete: /* kdch1 */
1376 case GDK_KEY_KP_Delete:
1377 esc = "[3~";break;
1378 case GDK_KEY_Home: /* khome */
1379 case GDK_KEY_KP_Home:
1380 esc = "OH";break;
1381 case GDK_KEY_End:
1382 case GDK_KEY_KP_End: /* kend */
1383 esc = "OF";break;
1384 case GDK_KEY_Insert: /* kich1 */
1385 case GDK_KEY_KP_Insert:
1386 esc = "[2~";break;
1387 case GDK_KEY_Up:
1388 case GDK_KEY_KP_Up: /* kcuu1 */
1389 esc = "OA";break;
1390 case GDK_KEY_Down: /* kcud1 */
1391 case GDK_KEY_KP_Down:
1392 esc = "OB";break;
1393 case GDK_KEY_Right:
1394 case GDK_KEY_KP_Right: /* kcuf1 */
1395 esc = "OC";break;
1396 case GDK_KEY_Left:
1397 case GDK_KEY_KP_Left: /* kcub1 */
1398 esc = "OD";break;
1399 case GDK_KEY_Page_Up:
1400 if (shift) {
1401 spiceterm_virtual_scroll (vt, -vt->height/2);
1402 goto ret;
1403 }
1404 esc = "[5~";break;
1405 case GDK_KEY_Page_Down:
1406 if (shift) {
1407 spiceterm_virtual_scroll (vt, vt->height/2);
1408 goto ret;
1409 }
1410 esc = "[6~";break;
1411 case GDK_KEY_F1:
1412 esc = "OP";break;
1413 case GDK_KEY_F2:
1414 esc = "OQ";break;
1415 case GDK_KEY_F3:
1416 esc = "OR";break;
1417 case GDK_KEY_F4:
1418 esc = "OS";break;
1419 case GDK_KEY_F5:
1420 esc = "[15~";break;
1421 case GDK_KEY_F6:
1422 esc = "[17~";break;
1423 case GDK_KEY_F7:
1424 esc = "[18~";break;
1425 case GDK_KEY_F8:
1426 esc = "[19~";break;
1427 case GDK_KEY_F9:
1428 esc = "[20~";break;
1429 case GDK_KEY_F10:
1430 esc = "[21~";break;
1431 case GDK_KEY_F11:
1432 esc = "[23~";break;
1433 case GDK_KEY_F12:
1434 esc = "[24~";break;
1435 default:
1436 if (keySym < 0x100) {
1437 uc = keySym;
1438 }
1439 break;
1440 }
1441 }
1442
1443 DPRINTF(1, "escape=%s unicode=%08x\n", esc, uc);
1444
1445 if (vt->y_displ != vt->y_base) {
1446 vt->y_displ = vt->y_base;
1447 spiceterm_refresh (vt);
1448 }
1449
1450 if (esc) {
1451 spiceterm_respond_esc(vt, esc);
1452 } else if (uc > 0) {
1453 spiceterm_respond_unichar2(vt, uc);
1454 }
1455 }
1456 }
1457
1458
1459 ret:
1460
1461 if (flags & 2) { // UP
1462 if (keySym == GDK_KEY_Shift_L || keySym == GDK_KEY_Shift_R) {
1463 shift = 0;
1464 } else if (keySym == GDK_KEY_Control_L || keySym == GDK_KEY_Control_R) {
1465 control = 0;
1466 }
1467 }
1468
1469 spiceterm_update_watch_mask(vt, TRUE);
1470 }
1471
1472 static uint8_t
1473 my_kbd_get_leds(SpiceKbdInstance *sin)
1474 {
1475 return 0;
1476 }
1477
1478 static SpiceKbdInterface my_keyboard_sif = {
1479 .base.type = SPICE_INTERFACE_KEYBOARD,
1480 .base.description = "spiceterm keyboard device",
1481 .base.major_version = SPICE_INTERFACE_KEYBOARD_MAJOR,
1482 .base.minor_version = SPICE_INTERFACE_KEYBOARD_MINOR,
1483 .push_keyval = my_kbd_push_keyval,
1484 .push_scan_freg = my_kbd_push_key,
1485 .get_leds = my_kbd_get_leds,
1486 };
1487
1488
1489 /* vdagent interface - to get mouse/clipboarde support */
1490
1491 #define VDAGENT_WBUF_SIZE (1024*50)
1492 static unsigned char vdagent_write_buffer[VDAGENT_WBUF_SIZE];
1493 static int vdagent_write_buffer_pos = 0;
1494 static int agent_owns_clipboard[256] = { 0, };
1495
1496 static void
1497 spiceterm_motion_event(spiceTerm *vt, uint32_t x, uint32_t y, uint32_t buttons)
1498 {
1499 DPRINTF(1, "mask=%08x x=%d y=%d", buttons, x ,y);
1500
1501 static int last_mask = 0;
1502 static int sel_start_pos = 0;
1503 static int sel_end_pos = 0;
1504 static int button2_released = 1;
1505
1506 int i;
1507 int cx = x/8;
1508 int cy = y/16;
1509
1510 if (cx < 0) cx = 0;
1511 if (cx >= vt->width) cx = vt->width - 1;
1512 if (cy < 0) cy = 0;
1513 if (cy >= vt->height) cy = vt->height - 1;
1514
1515 if (vt->report_mouse && buttons != last_mask) {
1516 last_mask = buttons;
1517 if (buttons & 2) {
1518 mouse_report(vt, 0, cx, cy);
1519 }
1520 if (buttons & 4) {
1521 mouse_report (vt, 1, cx, cy);
1522 }
1523 if (buttons & 8) {
1524 mouse_report(vt, 2, cx, cy);
1525 }
1526 if(!buttons) {
1527 mouse_report(vt, 3, cx, cy);
1528 }
1529 }
1530
1531 if (buttons & 4) {
1532
1533 if(button2_released) {
1534
1535 if (agent_owns_clipboard[VD_AGENT_CLIPBOARD_SELECTION_PRIMARY]) {
1536 if (vt->selection) {
1537 int i;
1538 for(i = 0; i < vt->selection_len; i++) {
1539 spiceterm_respond_unichar2(vt, vt->selection[i]);
1540 }
1541 spiceterm_update_watch_mask(vt, TRUE);
1542 if (vt->y_displ != vt->y_base) {
1543 vt->y_displ = vt->y_base;
1544 spiceterm_refresh(vt);
1545 }
1546 }
1547 } else {
1548 vdagent_request_clipboard(vt, VD_AGENT_CLIPBOARD_SELECTION_PRIMARY);
1549 }
1550 }
1551
1552 button2_released = 0;
1553 } else {
1554 button2_released = 1;
1555 }
1556
1557 if (buttons & 2) {
1558 int pos = cy*vt->width + cx;
1559
1560 // code borrowed from libvncserver (VNCconsole.c)
1561
1562 if (!vt->mark_active) {
1563
1564 spiceterm_unselect_all(vt);
1565
1566 vt->mark_active = 1;
1567 sel_start_pos = sel_end_pos = pos;
1568 spiceterm_toggle_marked_cell(vt, pos);
1569
1570 } else {
1571
1572 if (pos != sel_end_pos) {
1573 if (pos > sel_end_pos) {
1574 cx = sel_end_pos; cy=pos;
1575 } else {
1576 cx=pos; cy=sel_end_pos;
1577 }
1578
1579 if (cx < sel_start_pos) {
1580 if (cy < sel_start_pos) cy--;
1581 } else {
1582 cx++;
1583 }
1584
1585 while (cx <= cy) {
1586 spiceterm_toggle_marked_cell(vt, cx);
1587 cx++;
1588 }
1589
1590 sel_end_pos = pos;
1591 }
1592 }
1593
1594 } else if (vt->mark_active) {
1595 vt->mark_active = 0;
1596
1597 if (sel_start_pos > sel_end_pos) {
1598 int tmp = sel_start_pos - 1;
1599 sel_start_pos = sel_end_pos;
1600 sel_end_pos = tmp;
1601 }
1602
1603 int len = sel_end_pos - sel_start_pos + 1;
1604
1605 if (vt->selection) free (vt->selection);
1606 vt->selection = (gunichar2 *)malloc (len*sizeof(gunichar2));
1607 vt->selection_len = len;
1608
1609 for (i = 0; i < len; i++) {
1610 int pos = sel_start_pos + i;
1611 int x = pos % vt->width;
1612 int y1 = ((pos / vt->width) + vt->y_displ) % vt->total_height;
1613 TextCell *c = &vt->cells[y1*vt->width + x];
1614 vt->selection[i] = c->ch;
1615 c++;
1616 }
1617
1618 DPRINTF(1, "selection length = %d", vt->selection_len);
1619
1620 vdagent_grab_clipboard(vt, VD_AGENT_CLIPBOARD_SELECTION_PRIMARY);
1621 }
1622 }
1623
1624 static void vdagent_send_capabilities(spiceTerm *vt, uint32_t request)
1625 {
1626 VDAgentAnnounceCapabilities *caps;
1627 uint32_t size;
1628
1629 size = sizeof(*caps) + VD_AGENT_CAPS_BYTES;
1630 caps = calloc(1, size);
1631 g_assert(caps != NULL);
1632
1633 caps->request = request;
1634 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
1635 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
1636 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
1637 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
1638 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
1639 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SPARSE_MONITORS_CONFIG);
1640 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_GUEST_LINEEND_LF);
1641
1642 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
1643 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
1644
1645 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
1646 vdagent_write_buffer_pos += msg_size;
1647
1648 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
1649 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
1650
1651 hdr->port = VDP_CLIENT_PORT;
1652 hdr->size = sizeof(VDAgentMessage) + size;
1653 msg->protocol = VD_AGENT_PROTOCOL;
1654 msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
1655 msg->opaque = 0;
1656 msg->size = size;
1657
1658 memcpy(buf + sizeof(VDIChunkHeader) + sizeof(VDAgentMessage), (uint8_t *)caps, size);
1659
1660 spice_server_char_device_wakeup(&vt->vdagent_sin);
1661
1662 free(caps);
1663 }
1664
1665 static void
1666 dump_message(unsigned char *buf, int size)
1667 {
1668 int i;
1669
1670 for (i = 0; i < size; i++) {
1671 printf("%d %02X\n", i, buf[i]);
1672 }
1673
1674 // exit(0);
1675 }
1676
1677 static void vdagent_grab_clipboard(spiceTerm *vt, uint8_t selection)
1678 {
1679 uint32_t size;
1680
1681 agent_owns_clipboard[selection] = 1;
1682
1683 size = 8;
1684
1685 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
1686 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
1687
1688 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
1689 vdagent_write_buffer_pos += msg_size;
1690
1691 memset(buf, 0, msg_size);
1692
1693 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
1694 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
1695 uint8_t *grab = (uint8_t *)&msg[1];
1696 *((uint8_t *)grab) = selection;
1697 *((uint32_t *)(grab + 4)) = VD_AGENT_CLIPBOARD_UTF8_TEXT;
1698
1699 hdr->port = VDP_CLIENT_PORT;
1700 hdr->size = sizeof(VDAgentMessage) + size;
1701
1702 msg->protocol = VD_AGENT_PROTOCOL;
1703 msg->type = VD_AGENT_CLIPBOARD_GRAB;
1704 msg->opaque = 0;
1705 msg->size = size;
1706
1707 if (0) dump_message(buf, msg_size);
1708
1709 spice_server_char_device_wakeup(&vt->vdagent_sin);
1710 }
1711
1712 static void vdagent_request_clipboard(spiceTerm *vt, uint8_t selection)
1713 {
1714 uint32_t size;
1715
1716 agent_owns_clipboard[selection] = 1;
1717
1718 size = 4 + sizeof(VDAgentClipboardRequest);
1719
1720 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
1721 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
1722
1723 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
1724 vdagent_write_buffer_pos += msg_size;
1725
1726 memset(buf, 0, msg_size);
1727
1728 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
1729 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
1730 uint8_t *data = (uint8_t *)&msg[1];
1731 *((uint32_t *)data) = 0;
1732 data[0] = selection;
1733 ((uint32_t *)data)[1] = VD_AGENT_CLIPBOARD_UTF8_TEXT;
1734
1735 hdr->port = VDP_CLIENT_PORT;
1736 hdr->size = sizeof(VDAgentMessage) + size;
1737
1738 msg->protocol = VD_AGENT_PROTOCOL;
1739 msg->type = VD_AGENT_CLIPBOARD_REQUEST;
1740 msg->opaque = 0;
1741 msg->size = size;
1742
1743 if (0) dump_message(buf, msg_size);
1744
1745 spice_server_char_device_wakeup(&vt->vdagent_sin);
1746 }
1747
1748 static void vdagent_send_clipboard(spiceTerm *vt, uint8_t selection)
1749 {
1750 uint32_t size;
1751
1752 if (selection != VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
1753 fprintf(stderr, "clipboard select %d is not supported\n", selection);
1754 return;
1755 }
1756
1757 gchar *sel_data;
1758 glong sel_len;
1759 if (vt->utf8) {
1760 sel_data = g_utf16_to_utf8(vt->selection, vt->selection_len, NULL, &sel_len, NULL);
1761 } else {
1762 sel_len = vt->selection_len;
1763 sel_data = g_malloc(sel_len);
1764 int i;
1765 for (i = 0; i < sel_len; i++) { sel_data[i] = (char)vt->selection[i]; }
1766 sel_data[sel_len] = 0;
1767 }
1768
1769 size = 8 + sel_len;
1770
1771 int msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;
1772 g_assert((vdagent_write_buffer_pos + msg_size) < VDAGENT_WBUF_SIZE);
1773
1774 unsigned char *buf = vdagent_write_buffer + vdagent_write_buffer_pos;
1775 vdagent_write_buffer_pos += msg_size;
1776
1777 memset(buf, 0, msg_size);
1778
1779 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
1780 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
1781 uint8_t *data = (uint8_t *)&msg[1];
1782 *((uint8_t *)data) = selection;
1783 data += 4;
1784 *((uint32_t *)data) = VD_AGENT_CLIPBOARD_UTF8_TEXT;
1785 data += 4;
1786
1787 memcpy(data, sel_data, sel_len);
1788 g_free(sel_data);
1789
1790 hdr->port = VDP_CLIENT_PORT;
1791 hdr->size = sizeof(VDAgentMessage) + size;
1792
1793 msg->protocol = VD_AGENT_PROTOCOL;
1794 msg->type = VD_AGENT_CLIPBOARD;
1795 msg->opaque = 0;
1796 msg->size = size;
1797
1798 spice_server_char_device_wakeup(&vt->vdagent_sin);
1799 }
1800
1801 static int
1802 vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
1803 {
1804 spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, vdagent_sin);
1805
1806 VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
1807 VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
1808
1809 //g_assert(hdr->port == VDP_SERVER_PORT);
1810 g_assert(msg->protocol == VD_AGENT_PROTOCOL);
1811
1812 DPRINTF(1, "%d %d %d %d", len, hdr->port, msg->protocol, msg->type);
1813
1814 switch (msg->type) {
1815 case VD_AGENT_MOUSE_STATE: {
1816 VDAgentMouseState *info = (VDAgentMouseState *)&msg[1];
1817 spiceterm_motion_event(vt, info->x, info->y, info->buttons);
1818 break;
1819 }
1820 case VD_AGENT_ANNOUNCE_CAPABILITIES: {
1821 VDAgentAnnounceCapabilities *caps = (VDAgentAnnounceCapabilities *)&msg[1];
1822 DPRINTF(1, "VD_AGENT_ANNOUNCE_CAPABILITIES %d", caps->request);
1823 int i;
1824
1825 int caps_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(hdr->size);
1826 for (i = 0; i < VD_AGENT_END_CAP; i++) {
1827 DPRINTF(1, "CAPABILITIES %d %d", i, VD_AGENT_HAS_CAPABILITY(caps->caps, caps_size, i));
1828 }
1829
1830 vdagent_send_capabilities(vt, 0);
1831 break;
1832 }
1833 case VD_AGENT_CLIPBOARD_GRAB: {
1834 VDAgentClipboardGrab *grab = (VDAgentClipboardGrab *)&msg[1];
1835 uint8_t selection = *((uint8_t *)grab);
1836 DPRINTF(1, "VD_AGENT_CLIPBOARD_GRAB %d", selection);
1837 agent_owns_clipboard[selection] = 0;
1838 break;
1839 }
1840 case VD_AGENT_CLIPBOARD_REQUEST: {
1841 uint8_t *req = (uint8_t *)&msg[1];
1842 uint8_t selection = *((uint8_t *)req);
1843 uint32_t type = *((uint32_t *)(req + 4));
1844
1845 DPRINTF(1, "VD_AGENT_CLIPBOARD_REQUEST %d %d", selection, type);
1846
1847 vdagent_send_clipboard(vt, selection);
1848
1849 break;
1850 }
1851 case VD_AGENT_CLIPBOARD: {
1852 uint8_t *data = (uint8_t *)&msg[1];
1853 uint8_t selection = data[0];
1854 uint32_t type = *(uint32_t *)(data + 4);
1855 int size = msg->size - 8;
1856 DPRINTF(1, "VD_AGENT_CLIPBOARD %d %d %d", selection, type, size);
1857
1858 if (type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
1859 int i;
1860 for (i = 0; i < size; i++) {
1861 if ((vt->ibuf_count + 1) < IBUFSIZE) {
1862 vt->ibuf[vt->ibuf_count++] = *(char *)(data + 8 + i);
1863 } else {
1864 fprintf(stderr, "warning: input buffer overflow\n");
1865 }
1866 }
1867 spiceterm_update_watch_mask(vt, TRUE);
1868 }
1869 break;
1870 }
1871 case VD_AGENT_CLIPBOARD_RELEASE: {
1872 uint8_t *data = (uint8_t *)&msg[1];
1873 uint8_t selection = data[0];
1874
1875 DPRINTF(0, "VD_AGENT_CLIPBOARD_RELEASE %d", selection);
1876
1877 break;
1878 }
1879 case VD_AGENT_MONITORS_CONFIG:
1880 /* ignore for now */
1881 break;
1882 default:
1883 DPRINTF(0, "got uknown vdagent message type %d\n", msg->type);
1884 }
1885
1886 return len;
1887 }
1888
1889 static int
1890 vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len)
1891 {
1892 DPRINTF(0, "%d %d", len, vdagent_write_buffer_pos);
1893 g_assert(len >= 8);
1894
1895 if (!vdagent_write_buffer_pos) {
1896 return 0;
1897 }
1898
1899 int size = (len >= vdagent_write_buffer_pos) ? vdagent_write_buffer_pos : len;
1900 memcpy(buf, vdagent_write_buffer, size);
1901 if (size < vdagent_write_buffer_pos) {
1902 DPRINTF(0, "MOVE %d", size);
1903 memmove(vdagent_write_buffer, vdagent_write_buffer + size,
1904 vdagent_write_buffer_pos - size);
1905 }
1906 vdagent_write_buffer_pos -= size;
1907
1908 DPRINTF(0, "RET %d %d", size, vdagent_write_buffer_pos);
1909 return size;
1910 }
1911
1912 static void
1913 vmc_state(SpiceCharDeviceInstance *sin, int connected)
1914 {
1915 /* IGNORE */
1916 }
1917
1918 static SpiceCharDeviceInterface my_vdagent_sif = {
1919 .base.type = SPICE_INTERFACE_CHAR_DEVICE,
1920 .base.description = "spice virtual channel char device",
1921 .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
1922 .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
1923 .state = vmc_state,
1924 .write = vmc_write,
1925 .read = vmc_read,
1926 };
1927
1928 static spiceTerm *
1929 create_spiceterm(int argc, char** argv, int maxx, int maxy, guint timeout)
1930 {
1931 int i;
1932
1933 SpiceScreen *spice_screen;
1934
1935 SpiceCoreInterface *core = basic_event_loop_init();
1936 spice_screen = spice_screen_new(core, timeout);
1937 //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
1938
1939 spiceTerm *vt = (spiceTerm *)calloc (sizeof(spiceTerm), 1);
1940
1941 vt->keyboard_sin.base.sif = &my_keyboard_sif.base;
1942 spice_server_add_interface(spice_screen->server, &vt->keyboard_sin.base);
1943
1944 vt->vdagent_sin.base.sif = &my_vdagent_sif.base;
1945 vt->vdagent_sin.subtype = "vdagent";
1946 spice_server_add_interface(spice_screen->server, &vt->vdagent_sin.base);
1947
1948 // screen->setXCutText = spiceterm_set_xcut_text;
1949 // screen->ptrAddEvent = spiceterm_pointer_event;
1950 // screen->newClientHook = new_client;
1951 // screen->desktopName = "SPICE Command Terminal";
1952
1953 vt->maxx = spice_screen->width;
1954 vt->maxy = spice_screen->height;
1955
1956 vt->width = vt->maxx / 8;
1957 vt->height = vt->maxy / 16;
1958
1959 vt->total_height = vt->height * 20;
1960 vt->scroll_height = 0;
1961 vt->y_base = 0;
1962 vt->y_displ = 0;
1963
1964 vt->region_top = 0;
1965 vt->region_bottom = vt->height;
1966
1967 vt->g0enc = LAT1_MAP;
1968 vt->g1enc = GRAF_MAP;
1969 vt->cur_enc = vt->g0enc;
1970 vt->charset = 0;
1971
1972 /* default text attributes */
1973 vt->default_attrib.bold = 0;
1974 vt->default_attrib.uline = 0;
1975 vt->default_attrib.blink = 0;
1976 vt->default_attrib.invers = 0;
1977 vt->default_attrib.unvisible = 0;
1978 vt->default_attrib.fgcol = 7;
1979 vt->default_attrib.bgcol = 0;
1980
1981 vt->cur_attrib = vt->default_attrib;
1982
1983 vt->cells = (TextCell *)calloc (sizeof (TextCell), vt->width*vt->total_height);
1984
1985 for (i = 0; i < vt->width*vt->total_height; i++) {
1986 vt->cells[i].ch = ' ';
1987 vt->cells[i].attrib = vt->default_attrib;
1988 }
1989
1990 vt->altcells = (TextCell *)calloc (sizeof (TextCell), vt->width*vt->height);
1991
1992 vt->screen = spice_screen;
1993
1994 return vt;
1995 }
1996
1997 static gboolean
1998 master_error_callback(GIOChannel *channel, GIOCondition condition,
1999 gpointer data)
2000 {
2001 //spiceTerm *vt = (spiceTerm *)data;
2002
2003 DPRINTF(1, "condition %d", condition);
2004
2005 exit(0);
2006
2007 return FALSE;
2008 }
2009
2010 static void
2011 master_watch(int master, int event, void *opaque)
2012 {
2013 spiceTerm *vt = (spiceTerm *)opaque;
2014 int c;
2015
2016 // fixme: if (!vt->mark_active) {
2017
2018 if (event == SPICE_WATCH_EVENT_READ) {
2019 char buffer[1024];
2020 while ((c = read(master, buffer, 1024)) == -1) {
2021 if (errno != EAGAIN) break;
2022 }
2023 if (c == -1) {
2024 perror("master pipe read error"); // fixme
2025 }
2026 spiceterm_puts (vt, buffer, c);
2027 } else {
2028 if (vt->ibuf_count > 0) {
2029 DPRINTF(1, "write input %x %d", vt->ibuf[0], vt->ibuf_count);
2030 if ((c = write (master, vt->ibuf, vt->ibuf_count)) >= 0) {
2031 if (c == vt->ibuf_count) {
2032 vt->ibuf_count = 0;
2033 } else if (c > 0) {
2034 // not all data written
2035 memmove(vt->ibuf, vt->ibuf + c, vt->ibuf_count - c);
2036 vt->ibuf_count -= c;
2037 } else {
2038 // nothing written -ignore and try later
2039 }
2040 } else {
2041 perror("master pipe write error");
2042 }
2043 }
2044 if (vt->ibuf_count == 0) {
2045 spiceterm_update_watch_mask(vt, FALSE);
2046 }
2047 }
2048 }
2049
2050 int
2051 main (int argc, char** argv)
2052 {
2053 int i;
2054 char **cmdargv = NULL;
2055 char *command = "/bin/bash"; // execute normal shell as default
2056 int pid;
2057 int master;
2058 char ptyname[1024];
2059 struct winsize dimensions;
2060
2061 g_thread_init(NULL);
2062
2063 for (i = 1; i < argc; i++) {
2064 if (!strcmp (argv[i], "-c")) {
2065 command = argv[i+1];
2066 cmdargv = &argv[i+1];
2067 argc = i;
2068 argv[i] = NULL;
2069 break;
2070 }
2071 }
2072
2073 if (0) print_usage(NULL); // fixme:
2074
2075 spiceTerm *vt = create_spiceterm (argc, argv, 745, 400, 10);
2076
2077 setlocale(LC_ALL, ""); // set from environment
2078
2079 char *ctype = setlocale (LC_CTYPE, NULL); // query LC_CTYPE
2080
2081 // fixme: ist there a standard way to detect utf8 mode ?
2082 if (strcasestr (ctype, ".utf-8")||strcasestr (ctype, ".utf8")) {
2083 vt->utf8 = 1;
2084 }
2085
2086 dimensions.ws_col = vt->width;
2087 dimensions.ws_row = vt->height;
2088
2089 setenv("TERM", TERM, 1);
2090
2091 DPRINTF(1, "execute %s", command);
2092
2093 pid = forkpty (&master, ptyname, NULL, &dimensions);
2094 if(!pid) {
2095
2096 // install default signal handlers
2097 signal (SIGQUIT, SIG_DFL);
2098 signal (SIGTERM, SIG_DFL);
2099 signal (SIGINT, SIG_DFL);
2100
2101 if (cmdargv) {
2102 execvp (command, cmdargv);
2103 } else {
2104 execlp (command, command, NULL);
2105 }
2106 perror ("Error: exec failed\n");
2107 exit (-1); // should not be reached
2108 } else if (pid == -1) {
2109 perror ("Error: fork failed\n");
2110 exit (-1);
2111 }
2112
2113 /* watch for errors - we need to use glib directly because spice
2114 * does not have SPICE_WATCH_EVENT for this */
2115 GIOChannel *channel = g_io_channel_unix_new(master);
2116 g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
2117 g_io_channel_set_encoding(channel, NULL, NULL);
2118 g_io_add_watch(channel, G_IO_ERR|G_IO_HUP, master_error_callback, vt);
2119
2120 vt->screen->mwatch = vt->screen->core->watch_add(
2121 master, SPICE_WATCH_EVENT_READ /* |SPICE_WATCH_EVENT_WRITE */,
2122 master_watch, vt);
2123
2124 basic_event_loop_mainloop();
2125
2126 kill (pid, 9);
2127 int status;
2128 waitpid(pid, &status, 0);
2129
2130 exit (0);
2131 }