]> git.proxmox.com Git - spiceterm.git/blobdiff - spiceterm.c
allow to select text regions
[spiceterm.git] / spiceterm.c
index c460aff65df72287b3aa50263db7cfe31d2ab5d7..915b802d6a6095eb7d8f5c4d027aa1a2949e84fb 100644 (file)
@@ -86,32 +86,6 @@ print_usage(const char *msg)
     fprintf(stderr, "USAGE: spiceterm [spiceopts] [-c command [args]]\n");
 }
 
-/* Convert UCS2 to UTF8 sequence, trailing zero */
-/*
-static int
-ucs2_to_utf8 (gunichar2 c, char *out)
-{
-  if (c < 0x80) {
-    out[0] = c;                        //  0*******
-    out[1] = 0;
-    return 1;
-  } else if (c < 0x800) {
-    out[0] = 0xc0 | (c >> 6);  //  110***** 10******
-    out[1] = 0x80 | (c & 0x3f);
-    out[2] = 0;
-    return 2;
-  } else {
-    out[0] = 0xe0 | (c >> 12);         //  1110**** 10****** 10******
-    out[1] = 0x80 | ((c >> 6) & 0x3f);
-    out[2] = 0x80 | (c & 0x3f);
-    out[3] = 0;
-    return 3;
-  }
-
-  return 0;
-}
-*/
-
 static void
 draw_char_at (spiceTerm *vt, int x, int y, gunichar2 ch, TextAttributes attrib)
 {
@@ -159,6 +133,26 @@ spiceterm_clear_xy (spiceTerm *vt, int x, int y)
     }
 }
 
+void
+spiceterm_toggle_marked_cell(spiceTerm *vt, int pos)
+{
+    int x = (pos%vt->width);
+    int y = (pos/vt->width);
+
+    if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; }
+
+    int y1 = (vt->y_base + y) % vt->total_height;
+    int y2 = y1 - vt->y_displ;
+    if (y2 < 0) {
+        y2 += vt->total_height;
+    }
+    if (y2 < vt->height) {
+        TextCell *c = &vt->cells[y1 * vt->width + x];
+        c->attrib.selected =  c->attrib.selected ? 0 : 1;
+        spice_screen_draw_char(vt->screen, x, y, c->ch, c->attrib);
+    }
+}
+
 static void
 spiceterm_show_cursor (spiceTerm *vt, int show)
 {
@@ -561,22 +555,25 @@ spiceterm_set_mode (spiceTerm *vt, int on_off)
         if (vt->esc_ques) {          /* DEC private modes set/reset */
             switch(vt->esc_buf[i]) {
             case 10:                   /* X11 mouse reporting on/off */
-            case 1000:
+            case 1000:                 /* SET_VT200_MOUSE */
+            case 1002:                 /* xterm SET_BTN_EVENT_MOUSE */
                 vt->report_mouse = on_off;
                 break;
             case 1049:         /* start/end special app mode (smcup/rmcup) */
                 spiceterm_set_alternate_buffer (vt, on_off);
                 break;
             case 25:                   /* Cursor on/off */
-            case 9:                   /* X10 mouse reporting on/off */
+            case 9:                     /* X10 mouse reporting on/off */
             case 6:                    /* Origin relative/absolute */
             case 1:                    /* Cursor keys in appl mode*/
             case 5:                    /* Inverted screen on/off */
             case 7:                    /* Autowrap on/off */
             case 8:                    /* Autorepeat on/off */
                 break;
-            }
+             }
         } else { /* ANSI modes set/reset */
+            //g_assert_not_reached();
+
             /* fixme: implement me */
         }
     }
