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>
23 Note: most of the code here is copied from vncterm (which is
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <arpa/inet.h>
37 #include <pty.h> /* for openpty and forkpty */
40 #include <sys/ioctl.h>
45 #include "spiceterm.h"
49 #include <spice/enums.h>
50 #include <spice/macros.h>
51 #include <spice/qxl_dev.h>
53 #include <gdk/gdkkeysyms.h>
55 #include "event_loop.h"
56 #include "translations.h"
58 /* define this for debugging */
63 #define TERMIDCODE "[?1;2c" // vt100 ID
65 #define CHECK_ARGC(argc,argv,i) if (i >= argc-1) { \
66 fprintf (stderr, "ERROR: not enough arguments for: %s\n", argv[i]); \
71 /* these colours are from linux kernel drivers/char/vt.c */
73 unsigned char color_table
[] = { 0, 4, 2, 6, 1, 5, 3, 7,
74 8,12,10,14, 9,13,11,15 };
78 print_usage (const char *msg
)
80 if (msg
) { fprintf (stderr
, "ERROR: %s\n", msg
); }
81 fprintf (stderr
, "USAGE: spiceterm [spiceopts] [-c command [args]]\n");
84 /* Convert UCS2 to UTF8 sequence, trailing zero */
87 ucs2_to_utf8 (unicode c, char *out)
90 out[0] = c; // 0*******
93 } else if (c < 0x800) {
94 out[0] = 0xc0 | (c >> 6); // 110***** 10******
95 out[1] = 0x80 | (c & 0x3f);
99 out[0] = 0xe0 | (c >> 12); // 1110**** 10****** 10******
100 out[1] = 0x80 | ((c >> 6) & 0x3f);
101 out[2] = 0x80 | (c & 0x3f);
111 draw_char_at (spiceTerm
*vt
, int x
, int y
, unicode ch
, TextAttributes attrib
)
113 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) {
117 spice_screen_draw_char(vt
->screen
, x
, y
, ch
, attrib
);
121 spiceterm_update_xy (spiceTerm
*vt
, int x
, int y
)
123 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) { return; }
125 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
126 int y2
= y1
- vt
->y_displ
;
128 y2
+= vt
->total_height
;
130 if (y2
< vt
->height
) {
131 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
132 draw_char_at (vt
, x
, y2
, c
->ch
, c
->attrib
);
137 spiceterm_clear_xy (spiceTerm
*vt
, int x
, int y
)
139 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) { return; }
141 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
142 int y2
= y1
- vt
->y_displ
;
144 y2
+= vt
->total_height
;
146 if (y2
< vt
->height
) {
147 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
149 c
->attrib
= vt
->default_attrib
;
150 c
->attrib
.fgcol
= vt
->cur_attrib
.fgcol
;
151 c
->attrib
.bgcol
= vt
->cur_attrib
.bgcol
;
153 draw_char_at (vt
, x
, y
, c
->ch
, c
->attrib
);
158 spiceterm_show_cursor (spiceTerm
*vt
, int show
)
161 if (x
>= vt
->width
) {
165 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
166 int y
= y1
- vt
->y_displ
;
168 y
+= vt
->total_height
;
171 if (y
< vt
->height
) {
173 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
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
);
180 draw_char_at (vt
, x
, y
, c
->ch
, c
->attrib
);
186 spiceterm_refresh (spiceTerm
*vt
)
190 // rfbFillRect (vt->screen, 0, 0, vt->maxx, vt->maxy, vt->default_attrib.bgcol);
193 for(y
= 0; y
< vt
->height
; y
++) {
194 TextCell
*c
= vt
->cells
+ y1
* vt
->width
;
195 for(x
= 0; x
< vt
->width
; x
++) {
196 draw_char_at (vt
, x
, y
, c
->ch
, c
->attrib
);
199 if (++y1
== vt
->total_height
)
202 //rfbMarkRectAsModified (vt->screen, 0, 0, vt->maxx, vt->maxy);
204 spiceterm_show_cursor (vt
, 1);
208 spiceterm_scroll_down (spiceTerm
*vt
, int top
, int bottom
, int lines
)
210 if ((top
+ lines
) >= bottom
) {
211 lines
= bottom
- top
-1;
214 if (top
< 0 || bottom
> vt
->height
|| top
>= bottom
|| lines
< 1) {
219 for(i
= bottom
- top
- lines
- 1; i
>= 0; i
--) {
220 int src
= ((vt
->y_base
+ top
+ i
) % vt
->total_height
)*vt
->width
;
221 int dst
= ((vt
->y_base
+ top
+ lines
+ i
) % vt
->total_height
)*vt
->width
;
223 memmove(vt
->cells
+ dst
, vt
->cells
+ src
, vt
->width
*sizeof (TextCell
));
226 for (i
= 0; i
< lines
; i
++) {
228 TextCell
*c
= vt
->cells
+ ((vt
->y_base
+ top
+ i
) % vt
->total_height
)*vt
->width
;
229 for(j
= 0; j
< vt
->width
; j
++) {
230 c
->attrib
= vt
->default_attrib
;
241 spice_screen_scroll(vt
->screen
, 0, y1
, vt
->screen
->primary_width
, y2
, 0, y0
);
242 spice_screen_clear(vt
->screen
, 0, y0
, vt
->screen
->primary_width
, y1
);
246 spiceterm_scroll_up (spiceTerm
*vt
, int top
, int bottom
, int lines
, int moveattr
)
248 if ((top
+ lines
) >= bottom
) {
249 lines
= bottom
- top
- 1;
252 if (top
< 0 || bottom
> vt
->height
|| top
>= bottom
|| lines
< 1) {
259 int y1
= (top
+ lines
)*16;
262 spice_screen_scroll(vt
->screen
, 0, y0
, vt
->screen
->primary_width
, y2
-h
, 0, y1
);
263 spice_screen_clear(vt
->screen
, 0, y2
-h
, vt
->screen
->primary_width
, y2
);
272 for(i
= 0; i
< (bottom
- top
- lines
); i
++) {
273 int dst
= ((vt
->y_base
+ top
+ i
) % vt
->total_height
)*vt
->width
;
274 int src
= ((vt
->y_base
+ top
+ lines
+ i
) % vt
->total_height
)*vt
->width
;
276 memmove(vt
->cells
+ dst
, vt
->cells
+ src
, vt
->width
*sizeof (TextCell
));
279 for (i
= 1; i
<= lines
; i
++) {
281 TextCell
*c
= vt
->cells
+ ((vt
->y_base
+ bottom
- i
) % vt
->total_height
)*vt
->width
;
282 for(j
= 0; j
< vt
->width
; j
++) {
283 c
->attrib
= vt
->default_attrib
;
291 spiceterm_virtual_scroll (spiceTerm
*vt
, int lines
)
293 if (vt
->altbuf
|| lines
== 0) return;
297 int i
= vt
->scroll_height
;
298 if (i
> vt
->total_height
- vt
->height
)
299 i
= vt
->total_height
- vt
->height
;
300 int y1
= vt
->y_base
- i
;
302 y1
+= vt
->total_height
;
303 for(i
= 0; i
< lines
; i
++) {
304 if (vt
->y_displ
== y1
) break;
305 if (--vt
->y_displ
< 0) {
306 vt
->y_displ
= vt
->total_height
- 1;
311 for(i
= 0; i
< lines
; i
++) {
312 if (vt
->y_displ
== vt
->y_base
) break;
313 if (++vt
->y_displ
== vt
->total_height
) {
320 spiceterm_refresh (vt
);
323 spiceterm_respond_esc (spiceTerm
*vt
, const char *esc
)
325 int len
= strlen (esc
);
328 if (vt
->ibuf_count
< (IBUFSIZE
- 1 - len
)) {
329 vt
->ibuf
[vt
->ibuf_count
++] = 27;
330 for (i
= 0; i
< len
; i
++) {
331 vt
->ibuf
[vt
->ibuf_count
++] = esc
[i
];
337 spiceterm_put_lf (spiceTerm
*vt
)
339 if (vt
->cy
+ 1 == vt
->region_bottom
) {
341 if (vt
->altbuf
|| vt
->region_top
!= 0 || vt
->region_bottom
!= vt
->height
) {
342 spiceterm_scroll_up (vt
, vt
->region_top
, vt
->region_bottom
, 1, 1);
346 if (vt
->y_displ
== vt
->y_base
) {
347 spiceterm_scroll_up (vt
, vt
->region_top
, vt
->region_bottom
, 1, 0);
350 if (vt
->y_displ
== vt
->y_base
) {
351 if (++vt
->y_displ
== vt
->total_height
) {
356 if (++vt
->y_base
== vt
->total_height
) {
360 if (vt
->scroll_height
< vt
->total_height
) {
364 int y1
= (vt
->y_base
+ vt
->height
- 1) % vt
->total_height
;
365 TextCell
*c
= &vt
->cells
[y1
* vt
->width
];
367 for (x
= 0; x
< vt
->width
; x
++) {
369 c
->attrib
= vt
->default_attrib
;
373 // fprintf (stderr, "BASE: %d DISPLAY %d\n", vt->y_base, vt->y_displ);
375 } else if (vt
->cy
< vt
->height
- 1) {
382 spiceterm_csi_m (spiceTerm
*vt
)
386 for (i
= 0; i
< vt
->esc_count
; i
++) {
387 switch (vt
->esc_buf
[i
]) {
388 case 0: /* reset all console attributes to default */
389 vt
->cur_attrib
= vt
->default_attrib
;
392 vt
->cur_attrib
.bold
= 1;
395 vt
->cur_attrib
.uline
= 1;
398 vt
->cur_attrib
.blink
= 1;
401 vt
->cur_attrib
.invers
= 1;
404 vt
->cur_attrib
.unvisible
= 1;
407 vt
->cur_enc
= LAT1_MAP
;
408 // fixme: dispaly controls = 0 ?
409 // fixme: toggle meta = 0 ?
412 vt
->cur_enc
= IBMPC_MAP
;
413 // fixme: dispaly controls = 1 ?
414 // fixme: toggle meta = 0 ?
417 vt
->cur_enc
= IBMPC_MAP
;
418 // fixme: dispaly controls = 1 ?
419 // fixme: toggle meta = 1 ?
422 vt
->cur_attrib
.bold
= 0;
425 vt
->cur_attrib
.uline
= 0;
428 vt
->cur_attrib
.blink
= 0;
431 vt
->cur_attrib
.invers
= 0;
434 vt
->cur_attrib
.unvisible
= 0;
444 /* set foreground color */
445 vt
->cur_attrib
.fgcol
= color_table
[vt
->esc_buf
[i
] - 30];
448 /* reset color to default, enable underline */
449 vt
->cur_attrib
.fgcol
= vt
->default_attrib
.fgcol
;
450 vt
->cur_attrib
.uline
= 1;
453 /* reset color to default, disable underline */
454 vt
->cur_attrib
.fgcol
= vt
->default_attrib
.fgcol
;
455 vt
->cur_attrib
.uline
= 0;
465 /* set background color */
466 vt
->cur_attrib
.bgcol
= color_table
[vt
->esc_buf
[i
] - 40];
469 /* reset background color */
470 vt
->cur_attrib
.bgcol
= vt
->default_attrib
.bgcol
;
473 fprintf (stderr
, "unhandled ESC[%d m code\n",vt
->esc_buf
[i
]);
480 spiceterm_save_cursor (spiceTerm
*vt
)
482 vt
->cx_saved
= vt
->cx
;
483 vt
->cy_saved
= vt
->cy
;
484 vt
->cur_attrib_saved
= vt
->cur_attrib
;
485 vt
->charset_saved
= vt
->charset
;
486 vt
->g0enc_saved
= vt
->g0enc
;
487 vt
->g1enc_saved
= vt
->g1enc
;
488 vt
->cur_enc_saved
= vt
->cur_enc
;
492 spiceterm_restore_cursor (spiceTerm
*vt
)
494 vt
->cx
= vt
->cx_saved
;
495 vt
->cy
= vt
->cy_saved
;
496 vt
->cur_attrib
= vt
->cur_attrib_saved
;
497 vt
->charset
= vt
->charset_saved
;
498 vt
->g0enc
= vt
->g0enc_saved
;
499 vt
->g1enc
= vt
->g1enc_saved
;
500 vt
->cur_enc
= vt
->cur_enc_saved
;
504 spiceterm_set_alternate_buffer (spiceTerm
*vt
, int on_off
)
508 vt
->y_displ
= vt
->y_base
;
512 if (vt
->altbuf
) return;
516 /* alternate buffer & cursor */
518 spiceterm_save_cursor (vt
);
519 /* save screen to altcels */
520 for (y
= 0; y
< vt
->height
; y
++) {
521 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
522 for (x
= 0; x
< vt
->width
; x
++) {
523 vt
->altcells
[y
*vt
->width
+ x
] = vt
->cells
[y1
*vt
->width
+ x
];
528 for (y
= 0; y
<= vt
->height
; y
++) {
529 for (x
= 0; x
< vt
->width
; x
++) {
530 spiceterm_clear_xy (vt
, x
, y
);
536 if (vt
->altbuf
== 0) return;
540 /* restore saved data */
541 for (y
= 0; y
< vt
->height
; y
++) {
542 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
543 for (x
= 0; x
< vt
->width
; x
++) {
544 vt
->cells
[y1
*vt
->width
+ x
] = vt
->altcells
[y
*vt
->width
+ x
];
548 spiceterm_restore_cursor (vt
);
551 spiceterm_refresh (vt
);
555 spiceterm_set_mode (spiceTerm
*vt
, int on_off
)
559 for (i
= 0; i
<= vt
->esc_count
; i
++) {
560 if (vt
->esc_ques
) { /* DEC private modes set/reset */
561 switch(vt
->esc_buf
[i
]) {
562 case 10: /* X11 mouse reporting on/off */
564 vt
->report_mouse
= on_off
;
566 case 1049: /* start/end special app mode (smcup/rmcup) */
567 spiceterm_set_alternate_buffer (vt
, on_off
);
569 case 25: /* Cursor on/off */
570 case 9: /* X10 mouse reporting on/off */
571 case 6: /* Origin relative/absolute */
572 case 1: /* Cursor keys in appl mode*/
573 case 5: /* Inverted screen on/off */
574 case 7: /* Autowrap on/off */
575 case 8: /* Autorepeat on/off */
578 } else { /* ANSI modes set/reset */
579 /* fixme: implement me */
585 spiceterm_gotoxy (spiceTerm
*vt
, int x
, int y
)
587 /* verify all boundaries */
593 if (x
>= vt
->width
) {
603 if (y
>= vt
->height
) {
610 enum { ESnormal
, ESesc
, ESsquare
, ESgetpars
, ESgotpars
, ESfunckey
,
611 EShash
, ESsetG0
, ESsetG1
, ESpercent
, ESignore
, ESnonstd
,
612 ESpalette
, ESidquery
, ESosc1
, ESosc2
};
615 spiceterm_putchar (spiceTerm
*vt
, unicode ch
)
621 fprintf (stderr
, "CHAR:%2d: %4x '%c' (cur_enc %d) %d %d\n", vt
->tty_state
, ch
, ch
, vt
->cur_enc
, vt
->cx
, vt
->cy
);
624 switch(vt
->tty_state
) {
626 vt
->tty_state
= ESnormal
;
629 vt
->tty_state
= ESsquare
;
632 vt
->tty_state
= ESnonstd
;
635 vt
->tty_state
= ESpercent
;
638 spiceterm_save_cursor (vt
);
641 spiceterm_restore_cursor (vt
);
644 vt
->tty_state
= ESsetG0
; // SET G0
647 vt
->tty_state
= ESsetG1
; // SET G1
651 if (vt
->cy
== vt
->region_top
)
652 spiceterm_scroll_down (vt
, vt
->region_top
, vt
->region_bottom
, 1);
653 else if (vt
->cy
> 0) {
658 /* numeric keypad - ignored */
661 /* appl. keypad - ignored */
665 fprintf(stderr
, "got unhandled ESC%c %d\n", ch
, ch
);
670 case ESnonstd
: /* Operating System Controls */
671 vt
->tty_state
= ESnormal
;
674 case 'P': /* palette escape sequence */
675 for(i
= 0; i
< MAX_ESC_PARAMS
; i
++) {
680 vt
->tty_state
= ESpalette
;
682 case 'R': /* reset palette */
683 // fixme: reset_palette(vc);
690 vt
->osc_textbuf
[0] = 0;
691 vt
->tty_state
= ESosc1
;
695 fprintf (stderr
, "unhandled OSC %c\n", ch
);
697 vt
->tty_state
= ESnormal
;
702 vt
->tty_state
= ESnormal
;
704 vt
->tty_state
= ESosc2
;
707 fprintf (stderr
, "got illegal OSC sequence\n");
712 if (ch
!= 0x9c && ch
!= 7) {
714 while (vt
->osc_textbuf
[i
]) i
++;
715 vt
->osc_textbuf
[i
++] = ch
;
716 vt
->osc_textbuf
[i
] = 0;
719 fprintf (stderr
, "OSC:%c:%s\n", vt
->osc_cmd
, vt
->osc_textbuf
);
721 vt
->tty_state
= ESnormal
;
725 if ((ch
>= '0' && ch
<= '9') || (ch
>= 'A' && ch
<= 'F')
726 || (ch
>= 'a' && ch
<= 'f')) {
727 vt
->esc_buf
[vt
->esc_count
++] = (ch
> '9' ? (ch
& 0xDF) - 'A' + 10 : ch
- '0');
728 if (vt
->esc_count
== 7) {
729 // fixme: this does not work - please test
731 rfbColourMap *cmap =&vt->screen->colourMap;
733 int i = color_table[vt->esc_buf[0]] * 3, j = 1;
734 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
735 cmap->data.bytes[i++] += vt->esc_buf[j++];
736 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
737 cmap->data.bytes[i++] += vt->esc_buf[j++];
738 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
739 cmap->data.bytes[i] += vt->esc_buf[j];
743 vt
->tty_state
= ESnormal
;
746 vt
->tty_state
= ESnormal
;
749 for(i
= 0; i
< MAX_ESC_PARAMS
; i
++) {
755 vt
->tty_state
= ESgetpars
;
758 vt
->tty_state
= ESidquery
;
762 if ((vt
->esc_ques
= (ch
== '?'))) {
766 if (ch
>= '0' && ch
<= '9') {
768 if (vt
->esc_count
< MAX_ESC_PARAMS
) {
769 vt
->esc_buf
[vt
->esc_count
] = vt
->esc_buf
[vt
->esc_count
] * 10 + ch
- '0';
772 } else if (ch
== ';') {
776 if (vt
->esc_has_par
) {
779 vt
->tty_state
= ESgotpars
;
783 vt
->tty_state
= ESnormal
;
786 char *qes
= vt
->esc_ques
? "?" : "";
787 if (vt
->esc_count
== 0) {
788 fprintf(stderr
, "ESC[%s%c\n", qes
, ch
);
789 } else if (vt
->esc_count
== 1) {
790 fprintf(stderr
, "ESC[%s%d%c\n", qes
, vt
->esc_buf
[0], ch
);
793 fprintf(stderr
, "ESC[%s%d", qes
, vt
->esc_buf
[0]);
794 for (i
= 1; i
< vt
->esc_count
; i
++) {
795 fprintf(stderr
, ";%d", vt
->esc_buf
[i
]);
797 fprintf (stderr
, "%c\n", ch
);
803 spiceterm_set_mode (vt
, 1);
806 spiceterm_set_mode (vt
, 0);
809 if (!vt
->esc_count
) {
810 vt
->esc_count
++; // default parameter 0
812 spiceterm_csi_m (vt
);
815 /* report cursor position */
816 /* TODO: send ESC[row;colR */
820 if (vt
->esc_buf
[0] == 0) {
823 vt
->cy
-= vt
->esc_buf
[0];
830 /* move cursor down */
831 if (vt
->esc_buf
[0] == 0) {
834 vt
->cy
+= vt
->esc_buf
[0];
835 if (vt
->cy
>= vt
->height
) {
836 vt
->cy
= vt
->height
- 1;
841 /* move cursor right */
842 if (vt
->esc_buf
[0] == 0) {
845 vt
->cx
+= vt
->esc_buf
[0];
846 if (vt
->cx
>= vt
->width
) {
847 vt
->cx
= vt
->width
- 1;
851 /* move cursor left */
852 if (vt
->esc_buf
[0] == 0) {
855 vt
->cx
-= vt
->esc_buf
[0];
862 /* move cursor to column */
863 spiceterm_gotoxy (vt
, vt
->esc_buf
[0] - 1, vt
->cy
);
866 /* move cursor to row */
867 spiceterm_gotoxy (vt
, vt
->cx
, vt
->esc_buf
[0] - 1);
871 /* move cursor to row, column */
872 spiceterm_gotoxy (vt
, vt
->esc_buf
[1] - 1, vt
->esc_buf
[0] - 1);
875 switch (vt
->esc_buf
[0]) {
877 /* clear to end of screen */
878 for (y
= vt
->cy
; y
< vt
->height
; y
++) {
879 for (x
= 0; x
< vt
->width
; x
++) {
880 if (y
== vt
->cy
&& x
< vt
->cx
) {
883 spiceterm_clear_xy (vt
, x
, y
);
888 /* clear from beginning of screen */
889 for (y
= 0; y
<= vt
->cy
; y
++) {
890 for (x
= 0; x
< vt
->width
; x
++) {
891 if (y
== vt
->cy
&& x
> vt
->cx
) {
894 spiceterm_clear_xy (vt
, x
, y
);
899 /* clear entire screen */
900 for (y
= 0; y
<= vt
->height
; y
++) {
901 for (x
= 0; x
< vt
->width
; x
++) {
902 spiceterm_clear_xy (vt
, x
, y
);
909 switch (vt
->esc_buf
[0]) {
912 for(x
= vt
->cx
; x
< vt
->width
; x
++) {
913 spiceterm_clear_xy (vt
, x
, vt
->cy
);
917 /* clear from beginning of line */
918 for (x
= 0; x
<= vt
->cx
; x
++) {
919 spiceterm_clear_xy (vt
, x
, vt
->cy
);
923 /* clear entire line */
924 for(x
= 0; x
< vt
->width
; x
++) {
925 spiceterm_clear_xy (vt
, x
, vt
->cy
);
934 if (c
> vt
->height
- vt
->cy
)
935 c
= vt
->height
- vt
->cy
;
939 spiceterm_scroll_down (vt
, vt
->cy
, vt
->region_bottom
, c
);
945 if (c
> vt
->height
- vt
->cy
)
946 c
= vt
->height
- vt
->cy
;
950 spiceterm_scroll_up (vt
, vt
->cy
, vt
->region_bottom
, c
, 1);
956 spiceterm_scroll_down (vt
, vt
->region_top
, vt
->region_bottom
, c
);
962 spiceterm_scroll_up (vt
, vt
->region_top
, vt
->region_bottom
, c
, 1);
965 /* delete c character */
968 if (c
> vt
->width
- vt
->cx
)
969 c
= vt
->width
- vt
->cx
;
973 for (x
= vt
->cx
; x
< vt
->width
- c
; x
++) {
974 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
975 TextCell
*dst
= &vt
->cells
[y1
* vt
->width
+ x
];
976 TextCell
*src
= dst
+ c
;
978 spiceterm_update_xy (vt
, x
+ c
, vt
->cy
);
980 src
->attrib
= vt
->default_attrib
;
981 spiceterm_update_xy (vt
, x
, vt
->cy
);
985 /* save cursor position */
986 spiceterm_save_cursor (vt
);
989 /* restore cursor position */
990 spiceterm_restore_cursor (vt
);
993 /* erase c characters */
997 if (c
> (vt
->width
- vt
->cx
)) c
= vt
->width
- vt
->cx
;
999 for(i
= 0; i
< c
; i
++) {
1000 spiceterm_clear_xy (vt
, vt
->cx
+ i
, vt
->cy
);
1004 /* insert c character */
1006 if (c
> (vt
->width
- vt
->cx
)) {
1007 c
= vt
->width
- vt
->cx
;
1011 for (x
= vt
->width
- c
; x
>= vt
->cx
; x
--) {
1012 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
1013 TextCell
*src
= &vt
->cells
[y1
* vt
->width
+ x
];
1014 TextCell
*dst
= src
+ c
;
1016 spiceterm_update_xy (vt
, x
+ c
, vt
->cy
);
1018 src
->attrib
= vt
->cur_attrib
;
1019 spiceterm_update_xy (vt
, x
, vt
->cy
);
1025 if (!vt
->esc_buf
[0])
1027 if (!vt
->esc_buf
[1])
1028 vt
->esc_buf
[1] = vt
->height
;
1029 /* Minimum allowed region is 2 lines */
1030 if (vt
->esc_buf
[0] < vt
->esc_buf
[1] &&
1031 vt
->esc_buf
[1] <= vt
->height
) {
1032 vt
->region_top
= vt
->esc_buf
[0] - 1;
1033 vt
->region_bottom
= vt
->esc_buf
[1];
1035 vt
->cy
= vt
->region_top
;
1037 fprintf (stderr
, "set region %d %d\n", vt
->region_top
, vt
->region_bottom
);
1044 if (vt
->esc_count
== 0) {
1045 fprintf(stderr
, "unhandled escape ESC[%s%c\n", qes
, ch
);
1046 } else if (vt
->esc_count
== 1) {
1047 fprintf(stderr
, "unhandled escape ESC[%s%d%c\n", qes
, vt
->esc_buf
[0], ch
);
1050 fprintf(stderr
, "unhandled escape ESC[%s%d", qes
, vt
->esc_buf
[0]);
1051 for (i
= 1; i
< vt
->esc_count
; i
++) {
1052 fprintf(stderr
, ";%d", vt
->esc_buf
[i
]);
1054 fprintf (stderr
, "%c\n", ch
);
1061 case ESsetG0
: // Set G0
1062 vt
->tty_state
= ESnormal
;
1065 vt
->g0enc
= GRAF_MAP
;
1067 vt
->g0enc
= LAT1_MAP
;
1069 vt
->g0enc
= IBMPC_MAP
;
1071 vt
->g0enc
= USER_MAP
;
1073 if (vt
->charset
== 0)
1074 vt
->cur_enc
= vt
->g0enc
;
1077 case ESsetG1
: // Set G1
1078 vt
->tty_state
= ESnormal
;
1081 vt
->g1enc
= GRAF_MAP
;
1083 vt
->g1enc
= LAT1_MAP
;
1085 vt
->g1enc
= IBMPC_MAP
;
1087 vt
->g1enc
= USER_MAP
;
1089 if (vt
->charset
== 1)
1090 vt
->cur_enc
= vt
->g1enc
;
1093 case ESidquery
: // vt100 query id
1094 vt
->tty_state
= ESnormal
;
1098 fprintf (stderr
, "ESC[>c Query term ID\n");
1100 spiceterm_respond_esc (vt
, TERMIDCODE
);
1104 vt
->tty_state
= ESnormal
;
1106 case '@': /* defined in ISO 2022 */
1109 case 'G': /* prelim official escape code */
1110 case '8': /* retained for compatibility */
1115 default: // ESnormal
1116 vt
->tty_state
= ESnormal
;
1121 case 7: /* alert aka. bell */
1123 //rfbSendBell(vt->screen);
1125 case 8: /* backspace */
1129 case 9: /* tabspace */
1130 if (vt
->cx
+ (8 - (vt
->cx
% 8)) > vt
->width
) {
1132 spiceterm_put_lf (vt
);
1134 vt
->cx
= vt
->cx
+ (8 - (vt
->cx
% 8));
1140 spiceterm_put_lf (vt
);
1142 case 13: /* carriage return */
1146 /* SI (shift in), select character set 1 */
1148 vt
->cur_enc
= vt
->g1enc
;
1149 /* fixme: display controls = 1 */
1152 /* SO (shift out), select character set 0 */
1154 vt
->cur_enc
= vt
->g0enc
;
1155 /* fixme: display controls = 0 */
1158 vt
->tty_state
= ESesc
;
1160 case 127: /* delete */
1163 case 128+27: /* csi */
1164 vt
->tty_state
= ESsquare
;
1167 if (vt
->cx
>= vt
->width
) {
1170 spiceterm_put_lf (vt
);
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
;
1177 spiceterm_update_xy (vt
, vt
->cx
, vt
->cy
);
1186 spiceterm_puts (spiceTerm
*vt
, const char *buf
, int len
)
1190 spiceterm_show_cursor (vt
, 0);
1193 unsigned char c
= *buf
;
1197 if (vt
->tty_state
!= ESnormal
) {
1198 // never translate escape sequence
1200 } else if (vt
->utf8
&& !vt
->cur_enc
) {
1202 if(c
& 0x80) { // utf8 multi-byte sequence
1204 if (vt
->utf_count
> 0 && (c
& 0xc0) == 0x80) {
1205 // inside UTF8 sequence
1206 vt
->utf_char
= (vt
->utf_char
<< 6) | (c
& 0x3f);
1208 if (vt
->utf_count
== 0) {
1214 // first char of a UTF8 sequence
1215 if ((c
& 0xe0) == 0xc0) {
1217 vt
->utf_char
= (c
& 0x1f);
1218 } else if ((c
& 0xf0) == 0xe0) {
1220 vt
->utf_char
= (c
& 0x0f);
1221 } else if ((c
& 0xf8) == 0xf0) {
1223 vt
->utf_char
= (c
& 0x07);
1224 } else if ((c
& 0xfc) == 0xf8) {
1226 vt
->utf_char
= (c
& 0x03);
1227 } else if ((c
& 0xfe) == 0xfc) {
1229 vt
->utf_char
= (c
& 0x01);
1242 // never translate controls
1243 if (c
>= 32 && c
!= 127 && c
!= (128+27)) {
1244 tc
= translations
[vt
->cur_enc
][c
& 0x0ff];
1250 spiceterm_putchar (vt
, tc
);
1253 spiceterm_show_cursor (vt
, 1);
1260 spiceterm_set_xcut_text (char* str, int len, struct _rfbClientRec* cl)
1262 spiceTerm *vt =(spiceTerm *)cl->screen->screenData;
1264 // seems str is Latin-1 encoded
1265 if (vt->selection) free (vt->selection);
1266 vt->selection = (unicode *)malloc (len*sizeof (unicode));
1268 for (i = 0; i < len; i++) {
1269 vt->selection[i] = str[i] & 0xff;
1271 vt->selection_len = len;
1276 mouse_report (spiceTerm *vt, int butt, int mrx, int mry)
1280 sprintf (buf, "[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
1283 spiceterm_respond_esc (vt, buf);
1288 spiceterm_toggle_marked_cell (spiceTerm
*vt
, int pos
)
1292 int x= (pos%vt->width)*8;
1293 int y= (pos/vt->width)*16;
1296 rfbScreenInfoPtr s=vt->screen;
1298 char *b = s->frameBuffer+y*s->width+x;
1300 for (j=0; j < 16; j++) {
1301 for(i=0; i < 8; i++) {
1302 b[j*s->width+i] ^= 0x0f;
1303 rfbMarkRectAsModified (s, x, y, x+8, y+16);
1312 spiceterm_pointer_event (int buttonMask, int x, int y, rfbClientPtr cl)
1315 spiceTerm *vt =(spiceTerm *)cl->screen->screenData;
1316 static int button2_released = 1;
1317 static int last_mask = 0;
1318 static int sel_start_pos = 0;
1319 static int sel_end_pos = 0;
1326 if (cx >= vt->width) cx = vt->width - 1;
1328 if (cy >= vt->height) cy = vt->height - 1;
1330 if (vt->report_mouse && buttonMask != last_mask) {
1331 last_mask = buttonMask;
1332 if (buttonMask & 1) {
1333 mouse_report (vt, 0, cx, cy);
1335 if (buttonMask & 2) {
1336 mouse_report (vt, 1, cx, cy);
1338 if (buttonMask & 4) {
1339 mouse_report (vt, 2, cx, cy);
1342 mouse_report (vt, 3, cx, cy);
1346 if (buttonMask & 2) {
1347 if(button2_released && vt->selection) {
1349 for(i = 0; i < vt->selection_len; i++) {
1350 if (vt->ibuf_count < IBUFSIZE - 6) { // uft8 is max 6 characters wide
1352 vt->ibuf_count += ucs2_to_utf8 (vt->selection[i], &vt->ibuf[vt->ibuf_count]);
1354 vt->ibuf[vt->ibuf_count++] = vt->selection[i];
1358 if (vt->y_displ != vt->y_base) {
1359 vt->y_displ = vt->y_base;
1360 spiceterm_refresh (vt);
1363 button2_released = 0;
1365 button2_released = 1;
1368 if (buttonMask & 1) {
1369 int pos = cy*vt->width + cx;
1371 // code borrowed from libspiceserver (SPICEonsole.c)
1373 if (!vt->mark_active) {
1375 vt->mark_active = 1;
1376 sel_start_pos = sel_end_pos = pos;
1377 spiceterm_toggle_marked_cell (vt, pos);
1381 if (pos != sel_end_pos) {
1383 if (pos > sel_end_pos) {
1384 cx = sel_end_pos; cy=pos;
1386 cx=pos; cy=sel_end_pos;
1389 if (cx < sel_start_pos) {
1390 if (cy < sel_start_pos) cy--;
1396 spiceterm_toggle_marked_cell (vt, cx);
1404 } else if (vt->mark_active) {
1405 vt->mark_active = 0;
1407 if (sel_start_pos > sel_end_pos) {
1408 int tmp = sel_start_pos - 1;
1409 sel_start_pos = sel_end_pos;
1413 int len = sel_end_pos - sel_start_pos + 1;
1415 if (vt->selection) free (vt->selection);
1416 vt->selection = (unicode *)malloc (len*sizeof (unicode));
1417 vt->selection_len = len;
1418 char *sel_latin1 = (char *)malloc (len + 1);
1420 for (i = 0; i < len; i++) {
1421 int pos = sel_start_pos + i;
1422 int x = pos % vt->width;
1423 int y1 = ((pos / vt->width) + vt->y_displ) % vt->total_height;
1424 TextCell *c = &vt->cells[y1*vt->width + x];
1425 vt->selection[i] = c->ch;
1426 sel_latin1[i] = (char)c->ch;
1429 sel_latin1[len] = 0;
1430 rfbGotXCutText (vt->screen, sel_latin1, len);
1433 while (sel_start_pos <= sel_end_pos) {
1434 spiceterm_toggle_marked_cell (vt, sel_start_pos++);
1439 rfbDefaultPtrAddEvent (buttonMask, x, y, cl);
1444 static void my_kbd_push_key(SpiceKbdInstance
*sin
, uint8_t frag
)
1446 // spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, keyboard_sin);
1448 /* we no not need this */
1453 static void my_kbd_push_keyval(SpiceKbdInstance
*sin
, uint32_t keySym
, int flags
)
1455 spiceTerm
*vt
= SPICE_CONTAINEROF(sin
, spiceTerm
, keyboard_sin
);
1456 static int control
= 0;
1457 static int shift
= 0;
1462 fprintf (stderr
, "KEYEVENT:%d: %08x\n", flags
, keySym
);fflush (stderr
);
1464 if (keySym
== GDK_KEY_Shift_L
|| keySym
== GDK_KEY_Shift_R
) {
1466 } if (keySym
== GDK_KEY_Control_L
|| keySym
== GDK_KEY_Control_R
) {
1468 } else if (vt
->ibuf_count
< (IBUFSIZE
- 32)) {
1471 if(keySym
>= 'a' && keySym
<= 'z')
1472 uc
= keySym
- 'a' + 1;
1473 else if (keySym
>= 'A' && keySym
<= 'Z')
1474 uc
= keySym
- 'A' + 1;
1479 //printf("CONTROL: %08x %d\n", keySym, uc);
1483 case GDK_KEY_Escape
:
1485 case GDK_KEY_Return
:
1487 case GDK_KEY_BackSpace
:
1491 case GDK_KEY_Delete
: /* kdch1 */
1492 case GDK_KEY_KP_Delete
:
1494 case GDK_KEY_Home
: /* khome */
1495 case GDK_KEY_KP_Home
:
1498 case GDK_KEY_KP_End
: /* kend */
1500 case GDK_KEY_Insert
: /* kich1 */
1501 case GDK_KEY_KP_Insert
:
1504 case GDK_KEY_KP_Up
: /* kcuu1 */
1506 case GDK_KEY_Down
: /* kcud1 */
1507 case GDK_KEY_KP_Down
:
1510 case GDK_KEY_KP_Right
: /* kcuf1 */
1513 case GDK_KEY_KP_Left
: /* kcub1 */
1515 case GDK_KEY_Page_Up
:
1517 spiceterm_virtual_scroll (vt
, -vt
->height
/2);
1521 case GDK_KEY_Page_Down
:
1523 spiceterm_virtual_scroll (vt
, vt
->height
/2);
1552 if (keySym
< 0x100) {
1560 fprintf(stderr
, "KEYPRESS OUT:%s: %08x\n", esc
, uc
); fflush (stderr
);
1563 if (vt
->y_displ
!= vt
->y_base
) {
1564 vt
->y_displ
= vt
->y_base
;
1565 spiceterm_refresh (vt
);
1569 spiceterm_respond_esc(vt
, esc
);
1570 } else if (uc
> 0) {
1573 gint len
= g_unichar_to_utf8(uc
, buf
);
1577 for (i
= 0; i
< len
; i
++) {
1578 vt
->ibuf
[vt
->ibuf_count
++] = buf
[i
];
1582 vt
->ibuf
[vt
->ibuf_count
++] = (char)uc
;
1591 if (flags
& 2) { // UP
1592 //printf("KEYRELEASE %08x\n", keySym);
1594 if (keySym
== GDK_KEY_Shift_L
|| keySym
== GDK_KEY_Shift_R
) {
1596 } else if (keySym
== GDK_KEY_Control_L
|| keySym
== GDK_KEY_Control_R
) {
1601 vt
->screen
->core
->watch_update_mask(vt
->screen
->mwatch
,
1602 SPICE_WATCH_EVENT_READ
|SPICE_WATCH_EVENT_WRITE
);
1605 static uint8_t my_kbd_get_leds(SpiceKbdInstance
*sin
)
1610 static SpiceKbdInterface my_keyboard_sif
= {
1611 .base
.type
= SPICE_INTERFACE_KEYBOARD
,
1612 .base
.description
= "spiceterm keyboard device",
1613 .base
.major_version
= SPICE_INTERFACE_KEYBOARD_MAJOR
,
1614 .base
.minor_version
= SPICE_INTERFACE_KEYBOARD_MINOR
,
1615 .push_keyval
= my_kbd_push_keyval
,
1616 .push_scan_freg
= my_kbd_push_key
,
1617 .get_leds
= my_kbd_get_leds
,
1621 create_spiceterm (int argc
, char** argv
, int maxx
, int maxy
)
1625 SpiceScreen
*spice_screen
;
1627 SpiceCoreInterface
*core
= basic_event_loop_init();
1628 spice_screen
= spice_screen_new(core
);
1629 //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
1630 spice_screen_add_display_interface(spice_screen
);
1631 spice_screen_add_agent_interface(spice_screen
->server
);
1633 spiceTerm
*vt
= (spiceTerm
*)calloc (sizeof(spiceTerm
), 1);
1635 vt
->keyboard_sin
.base
.sif
= &my_keyboard_sif
.base
;
1636 spice_server_add_interface(spice_screen
->server
, &vt
->keyboard_sin
.base
);
1638 // screen->setXCutText = spiceterm_set_xcut_text;
1639 // screen->ptrAddEvent = spiceterm_pointer_event;
1640 // screen->newClientHook = new_client;
1641 // screen->desktopName = "SPICE Command Terminal";
1643 vt
->maxx
= spice_screen
->width
;
1644 vt
->maxy
= spice_screen
->height
;
1646 vt
->width
= vt
->maxx
/ 8;
1647 vt
->height
= vt
->maxy
/ 16;
1649 vt
->total_height
= vt
->height
* 20;
1650 vt
->scroll_height
= 0;
1655 vt
->region_bottom
= vt
->height
;
1657 vt
->g0enc
= LAT1_MAP
;
1658 vt
->g1enc
= GRAF_MAP
;
1659 vt
->cur_enc
= vt
->g0enc
;
1662 /* default text attributes */
1663 vt
->default_attrib
.bold
= 0;
1664 vt
->default_attrib
.uline
= 0;
1665 vt
->default_attrib
.blink
= 0;
1666 vt
->default_attrib
.invers
= 0;
1667 vt
->default_attrib
.unvisible
= 0;
1668 vt
->default_attrib
.fgcol
= 7;
1669 vt
->default_attrib
.bgcol
= 0;
1671 vt
->cur_attrib
= vt
->default_attrib
;
1673 vt
->cells
= (TextCell
*)calloc (sizeof (TextCell
), vt
->width
*vt
->total_height
);
1675 for (i
= 0; i
< vt
->width
*vt
->total_height
; i
++) {
1676 vt
->cells
[i
].ch
= ' ';
1677 vt
->cells
[i
].attrib
= vt
->default_attrib
;
1680 vt
->altcells
= (TextCell
*)calloc (sizeof (TextCell
), vt
->width
*vt
->height
);
1682 vt
->screen
= spice_screen
;
1687 static void master_watch(int master
, int event
, void *opaque
)
1689 spiceTerm
*vt
= (spiceTerm
*)opaque
;
1691 printf("CHANNEL EVENT %d\n", event
);
1693 // fixme: if (!vt->mark_active) {
1695 if (event
== SPICE_WATCH_EVENT_READ
) {
1698 while ((c
= read(master
, buffer
, 1024)) == -1) {
1699 if (errno
!= EAGAIN
) break;
1702 g_error("got read error"); // fixme
1704 spiceterm_puts (vt
, buffer
, c
);
1706 if (vt
->ibuf_count
> 0) {
1707 printf ("DEBUG: WRITE %x %d\n", vt
->ibuf
[0], vt
->ibuf_count
);
1708 write (master
, vt
->ibuf
, vt
->ibuf_count
);
1709 vt
->ibuf_count
= 0; // fixme: what if not all data written
1711 vt
->screen
->core
->watch_update_mask(vt
->screen
->mwatch
, SPICE_WATCH_EVENT_READ
);
1716 main (int argc
, char** argv
)
1719 char **cmdargv
= NULL
;
1720 char *command
= "/bin/bash"; // execute normal shell as default
1724 struct winsize dimensions
;
1726 g_thread_init(NULL
);
1728 for (i
= 1; i
< argc
; i
++) {
1729 if (!strcmp (argv
[i
], "-c")) {
1730 command
= argv
[i
+1];
1731 cmdargv
= &argv
[i
+1];
1738 if (0) print_usage(NULL
); // fixme:
1740 spiceTerm
*vt
= create_spiceterm (argc
, argv
, 745, 400);
1742 setlocale(LC_ALL
, ""); // set from environment
1744 char *ctype
= setlocale (LC_CTYPE
, NULL
); // query LC_CTYPE
1746 // fixme: ist there a standard way to detect utf8 mode ?
1747 if (strcasestr (ctype
, ".utf-8")||strcasestr (ctype
, ".utf8")) {
1751 dimensions
.ws_col
= vt
->width
;
1752 dimensions
.ws_row
= vt
->height
;
1754 setenv ("TERM", TERM
, 1);
1756 printf("EXEC: %s\n", command
);
1758 pid
= forkpty (&master
, ptyname
, NULL
, &dimensions
);
1761 // install default signal handlers
1762 signal (SIGQUIT
, SIG_DFL
);
1763 signal (SIGTERM
, SIG_DFL
);
1764 signal (SIGINT
, SIG_DFL
);
1767 execvp (command
, cmdargv
);
1769 execlp (command
, command
, NULL
);
1771 perror ("Error: exec failed\n");
1772 exit (-1); // should not be reached
1773 } else if (pid
== -1) {
1774 perror ("Error: fork failed\n");
1779 vt
->screen
->mwatch
= vt
->screen
->core
->watch_add(
1780 master
, SPICE_WATCH_EVENT_READ
/* |SPICE_WATCH_EVENT_WRITE */,
1783 basic_event_loop_mainloop();
1785 //rfbProcessEvents (vt->screen, 40000); /* 40 ms */
1788 if (vt->ibuf_count > 0) {
1789 printf ("DEBUG: WRITE %d %d\n", vt->ibuf[0], vt->ibuf_count);
1790 write (master, vt->ibuf, vt->ibuf_count);
1792 last_time = time (NULL);
1798 waitpid(pid
, &status
, 0);