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"
60 #define DPRINTF(x, format, ...) { \
62 printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
68 #define TERMIDCODE "[?1;2c" // vt100 ID
70 #define CHECK_ARGC(argc,argv,i) if (i >= argc-1) { \
71 fprintf(stderr, "ERROR: not enough arguments for: %s\n", argv[i]); \
76 /* these colours are from linux kernel drivers/char/vt.c */
78 unsigned char color_table
[] = { 0, 4, 2, 6, 1, 5, 3, 7,
79 8,12,10,14, 9,13,11,15 };
83 print_usage(const char *msg
)
85 if (msg
) { fprintf(stderr
, "ERROR: %s\n", msg
); }
86 fprintf(stderr
, "USAGE: spiceterm [spiceopts] [-c command [args]]\n");
90 draw_char_at (spiceTerm
*vt
, int x
, int y
, gunichar2 ch
, TextAttributes attrib
)
92 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) {
96 spice_screen_draw_char(vt
->screen
, x
, y
, ch
, attrib
);
100 spiceterm_update_xy (spiceTerm
*vt
, int x
, int y
)
102 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) { return; }
104 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
105 int y2
= y1
- vt
->y_displ
;
107 y2
+= vt
->total_height
;
109 if (y2
< vt
->height
) {
110 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
111 draw_char_at (vt
, x
, y2
, c
->ch
, c
->attrib
);
116 spiceterm_clear_xy (spiceTerm
*vt
, int x
, int y
)
118 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) { return; }
120 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
121 int y2
= y1
- vt
->y_displ
;
123 y2
+= vt
->total_height
;
125 if (y2
< vt
->height
) {
126 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
128 c
->attrib
= vt
->default_attrib
;
129 c
->attrib
.fgcol
= vt
->cur_attrib
.fgcol
;
130 c
->attrib
.bgcol
= vt
->cur_attrib
.bgcol
;
132 draw_char_at (vt
, x
, y
, c
->ch
, c
->attrib
);
137 spiceterm_toggle_marked_cell(spiceTerm
*vt
, int pos
)
139 int x
= (pos
%vt
->width
);
140 int y
= (pos
/vt
->width
);
142 if (x
< 0 || y
< 0 || x
>= vt
->width
|| y
>= vt
->height
) { return; }
144 int y1
= (vt
->y_displ
+ y
) % vt
->total_height
;
146 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
147 c
->attrib
.selected
= c
->attrib
.selected
? 0 : 1;
149 if (y
< vt
->height
) {
150 draw_char_at(vt
, x
, y
, c
->ch
, c
->attrib
);
155 spiceterm_show_cursor (spiceTerm
*vt
, int show
)
158 if (x
>= vt
->width
) {
162 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
163 int y
= y1
- vt
->y_displ
;
165 y
+= vt
->total_height
;
168 if (y
< vt
->height
) {
170 TextCell
*c
= &vt
->cells
[y1
* vt
->width
+ x
];
173 TextAttributes attrib
= vt
->default_attrib
;
174 attrib
.invers
= !(attrib
.invers
); /* invert fg and bg */
175 draw_char_at (vt
, x
, y
, c
->ch
, attrib
);
177 draw_char_at (vt
, x
, y
, c
->ch
, c
->attrib
);
183 spiceterm_refresh(spiceTerm
*vt
)
188 for(y
= 0; y
< vt
->height
; y
++) {
189 TextCell
*c
= vt
->cells
+ y1
* vt
->width
;
190 for(x
= 0; x
< vt
->width
; x
++) {
191 draw_char_at(vt
, x
, y
, c
->ch
, c
->attrib
);
194 if (++y1
== vt
->total_height
)
198 spiceterm_show_cursor(vt
, 1);
202 spiceterm_unselect_all(spiceTerm
*vt
)
206 for (i
= 0; i
< vt
->width
*vt
->total_height
; i
++) {
207 if (vt
->cells
[i
].attrib
.selected
) {
208 vt
->cells
[i
].attrib
.selected
= 0;
212 spiceterm_refresh(vt
);
216 spiceterm_scroll_down (spiceTerm
*vt
, int top
, int bottom
, int lines
)
218 if ((top
+ lines
) >= bottom
) {
219 lines
= bottom
- top
-1;
222 if (top
< 0 || bottom
> vt
->height
|| top
>= bottom
|| lines
< 1) {
227 for(i
= bottom
- top
- lines
- 1; i
>= 0; i
--) {
228 int src
= ((vt
->y_base
+ top
+ i
) % vt
->total_height
)*vt
->width
;
229 int dst
= ((vt
->y_base
+ top
+ lines
+ i
) % vt
->total_height
)*vt
->width
;
231 memmove(vt
->cells
+ dst
, vt
->cells
+ src
, vt
->width
*sizeof (TextCell
));
234 for (i
= 0; i
< lines
; i
++) {
236 TextCell
*c
= vt
->cells
+ ((vt
->y_base
+ top
+ i
) % vt
->total_height
)*vt
->width
;
237 for(j
= 0; j
< vt
->width
; j
++) {
238 c
->attrib
= vt
->default_attrib
;
249 spice_screen_scroll(vt
->screen
, 0, y1
, vt
->screen
->primary_width
, y2
, 0, y0
);
250 spice_screen_clear(vt
->screen
, 0, y0
, vt
->screen
->primary_width
, y1
);
254 spiceterm_scroll_up (spiceTerm
*vt
, int top
, int bottom
, int lines
, int moveattr
)
256 if ((top
+ lines
) >= bottom
) {
257 lines
= bottom
- top
- 1;
260 if (top
< 0 || bottom
> vt
->height
|| top
>= bottom
|| lines
< 1) {
267 int y1
= (top
+ lines
)*16;
270 spice_screen_scroll(vt
->screen
, 0, y0
, vt
->screen
->primary_width
, y2
-h
, 0, y1
);
271 spice_screen_clear(vt
->screen
, 0, y2
-h
, vt
->screen
->primary_width
, y2
);
280 for(i
= 0; i
< (bottom
- top
- lines
); i
++) {
281 int dst
= ((vt
->y_base
+ top
+ i
) % vt
->total_height
)*vt
->width
;
282 int src
= ((vt
->y_base
+ top
+ lines
+ i
) % vt
->total_height
)*vt
->width
;
284 memmove(vt
->cells
+ dst
, vt
->cells
+ src
, vt
->width
*sizeof (TextCell
));
287 for (i
= 1; i
<= lines
; i
++) {
289 TextCell
*c
= vt
->cells
+ ((vt
->y_base
+ bottom
- i
) % vt
->total_height
)*vt
->width
;
290 for(j
= 0; j
< vt
->width
; j
++) {
291 c
->attrib
= vt
->default_attrib
;
299 spiceterm_virtual_scroll (spiceTerm
*vt
, int lines
)
301 if (vt
->altbuf
|| lines
== 0) return;
305 int i
= vt
->scroll_height
;
306 if (i
> vt
->total_height
- vt
->height
)
307 i
= vt
->total_height
- vt
->height
;
308 int y1
= vt
->y_base
- i
;
310 y1
+= vt
->total_height
;
311 for(i
= 0; i
< lines
; i
++) {
312 if (vt
->y_displ
== y1
) break;
313 if (--vt
->y_displ
< 0) {
314 vt
->y_displ
= vt
->total_height
- 1;
319 for(i
= 0; i
< lines
; i
++) {
320 if (vt
->y_displ
== vt
->y_base
) break;
321 if (++vt
->y_displ
== vt
->total_height
) {
327 spiceterm_refresh (vt
);
331 spiceterm_respond_esc (spiceTerm
*vt
, const char *esc
)
333 int len
= strlen (esc
);
336 if (vt
->ibuf_count
< (IBUFSIZE
- 1 - len
)) {
337 vt
->ibuf
[vt
->ibuf_count
++] = 27;
338 for (i
= 0; i
< len
; i
++) {
339 vt
->ibuf
[vt
->ibuf_count
++] = esc
[i
];
345 spiceterm_put_lf (spiceTerm
*vt
)
347 if (vt
->cy
+ 1 == vt
->region_bottom
) {
349 if (vt
->altbuf
|| vt
->region_top
!= 0 || vt
->region_bottom
!= vt
->height
) {
350 spiceterm_scroll_up (vt
, vt
->region_top
, vt
->region_bottom
, 1, 1);
354 if (vt
->y_displ
== vt
->y_base
) {
355 spiceterm_scroll_up (vt
, vt
->region_top
, vt
->region_bottom
, 1, 0);
358 if (vt
->y_displ
== vt
->y_base
) {
359 if (++vt
->y_displ
== vt
->total_height
) {
364 if (++vt
->y_base
== vt
->total_height
) {
368 if (vt
->scroll_height
< vt
->total_height
) {
372 int y1
= (vt
->y_base
+ vt
->height
- 1) % vt
->total_height
;
373 TextCell
*c
= &vt
->cells
[y1
* vt
->width
];
375 for (x
= 0; x
< vt
->width
; x
++) {
377 c
->attrib
= vt
->default_attrib
;
381 // fprintf (stderr, "BASE: %d DISPLAY %d\n", vt->y_base, vt->y_displ);
383 } else if (vt
->cy
< vt
->height
- 1) {
389 spiceterm_csi_m (spiceTerm
*vt
)
393 for (i
= 0; i
< vt
->esc_count
; i
++) {
394 switch (vt
->esc_buf
[i
]) {
395 case 0: /* reset all console attributes to default */
396 vt
->cur_attrib
= vt
->default_attrib
;
399 vt
->cur_attrib
.bold
= 1;
402 vt
->cur_attrib
.uline
= 1;
405 vt
->cur_attrib
.blink
= 1;
408 vt
->cur_attrib
.invers
= 1;
411 vt
->cur_attrib
.unvisible
= 1;
414 vt
->cur_enc
= LAT1_MAP
;
415 // fixme: dispaly controls = 0 ?
416 // fixme: toggle meta = 0 ?
419 vt
->cur_enc
= IBMPC_MAP
;
420 // fixme: dispaly controls = 1 ?
421 // fixme: toggle meta = 0 ?
424 vt
->cur_enc
= IBMPC_MAP
;
425 // fixme: dispaly controls = 1 ?
426 // fixme: toggle meta = 1 ?
429 vt
->cur_attrib
.bold
= 0;
432 vt
->cur_attrib
.uline
= 0;
435 vt
->cur_attrib
.blink
= 0;
438 vt
->cur_attrib
.invers
= 0;
441 vt
->cur_attrib
.unvisible
= 0;
451 /* set foreground color */
452 vt
->cur_attrib
.fgcol
= color_table
[vt
->esc_buf
[i
] - 30];
455 /* reset color to default, enable underline */
456 vt
->cur_attrib
.fgcol
= vt
->default_attrib
.fgcol
;
457 vt
->cur_attrib
.uline
= 1;
460 /* reset color to default, disable underline */
461 vt
->cur_attrib
.fgcol
= vt
->default_attrib
.fgcol
;
462 vt
->cur_attrib
.uline
= 0;
472 /* set background color */
473 vt
->cur_attrib
.bgcol
= color_table
[vt
->esc_buf
[i
] - 40];
476 /* reset background color */
477 vt
->cur_attrib
.bgcol
= vt
->default_attrib
.bgcol
;
480 fprintf(stderr
, "unhandled ESC[%d m code\n",vt
->esc_buf
[i
]);
487 spiceterm_save_cursor (spiceTerm
*vt
)
489 vt
->cx_saved
= vt
->cx
;
490 vt
->cy_saved
= vt
->cy
;
491 vt
->cur_attrib_saved
= vt
->cur_attrib
;
492 vt
->charset_saved
= vt
->charset
;
493 vt
->g0enc_saved
= vt
->g0enc
;
494 vt
->g1enc_saved
= vt
->g1enc
;
495 vt
->cur_enc_saved
= vt
->cur_enc
;
499 spiceterm_restore_cursor (spiceTerm
*vt
)
501 vt
->cx
= vt
->cx_saved
;
502 vt
->cy
= vt
->cy_saved
;
503 vt
->cur_attrib
= vt
->cur_attrib_saved
;
504 vt
->charset
= vt
->charset_saved
;
505 vt
->g0enc
= vt
->g0enc_saved
;
506 vt
->g1enc
= vt
->g1enc_saved
;
507 vt
->cur_enc
= vt
->cur_enc_saved
;
511 spiceterm_set_alternate_buffer (spiceTerm
*vt
, int on_off
)
515 vt
->y_displ
= vt
->y_base
;
519 if (vt
->altbuf
) return;
523 /* alternate buffer & cursor */
525 spiceterm_save_cursor (vt
);
526 /* save screen to altcels */
527 for (y
= 0; y
< vt
->height
; y
++) {
528 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
529 for (x
= 0; x
< vt
->width
; x
++) {
530 vt
->altcells
[y
*vt
->width
+ x
] = vt
->cells
[y1
*vt
->width
+ x
];
535 for (y
= 0; y
<= vt
->height
; y
++) {
536 for (x
= 0; x
< vt
->width
; x
++) {
537 spiceterm_clear_xy (vt
, x
, y
);
543 if (vt
->altbuf
== 0) return;
547 /* restore saved data */
548 for (y
= 0; y
< vt
->height
; y
++) {
549 int y1
= (vt
->y_base
+ y
) % vt
->total_height
;
550 for (x
= 0; x
< vt
->width
; x
++) {
551 vt
->cells
[y1
*vt
->width
+ x
] = vt
->altcells
[y
*vt
->width
+ x
];
555 spiceterm_restore_cursor (vt
);
558 spiceterm_refresh (vt
);
562 spiceterm_set_mode (spiceTerm
*vt
, int on_off
)
566 for (i
= 0; i
<= vt
->esc_count
; i
++) {
567 if (vt
->esc_ques
) { /* DEC private modes set/reset */
568 switch(vt
->esc_buf
[i
]) {
569 case 10: /* X11 mouse reporting on/off */
570 case 1000: /* SET_VT200_MOUSE */
571 case 1002: /* xterm SET_BTN_EVENT_MOUSE */
572 vt
->report_mouse
= on_off
;
574 case 1049: /* start/end special app mode (smcup/rmcup) */
575 spiceterm_set_alternate_buffer (vt
, on_off
);
577 case 25: /* Cursor on/off */
578 case 9: /* X10 mouse reporting on/off */
579 case 6: /* Origin relative/absolute */
580 case 1: /* Cursor keys in appl mode*/
581 case 5: /* Inverted screen on/off */
582 case 7: /* Autowrap on/off */
583 case 8: /* Autorepeat on/off */
586 } else { /* ANSI modes set/reset */
587 //g_assert_not_reached();
589 /* fixme: implement me */
595 spiceterm_gotoxy (spiceTerm
*vt
, int x
, int y
)
597 /* verify all boundaries */
603 if (x
>= vt
->width
) {
613 if (y
>= vt
->height
) {
621 debug_print_escape_buffer(spiceTerm
*vt
, const char *func
, const char *prefix
,
622 const char *qes
, gunichar2 ch
)
625 if (vt
->esc_count
== 0) {
626 printf("%s:%s ESC[%s%c\n", func
, prefix
, qes
, ch
);
627 } else if (vt
->esc_count
== 1) {
628 printf("%s:%s ESC[%s%d%c\n", func
, prefix
, qes
, vt
->esc_buf
[0], ch
);
631 printf("%s:%s ESC[%s%d", func
, prefix
, qes
, vt
->esc_buf
[0]);
632 for (i
= 1; i
< vt
->esc_count
; i
++) {
633 printf(";%d", vt
->esc_buf
[i
]);
640 enum { ESnormal
, ESesc
, ESsquare
, ESgetpars
, ESgotpars
, ESfunckey
,
641 EShash
, ESsetG0
, ESsetG1
, ESpercent
, ESignore
, ESnonstd
,
642 ESpalette
, ESidquery
, ESosc1
, ESosc2
};
645 spiceterm_putchar (spiceTerm
*vt
, gunichar2 ch
)
649 if (debug
&& !vt
->tty_state
) {
650 DPRINTF(1, "CHAR:%2d: %4x '%c' (cur_enc %d) %d %d",
651 vt
->tty_state
, ch
, ch
, vt
->cur_enc
, vt
->cx
, vt
->cy
);
654 switch(vt
->tty_state
) {
656 vt
->tty_state
= ESnormal
;
659 vt
->tty_state
= ESsquare
;
662 vt
->tty_state
= ESnonstd
;
665 vt
->tty_state
= ESpercent
;
668 spiceterm_save_cursor (vt
);
671 spiceterm_restore_cursor (vt
);
674 vt
->tty_state
= ESsetG0
; // SET G0
677 vt
->tty_state
= ESsetG1
; // SET G1
681 if (vt
->cy
== vt
->region_top
)
682 spiceterm_scroll_down (vt
, vt
->region_top
, vt
->region_bottom
, 1);
683 else if (vt
->cy
> 0) {
688 /* numeric keypad - ignored */
691 /* appl. keypad - ignored */
694 DPRINTF(1, "got unhandled ESC%c %d", ch
, ch
);
698 case ESnonstd
: /* Operating System Controls */
699 vt
->tty_state
= ESnormal
;
702 case 'P': /* palette escape sequence */
703 for(i
= 0; i
< MAX_ESC_PARAMS
; i
++) {
708 vt
->tty_state
= ESpalette
;
710 case 'R': /* reset palette */
711 // fixme: reset_palette(vc);
718 vt
->osc_textbuf
[0] = 0;
719 vt
->tty_state
= ESosc1
;
722 DPRINTF(1, "got unhandled OSC %c", ch
);
723 vt
->tty_state
= ESnormal
;
728 vt
->tty_state
= ESnormal
;
730 vt
->tty_state
= ESosc2
;
732 DPRINTF(1, "got illegal OSC sequence");
736 if (ch
!= 0x9c && ch
!= 7) {
738 while (vt
->osc_textbuf
[i
]) i
++;
739 vt
->osc_textbuf
[i
++] = ch
;
740 vt
->osc_textbuf
[i
] = 0;
742 DPRINTF(1, "OSC:%c:%s", vt
->osc_cmd
, vt
->osc_textbuf
);
743 vt
->tty_state
= ESnormal
;
747 if ((ch
>= '0' && ch
<= '9') || (ch
>= 'A' && ch
<= 'F')
748 || (ch
>= 'a' && ch
<= 'f')) {
749 vt
->esc_buf
[vt
->esc_count
++] = (ch
> '9' ? (ch
& 0xDF) - 'A' + 10 : ch
- '0');
750 if (vt
->esc_count
== 7) {
751 // fixme: this does not work - please test
753 rfbColourMap *cmap =&vt->screen->colourMap;
755 int i = color_table[vt->esc_buf[0]] * 3, j = 1;
756 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
757 cmap->data.bytes[i++] += vt->esc_buf[j++];
758 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
759 cmap->data.bytes[i++] += vt->esc_buf[j++];
760 cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
761 cmap->data.bytes[i] += vt->esc_buf[j];
765 vt
->tty_state
= ESnormal
;
768 vt
->tty_state
= ESnormal
;
771 for(i
= 0; i
< MAX_ESC_PARAMS
; i
++) {
777 vt
->tty_state
= ESgetpars
;
780 vt
->tty_state
= ESidquery
;
784 if ((vt
->esc_ques
= (ch
== '?'))) {
788 if (ch
>= '0' && ch
<= '9') {
790 if (vt
->esc_count
< MAX_ESC_PARAMS
) {
791 vt
->esc_buf
[vt
->esc_count
] = vt
->esc_buf
[vt
->esc_count
] * 10 + ch
- '0';
794 } else if (ch
== ';') {
798 if (vt
->esc_has_par
) {
801 vt
->tty_state
= ESgotpars
;
805 vt
->tty_state
= ESnormal
;
807 char *qes
= vt
->esc_ques
? "?" : "";
810 debug_print_escape_buffer(vt
, __func__
, "", qes
, ch
);
815 spiceterm_set_mode(vt
, 1);
818 spiceterm_set_mode(vt
, 0);
821 if (!vt
->esc_count
) {
822 vt
->esc_count
++; // default parameter 0
824 spiceterm_csi_m (vt
);
827 /* report cursor position */
828 /* TODO: send ESC[row;colR */
832 if (vt
->esc_buf
[0] == 0) {
835 vt
->cy
-= vt
->esc_buf
[0];
842 /* move cursor down */
843 if (vt
->esc_buf
[0] == 0) {
846 vt
->cy
+= vt
->esc_buf
[0];
847 if (vt
->cy
>= vt
->height
) {
848 vt
->cy
= vt
->height
- 1;
853 /* move cursor right */
854 if (vt
->esc_buf
[0] == 0) {
857 vt
->cx
+= vt
->esc_buf
[0];
858 if (vt
->cx
>= vt
->width
) {
859 vt
->cx
= vt
->width
- 1;
863 /* move cursor left */
864 if (vt
->esc_buf
[0] == 0) {
867 vt
->cx
-= vt
->esc_buf
[0];
874 /* move cursor to column */
875 spiceterm_gotoxy (vt
, vt
->esc_buf
[0] - 1, vt
->cy
);
878 /* move cursor to row */
879 spiceterm_gotoxy (vt
, vt
->cx
, vt
->esc_buf
[0] - 1);
883 /* move cursor to row, column */
884 spiceterm_gotoxy (vt
, vt
->esc_buf
[1] - 1, vt
->esc_buf
[0] - 1);
887 switch (vt
->esc_buf
[0]) {
889 /* clear to end of screen */
890 for (y
= vt
->cy
; y
< vt
->height
; y
++) {
891 for (x
= 0; x
< vt
->width
; x
++) {
892 if (y
== vt
->cy
&& x
< vt
->cx
) {
895 spiceterm_clear_xy (vt
, x
, y
);
900 /* clear from beginning of screen */
901 for (y
= 0; y
<= vt
->cy
; y
++) {
902 for (x
= 0; x
< vt
->width
; x
++) {
903 if (y
== vt
->cy
&& x
> vt
->cx
) {
906 spiceterm_clear_xy (vt
, x
, y
);
911 /* clear entire screen */
912 for (y
= 0; y
<= vt
->height
; y
++) {
913 for (x
= 0; x
< vt
->width
; x
++) {
914 spiceterm_clear_xy (vt
, x
, y
);
921 switch (vt
->esc_buf
[0]) {
924 for(x
= vt
->cx
; x
< vt
->width
; x
++) {
925 spiceterm_clear_xy (vt
, x
, vt
->cy
);
929 /* clear from beginning of line */
930 for (x
= 0; x
<= vt
->cx
; x
++) {
931 spiceterm_clear_xy (vt
, x
, vt
->cy
);
935 /* clear entire line */
936 for(x
= 0; x
< vt
->width
; x
++) {
937 spiceterm_clear_xy (vt
, x
, vt
->cy
);
946 if (c
> vt
->height
- vt
->cy
)
947 c
= vt
->height
- vt
->cy
;
951 spiceterm_scroll_down (vt
, vt
->cy
, vt
->region_bottom
, c
);
957 if (c
> vt
->height
- vt
->cy
)
958 c
= vt
->height
- vt
->cy
;
962 spiceterm_scroll_up (vt
, vt
->cy
, vt
->region_bottom
, c
, 1);
968 spiceterm_scroll_down (vt
, vt
->region_top
, vt
->region_bottom
, c
);
974 spiceterm_scroll_up (vt
, vt
->region_top
, vt
->region_bottom
, c
, 1);
977 /* delete c character */
980 if (c
> vt
->width
- vt
->cx
)
981 c
= vt
->width
- vt
->cx
;
985 for (x
= vt
->cx
; x
< vt
->width
- c
; x
++) {
986 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
987 TextCell
*dst
= &vt
->cells
[y1
* vt
->width
+ x
];
988 TextCell
*src
= dst
+ c
;
990 spiceterm_update_xy (vt
, x
+ c
, vt
->cy
);
992 src
->attrib
= vt
->default_attrib
;
993 spiceterm_update_xy (vt
, x
, vt
->cy
);
997 /* save cursor position */
998 spiceterm_save_cursor (vt
);
1001 /* restore cursor position */
1002 spiceterm_restore_cursor (vt
);
1005 /* erase c characters */
1009 if (c
> (vt
->width
- vt
->cx
)) c
= vt
->width
- vt
->cx
;
1011 for(i
= 0; i
< c
; i
++) {
1012 spiceterm_clear_xy (vt
, vt
->cx
+ i
, vt
->cy
);
1016 /* insert c character */
1018 if (c
> (vt
->width
- vt
->cx
)) {
1019 c
= vt
->width
- vt
->cx
;
1023 for (x
= vt
->width
- c
; x
>= vt
->cx
; x
--) {
1024 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
1025 TextCell
*src
= &vt
->cells
[y1
* vt
->width
+ x
];
1026 TextCell
*dst
= src
+ c
;
1028 spiceterm_update_xy (vt
, x
+ c
, vt
->cy
);
1030 src
->attrib
= vt
->cur_attrib
;
1031 spiceterm_update_xy (vt
, x
, vt
->cy
);
1037 if (!vt
->esc_buf
[0])
1039 if (!vt
->esc_buf
[1])
1040 vt
->esc_buf
[1] = vt
->height
;
1041 /* Minimum allowed region is 2 lines */
1042 if (vt
->esc_buf
[0] < vt
->esc_buf
[1] &&
1043 vt
->esc_buf
[1] <= vt
->height
) {
1044 vt
->region_top
= vt
->esc_buf
[0] - 1;
1045 vt
->region_bottom
= vt
->esc_buf
[1];
1047 vt
->cy
= vt
->region_top
;
1048 DPRINTF(1, "set region %d %d", vt
->region_top
, vt
->region_bottom
);
1054 debug_print_escape_buffer(vt
, __func__
, " unhandled escape", qes
, ch
);
1060 case ESsetG0
: // Set G0
1061 vt
->tty_state
= ESnormal
;
1064 vt
->g0enc
= GRAF_MAP
;
1066 vt
->g0enc
= LAT1_MAP
;
1068 vt
->g0enc
= IBMPC_MAP
;
1070 vt
->g0enc
= USER_MAP
;
1072 if (vt
->charset
== 0)
1073 vt
->cur_enc
= vt
->g0enc
;
1076 case ESsetG1
: // Set G1
1077 vt
->tty_state
= ESnormal
;
1080 vt
->g1enc
= GRAF_MAP
;
1082 vt
->g1enc
= LAT1_MAP
;
1084 vt
->g1enc
= IBMPC_MAP
;
1086 vt
->g1enc
= USER_MAP
;
1088 if (vt
->charset
== 1)
1089 vt
->cur_enc
= vt
->g1enc
;
1092 case ESidquery
: // vt100 query id
1093 vt
->tty_state
= ESnormal
;
1096 DPRINTF(1, "ESC[>c Query term ID");
1097 spiceterm_respond_esc (vt
, TERMIDCODE
);
1101 vt
->tty_state
= ESnormal
;
1103 case '@': /* defined in ISO 2022 */
1106 case 'G': /* prelim official escape code */
1107 case '8': /* retained for compatibility */
1112 default: // ESnormal
1113 vt
->tty_state
= ESnormal
;
1118 case 7: /* alert aka. bell */
1120 //rfbSendBell(vt->screen);
1122 case 8: /* backspace */
1126 case 9: /* tabspace */
1127 if (vt
->cx
+ (8 - (vt
->cx
% 8)) > vt
->width
) {
1129 spiceterm_put_lf (vt
);
1131 vt
->cx
= vt
->cx
+ (8 - (vt
->cx
% 8));
1137 spiceterm_put_lf (vt
);
1139 case 13: /* carriage return */
1143 /* SI (shift in), select character set 1 */
1145 vt
->cur_enc
= vt
->g1enc
;
1146 /* fixme: display controls = 1 */
1149 /* SO (shift out), select character set 0 */
1151 vt
->cur_enc
= vt
->g0enc
;
1152 /* fixme: display controls = 0 */
1155 vt
->tty_state
= ESesc
;
1157 case 127: /* delete */
1160 case 128+27: /* csi */
1161 vt
->tty_state
= ESsquare
;
1164 if (vt
->cx
>= vt
->width
) {
1167 spiceterm_put_lf (vt
);
1170 int y1
= (vt
->y_base
+ vt
->cy
) % vt
->total_height
;
1171 TextCell
*c
= &vt
->cells
[y1
*vt
->width
+ vt
->cx
];
1172 c
->attrib
= vt
->cur_attrib
;
1174 spiceterm_update_xy (vt
, vt
->cx
, vt
->cy
);
1183 spiceterm_puts (spiceTerm
*vt
, const char *buf
, int len
)
1187 spiceterm_show_cursor (vt
, 0);
1190 unsigned char c
= *buf
;
1194 if (vt
->tty_state
!= ESnormal
) {
1195 // never translate escape sequence
1197 } else if (vt
->utf8
&& !vt
->cur_enc
) {
1199 if(c
& 0x80) { // utf8 multi-byte sequence
1201 if (vt
->utf_count
> 0 && (c
& 0xc0) == 0x80) {
1202 // inside UTF8 sequence
1203 vt
->utf_char
= (vt
->utf_char
<< 6) | (c
& 0x3f);
1205 if (vt
->utf_count
== 0) {
1211 // first char of a UTF8 sequence
1212 if ((c
& 0xe0) == 0xc0) {
1214 vt
->utf_char
= (c
& 0x1f);
1215 } else if ((c
& 0xf0) == 0xe0) {
1217 vt
->utf_char
= (c
& 0x0f);
1218 } else if ((c
& 0xf8) == 0xf0) {
1220 vt
->utf_char
= (c
& 0x07);
1221 } else if ((c
& 0xfc) == 0xf8) {
1223 vt
->utf_char
= (c
& 0x03);
1224 } else if ((c
& 0xfe) == 0xfc) {
1226 vt
->utf_char
= (c
& 0x01);
1239 // never translate controls
1240 if (c
>= 32 && c
!= 127 && c
!= (128+27)) {
1241 tc
= translations
[vt
->cur_enc
][c
& 0x0ff];
1247 spiceterm_putchar (vt
, tc
);
1250 spiceterm_show_cursor (vt
, 1);
1257 spiceterm_set_xcut_text (char* str, int len, struct _rfbClientRec* cl)
1259 spiceTerm *vt =(spiceTerm *)cl->screen->screenData;
1261 // seems str is Latin-1 encoded
1262 if (vt->selection) free (vt->selection);
1263 vt->selection = (gunichar2 *)malloc (len*sizeof (gunichar2));
1265 for (i = 0; i < len; i++) {
1266 vt->selection[i] = str[i] & 0xff;
1268 vt->selection_len = len;
1273 spiceterm_update_watch_mask(spiceTerm
*vt
, gboolean writable
)
1275 g_assert(vt
!= NULL
);
1277 int mask
= SPICE_WATCH_EVENT_READ
;
1280 mask
|= SPICE_WATCH_EVENT_WRITE
;
1283 vt
->screen
->core
->watch_update_mask(vt
->screen
->mwatch
, mask
);
1287 mouse_report(spiceTerm
*vt
, int butt
, int mrx
, int mry
)
1291 sprintf (buf
, "[M%c%c%c", (char)(' ' + butt
), (char)('!' + mrx
),
1294 spiceterm_respond_esc(vt
, buf
);
1296 spiceterm_update_watch_mask(vt
, TRUE
);
1300 spiceterm_respond_unichar2(spiceTerm
*vt
, gunichar2 uc
)
1304 gint len
= g_unichar_to_utf8(uc
, buf
);
1307 if ((vt
->ibuf_count
+ len
) < IBUFSIZE
) {
1309 for (i
= 0; i
< len
; i
++) {
1310 vt
->ibuf
[vt
->ibuf_count
++] = buf
[i
];
1313 fprintf(stderr
, "warning: input buffer overflow\n");
1317 if ((vt
->ibuf_count
+ 1) < IBUFSIZE
) {
1318 vt
->ibuf
[vt
->ibuf_count
++] = (char)uc
;
1320 fprintf(stderr
, "warning: input buffer overflow\n");
1326 spiceterm_motion_event(spiceTerm
*vt
, uint32_t x
, uint32_t y
, uint32_t buttons
)
1328 DPRINTF(1, "mask=%08x x=%d y=%d", buttons
, x
,y
);
1330 static int last_mask
= 0;
1331 static int sel_start_pos
= 0;
1332 static int sel_end_pos
= 0;
1333 static int button2_released
= 1;
1340 if (cx
>= vt
->width
) cx
= vt
->width
- 1;
1342 if (cy
>= vt
->height
) cy
= vt
->height
- 1;
1344 if (vt
->report_mouse
&& buttons
!= last_mask
) {
1345 last_mask
= buttons
;
1347 mouse_report(vt
, 0, cx
, cy
);
1350 mouse_report (vt
, 1, cx
, cy
);
1353 mouse_report (vt
, 2, cx
, cy
);
1356 mouse_report (vt
, 3, cx
, cy
);
1361 if(button2_released
&& vt
->selection
) {
1363 for(i
= 0; i
< vt
->selection_len
; i
++) {
1364 spiceterm_respond_unichar2(vt
, vt
->selection
[i
]);
1366 spiceterm_update_watch_mask(vt
, TRUE
);
1367 if (vt
->y_displ
!= vt
->y_base
) {
1368 vt
->y_displ
= vt
->y_base
;
1369 spiceterm_refresh(vt
);
1372 button2_released
= 0;
1374 button2_released
= 1;
1378 int pos
= cy
*vt
->width
+ cx
;
1380 // code borrowed from libvncserver (VNCconsole.c)
1382 if (!vt
->mark_active
) {
1384 spiceterm_unselect_all(vt
);
1386 vt
->mark_active
= 1;
1387 sel_start_pos
= sel_end_pos
= pos
;
1388 spiceterm_toggle_marked_cell(vt
, pos
);
1392 if (pos
!= sel_end_pos
) {
1394 if (pos
> sel_end_pos
) {
1395 cx
= sel_end_pos
; cy
=pos
;
1397 cx
=pos
; cy
=sel_end_pos
;
1400 if (cx
< sel_start_pos
) {
1401 if (cy
< sel_start_pos
) cy
--;
1407 spiceterm_toggle_marked_cell(vt
, cx
);
1415 } else if (vt
->mark_active
) {
1416 vt
->mark_active
= 0;
1418 if (sel_start_pos
> sel_end_pos
) {
1419 int tmp
= sel_start_pos
- 1;
1420 sel_start_pos
= sel_end_pos
;
1424 int len
= sel_end_pos
- sel_start_pos
+ 1;
1426 if (vt
->selection
) free (vt
->selection
);
1427 vt
->selection
= (gunichar2
*)malloc (len
*sizeof(gunichar2
));
1428 vt
->selection_len
= len
;
1430 for (i
= 0; i
< len
; i
++) {
1431 int pos
= sel_start_pos
+ i
;
1432 int x
= pos
% vt
->width
;
1433 int y1
= ((pos
/ vt
->width
) + vt
->y_displ
) % vt
->total_height
;
1434 TextCell
*c
= &vt
->cells
[y1
*vt
->width
+ x
];
1435 vt
->selection
[i
] = c
->ch
;
1439 DPRINTF(1, "selection length = %d", vt
->selection_len
);
1441 // fixme: tell client we have something seletced
1442 //rfbGotXCutText (vt->screen, sel_latin1, len);
1447 my_kbd_push_key(SpiceKbdInstance
*sin
, uint8_t frag
)
1449 // spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, keyboard_sin);
1451 /* we no not need this */
1457 my_kbd_push_keyval(SpiceKbdInstance
*sin
, uint32_t keySym
, int flags
)
1459 spiceTerm
*vt
= SPICE_CONTAINEROF(sin
, spiceTerm
, keyboard_sin
);
1460 static int control
= 0;
1461 static int shift
= 0;
1466 DPRINTF(1, "flags=%d keySym=%08x", flags
, keySym
);
1469 if (keySym
== GDK_KEY_Shift_L
|| keySym
== GDK_KEY_Shift_R
) {
1471 } if (keySym
== GDK_KEY_Control_L
|| keySym
== GDK_KEY_Control_R
) {
1473 } else if (vt
->ibuf_count
< (IBUFSIZE
- 32)) {
1476 if(keySym
>= 'a' && keySym
<= 'z')
1477 uc
= keySym
- 'a' + 1;
1478 else if (keySym
>= 'A' && keySym
<= 'Z')
1479 uc
= keySym
- 'A' + 1;
1485 case GDK_KEY_Escape
:
1487 case GDK_KEY_Return
:
1489 case GDK_KEY_BackSpace
:
1493 case GDK_KEY_Delete
: /* kdch1 */
1494 case GDK_KEY_KP_Delete
:
1496 case GDK_KEY_Home
: /* khome */
1497 case GDK_KEY_KP_Home
:
1500 case GDK_KEY_KP_End
: /* kend */
1502 case GDK_KEY_Insert
: /* kich1 */
1503 case GDK_KEY_KP_Insert
:
1506 case GDK_KEY_KP_Up
: /* kcuu1 */
1508 case GDK_KEY_Down
: /* kcud1 */
1509 case GDK_KEY_KP_Down
:
1512 case GDK_KEY_KP_Right
: /* kcuf1 */
1515 case GDK_KEY_KP_Left
: /* kcub1 */
1517 case GDK_KEY_Page_Up
:
1519 spiceterm_virtual_scroll (vt
, -vt
->height
/2);
1523 case GDK_KEY_Page_Down
:
1525 spiceterm_virtual_scroll (vt
, vt
->height
/2);
1554 if (keySym
< 0x100) {
1561 DPRINTF(1, "escape=%s unicode=%08x\n", esc
, uc
);
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) {
1571 spiceterm_respond_unichar2(vt
, uc
);
1579 if (flags
& 2) { // UP
1580 if (keySym
== GDK_KEY_Shift_L
|| keySym
== GDK_KEY_Shift_R
) {
1582 } else if (keySym
== GDK_KEY_Control_L
|| keySym
== GDK_KEY_Control_R
) {
1587 spiceterm_update_watch_mask(vt
, TRUE
);
1591 my_kbd_get_leds(SpiceKbdInstance
*sin
)
1596 static SpiceKbdInterface my_keyboard_sif
= {
1597 .base
.type
= SPICE_INTERFACE_KEYBOARD
,
1598 .base
.description
= "spiceterm keyboard device",
1599 .base
.major_version
= SPICE_INTERFACE_KEYBOARD_MAJOR
,
1600 .base
.minor_version
= SPICE_INTERFACE_KEYBOARD_MINOR
,
1601 .push_keyval
= my_kbd_push_keyval
,
1602 .push_scan_freg
= my_kbd_push_key
,
1603 .get_leds
= my_kbd_get_leds
,
1606 /* vdagent interface - to get mouse/clipboarde support */
1608 vmc_write(SpiceCharDeviceInstance
*sin
, const uint8_t *buf
, int len
)
1610 spiceTerm
*vt
= SPICE_CONTAINEROF(sin
, spiceTerm
, vdagent_sin
);
1612 VDIChunkHeader
*hdr
= (VDIChunkHeader
*)buf
;
1613 VDAgentMessage
*msg
= (VDAgentMessage
*)&hdr
[1];
1615 //g_assert(hdr->port == VDP_SERVER_PORT);
1616 g_assert(msg
->protocol
== VD_AGENT_PROTOCOL
);
1618 DPRINTF(1, "%d %d %d %d", len
, hdr
->port
, msg
->protocol
, msg
->type
);
1620 if (msg
->type
== VD_AGENT_MOUSE_STATE
) {
1621 VDAgentMouseState
*info
= (VDAgentMouseState
*)&msg
[1];
1622 spiceterm_motion_event(vt
, info
->x
, info
->y
, info
->buttons
);
1623 } else if (msg
->type
== VD_AGENT_ANNOUNCE_CAPABILITIES
) {
1624 /* ignore for now */
1625 } else if (msg
->type
== VD_AGENT_MONITORS_CONFIG
) {
1626 /* ignore for now */
1628 DPRINTF(0, "got uknown vdagent message type %d\n", msg
->type
);
1635 vmc_read(SpiceCharDeviceInstance
*sin
, uint8_t *buf
, int len
)
1637 DPRINTF(1, "%d", len
);
1643 vmc_state(SpiceCharDeviceInstance
*sin
, int connected
)
1648 static SpiceCharDeviceInterface my_vdagent_sif
= {
1649 .base
.type
= SPICE_INTERFACE_CHAR_DEVICE
,
1650 .base
.description
= "spice virtual channel char device",
1651 .base
.major_version
= SPICE_INTERFACE_CHAR_DEVICE_MAJOR
,
1652 .base
.minor_version
= SPICE_INTERFACE_CHAR_DEVICE_MINOR
,
1659 create_spiceterm(int argc
, char** argv
, int maxx
, int maxy
, guint timeout
)
1663 SpiceScreen
*spice_screen
;
1665 SpiceCoreInterface
*core
= basic_event_loop_init();
1666 spice_screen
= spice_screen_new(core
, timeout
);
1667 //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
1669 spiceTerm
*vt
= (spiceTerm
*)calloc (sizeof(spiceTerm
), 1);
1671 vt
->keyboard_sin
.base
.sif
= &my_keyboard_sif
.base
;
1672 spice_server_add_interface(spice_screen
->server
, &vt
->keyboard_sin
.base
);
1674 vt
->vdagent_sin
.base
.sif
= &my_vdagent_sif
.base
;
1675 vt
->vdagent_sin
.subtype
= "vdagent";
1676 spice_server_add_interface(spice_screen
->server
, &vt
->vdagent_sin
.base
);
1678 // screen->setXCutText = spiceterm_set_xcut_text;
1679 // screen->ptrAddEvent = spiceterm_pointer_event;
1680 // screen->newClientHook = new_client;
1681 // screen->desktopName = "SPICE Command Terminal";
1683 vt
->maxx
= spice_screen
->width
;
1684 vt
->maxy
= spice_screen
->height
;
1686 vt
->width
= vt
->maxx
/ 8;
1687 vt
->height
= vt
->maxy
/ 16;
1689 vt
->total_height
= vt
->height
* 20;
1690 vt
->scroll_height
= 0;
1695 vt
->region_bottom
= vt
->height
;
1697 vt
->g0enc
= LAT1_MAP
;
1698 vt
->g1enc
= GRAF_MAP
;
1699 vt
->cur_enc
= vt
->g0enc
;
1702 /* default text attributes */
1703 vt
->default_attrib
.bold
= 0;
1704 vt
->default_attrib
.uline
= 0;
1705 vt
->default_attrib
.blink
= 0;
1706 vt
->default_attrib
.invers
= 0;
1707 vt
->default_attrib
.unvisible
= 0;
1708 vt
->default_attrib
.fgcol
= 7;
1709 vt
->default_attrib
.bgcol
= 0;
1711 vt
->cur_attrib
= vt
->default_attrib
;
1713 vt
->cells
= (TextCell
*)calloc (sizeof (TextCell
), vt
->width
*vt
->total_height
);
1715 for (i
= 0; i
< vt
->width
*vt
->total_height
; i
++) {
1716 vt
->cells
[i
].ch
= ' ';
1717 vt
->cells
[i
].attrib
= vt
->default_attrib
;
1720 vt
->altcells
= (TextCell
*)calloc (sizeof (TextCell
), vt
->width
*vt
->height
);
1722 vt
->screen
= spice_screen
;
1728 master_error_callback(GIOChannel
*channel
, GIOCondition condition
,
1731 //spiceTerm *vt = (spiceTerm *)data;
1733 DPRINTF(1, "condition %d", condition
);
1741 master_watch(int master
, int event
, void *opaque
)
1743 spiceTerm
*vt
= (spiceTerm
*)opaque
;
1746 // fixme: if (!vt->mark_active) {
1748 if (event
== SPICE_WATCH_EVENT_READ
) {
1750 while ((c
= read(master
, buffer
, 1024)) == -1) {
1751 if (errno
!= EAGAIN
) break;
1754 perror("master pipe read error"); // fixme
1756 spiceterm_puts (vt
, buffer
, c
);
1758 if (vt
->ibuf_count
> 0) {
1759 DPRINTF(1, "write input %x %d", vt
->ibuf
[0], vt
->ibuf_count
);
1760 if ((c
= write (master
, vt
->ibuf
, vt
->ibuf_count
)) >= 0) {
1761 if (c
== vt
->ibuf_count
) {
1764 // not all data written
1765 memmove(vt
->ibuf
, vt
->ibuf
+ c
, vt
->ibuf_count
- c
);
1766 vt
->ibuf_count
-= c
;
1768 // nothing written -ignore and try later
1771 perror("master pipe write error");
1774 if (vt
->ibuf_count
== 0) {
1775 spiceterm_update_watch_mask(vt
, FALSE
);
1781 main (int argc
, char** argv
)
1784 char **cmdargv
= NULL
;
1785 char *command
= "/bin/bash"; // execute normal shell as default
1789 struct winsize dimensions
;
1791 g_thread_init(NULL
);
1793 for (i
= 1; i
< argc
; i
++) {
1794 if (!strcmp (argv
[i
], "-c")) {
1795 command
= argv
[i
+1];
1796 cmdargv
= &argv
[i
+1];
1803 if (0) print_usage(NULL
); // fixme:
1805 spiceTerm
*vt
= create_spiceterm (argc
, argv
, 745, 400, 10);
1807 setlocale(LC_ALL
, ""); // set from environment
1809 char *ctype
= setlocale (LC_CTYPE
, NULL
); // query LC_CTYPE
1811 // fixme: ist there a standard way to detect utf8 mode ?
1812 if (strcasestr (ctype
, ".utf-8")||strcasestr (ctype
, ".utf8")) {
1816 dimensions
.ws_col
= vt
->width
;
1817 dimensions
.ws_row
= vt
->height
;
1819 setenv("TERM", TERM
, 1);
1821 DPRINTF(1, "execute %s", command
);
1823 pid
= forkpty (&master
, ptyname
, NULL
, &dimensions
);
1826 // install default signal handlers
1827 signal (SIGQUIT
, SIG_DFL
);
1828 signal (SIGTERM
, SIG_DFL
);
1829 signal (SIGINT
, SIG_DFL
);
1832 execvp (command
, cmdargv
);
1834 execlp (command
, command
, NULL
);
1836 perror ("Error: exec failed\n");
1837 exit (-1); // should not be reached
1838 } else if (pid
== -1) {
1839 perror ("Error: fork failed\n");
1843 /* watch for errors - we need to use glib directly because spice
1844 * does not have SPICE_WATCH_EVENT for this */
1845 GIOChannel
*channel
= g_io_channel_unix_new(master
);
1846 g_io_channel_set_flags(channel
, G_IO_FLAG_NONBLOCK
, NULL
);
1847 g_io_channel_set_encoding(channel
, NULL
, NULL
);
1848 g_io_add_watch(channel
, G_IO_ERR
|G_IO_HUP
, master_error_callback
, vt
);
1850 vt
->screen
->mwatch
= vt
->screen
->core
->watch_add(
1851 master
, SPICE_WATCH_EVENT_READ
/* |SPICE_WATCH_EVENT_WRITE */,
1854 basic_event_loop_mainloop();
1858 waitpid(pid
, &status
, 0);