@@ -803,10 +800,10 @@ spiceterm_putchar (spiceTerm *vt, gunichar2 ch)
 
         switch (ch) {
         case 'h':
-            spiceterm_set_mode (vt, 1);
+            spiceterm_set_mode(vt, 1);
             break;
         case 'l':
-            spiceterm_set_mode (vt, 0);
+            spiceterm_set_mode(vt, 0);
             break;
         case 'm':
             if (!vt->esc_count) {
@@ -1086,7 +1083,7 @@ spiceterm_putchar (spiceTerm *vt, gunichar2 ch)
         if (ch == 'c') {
             DPRINTF(1, "ESC[>c   Query term ID");
             spiceterm_respond_esc (vt, TERMIDCODE);
-        }
+         }
         break;
     case ESpercent:
         vt->tty_state = ESnormal;
@@ -1259,175 +1256,184 @@ spiceterm_set_xcut_text (char* str, int len, struct _rfbClientRec* cl)
   vt->selection_len = len;
 }
 */
-/*
+
 static void
-mouse_report (spiceTerm *vt, int butt, int mrx, int mry)
+spiceterm_update_watch_mask(spiceTerm *vt, gboolean writable)
 {
-  char buf[8];
+    g_assert(vt != NULL);
 
-  sprintf (buf, "[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
-          (char)('!' + mry));
+    int mask = SPICE_WATCH_EVENT_READ;
+
+    if (writable) {
+        mask |= SPICE_WATCH_EVENT_WRITE;
+    }
 
-  spiceterm_respond_esc (vt, buf);
+    vt->screen->core->watch_update_mask(vt->screen->mwatch, mask);
 }
-*/
 
-void
-spiceterm_toggle_marked_cell (spiceTerm *vt, int pos)
+static void
+mouse_report(spiceTerm *vt, int butt, int mrx, int mry)
 {
+    char buf[8];
 
-/* fixme:
-  int x= (pos%vt->width)*8;
-  int y= (pos/vt->width)*16;
+    sprintf (buf, "[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
+             (char)('!' + mry));
 
-  int i,j;
-  rfbScreenInfoPtr s=vt->screen;
+    spiceterm_respond_esc(vt, buf);
 
-  char *b = s->frameBuffer+y*s->width+x;
+    spiceterm_update_watch_mask(vt, TRUE);
+}
 
-  for (j=0; j < 16; j++) {
-    for(i=0; i < 8; i++) {
-      b[j*s->width+i] ^= 0x0f;
-      rfbMarkRectAsModified (s, x, y, x+8, y+16);
+static void
+spiceterm_respond_unichar2(spiceTerm *vt, gunichar2 uc)
+{
+    if (vt->utf8) {
+        gchar buf[10];
+        gint len = g_unichar_to_utf8(uc, buf);
+
+        if (len > 0) {
+            if ((vt->ibuf_count + len) < IBUFSIZE) {
+                int i;
+                for (i = 0; i < len; i++) {
+                    vt->ibuf[vt->ibuf_count++] = buf[i];
+                }
+            } else {
+                fprintf(stderr, "warning: input buffer overflow\n");
+            }
+        }
+    } else {
+        if ((vt->ibuf_count + 1) < IBUFSIZE) {
+            vt->ibuf[vt->ibuf_count++] = (char)uc;
+        } else {
+            fprintf(stderr, "warning: input buffer overflow\n");
+        }
     }
-  }
-*/
 }
 
-/* fixme:
-
-void
-spiceterm_pointer_event (int buttonMask, int x, int y, rfbClientPtr cl)
+static void
+spiceterm_motion_event(spiceTerm *vt, uint32_t x, uint32_t y, uint32_t buttons)
 {
+    DPRINTF(1, "mask=%08x x=%d y=%d", buttons, x ,y);
 
-  spiceTerm *vt =(spiceTerm *)cl->screen->screenData;
-  static int button2_released = 1;
-  static int last_mask = 0;
-  static int sel_start_pos = 0;
-  static int sel_end_pos = 0;
-  int i;
-
-  int cx = x/8;
-  int cy = y/16;
-
-  if (cx < 0) cx = 0;
-  if (cx >= vt->width) cx = vt->width - 1;
-  if (cy < 0) cy = 0;
-  if (cy >= vt->height) cy = vt->height - 1;
+    static int last_mask = 0;
+    static int sel_start_pos = 0;
+    static int sel_end_pos = 0;
+    static int button2_released = 1;
 
-  if (vt->report_mouse && buttonMask != last_mask) {
-    last_mask = buttonMask;
-    if (buttonMask & 1) {
-      mouse_report (vt, 0, cx, cy);
-    }
-    if (buttonMask & 2) {
-      mouse_report (vt, 1, cx, cy);
-    }
-    if (buttonMask & 4) {
-      mouse_report (vt, 2, cx, cy);
-    }
-    if (!buttonMask) {
-      mouse_report (vt, 3, cx, cy);
+    int i;
+    int cx = x/8;
+    int cy = y/16;
+
+    if (cx < 0) cx = 0;
+    if (cx >= vt->width) cx = vt->width - 1;
+    if (cy < 0) cy = 0;
+    if (cy >= vt->height) cy = vt->height - 1;
+
+    if (vt->report_mouse && buttons != last_mask) {
+        last_mask = buttons;
+        if (buttons & 2) {
+            mouse_report(vt, 0, cx, cy);
+        }
+        if (buttons & 4) {
+            mouse_report (vt, 1, cx, cy);
+        }
+        if (buttons & 8) {
+            mouse_report (vt, 2, cx, cy);
+        }
+        if (!buttons) {
+            mouse_report (vt, 3, cx, cy);
+        }
     }
-  }
 
-  if (buttonMask & 2) {
-    if(button2_released && vt->selection) {
-      int i;
-      for(i = 0; i < vt->selection_len; i++) {
-       if (vt->ibuf_count < IBUFSIZE - 6) { // uft8 is max 6 characters wide
-         if (vt->utf8) {
-           vt->ibuf_count += ucs2_to_utf8 (vt->selection[i], &vt->ibuf[vt->ibuf_count]);
-         } else  {
-           vt->ibuf[vt->ibuf_count++] = vt->selection[i];
-         }
-       }
-      }
-      if (vt->y_displ != vt->y_base) {
-       vt->y_displ = vt->y_base;
-       spiceterm_refresh (vt);
-      }
+    if (buttons & 4) {
+        if(button2_released && vt->selection) {
+            int i;
+            for(i = 0; i < vt->selection_len; i++) {
+                spiceterm_respond_unichar2(vt, vt->selection[i]);
+            }
+            spiceterm_update_watch_mask(vt, TRUE);
+            if (vt->y_displ != vt->y_base) {
+                vt->y_displ = vt->y_base;
+                spiceterm_refresh(vt);
+            }
+        }
+        button2_released = 0;
+    } else {
+        button2_released = 1;
     }
-    button2_released = 0;
-  } else {
-    button2_released = 1;
-  }
 
-  if (buttonMask & 1) {
-    int pos = cy*vt->width + cx;
+    if (buttons & 2) {
+        int pos = cy*vt->width + cx;
 
-    // code borrowed from libvncserver (VNCconsole.c)
+        // code borrowed from libvncserver (VNCconsole.c)
 
-    if (!vt->mark_active) {
+        if (!vt->mark_active) {
 
-      vt->mark_active = 1;
-      sel_start_pos = sel_end_pos = pos;
-      spiceterm_toggle_marked_cell (vt, pos);
+            if (sel_start_pos != sel_end_pos) {
+                while (sel_start_pos <= sel_end_pos) {
+                    spiceterm_toggle_marked_cell(vt, sel_start_pos++);
+                }
+            }
 
-    } else {
+            vt->mark_active = 1;
+            sel_start_pos = sel_end_pos = pos;
+            spiceterm_toggle_marked_cell(vt, pos);
 
-      if (pos != sel_end_pos) {
+        } else {
 
-       if (pos > sel_end_pos) {
-         cx = sel_end_pos; cy=pos;
-       } else {
-         cx=pos; cy=sel_end_pos;
-       }
+            if (pos != sel_end_pos) {
 
-       if (cx < sel_start_pos) {
-         if (cy < sel_start_pos) cy--;
-       } else {
-         cx++;
-       }
+                if (pos > sel_end_pos) {
+                    cx = sel_end_pos; cy=pos;
+                } else {
+                    cx=pos; cy=sel_end_pos;
+                }
 
-       while (cx <= cy) {
-         spiceterm_toggle_marked_cell (vt, cx);
-         cx++;
-       }
+                if (cx < sel_start_pos) {
+                    if (cy < sel_start_pos) cy--;
+                } else {
+                    cx++;
+                }
 
-       sel_end_pos = pos;
-      }
-    }
+                while (cx <= cy) {
+                    spiceterm_toggle_marked_cell(vt, cx);
+                    cx++;
+                }
 
-  } else if (vt->mark_active) {
-    vt->mark_active = 0;
+                sel_end_pos = pos;
+            }
+        }
 
-    if (sel_start_pos > sel_end_pos) {
-      int tmp = sel_start_pos - 1;
-      sel_start_pos = sel_end_pos;
-      sel_end_pos = tmp;
-    }
+    } else if (vt->mark_active) {
+        vt->mark_active = 0;
 
-    int len = sel_end_pos - sel_start_pos + 1;
-
-    if (vt->selection) free (vt->selection);
-    vt->selection = (gunichar2 *)malloc (len*sizeof (gunichar2));
-    vt->selection_len = len;
-    char *sel_latin1 = (char *)malloc (len + 1);
-
-    for (i = 0; i < len; i++) {
-      int pos = sel_start_pos + i;
-      int x = pos % vt->width;
-      int y1 = ((pos / vt->width) + vt->y_displ) % vt->total_height;
-      TextCell *c = &vt->cells[y1*vt->width + x];
-      vt->selection[i] = c->ch;
-      sel_latin1[i] = (char)c->ch;
-      c++;
-    }
-    sel_latin1[len] = 0;
-    rfbGotXCutText (vt->screen, sel_latin1, len);
-    free (sel_latin1);
+        if (sel_start_pos > sel_end_pos) {
+            int tmp = sel_start_pos - 1;
+            sel_start_pos = sel_end_pos;
+            sel_end_pos = tmp;
+        }
 
-    while (sel_start_pos <= sel_end_pos) {
-      spiceterm_toggle_marked_cell (vt, sel_start_pos++);
-    }
+        int len = sel_end_pos - sel_start_pos + 1;
 
-  }
+        if (vt->selection) free (vt->selection);
+        vt->selection = (gunichar2 *)malloc (len*sizeof(gunichar2));
+        vt->selection_len = len;
 
-  rfbDefaultPtrAddEvent (buttonMask, x, y, cl);
+        for (i = 0; i < len; i++) {
+            int pos = sel_start_pos + i;
+            int x = pos % vt->width;
+            int y1 = ((pos / vt->width) + vt->y_displ) % vt->total_height;
+            TextCell *c = &vt->cells[y1*vt->width + x];
+            vt->selection[i] = c->ch;
+            c++;
+        }
+
+        DPRINTF(1, "selection length = %d", vt->selection_len);
 
+        // fixme: tell client we have something seletced
+        //rfbGotXCutText (vt->screen, sel_latin1, len);
+    }
 }
-*/
 
 static void 
 my_kbd_push_key(SpiceKbdInstance *sin, uint8_t frag)
@@ -1447,7 +1453,7 @@ my_kbd_push_keyval(SpiceKbdInstance *sin, uint32_t keySym, int flags)
     static int shift = 0;
     char *esc = NULL;
 
-    guint uc = 0;
+    gunichar2 uc = 0;
 
     DPRINTF(1, "flags=%d keySym=%08x", flags, keySym);
 
@@ -1554,19 +1560,7 @@ my_kbd_push_keyval(SpiceKbdInstance *sin, uint32_t keySym, int flags)
             if (esc) {
                 spiceterm_respond_esc(vt, esc);
             } else if (uc > 0) {
-                if (vt->utf8) {
-                    gchar buf[10];
-                    gint len = g_unichar_to_utf8(uc, buf);
-
-                    if (len > 0) {
-                        int i;
-                        for (i = 0; i < len; i++) {
-                            vt->ibuf[vt->ibuf_count++] = buf[i];
-                        }
-                    }
-                } else {
-                    vt->ibuf[vt->ibuf_count++] = (char)uc;
-                }
+                spiceterm_respond_unichar2(vt, uc);
             }
         }
     }
@@ -1582,8 +1576,7 @@ ret:
         }
     }
 
-    vt->screen->core->watch_update_mask(vt->screen->mwatch,
-                                        SPICE_WATCH_EVENT_READ|SPICE_WATCH_EVENT_WRITE);
+    spiceterm_update_watch_mask(vt, TRUE);
 }
 
 static uint8_t 
@@ -1593,7 +1586,7 @@ my_kbd_get_leds(SpiceKbdInstance *sin)
 }
 
 static SpiceKbdInterface my_keyboard_sif = {
-    .base.type          = SPICE_INTERFACE_KEYBOARD ,
+    .base.type          = SPICE_INTERFACE_KEYBOARD,
     .base.description   = "spiceterm keyboard device",
     .base.major_version = SPICE_INTERFACE_KEYBOARD_MAJOR,
     .base.minor_version = SPICE_INTERFACE_KEYBOARD_MINOR,
@@ -1602,15 +1595,67 @@ static SpiceKbdInterface my_keyboard_sif = {
     .get_leds           = my_kbd_get_leds,
 };
 
-spiceTerm *
-create_spiceterm(int argc, char** argv, int maxx, int maxy)
+/* vdagent interface - to get mouse/clipboarde support */
+static int 
+vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
+{
+    spiceTerm *vt = SPICE_CONTAINEROF(sin, spiceTerm, vdagent_sin);
+
+    VDIChunkHeader *hdr = (VDIChunkHeader *)buf;
+    VDAgentMessage *msg = (VDAgentMessage *)&hdr[1];
+
+    //g_assert(hdr->port == VDP_SERVER_PORT); 
+    g_assert(msg->protocol == VD_AGENT_PROTOCOL);
+
+    DPRINTF(1, "%d %d %d %d", len, hdr->port, msg->protocol, msg->type);
+
+    if (msg->type == VD_AGENT_MOUSE_STATE) {
+        VDAgentMouseState *info = (VDAgentMouseState *)&msg[1];
+        spiceterm_motion_event(vt, info->x, info->y, info->buttons);
+    } else if (msg->type == VD_AGENT_ANNOUNCE_CAPABILITIES) {
+        /* ignore for now */
+    } else if (msg->type == VD_AGENT_MONITORS_CONFIG) {
+        /* ignore for now */
+    } else {
+        DPRINTF(0, "got uknown vdagent message type %d\n", msg->type);
+    }
+
+    return len;
+}
+
+static int 
+vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len)
+{
+    DPRINTF(1, "%d", len);
+
+    return 0;
+}
+
+static void 
+vmc_state(SpiceCharDeviceInstance *sin, int connected)
+{
+    /* IGNORE */
+}
+
+static SpiceCharDeviceInterface my_vdagent_sif = {
+    .base.type          = SPICE_INTERFACE_CHAR_DEVICE,
+    .base.description   = "spice virtual channel char device",
+    .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
+    .state              = vmc_state,
+    .write              = vmc_write,
+    .read               = vmc_read,
+};
+
+static spiceTerm *
+create_spiceterm(int argc, char** argv, int maxx, int maxy, guint timeout)
 {
     int i;
 
     SpiceScreen *spice_screen;
 
     SpiceCoreInterface *core = basic_event_loop_init();
-    spice_screen = spice_screen_new(core);
+    spice_screen = spice_screen_new(core, timeout);
     //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
 
     spiceTerm *vt = (spiceTerm *)calloc (sizeof(spiceTerm), 1);
@@ -1618,6 +1663,10 @@ create_spiceterm(int argc, char** argv, int maxx, int maxy)
     vt->keyboard_sin.base.sif = &my_keyboard_sif.base;
     spice_server_add_interface(spice_screen->server, &vt->keyboard_sin.base);
 
+    vt->vdagent_sin.base.sif = &my_vdagent_sif.base;
+    vt->vdagent_sin.subtype = "vdagent";
+    spice_server_add_interface(spice_screen->server, &vt->vdagent_sin.base);
+
     // screen->setXCutText = spiceterm_set_xcut_text;
     // screen->ptrAddEvent = spiceterm_pointer_event;
     // screen->newClientHook = new_client;
@@ -1667,30 +1716,56 @@ create_spiceterm(int argc, char** argv, int maxx, int maxy)
     return vt;
 }
 
+static gboolean 
+master_error_callback(GIOChannel *channel, GIOCondition condition, 
+                      gpointer data)
+{
+    //spiceTerm *vt = (spiceTerm *)data;
+
+    DPRINTF(1, "condition %d", condition);
+
+    exit(0);
+
+    return FALSE;
+}
+
 static void 
 master_watch(int master, int event, void *opaque)
 {
     spiceTerm *vt = (spiceTerm *)opaque;
+    int c;
 
     // fixme: if (!vt->mark_active) {
 
     if (event == SPICE_WATCH_EVENT_READ) {
         char buffer[1024];
-        int c;
         while ((c = read(master, buffer, 1024)) == -1) {
            if (errno != EAGAIN) break;
         }
         if (c == -1) {
-            g_error("got read error"); // fixme
+            perror("master pipe read error"); // fixme
         }
         spiceterm_puts (vt, buffer, c);
     } else {
         if (vt->ibuf_count > 0) {
             DPRINTF(1, "write input %x %d", vt->ibuf[0], vt->ibuf_count);
-            write (master, vt->ibuf, vt->ibuf_count);
-            vt->ibuf_count = 0; // fixme: what if not all data written
+            if ((c = write (master, vt->ibuf, vt->ibuf_count)) >= 0) {
+                if (c == vt->ibuf_count) {
+                    vt->ibuf_count = 0;                   
+                } else if (c > 0) {
+                    // not all data written
+                    memmove(vt->ibuf, vt->ibuf + c, vt->ibuf_count - c);
+                    vt->ibuf_count -= c; 
+                } else {
+                    // nothing written -ignore and try later
+                }
+            } else {
+                perror("master pipe write error");
+            }
+        }
+        if (vt->ibuf_count == 0) {
+            spiceterm_update_watch_mask(vt, FALSE);
         }
-        vt->screen->core->watch_update_mask(vt->screen->mwatch, SPICE_WATCH_EVENT_READ);
     }
 }
 
@@ -1719,7 +1794,7 @@ main (int argc, char** argv)
 
     if (0) print_usage(NULL); // fixme:
 
-    spiceTerm *vt = create_spiceterm (argc, argv, 745, 400);
+    spiceTerm *vt = create_spiceterm (argc, argv, 745, 400, 10);
 
     setlocale(LC_ALL, ""); // set from environment
 
@@ -1733,7 +1808,7 @@ main (int argc, char** argv)
     dimensions.ws_col = vt->width;
     dimensions.ws_row = vt->height;
 
-    setenv ("TERM", TERM, 1);
+    setenv("TERM", TERM, 1);
 
     DPRINTF(1, "execute %s", command);
 
@@ -1757,24 +1832,19 @@ main (int argc, char** argv)
         exit (-1);
     }
 
-
+    /* watch for errors - we need to use glib directly because spice
+     * does not have SPICE_WATCH_EVENT for this */
+    GIOChannel *channel = g_io_channel_unix_new(master);
+    g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
+    g_io_channel_set_encoding(channel, NULL, NULL);
+    g_io_add_watch(channel, G_IO_ERR|G_IO_HUP, master_error_callback, vt);
     vt->screen->mwatch = vt->screen->core->watch_add(
         master, SPICE_WATCH_EVENT_READ /* |SPICE_WATCH_EVENT_WRITE */,
         master_watch, vt);
 
     basic_event_loop_mainloop();
 
-    //rfbProcessEvents (vt->screen, 40000); /* 40 ms */
-
-    /*
-      if (vt->ibuf_count > 0) {
-      printf ("DEBUG: WRITE %d %d\n", vt->ibuf[0], vt->ibuf_count);
-      write (master, vt->ibuf, vt->ibuf_count);
-      vt->ibuf_count = 0;
-      last_time = time (NULL);
-      }
-    */
-
     kill (pid, 9);
     int status;
     waitpid(pid, &status, 0);