mirror of
git://git.proxmox.com/git/vncterm.git
synced 2024-12-22 21:33:49 +03:00
07a910f022
this patch makes use of unifont with genfont2 and introduces support for wide-characters and combining glyphs for this we have to save the width and possible diacritic in the textcell, which means we also have to reset them properly and give those values also to draw_char_at to determine the width we use the wcwidth provided by unifont (because in unifont some characters are wide which normally are not) we mmap the generated fontfile, and drop the glyphs.h, which reduces the initial memory use, and shares the pages between processes Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2514 lines
56 KiB
C
2514 lines
56 KiB
C
/*
|
|
|
|
Copyright (C) 2007-2011 Proxmox Server Solutions GmbH
|
|
|
|
Copyright: vzdump is under GNU GPL, the GNU General Public License.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 dated June, 1991.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
|
|
Author: Dietmar Maurer <dietmar@proxmox.com>
|
|
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mman.h>
|
|
#include <fcntl.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <rfb/rfb.h>
|
|
#include <rfb/keysym.h>
|
|
#include <pty.h> /* for openpty and forkpty */
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
#include <locale.h>
|
|
|
|
#include "vncterm.h"
|
|
|
|
#include <gnutls/gnutls.h>
|
|
#include <gnutls/x509.h>
|
|
|
|
/* define this for debugging */
|
|
//#define DEBUG
|
|
|
|
char *auth_path = "/";
|
|
char *auth_perm = "Sys.Console";
|
|
|
|
uint16_t screen_width = 744;
|
|
uint16_t screen_height = 400;
|
|
|
|
int use_x509 = 1;
|
|
|
|
extern int wcwidth (wchar_t wc);
|
|
unsigned char *fontdata;
|
|
|
|
#define FONTFILE "/usr/share/vncterm/font.data"
|
|
#define GLYPHLINES 16
|
|
|
|
static char *
|
|
urlencode(char *buf, const char *value)
|
|
{
|
|
static const char *hexchar = "0123456789abcdef";
|
|
char *p = buf;
|
|
int i;
|
|
int l = strlen(value);
|
|
for (i = 0; i < l; i++) {
|
|
char c = value[i];
|
|
if (('a' <= c && c <= 'z') ||
|
|
('A' <= c && c <= 'Z') ||
|
|
('0' <= c && c <= '9')) {
|
|
*p++ = c;
|
|
} else if (c == 32) {
|
|
*p++ = '+';
|
|
} else {
|
|
*p++ = '%';
|
|
*p++ = hexchar[c >> 4];
|
|
*p++ = hexchar[c & 15];
|
|
}
|
|
}
|
|
*p = 0;
|
|
|
|
return p;
|
|
}
|
|
|
|
static int
|
|
pve_auth_verify(const char *clientip, const char *username, const char *passwd)
|
|
{
|
|
struct sockaddr_in server;
|
|
|
|
int sfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sfd == -1) {
|
|
perror("pve_auth_verify: socket failed");
|
|
return -1;
|
|
}
|
|
|
|
struct hostent *he;
|
|
if ((he = gethostbyname("localhost")) == NULL) {
|
|
fprintf(stderr, "pve_auth_verify: error resolving hostname\n");
|
|
goto err;
|
|
}
|
|
|
|
memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length);
|
|
server.sin_family = AF_INET;
|
|
server.sin_port = htons(85);
|
|
|
|
if (connect(sfd, (struct sockaddr *)&server, sizeof(server))) {
|
|
perror("pve_auth_verify: error connecting to server");
|
|
goto err;
|
|
}
|
|
|
|
char buf[8192];
|
|
char form[8192];
|
|
|
|
char *p = form;
|
|
p = urlencode(p, "username");
|
|
*p++ = '=';
|
|
p = urlencode(p, username);
|
|
|
|
*p++ = '&';
|
|
p = urlencode(p, "password");
|
|
*p++ = '=';
|
|
p = urlencode(p, passwd);
|
|
|
|
*p++ = '&';
|
|
p = urlencode(p, "path");
|
|
*p++ = '=';
|
|
p = urlencode(p, auth_path);
|
|
|
|
*p++ = '&';
|
|
p = urlencode(p, "privs");
|
|
*p++ = '=';
|
|
p = urlencode(p, auth_perm);
|
|
|
|
sprintf(buf, "POST /api2/json/access/ticket HTTP/1.1\n"
|
|
"Host: localhost:85\n"
|
|
"Connection: close\n"
|
|
"PVEClientIP: %s\n"
|
|
"Content-Type: application/x-www-form-urlencoded\n"
|
|
"Content-Length: %zd\n\n%s\n", clientip, strlen(form), form);
|
|
ssize_t len = strlen(buf);
|
|
ssize_t sb = send(sfd, buf, len, 0);
|
|
if (sb < 0) {
|
|
perror("pve_auth_verify: send failed");
|
|
goto err;
|
|
}
|
|
if (sb != len) {
|
|
fprintf(stderr, "pve_auth_verify: partial send error\n");
|
|
goto err;
|
|
}
|
|
|
|
len = recv(sfd, buf, sizeof(buf) - 1, 0);
|
|
if (len < 0) {
|
|
perror("pve_auth_verify: recv failed");
|
|
goto err;
|
|
}
|
|
|
|
buf[len] = 0;
|
|
|
|
//printf("DATA:%s\n", buf);
|
|
|
|
shutdown(sfd, SHUT_RDWR);
|
|
|
|
return strncmp(buf, "HTTP/1.1 200 OK", 15);
|
|
|
|
err:
|
|
shutdown(sfd, SHUT_RDWR);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void vnc_debug_gnutls_log(int level, const char* str) {
|
|
fprintf(stderr, "%d %s", level, str);
|
|
}
|
|
#endif
|
|
|
|
#define DH_BITS 2048
|
|
static gnutls_dh_params_t dh_params;
|
|
|
|
typedef struct {
|
|
gnutls_session_t session;
|
|
} tls_client_t;
|
|
|
|
static ssize_t
|
|
vnc_tls_push(
|
|
gnutls_transport_ptr_t transport,
|
|
const void *data,
|
|
size_t len)
|
|
{
|
|
rfbClientPtr cl = (rfbClientPtr)transport;
|
|
int n;
|
|
|
|
retry:
|
|
n = send(cl->sock, data, len, 0);
|
|
if (n < 0) {
|
|
if (errno == EINTR)
|
|
goto retry;
|
|
return -1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static ssize_t
|
|
vnc_tls_pull(
|
|
gnutls_transport_ptr_t transport,
|
|
void *data,
|
|
size_t len)
|
|
{
|
|
rfbClientPtr cl = (rfbClientPtr)transport;
|
|
int n;
|
|
|
|
retry:
|
|
n = recv(cl->sock, data, len, 0);
|
|
if (n < 0) {
|
|
if (errno == EINTR)
|
|
goto retry;
|
|
return -1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
ssize_t vnc_tls_read(rfbClientPtr cl, void *buf, size_t count)
|
|
{
|
|
tls_client_t *sd = (tls_client_t *)cl->clientData;
|
|
|
|
int ret = gnutls_read(sd->session, buf, count);
|
|
if (ret < 0) {
|
|
if (ret == GNUTLS_E_AGAIN)
|
|
errno = EAGAIN;
|
|
else
|
|
errno = EIO;
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
ssize_t vnc_tls_write(rfbClientPtr cl, void *buf, size_t count)
|
|
{
|
|
tls_client_t *sd = (tls_client_t *)cl->clientData;
|
|
|
|
int ret = gnutls_write(sd->session, buf, count);
|
|
if (ret < 0) {
|
|
if (ret == GNUTLS_E_AGAIN)
|
|
errno = EAGAIN;
|
|
else
|
|
errno = EIO;
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gnutls_anon_server_credentials
|
|
tls_initialize_anon_cred(void)
|
|
{
|
|
gnutls_anon_server_credentials anon_cred;
|
|
int ret;
|
|
|
|
if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) {
|
|
rfbLog("can't allocate credentials: %s\n", gnutls_strerror(ret));
|
|
return NULL;
|
|
}
|
|
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030506
|
|
gnutls_anon_set_server_known_dh_params(anon_cred, GNUTLS_SEC_PARAM_MEDIUM);
|
|
#else
|
|
gnutls_anon_set_server_dh_params(anon_cred, dh_params);
|
|
#endif
|
|
|
|
return anon_cred;
|
|
}
|
|
|
|
static gnutls_certificate_credentials_t
|
|
tls_initialize_x509_cred(void)
|
|
{
|
|
gnutls_certificate_credentials_t x509_cred;
|
|
int ret;
|
|
|
|
/* Paths to x509 certs/keys */
|
|
char *x509cacert = "/etc/pve/pve-root-ca.pem";
|
|
char *x509cert = "/etc/pve/local/pve-ssl.pem";
|
|
char *x509key = "/etc/pve/local/pve-ssl.key";
|
|
|
|
if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
|
|
rfbLog("can't allocate credentials: %s\n", gnutls_strerror(ret));
|
|
return NULL;
|
|
}
|
|
|
|
if ((ret = gnutls_certificate_set_x509_trust_file
|
|
(x509_cred, x509cacert, GNUTLS_X509_FMT_PEM)) < 0) {
|
|
rfbLog("can't load CA certificate: %s\n", gnutls_strerror(ret));
|
|
gnutls_certificate_free_credentials(x509_cred);
|
|
return NULL;
|
|
}
|
|
|
|
if ((ret = gnutls_certificate_set_x509_key_file
|
|
(x509_cred, x509cert, x509key, GNUTLS_X509_FMT_PEM)) < 0) {
|
|
rfbLog("can't load certificate & key: %s\n", gnutls_strerror(ret));
|
|
gnutls_certificate_free_credentials(x509_cred);
|
|
return NULL;
|
|
}
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030506
|
|
/* only available since GnuTLS 3.5.6, on previous versions see
|
|
* gnutls_certificate_set_dh_params(). */
|
|
gnutls_certificate_set_known_dh_params(x509_cred, GNUTLS_SEC_PARAM_MEDIUM);
|
|
#else
|
|
gnutls_certificate_set_dh_params (x509_cred, dh_params);
|
|
#endif
|
|
|
|
return x509_cred;
|
|
}
|
|
|
|
/* rfb tls security handler */
|
|
|
|
#define rfbSecTypeVencrypt 19
|
|
#define rfbVencryptTlsPlain 259
|
|
#define rfbVencryptX509Plain 262
|
|
|
|
void rfbEncodeU32(char *buf, uint32_t value)
|
|
{
|
|
buf[0] = (value >> 24) & 0xFF;
|
|
buf[1] = (value >> 16) & 0xFF;
|
|
buf[2] = (value >> 8) & 0xFF;
|
|
buf[3] = value & 0xFF;
|
|
}
|
|
|
|
uint32_t rfbDecodeU32(char *data, size_t offset)
|
|
{
|
|
return ((data[offset] << 24) | (data[offset + 1] << 16) |
|
|
(data[offset + 2] << 8) | data[offset + 3]);
|
|
}
|
|
|
|
static void
|
|
vencrypt_subauth_plain(rfbClientPtr cl)
|
|
{
|
|
const char *err = NULL;
|
|
char buf[4096];
|
|
int n;
|
|
|
|
char clientip[INET6_ADDRSTRLEN];
|
|
clientip[0] = 0;
|
|
struct sockaddr_in client;
|
|
socklen_t addrlen = sizeof(client);
|
|
if (getpeername(cl->sock, &client, &addrlen) == 0) {
|
|
inet_ntop(client.sin_family, &client.sin_addr,
|
|
clientip, sizeof(clientip));
|
|
}
|
|
|
|
if ((n = rfbReadExact(cl, buf, 8)) <= 0) {
|
|
err = n ? "read failed" : "client gone";
|
|
goto err;
|
|
}
|
|
|
|
uint32_t ulen = rfbDecodeU32(buf, 0);
|
|
uint32_t pwlen = rfbDecodeU32(buf, 4);
|
|
|
|
if (!ulen) {
|
|
err = "No User name.";
|
|
goto err;
|
|
}
|
|
if (ulen >= 255) {
|
|
err = "User name too long.";
|
|
goto err;
|
|
}
|
|
if (!pwlen) {
|
|
err = "Password too short";
|
|
goto err;
|
|
}
|
|
if (pwlen >= 511) {
|
|
err = "Password too long.";
|
|
goto err;
|
|
}
|
|
|
|
if ((n = rfbReadExact(cl, buf, ulen)) <= 0) {
|
|
err = n ? "read failed" : "client gone";
|
|
goto err;
|
|
}
|
|
buf[ulen] = 0;
|
|
char *username = buf;
|
|
char *passwd = buf + ulen + 1;
|
|
if ((n = rfbReadExact(cl, passwd, pwlen)) <= 0) {
|
|
err = n ? "read failed" : "client gone";
|
|
goto err;
|
|
}
|
|
passwd[pwlen] = 0;
|
|
|
|
rfbLog("VencryptPlain: username: %s pw: %s\n", username, passwd);
|
|
|
|
if (pve_auth_verify(clientip, username, passwd) == 0) {
|
|
rfbEncodeU32(buf, 0); /* Accept auth completion */
|
|
rfbWriteExact(cl, buf, 4);
|
|
cl->state = RFB_INITIALISATION;
|
|
return;
|
|
}
|
|
|
|
err = "Authentication failed";
|
|
err:
|
|
rfbLog("VencryptPlain: %s\n", err ? err : "no reason specified");
|
|
if (err) {
|
|
rfbEncodeU32(buf, 1); /* Reject auth */
|
|
rfbWriteExact(cl, buf, 4);
|
|
if (cl->protocolMinorVersion >= 8) {
|
|
int elen = strlen(err);
|
|
rfbEncodeU32(buf, elen);
|
|
rfbWriteExact(cl, buf, 4);
|
|
rfbWriteExact(cl, err, elen);
|
|
}
|
|
}
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
rfbVncAuthVencrypt(rfbClientPtr cl)
|
|
{
|
|
int ret;
|
|
|
|
/* Send VeNCrypt version 0.2 */
|
|
char buf[256];
|
|
buf[0] = 0;
|
|
buf[1] = 2;
|
|
|
|
if (rfbWriteExact(cl, buf, 2) < 0) {
|
|
rfbLogPerror("rfbVncAuthVencrypt: write");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
int n = rfbReadExact(cl, buf, 2);
|
|
if (n <= 0) {
|
|
if (n == 0)
|
|
rfbLog("rfbVncAuthVencrypt: client gone\n");
|
|
else
|
|
rfbLogPerror("rfbVncAuthVencrypt: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if (buf[0] != 0 || buf[1] != 2) {
|
|
rfbLog("Unsupported VeNCrypt protocol %d.%d\n",
|
|
(int)buf[0], (int)buf[1]);
|
|
buf[0] = 1; /* Reject version */
|
|
rfbWriteExact(cl, buf, 1);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
/* Sending allowed auth */
|
|
int req_auth = use_x509 ? rfbVencryptX509Plain : rfbVencryptTlsPlain;
|
|
|
|
buf[0] = 0; /* Accept version */
|
|
buf[1] = 1; /* number of sub auths */
|
|
rfbEncodeU32(buf+2, req_auth);
|
|
if (rfbWriteExact(cl, buf, 6) < 0) {
|
|
rfbLogPerror("rfbVncAuthVencrypt: write");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
n = rfbReadExact(cl, buf, 4);
|
|
if (n <= 0) {
|
|
if (n == 0)
|
|
rfbLog("rfbVncAuthVencrypt: client gone\n");
|
|
else
|
|
rfbLogPerror("rfbVncAuthVencrypt: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
int auth = rfbDecodeU32(buf, 0);
|
|
if (auth != req_auth) {
|
|
buf[0] = 1; /* Reject auth*/
|
|
rfbWriteExact(cl, buf, 1);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
buf[0] = 1; /* Accept auth */
|
|
if (rfbWriteExact(cl, buf, 1) < 0) {
|
|
rfbLogPerror("rfbVncAuthVencrypt: write");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
tls_client_t *sd = calloc(1, sizeof(tls_client_t));
|
|
|
|
if (sd->session == NULL) {
|
|
if (gnutls_init(&sd->session, GNUTLS_SERVER) < 0) {
|
|
rfbLog("gnutls_init failed\n");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
|
|
}
|
|
|
|
if ((ret = gnutls_set_default_priority(sd->session)) < 0) {
|
|
rfbLog("gnutls_set_default_priority failed: %s\n", gnutls_strerror(ret));
|
|
sd->session = NULL;
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
static const char *priority_str_x509 = "NORMAL";
|
|
static const char *priority_str_anon = "NORMAL:+ANON-ECDH:+ANON-DH";
|
|
if ((ret = gnutls_priority_set_direct(sd->session, use_x509 ? priority_str_x509 : priority_str_anon, NULL)) < 0) {
|
|
rfbLog("gnutls_priority_set_direct failed: %s\n", gnutls_strerror(ret));
|
|
sd->session = NULL;
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if (use_x509) {
|
|
gnutls_certificate_server_credentials x509_cred;
|
|
|
|
if (!(x509_cred = tls_initialize_x509_cred())) {
|
|
sd->session = NULL;
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if (gnutls_credentials_set(sd->session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
|
|
sd->session = NULL;
|
|
gnutls_certificate_free_credentials(x509_cred);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
gnutls_anon_server_credentials anon_cred;
|
|
|
|
if (!(anon_cred = tls_initialize_anon_cred())) {
|
|
sd->session = NULL;
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if ((ret = gnutls_credentials_set(sd->session, GNUTLS_CRD_ANON, anon_cred)) < 0) {
|
|
rfbLog("gnutls_credentials_set failed: %s\n", gnutls_strerror(ret));
|
|
gnutls_anon_free_server_credentials(anon_cred);
|
|
sd->session = NULL;
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
gnutls_transport_set_ptr(sd->session, (gnutls_transport_ptr_t)cl);
|
|
gnutls_transport_set_push_function(sd->session, vnc_tls_push);
|
|
gnutls_transport_set_pull_function(sd->session, vnc_tls_pull);
|
|
}
|
|
|
|
|
|
retry:
|
|
if ((ret = gnutls_handshake(sd->session)) < 0) {
|
|
if (!gnutls_error_is_fatal(ret)) {
|
|
usleep(100000);
|
|
goto retry;
|
|
}
|
|
rfbLog("rfbVncAuthVencrypt: handshake failed\n");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
/* set up TLS read/write hooks */
|
|
cl->clientData = sd;
|
|
cl->sock_read_fn = &vnc_tls_read;
|
|
cl->sock_write_fn = &vnc_tls_write;
|
|
|
|
vencrypt_subauth_plain(cl);
|
|
}
|
|
|
|
static rfbSecurityHandler VncSecurityHandlerVencrypt = {
|
|
rfbSecTypeVencrypt,
|
|
rfbVncAuthVencrypt,
|
|
NULL
|
|
};
|
|
|
|
#define TERM "xterm"
|
|
|
|
#define TERMIDCODE "[?1;2c" // vt100 ID
|
|
|
|
#define CHECK_ARGC(argc,argv,i) if (i >= argc-1) { \
|
|
fprintf (stderr, "ERROR: not enough arguments for: %s\n", argv[i]); \
|
|
print_usage (NULL); \
|
|
exit(1); \
|
|
}
|
|
|
|
/* these colours are from linux kernel drivers/char/vt.c */
|
|
|
|
static int idle_timeout = 1;
|
|
|
|
unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7,
|
|
8,12,10,14, 9,13,11,15 };
|
|
|
|
/* the default colour table, for VGA+ colour systems */
|
|
int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
|
|
0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
|
|
int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
|
|
0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
|
|
int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
|
|
0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
|
|
|
|
static void
|
|
print_usage (const char *msg)
|
|
{
|
|
if (msg) { fprintf (stderr, "ERROR: %s\n", msg); }
|
|
fprintf (stderr, "USAGE: vncterm [vncopts] [-c command [args]]\n");
|
|
}
|
|
|
|
/* Convert UCS2 to UTF8 sequence, trailing zero */
|
|
static int
|
|
ucs2_to_utf8 (unicode 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
|
|
rfb_draw_char (rfbScreenInfoPtr rfbScreen, int x, int y,
|
|
unicode c, rfbPixel col, short width)
|
|
{
|
|
int i,j;
|
|
unsigned char *data= fontdata + c*(GLYPHLINES*2);
|
|
unsigned char d=*data;
|
|
int rowstride=rfbScreen->paddedWidthInBytes;
|
|
char *colour=(char*)&col;
|
|
|
|
for(j = 0; j < GLYPHLINES; j++) {
|
|
for(i = 0; i < 8*width; i++) {
|
|
if ((i&7) == 0) {
|
|
d=*data;
|
|
data++;
|
|
}
|
|
if (d&0x80)
|
|
*(rfbScreen->frameBuffer+(y+j)*rowstride+(x+i)) = *colour;
|
|
d<<=1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
draw_char_at (vncTerm *vt, int x, int y, unicode ch, TextAttributes attrib, short width, unicode combiningglyph)
|
|
{
|
|
if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; }
|
|
|
|
// non printable character
|
|
if (width < 1) return;
|
|
|
|
int rx = x*8;
|
|
int ry = y*16;
|
|
int rxe = x*8+8*width;
|
|
int rye = y*16+16;
|
|
|
|
int fg, bg;
|
|
|
|
if (attrib.invers) {
|
|
bg = attrib.fgcol;
|
|
fg = attrib.bgcol;
|
|
} else {
|
|
bg = attrib.bgcol;
|
|
fg = attrib.fgcol;
|
|
}
|
|
|
|
rfbFillRect (vt->screen, rx, ry, rxe, rye, bg);
|
|
|
|
if (attrib.bold) {
|
|
fg += 8;
|
|
}
|
|
|
|
// unsuported attributes = (attrib.blink || attrib.unvisible)
|
|
|
|
rfb_draw_char (vt->screen, rx, ry, ch, fg, width);
|
|
|
|
if (combiningglyph) {
|
|
rfb_draw_char (vt->screen, rx, ry, combiningglyph, fg, 1);
|
|
}
|
|
|
|
if (attrib.uline) {
|
|
rfbDrawLine (vt->screen, rx, ry + 14, rxe, ry + 14, fg);
|
|
}
|
|
|
|
rfbMarkRectAsModified (vt->screen, rx, ry, rxe, rye);
|
|
|
|
}
|
|
|
|
static void
|
|
vncterm_update_xy (vncTerm *vt, int x, int y)
|
|
{
|
|
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];
|
|
draw_char_at (vt, x, y2, c->ch, c->attrib, c->width, c->combiningglyph);
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_clear_xy (vncTerm *vt, int x, int y)
|
|
{
|
|
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->ch = ' ';
|
|
c->attrib = vt->default_attrib;
|
|
c->attrib.fgcol = vt->cur_attrib.fgcol;
|
|
c->attrib.bgcol = vt->cur_attrib.bgcol;
|
|
c->width = 1;
|
|
c->combiningglyph = 0;
|
|
|
|
draw_char_at (vt, x, y, c->ch, c->attrib, c->width, c->combiningglyph);
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_show_cursor (vncTerm *vt, int show)
|
|
{
|
|
int x = vt->cx;
|
|
if (x >= vt->width) {
|
|
x = vt->width - 1;
|
|
}
|
|
|
|
int y1 = (vt->y_base + vt->cy) % vt->total_height;
|
|
int y = y1 - vt->y_displ;
|
|
if (y < 0) {
|
|
y += vt->total_height;
|
|
}
|
|
|
|
if (y < vt->height) {
|
|
|
|
TextCell *c = &vt->cells[y1 * vt->width + x];
|
|
|
|
if (show) {
|
|
TextAttributes attrib = vt->default_attrib;
|
|
attrib.invers = !(attrib.invers); /* invert fg and bg */
|
|
draw_char_at (vt, x, y, c->ch, attrib, c->width, c->combiningglyph);
|
|
} else {
|
|
draw_char_at (vt, x, y, c->ch, c->attrib, c->width, c->combiningglyph);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_refresh (vncTerm *vt)
|
|
{
|
|
int x, y, y1;
|
|
|
|
rfbFillRect (vt->screen, 0, 0, vt->maxx, vt->maxy, vt->default_attrib.bgcol);
|
|
|
|
y1 = vt->y_displ;
|
|
for(y = 0; y < vt->height; y++) {
|
|
TextCell *c = vt->cells + y1 * vt->width;
|
|
for(x = 0; x < vt->width; x++) {
|
|
draw_char_at (vt, x, y, c->ch, c->attrib, c->width, c->combiningglyph);
|
|
c += c->width;
|
|
}
|
|
if (++y1 == vt->total_height)
|
|
y1 = 0;
|
|
}
|
|
rfbMarkRectAsModified (vt->screen, 0, 0, vt->maxx, vt->maxy);
|
|
|
|
vncterm_show_cursor (vt, 1);
|
|
}
|
|
|
|
static void
|
|
vncterm_scroll_down (vncTerm *vt, int top, int bottom, int lines)
|
|
{
|
|
if ((top + lines) >= bottom) {
|
|
lines = bottom - top -1;
|
|
}
|
|
|
|
if (top < 0 || bottom > vt->height || top >= bottom || lines < 1) {
|
|
return;
|
|
}
|
|
|
|
int h = lines * 16;
|
|
int y0 = top*16;
|
|
int y1 = y0 + h;
|
|
int y2 = bottom*16;
|
|
int rowstride = vt->screen->paddedWidthInBytes;
|
|
int rows = (bottom - top - lines)*16;
|
|
|
|
char *in = vt->screen->frameBuffer+y0*rowstride;
|
|
char *out = vt->screen->frameBuffer+y1*rowstride;
|
|
memmove(out,in, rowstride*rows);
|
|
|
|
memset(vt->screen->frameBuffer+y0*rowstride, 0, h*rowstride);
|
|
rfbMarkRectAsModified (vt->screen, 0, y0, vt->screen->width, y2);
|
|
|
|
int i;
|
|
for(i = bottom - top - lines - 1; i >= 0; i--) {
|
|
int src = ((vt->y_base + top + i) % vt->total_height)*vt->width;
|
|
int dst = ((vt->y_base + top + lines + i) % vt->total_height)*vt->width;
|
|
|
|
memmove(vt->cells + dst, vt->cells + src, vt->width*sizeof (TextCell));
|
|
}
|
|
|
|
for (i = 0; i < lines; i++) {
|
|
int j;
|
|
TextCell *c = vt->cells + ((vt->y_base + top + i) % vt->total_height)*vt->width;
|
|
for(j = 0; j < vt->width; j++) {
|
|
c->attrib = vt->default_attrib;
|
|
c->ch = ' ';
|
|
c->width = 1;
|
|
c->combiningglyph = 0;
|
|
c++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_scroll_up (vncTerm *vt, int top, int bottom, int lines, int moveattr)
|
|
{
|
|
if ((top + lines) >= bottom) {
|
|
lines = bottom - top - 1;
|
|
}
|
|
|
|
if (top < 0 || bottom > vt->height || top >= bottom || lines < 1) {
|
|
return;
|
|
}
|
|
|
|
int h = lines * 16;
|
|
int y0 = top*16;
|
|
int y1 = (top + lines)*16;
|
|
int y2 = bottom*16;
|
|
int rowstride = vt->screen->paddedWidthInBytes;
|
|
int rows = (bottom - top - lines)*16;
|
|
|
|
char *in = vt->screen->frameBuffer+y1*rowstride;
|
|
char *out = vt->screen->frameBuffer+y0*rowstride;
|
|
memmove(out,in, rowstride*rows);
|
|
|
|
memset(vt->screen->frameBuffer+(y2-h)*rowstride, 0, h*rowstride);
|
|
|
|
rfbMarkRectAsModified (vt->screen, 0, y0, vt->screen->width, y2);
|
|
|
|
if (!moveattr) return;
|
|
|
|
// move attributes
|
|
|
|
int i;
|
|
for(i = 0; i < (bottom - top - lines); i++) {
|
|
int dst = ((vt->y_base + top + i) % vt->total_height)*vt->width;
|
|
int src = ((vt->y_base + top + lines + i) % vt->total_height)*vt->width;
|
|
|
|
memmove(vt->cells + dst, vt->cells + src, vt->width*sizeof (TextCell));
|
|
}
|
|
|
|
for (i = 1; i <= lines; i++) {
|
|
int j;
|
|
TextCell *c = vt->cells + ((vt->y_base + bottom - i) % vt->total_height)*vt->width;
|
|
for(j = 0; j < vt->width; j++) {
|
|
c->attrib = vt->default_attrib;
|
|
c->ch = ' ';
|
|
c->width = 1;
|
|
c->combiningglyph = 0;
|
|
c++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_virtual_scroll (vncTerm *vt, int lines)
|
|
{
|
|
if (vt->altbuf || lines == 0) return;
|
|
|
|
if (lines < 0) {
|
|
lines = -lines;
|
|
int i = vt->scroll_height;
|
|
if (i > vt->total_height - vt->height)
|
|
i = vt->total_height - vt->height;
|
|
int y1 = vt->y_base - i;
|
|
if (y1 < 0)
|
|
y1 += vt->total_height;
|
|
for(i = 0; i < lines; i++) {
|
|
if (vt->y_displ == y1) break;
|
|
if (--vt->y_displ < 0) {
|
|
vt->y_displ = vt->total_height - 1;
|
|
}
|
|
}
|
|
} else {
|
|
int i;
|
|
for(i = 0; i < lines; i++) {
|
|
if (vt->y_displ == vt->y_base) break;
|
|
if (++vt->y_displ == vt->total_height) {
|
|
vt->y_displ = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
vncterm_refresh (vt);
|
|
}
|
|
static void
|
|
vncterm_respond_esc (vncTerm *vt, const char *esc)
|
|
{
|
|
int len = strlen (esc);
|
|
int i;
|
|
|
|
if (vt->ibuf_count < (IBUFSIZE - 1 - len)) {
|
|
vt->ibuf[vt->ibuf_count++] = 27;
|
|
for (i = 0; i < len; i++) {
|
|
vt->ibuf[vt->ibuf_count++] = esc[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_put_lf (vncTerm *vt)
|
|
{
|
|
if (vt->cy + 1 == vt->region_bottom) {
|
|
|
|
if (vt->altbuf || vt->region_top != 0 || vt->region_bottom != vt->height) {
|
|
vncterm_scroll_up (vt, vt->region_top, vt->region_bottom, 1, 1);
|
|
return;
|
|
}
|
|
|
|
if (vt->y_displ == vt->y_base) {
|
|
vncterm_scroll_up (vt, vt->region_top, vt->region_bottom, 1, 0);
|
|
}
|
|
|
|
if (vt->y_displ == vt->y_base) {
|
|
if (++vt->y_displ == vt->total_height) {
|
|
vt->y_displ = 0;
|
|
}
|
|
}
|
|
|
|
if (++vt->y_base == vt->total_height) {
|
|
vt->y_base = 0;
|
|
}
|
|
|
|
if (vt->scroll_height < vt->total_height) {
|
|
vt->scroll_height++;
|
|
}
|
|
|
|
int y1 = (vt->y_base + vt->height - 1) % vt->total_height;
|
|
TextCell *c = &vt->cells[y1 * vt->width];
|
|
int x;
|
|
for (x = 0; x < vt->width; x++) {
|
|
c->ch = ' ';
|
|
c->width = 1;
|
|
c->combiningglyph = 0;
|
|
c->attrib = vt->default_attrib;
|
|
c++;
|
|
}
|
|
|
|
// fprintf (stderr, "BASE: %d DISPLAY %d\n", vt->y_base, vt->y_displ);
|
|
|
|
} else if (vt->cy < vt->height - 1) {
|
|
vt->cy += 1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
vncterm_csi_m (vncTerm *vt)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < vt->esc_count; i++) {
|
|
switch (vt->esc_buf[i]) {
|
|
case 0: /* reset all console attributes to default */
|
|
vt->cur_attrib = vt->default_attrib;
|
|
break;
|
|
case 1:
|
|
vt->cur_attrib.bold = 1;
|
|
break;
|
|
case 4:
|
|
vt->cur_attrib.uline = 1;
|
|
break;
|
|
case 5:
|
|
vt->cur_attrib.blink = 1;
|
|
break;
|
|
case 7:
|
|
vt->cur_attrib.invers = 1;
|
|
break;
|
|
case 8:
|
|
vt->cur_attrib.unvisible = 1;
|
|
break;
|
|
case 10:
|
|
vt->cur_enc = LAT1_MAP;
|
|
// fixme: dispaly controls = 0 ?
|
|
// fixme: toggle meta = 0 ?
|
|
break;
|
|
case 11:
|
|
vt->cur_enc = IBMPC_MAP;
|
|
// fixme: dispaly controls = 1 ?
|
|
// fixme: toggle meta = 0 ?
|
|
break;
|
|
case 12:
|
|
vt->cur_enc = IBMPC_MAP;
|
|
// fixme: dispaly controls = 1 ?
|
|
// fixme: toggle meta = 1 ?
|
|
break;
|
|
case 22:
|
|
vt->cur_attrib.bold = 0;
|
|
break;
|
|
case 24:
|
|
vt->cur_attrib.uline = 0;
|
|
break;
|
|
case 25:
|
|
vt->cur_attrib.blink = 0;
|
|
break;
|
|
case 27:
|
|
vt->cur_attrib.invers = 0;
|
|
break;
|
|
case 28:
|
|
vt->cur_attrib.unvisible = 0;
|
|
break;
|
|
case 30:
|
|
case 31:
|
|
case 32:
|
|
case 33:
|
|
case 34:
|
|
case 35:
|
|
case 36:
|
|
case 37:
|
|
/* set foreground color */
|
|
vt->cur_attrib.fgcol = color_table [vt->esc_buf[i] - 30];
|
|
break;
|
|
case 38:
|
|
/* reset color to default, enable underline */
|
|
vt->cur_attrib.fgcol = vt->default_attrib.fgcol;
|
|
vt->cur_attrib.uline = 1;
|
|
break;
|
|
case 39:
|
|
/* reset color to default, disable underline */
|
|
vt->cur_attrib.fgcol = vt->default_attrib.fgcol;
|
|
vt->cur_attrib.uline = 0;
|
|
break;
|
|
case 40:
|
|
case 41:
|
|
case 42:
|
|
case 43:
|
|
case 44:
|
|
case 45:
|
|
case 46:
|
|
case 47:
|
|
/* set background color */
|
|
vt->cur_attrib.bgcol = color_table [vt->esc_buf[i] - 40];
|
|
break;
|
|
case 49:
|
|
/* reset background color */
|
|
vt->cur_attrib.bgcol = vt->default_attrib.bgcol;
|
|
break;
|
|
default:
|
|
fprintf (stderr, "unhandled ESC[%d m code\n",vt->esc_buf[i]);
|
|
//fixme: implement
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_save_cursor (vncTerm *vt)
|
|
{
|
|
vt->cx_saved = vt->cx;
|
|
vt->cy_saved = vt->cy;
|
|
vt->cur_attrib_saved = vt->cur_attrib;
|
|
vt->charset_saved = vt->charset;
|
|
vt->g0enc_saved = vt->g0enc;
|
|
vt->g1enc_saved = vt->g1enc;
|
|
vt->cur_enc_saved = vt->cur_enc;
|
|
}
|
|
|
|
static void
|
|
vncterm_restore_cursor (vncTerm *vt)
|
|
{
|
|
vt->cx = vt->cx_saved;
|
|
vt->cy = vt->cy_saved;
|
|
vt->cur_attrib = vt->cur_attrib_saved;
|
|
vt->charset = vt->charset_saved;
|
|
vt->g0enc = vt->g0enc_saved;
|
|
vt->g1enc = vt->g1enc_saved;
|
|
vt->cur_enc = vt->cur_enc_saved;
|
|
}
|
|
|
|
static void
|
|
vncterm_set_alternate_buffer (vncTerm *vt, int on_off)
|
|
{
|
|
int x, y;
|
|
|
|
vt->y_displ = vt->y_base;
|
|
|
|
if (on_off) {
|
|
|
|
if (vt->altbuf) return;
|
|
|
|
vt->altbuf = 1;
|
|
|
|
/* alternate buffer & cursor */
|
|
|
|
vncterm_save_cursor (vt);
|
|
/* save screen to altcels */
|
|
for (y = 0; y < vt->height; y++) {
|
|
int y1 = (vt->y_base + y) % vt->total_height;
|
|
for (x = 0; x < vt->width; x++) {
|
|
vt->altcells[y*vt->width + x] = vt->cells[y1*vt->width + x];
|
|
}
|
|
}
|
|
|
|
/* clear screen */
|
|
for (y = 0; y <= vt->height; y++) {
|
|
for (x = 0; x < vt->width; x++) {
|
|
vncterm_clear_xy (vt, x, y);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (vt->altbuf == 0) return;
|
|
|
|
vt->altbuf = 0;
|
|
|
|
/* restore saved data */
|
|
for (y = 0; y < vt->height; y++) {
|
|
int y1 = (vt->y_base + y) % vt->total_height;
|
|
for (x = 0; x < vt->width; x++) {
|
|
vt->cells[y1*vt->width + x] = vt->altcells[y*vt->width + x];
|
|
}
|
|
}
|
|
|
|
vncterm_restore_cursor (vt);
|
|
}
|
|
|
|
vncterm_refresh (vt);
|
|
}
|
|
|
|
static void
|
|
vncterm_set_mode (vncTerm *vt, int on_off)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i <= vt->esc_count; i++) {
|
|
if (vt->esc_ques) { /* DEC private modes set/reset */
|
|
switch(vt->esc_buf[i]) {
|
|
case 10: /* X11 mouse reporting on/off */
|
|
case 1000:
|
|
vt->report_mouse = on_off;
|
|
break;
|
|
case 1049: /* start/end special app mode (smcup/rmcup) */
|
|
vncterm_set_alternate_buffer (vt, on_off);
|
|
break;
|
|
case 25: /* Cursor 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 */
|
|
/* fixme: implement me */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
vncterm_gotoxy (vncTerm *vt, int x, int y)
|
|
{
|
|
/* verify all boundaries */
|
|
|
|
if (x < 0) {
|
|
x = 0;
|
|
} else if (x >= vt->width) {
|
|
x = vt->width - 1;
|
|
}
|
|
|
|
vt->cx = x;
|
|
|
|
if (y < 0) {
|
|
y = 0;
|
|
} else if (y >= vt->height) {
|
|
y = vt->height - 1;
|
|
}
|
|
|
|
vt->cy = y;
|
|
}
|
|
|
|
enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
|
|
EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
|
|
ESpalette, ESidquery, ESosc1, ESosc2};
|
|
|
|
static void
|
|
vncterm_putchar (vncTerm *vt, unicode ch)
|
|
{
|
|
int x, y, i, c;
|
|
|
|
#ifdef DEBUG
|
|
if (!vt->tty_state)
|
|
fprintf (stderr, "CHAR:%2d: %4x '%c' (cur_enc %d) %d %d\n", vt->tty_state, ch, ch, vt->cur_enc, vt->cx, vt->cy);
|
|
#endif
|
|
|
|
switch(vt->tty_state) {
|
|
case ESesc:
|
|
vt->tty_state = ESnormal;
|
|
switch (ch) {
|
|
case '[':
|
|
vt->tty_state = ESsquare;
|
|
break;
|
|
case ']':
|
|
vt->tty_state = ESnonstd;
|
|
break;
|
|
case '%':
|
|
vt->tty_state = ESpercent;
|
|
break;
|
|
case '7':
|
|
vncterm_save_cursor (vt);
|
|
break;
|
|
case '8':
|
|
vncterm_restore_cursor (vt);
|
|
break;
|
|
case '(':
|
|
vt->tty_state = ESsetG0; // SET G0
|
|
break;
|
|
case ')':
|
|
vt->tty_state = ESsetG1; // SET G1
|
|
break;
|
|
case 'M':
|
|
/* cursor up (ri) */
|
|
if (vt->cy == vt->region_top)
|
|
vncterm_scroll_down (vt, vt->region_top, vt->region_bottom, 1);
|
|
else if (vt->cy > 0) {
|
|
vt->cy--;
|
|
}
|
|
break;
|
|
case '>':
|
|
/* numeric keypad - ignored */
|
|
break;
|
|
case '=':
|
|
/* appl. keypad - ignored */
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "got unhandled ESC%c %d\n", ch, ch);
|
|
#endif
|
|
break;
|
|
}
|
|
break;
|
|
case ESnonstd: /* Operating System Controls */
|
|
vt->tty_state = ESnormal;
|
|
|
|
switch (ch) {
|
|
case 'P': /* palette escape sequence */
|
|
for(i = 0; i < MAX_ESC_PARAMS; i++) {
|
|
vt->esc_buf[i] = 0;
|
|
}
|
|
|
|
vt->esc_count = 0;
|
|
vt->tty_state = ESpalette;
|
|
break;
|
|
case 'R': /* reset palette */
|
|
// fixme: reset_palette(vc);
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '4':
|
|
vt->osc_cmd = ch;
|
|
vt->osc_textbuf[0] = 0;
|
|
vt->tty_state = ESosc1;
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "unhandled OSC %c\n", ch);
|
|
#endif
|
|
vt->tty_state = ESnormal;
|
|
break;
|
|
}
|
|
break;
|
|
case ESosc1:
|
|
vt->tty_state = ESnormal;
|
|
if (ch == ';') {
|
|
vt->tty_state = ESosc2;
|
|
} else {
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "got illegal OSC sequence\n");
|
|
#endif
|
|
}
|
|
break;
|
|
case ESosc2:
|
|
if (ch != 0x9c && ch != 7) {
|
|
int i = 0;
|
|
while (vt->osc_textbuf[i]) i++;
|
|
vt->osc_textbuf[i++] = ch;
|
|
vt->osc_textbuf[i] = 0;
|
|
} else {
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "OSC:%c:%s\n", vt->osc_cmd, vt->osc_textbuf);
|
|
#endif
|
|
vt->tty_state = ESnormal;
|
|
}
|
|
break;
|
|
case ESpalette:
|
|
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
|
|
|| (ch >= 'a' && ch <= 'f')) {
|
|
vt->esc_buf[vt->esc_count++] = (ch > '9' ? (ch & 0xDF) - 'A' + 10 : ch - '0');
|
|
if (vt->esc_count == 7) {
|
|
// fixme: this does not work - please test
|
|
rfbColourMap *cmap =&vt->screen->colourMap;
|
|
|
|
int i = color_table[vt->esc_buf[0]] * 3, j = 1;
|
|
cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
|
|
cmap->data.bytes[i++] += vt->esc_buf[j++];
|
|
cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
|
|
cmap->data.bytes[i++] += vt->esc_buf[j++];
|
|
cmap->data.bytes[i] = 16 * vt->esc_buf[j++];
|
|
cmap->data.bytes[i] += vt->esc_buf[j];
|
|
|
|
//set_palette(vc); ?
|
|
|
|
vt->tty_state = ESnormal;
|
|
}
|
|
} else
|
|
vt->tty_state = ESnormal;
|
|
break;
|
|
case ESsquare:
|
|
for(i = 0; i < MAX_ESC_PARAMS; i++) {
|
|
vt->esc_buf[i] = 0;
|
|
}
|
|
|
|
vt->esc_count = 0;
|
|
vt->esc_has_par = 0;
|
|
vt->tty_state = ESgetpars;
|
|
|
|
if (ch == '>') {
|
|
vt->tty_state = ESidquery;
|
|
break;
|
|
}
|
|
|
|
if ((vt->esc_ques = (ch == '?'))) {
|
|
break;
|
|
}
|
|
case ESgetpars:
|
|
if (ch >= '0' && ch <= '9') {
|
|
vt->esc_has_par = 1;
|
|
if (vt->esc_count < MAX_ESC_PARAMS) {
|
|
vt->esc_buf[vt->esc_count] = vt->esc_buf[vt->esc_count] * 10 + ch - '0';
|
|
}
|
|
break;
|
|
} else if (ch == ';') {
|
|
vt->esc_has_par = 1;
|
|
vt->esc_count++;
|
|
break;
|
|
} else {
|
|
if (vt->esc_has_par) {
|
|
vt->esc_count++;
|
|
}
|
|
vt->tty_state = ESgotpars;
|
|
}
|
|
case ESgotpars:
|
|
|
|
vt->tty_state = ESnormal;
|
|
|
|
#ifdef DEBUG
|
|
char *qes = vt->esc_ques ? "?" : "";
|
|
if (vt->esc_count == 0) {
|
|
fprintf(stderr, "ESC[%s%c\n", qes, ch);
|
|
} else if (vt->esc_count == 1) {
|
|
fprintf(stderr, "ESC[%s%d%c\n", qes, vt->esc_buf[0], ch);
|
|
} else {
|
|
int i;
|
|
fprintf(stderr, "ESC[%s%d", qes, vt->esc_buf[0]);
|
|
for (i = 1; i < vt->esc_count; i++) {
|
|
fprintf(stderr, ";%d", vt->esc_buf[i]);
|
|
}
|
|
fprintf (stderr, "%c\n", ch);
|
|
}
|
|
#endif
|
|
|
|
switch (ch) {
|
|
case 'h':
|
|
vncterm_set_mode (vt, 1);
|
|
break;
|
|
case 'l':
|
|
vncterm_set_mode (vt, 0);
|
|
break;
|
|
case 'm':
|
|
if (!vt->esc_count) {
|
|
vt->esc_count++; // default parameter 0
|
|
}
|
|
vncterm_csi_m (vt);
|
|
break;
|
|
case 'n':
|
|
/* report cursor position */
|
|
/* TODO: send ESC[row;colR */
|
|
break;
|
|
case 'A':
|
|
/* move cursor up */
|
|
if (vt->esc_buf[0] == 0) {
|
|
vt->esc_buf[0] = 1;
|
|
}
|
|
vncterm_gotoxy (vt, vt->cx, vt->cy - vt->esc_buf[0]);
|
|
break;
|
|
case 'B':
|
|
case 'e':
|
|
/* move cursor down */
|
|
if (vt->esc_buf[0] == 0) {
|
|
vt->esc_buf[0] = 1;
|
|
}
|
|
vncterm_gotoxy (vt, vt->cx, vt->cy + vt->esc_buf[0]);
|
|
break;
|
|
case 'C':
|
|
case 'a':
|
|
/* move cursor right */
|
|
if (vt->esc_buf[0] == 0) {
|
|
vt->esc_buf[0] = 1;
|
|
}
|
|
vncterm_gotoxy (vt, vt->cx + vt->esc_buf[0], vt->cy);
|
|
break;
|
|
case 'D':
|
|
/* move cursor left */
|
|
if (vt->esc_buf[0] == 0) {
|
|
vt->esc_buf[0] = 1;
|
|
}
|
|
vncterm_gotoxy (vt, vt->cx - vt->esc_buf[0], vt->cy);
|
|
break;
|
|
case 'G':
|
|
case '`':
|
|
/* move cursor to column */
|
|
vncterm_gotoxy (vt, vt->esc_buf[0] - 1, vt->cy);
|
|
break;
|
|
case 'd':
|
|
/* move cursor to row */
|
|
vncterm_gotoxy (vt, vt->cx , vt->esc_buf[0] - 1);
|
|
break;
|
|
case 'f':
|
|
case 'H':
|
|
/* move cursor to row, column */
|
|
vncterm_gotoxy (vt, vt->esc_buf[1] - 1, vt->esc_buf[0] - 1);
|
|
break;
|
|
case 'J':
|
|
switch (vt->esc_buf[0]) {
|
|
case 0:
|
|
/* clear to end of screen */
|
|
for (y = vt->cy; y < vt->height; y++) {
|
|
for (x = 0; x < vt->width; x++) {
|
|
if (y == vt->cy && x < vt->cx) {
|
|
continue;
|
|
}
|
|
vncterm_clear_xy (vt, x, y);
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
/* clear from beginning of screen */
|
|
for (y = 0; y <= vt->cy; y++) {
|
|
for (x = 0; x < vt->width; x++) {
|
|
if (y == vt->cy && x > vt->cx) {
|
|
break;
|
|
}
|
|
vncterm_clear_xy (vt, x, y);
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
/* clear entire screen */
|
|
for (y = 0; y <= vt->height; y++) {
|
|
for (x = 0; x < vt->width; x++) {
|
|
vncterm_clear_xy (vt, x, y);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 'K':
|
|
switch (vt->esc_buf[0]) {
|
|
case 0:
|
|
/* clear to eol */
|
|
for(x = vt->cx; x < vt->width; x++) {
|
|
vncterm_clear_xy (vt, x, vt->cy);
|
|
}
|
|
break;
|
|
case 1:
|
|
/* clear from beginning of line */
|
|
for (x = 0; x <= vt->cx; x++) {
|
|
vncterm_clear_xy (vt, x, vt->cy);
|
|
}
|
|
break;
|
|
case 2:
|
|
/* clear entire line */
|
|
for(x = 0; x < vt->width; x++) {
|
|
vncterm_clear_xy (vt, x, vt->cy);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 'L':
|
|
/* insert line */
|
|
c = vt->esc_buf[0];
|
|
|
|
if (c > vt->height - vt->cy)
|
|
c = vt->height - vt->cy;
|
|
else if (!c)
|
|
c = 1;
|
|
|
|
vncterm_scroll_down (vt, vt->cy, vt->region_bottom, c);
|
|
break;
|
|
case 'M':
|
|
/* delete line */
|
|
c = vt->esc_buf[0];
|
|
|
|
if (c > vt->height - vt->cy)
|
|
c = vt->height - vt->cy;
|
|
else if (!c)
|
|
c = 1;
|
|
|
|
vncterm_scroll_up (vt, vt->cy, vt->region_bottom, c, 1);
|
|
break;
|
|
case 'T':
|
|
/* scroll down */
|
|
c = vt->esc_buf[0];
|
|
if (!c) c = 1;
|
|
vncterm_scroll_down (vt, vt->region_top, vt->region_bottom, c);
|
|
break;
|
|
case 'S':
|
|
/* scroll up */
|
|
c = vt->esc_buf[0];
|
|
if (!c) c = 1;
|
|
vncterm_scroll_up (vt, vt->region_top, vt->region_bottom, c, 1);
|
|
break;
|
|
case 'P':
|
|
/* delete c character */
|
|
c = vt->esc_buf[0];
|
|
|
|
if (c > vt->width - vt->cx)
|
|
c = vt->width - vt->cx;
|
|
else if (!c)
|
|
c = 1;
|
|
|
|
for (x = vt->cx; x < vt->width - c; x++) {
|
|
int y1 = (vt->y_base + vt->cy) % vt->total_height;
|
|
TextCell *dst = &vt->cells[y1 * vt->width + x];
|
|
TextCell *src = dst + c;
|
|
*dst = *src;
|
|
vncterm_update_xy (vt, x + c, vt->cy);
|
|
src->ch = ' ';
|
|
src->width = 1;
|
|
src->combiningglyph = 0;
|
|
src->attrib = vt->default_attrib;
|
|
vncterm_update_xy (vt, x, vt->cy);
|
|
}
|
|
break;
|
|
case 's':
|
|
/* save cursor position */
|
|
vncterm_save_cursor (vt);
|
|
break;
|
|
case 'u':
|
|
/* restore cursor position */
|
|
vncterm_restore_cursor (vt);
|
|
break;
|
|
case 'X':
|
|
/* erase c characters */
|
|
c = vt->esc_buf[0];
|
|
if (!c) c = 1;
|
|
|
|
if (c > (vt->width - vt->cx)) c = vt->width - vt->cx;
|
|
|
|
for(i = 0; i < c; i++) {
|
|
vncterm_clear_xy (vt, vt->cx + i, vt->cy);
|
|
}
|
|
break;
|
|
case '@':
|
|
/* insert c character */
|
|
c = vt->esc_buf[0];
|
|
if (c > (vt->width - vt->cx)) {
|
|
c = vt->width - vt->cx;
|
|
}
|
|
if (!c) c = 1;
|
|
|
|
for (x = vt->width - c; x >= vt->cx; x--) {
|
|
int y1 = (vt->y_base + vt->cy) % vt->total_height;
|
|
TextCell *src = &vt->cells[y1 * vt->width + x];
|
|
TextCell *dst = src + c;
|
|
*dst = *src;
|
|
vncterm_update_xy (vt, x + c, vt->cy);
|
|
src->ch = ' ';
|
|
src->width = 1;
|
|
src->combiningglyph = 0;
|
|
src->attrib = vt->cur_attrib;
|
|
vncterm_update_xy (vt, x, vt->cy);
|
|
}
|
|
|
|
break;
|
|
case 'r':
|
|
/* set region */
|
|
if (!vt->esc_buf[0])
|
|
vt->esc_buf[0]++;
|
|
if (!vt->esc_buf[1])
|
|
vt->esc_buf[1] = vt->height;
|
|
/* Minimum allowed region is 2 lines */
|
|
if (vt->esc_buf[0] < vt->esc_buf[1] &&
|
|
vt->esc_buf[1] <= vt->height) {
|
|
vt->region_top = vt->esc_buf[0] - 1;
|
|
vt->region_bottom = vt->esc_buf[1];
|
|
vt->cx = 0;
|
|
vt->cy = vt->region_top;
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "set region %d %d\n", vt->region_top, vt->region_bottom);
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
if (vt->esc_count == 0) {
|
|
fprintf(stderr, "unhandled escape ESC[%s%c\n", qes, ch);
|
|
} else if (vt->esc_count == 1) {
|
|
fprintf(stderr, "unhandled escape ESC[%s%d%c\n", qes, vt->esc_buf[0], ch);
|
|
} else {
|
|
int i;
|
|
fprintf(stderr, "unhandled escape ESC[%s%d", qes, vt->esc_buf[0]);
|
|
for (i = 1; i < vt->esc_count; i++) {
|
|
fprintf(stderr, ";%d", vt->esc_buf[i]);
|
|
}
|
|
fprintf (stderr, "%c\n", ch);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
vt->esc_ques = 0;
|
|
break;
|
|
case ESsetG0: // Set G0
|
|
vt->tty_state = ESnormal;
|
|
|
|
if (ch == '0')
|
|
vt->g0enc = GRAF_MAP;
|
|
else if (ch == 'B')
|
|
vt->g0enc = LAT1_MAP;
|
|
else if (ch == 'U')
|
|
vt->g0enc = IBMPC_MAP;
|
|
else if (ch == 'K')
|
|
vt->g0enc = USER_MAP;
|
|
|
|
if (vt->charset == 0)
|
|
vt->cur_enc = vt->g0enc;
|
|
|
|
break;
|
|
case ESsetG1: // Set G1
|
|
vt->tty_state = ESnormal;
|
|
|
|
if (ch == '0')
|
|
vt->g1enc = GRAF_MAP;
|
|
else if (ch == 'B')
|
|
vt->g1enc = LAT1_MAP;
|
|
else if (ch == 'U')
|
|
vt->g1enc = IBMPC_MAP;
|
|
else if (ch == 'K')
|
|
vt->g1enc = USER_MAP;
|
|
|
|
if (vt->charset == 1)
|
|
vt->cur_enc = vt->g1enc;
|
|
|
|
break;
|
|
case ESidquery: // vt100 query id
|
|
vt->tty_state = ESnormal;
|
|
|
|
if (ch == 'c') {
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "ESC[>c Query term ID\n");
|
|
#endif
|
|
vncterm_respond_esc (vt, TERMIDCODE);
|
|
}
|
|
break;
|
|
case ESpercent:
|
|
vt->tty_state = ESnormal;
|
|
switch (ch) {
|
|
case '@': /* defined in ISO 2022 */
|
|
vt->utf8 = 0;
|
|
break;
|
|
case 'G': /* prelim official escape code */
|
|
case '8': /* retained for compatibility */
|
|
vt->utf8 = 1;
|
|
break;
|
|
}
|
|
break;
|
|
default: // ESnormal
|
|
vt->tty_state = ESnormal;
|
|
|
|
switch(ch) {
|
|
case 0:
|
|
break;
|
|
case 7: /* alert aka. bell */
|
|
rfbSendBell(vt->screen);
|
|
break;
|
|
case 8: /* backspace */
|
|
if (vt->cx > 0)
|
|
vt->cx--;
|
|
break;
|
|
case 9: /* tabspace */
|
|
if (vt->cx + (8 - (vt->cx % 8)) > vt->width) {
|
|
vt->cx = 0;
|
|
vncterm_put_lf (vt);
|
|
} else {
|
|
vt->cx = vt->cx + (8 - (vt->cx % 8));
|
|
}
|
|
break;
|
|
case 10: /* LF,*/
|
|
case 11: /* VT */
|
|
case 12: /* FF */
|
|
vncterm_put_lf (vt);
|
|
break;
|
|
case 13: /* carriage return */
|
|
vt->cx = 0;
|
|
break;
|
|
case 14:
|
|
/* SI (shift in), select character set 1 */
|
|
vt->charset = 1;
|
|
vt->cur_enc = vt->g1enc;
|
|
/* fixme: display controls = 1 */
|
|
break;
|
|
case 15:
|
|
/* SO (shift out), select character set 0 */
|
|
vt->charset = 0;
|
|
vt->cur_enc = vt->g0enc;
|
|
/* fixme: display controls = 0 */
|
|
break;
|
|
case 27: /* esc */
|
|
vt->tty_state = ESesc;
|
|
break;
|
|
case 127: /* delete */
|
|
/* ignore */
|
|
break;
|
|
case 128+27: /* csi */
|
|
vt->tty_state = ESsquare;
|
|
break;
|
|
default:
|
|
if (vt->cx >= vt->width) {
|
|
/* line wrap */
|
|
vt->cx = 0;
|
|
vncterm_put_lf (vt);
|
|
}
|
|
|
|
int y1 = (vt->y_base + vt->cy) % vt->total_height;
|
|
int width = wcwidth(ch);
|
|
if (width > 0) {
|
|
// normal/wide character
|
|
TextCell *c = &vt->cells[y1*vt->width + vt->cx];
|
|
c->attrib = vt->cur_attrib;
|
|
c->ch = ch;
|
|
c->width = width;
|
|
c->combiningglyph = 0;
|
|
vncterm_update_xy (vt, vt->cx, vt->cy);
|
|
vt->cx += width;
|
|
} else if (width == 0) {
|
|
// combiningglyph
|
|
TextCell *c = &vt->cells[y1*vt->width + vt->cx - 1];
|
|
c->attrib = vt->cur_attrib;
|
|
c->combiningglyph = ch;
|
|
vncterm_update_xy (vt, vt->cx - 1, vt->cy);
|
|
} else {
|
|
// non printable character, so we do not save them
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
vncterm_puts (vncTerm *vt, const char *buf, int len)
|
|
{
|
|
unicode tc;
|
|
|
|
vncterm_show_cursor (vt, 0);
|
|
|
|
while (len) {
|
|
unsigned char c = *buf;
|
|
len--;
|
|
buf++;
|
|
|
|
if (vt->tty_state != ESnormal) {
|
|
// never translate escape sequence
|
|
tc = c;
|
|
} else if (vt->utf8 && !vt->cur_enc) {
|
|
|
|
if(c & 0x80) { // utf8 multi-byte sequence
|
|
|
|
if (vt->utf_count > 0 && (c & 0xc0) == 0x80) {
|
|
// inside UTF8 sequence
|
|
vt->utf_char = (vt->utf_char << 6) | (c & 0x3f);
|
|
vt->utf_count--;
|
|
if (vt->utf_count == 0) {
|
|
if (vt->utf_char <= USHRT_MAX) {
|
|
tc = vt->utf_char;
|
|
} else {
|
|
tc = 0;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
} else {
|
|
// first char of a UTF8 sequence
|
|
if ((c & 0xe0) == 0xc0) {
|
|
vt->utf_count = 1;
|
|
vt->utf_char = (c & 0x1f);
|
|
} else if ((c & 0xf0) == 0xe0) {
|
|
vt->utf_count = 2;
|
|
vt->utf_char = (c & 0x0f);
|
|
} else if ((c & 0xf8) == 0xf0) {
|
|
vt->utf_count = 3;
|
|
vt->utf_char = (c & 0x07);
|
|
} else if ((c & 0xfc) == 0xf8) {
|
|
vt->utf_count = 4;
|
|
vt->utf_char = (c & 0x03);
|
|
} else if ((c & 0xfe) == 0xfc) {
|
|
vt->utf_count = 5;
|
|
vt->utf_char = (c & 0x01);
|
|
} else
|
|
vt->utf_count = 0;
|
|
|
|
continue;
|
|
}
|
|
} else {
|
|
// utf8 single byte
|
|
tc = c;
|
|
vt->utf_count = 0;
|
|
}
|
|
|
|
} else {
|
|
// never translate controls
|
|
if (c >= 32 && c != 127 && c != (128+27)) {
|
|
tc = translations[vt->cur_enc][c & 0x0ff];
|
|
} else {
|
|
tc = c;
|
|
}
|
|
}
|
|
|
|
vncterm_putchar (vt, tc);
|
|
}
|
|
|
|
vncterm_show_cursor (vt, 1);
|
|
return len;
|
|
}
|
|
|
|
void
|
|
vncterm_kbd_event (rfbBool down, rfbKeySym keySym, rfbClientPtr cl)
|
|
{
|
|
vncTerm *vt =(vncTerm *)cl->screen->screenData;
|
|
static int control = 0;
|
|
static int shift = 0;
|
|
char *esc = NULL;
|
|
|
|
//fprintf (stderr, "KEYEVENT:%d: %08x\n", down == 0, keySym);fflush (stderr);
|
|
if (down) {
|
|
//fprintf (stderr, "KEYPRESS: %d\n", keySym);fflush (stderr);
|
|
|
|
if (keySym == XK_Shift_L || keySym == XK_Shift_R) {
|
|
shift = 1;
|
|
} if (keySym == XK_Control_L || keySym == XK_Control_R) {
|
|
control = 1;
|
|
} else if (vt->ibuf_count < (IBUFSIZE - 32)) {
|
|
|
|
if (control) {
|
|
if(keySym >= 'a' && keySym <= 'z')
|
|
keySym -= 'a' -1;
|
|
else if (keySym >= 'A' && keySym <= 'Z')
|
|
keySym -= 'A'-1;
|
|
else
|
|
keySym=0xffff;
|
|
} else {
|
|
switch (keySym) {
|
|
case XK_Escape:
|
|
keySym=27; break;
|
|
case XK_Return:
|
|
keySym='\r'; break;
|
|
case XK_BackSpace:
|
|
keySym=8; break;
|
|
case XK_Tab:
|
|
keySym='\t'; break;
|
|
case XK_Delete: /* kdch1 */
|
|
case XK_KP_Delete:
|
|
esc = "[3~";break;
|
|
case XK_Home: /* khome */
|
|
case XK_KP_Home:
|
|
esc = "OH";break;
|
|
case XK_End:
|
|
case XK_KP_End: /* kend */
|
|
esc = "OF";break;
|
|
case XK_Insert: /* kich1 */
|
|
case XK_KP_Insert:
|
|
esc = "[2~";break;
|
|
case XK_Up:
|
|
case XK_KP_Up: /* kcuu1 */
|
|
esc = "OA";break;
|
|
case XK_Down: /* kcud1 */
|
|
case XK_KP_Down:
|
|
esc = "OB";break;
|
|
case XK_Right:
|
|
case XK_KP_Right: /* kcuf1 */
|
|
esc = "OC";break;
|
|
case XK_Left:
|
|
case XK_KP_Left: /* kcub1 */
|
|
esc = "OD";break;
|
|
case XK_Page_Up:
|
|
if (shift) {
|
|
vncterm_virtual_scroll (vt, -vt->height/2);
|
|
return;
|
|
}
|
|
esc = "[5~";break;
|
|
case XK_Page_Down:
|
|
if (shift) {
|
|
vncterm_virtual_scroll (vt, vt->height/2);
|
|
return;
|
|
}
|
|
esc = "[6~";break;
|
|
case XK_F1:
|
|
esc = "OP";break;
|
|
case XK_F2:
|
|
esc = "OQ";break;
|
|
case XK_F3:
|
|
esc = "OR";break;
|
|
case XK_F4:
|
|
esc = "OS";break;
|
|
case XK_F5:
|
|
esc = "[15~";break;
|
|
case XK_F6:
|
|
esc = "[17~";break;
|
|
case XK_F7:
|
|
esc = "[18~";break;
|
|
case XK_F8:
|
|
esc = "[19~";break;
|
|
case XK_F9:
|
|
esc = "[20~";break;
|
|
case XK_F10:
|
|
esc = "[21~";break;
|
|
case XK_F11:
|
|
esc = "[23~";break;
|
|
case XK_F12:
|
|
esc = "[24~";break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "KEYPRESS OUT:%s: %d\n", esc, keySym); fflush (stderr);
|
|
#endif
|
|
|
|
if (vt->y_displ != vt->y_base) {
|
|
vt->y_displ = vt->y_base;
|
|
vncterm_refresh (vt);
|
|
}
|
|
|
|
if (esc) {
|
|
vncterm_respond_esc (vt, esc);
|
|
} else if(keySym<0x100) {
|
|
if (vt->utf8) {
|
|
int len = ucs2_to_utf8 (keySym & 0x0fff, &vt->ibuf[vt->ibuf_count]);
|
|
vt->ibuf_count += len;
|
|
} else {
|
|
vt->ibuf[vt->ibuf_count++] = (char)keySym;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (keySym == XK_Shift_L || keySym == XK_Shift_R) {
|
|
shift = 0;
|
|
} else if (keySym == XK_Control_L || keySym == XK_Control_R) {
|
|
control = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vncterm_set_xcut_text (char* str, int len, struct _rfbClientRec* cl)
|
|
{
|
|
vncTerm *vt =(vncTerm *)cl->screen->screenData;
|
|
|
|
// seems str is Latin-1 encoded
|
|
if (vt->selection) free (vt->selection);
|
|
vt->selection = (unicode *)malloc (len*sizeof (unicode));
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
vt->selection[i] = str[i] & 0xff;
|
|
}
|
|
vt->selection_len = len;
|
|
}
|
|
|
|
static void
|
|
mouse_report (vncTerm *vt, int butt, int mrx, int mry)
|
|
{
|
|
char buf[8];
|
|
|
|
sprintf (buf, "[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
|
|
(char)('!' + mry));
|
|
|
|
vncterm_respond_esc (vt, buf);
|
|
}
|
|
|
|
void
|
|
vncterm_toggle_marked_cell (vncTerm *vt, int pos)
|
|
{
|
|
int x= (pos%vt->width)*8;
|
|
int y= (pos/vt->width)*16;
|
|
|
|
int i,j;
|
|
rfbScreenInfoPtr s=vt->screen;
|
|
|
|
char *b = s->frameBuffer+y*s->width+x;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vncterm_pointer_event (int buttonMask, int x, int y, rfbClientPtr cl)
|
|
{
|
|
vncTerm *vt =(vncTerm *)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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
vncterm_refresh (vt);
|
|
}
|
|
}
|
|
button2_released = 0;
|
|
} else {
|
|
button2_released = 1;
|
|
}
|
|
|
|
if (buttonMask & 1) {
|
|
int pos = cy*vt->width + cx;
|
|
|
|
// code borrowed from libvncserver (VNConsole.c)
|
|
|
|
if (!vt->mark_active) {
|
|
|
|
vt->mark_active = 1;
|
|
sel_start_pos = sel_end_pos = pos;
|
|
vncterm_toggle_marked_cell (vt, pos);
|
|
|
|
} else {
|
|
|
|
if (pos != sel_end_pos) {
|
|
|
|
if (pos > sel_end_pos) {
|
|
cx = sel_end_pos; cy=pos;
|
|
} else {
|
|
cx=pos; cy=sel_end_pos;
|
|
}
|
|
|
|
if (cx < sel_start_pos) {
|
|
if (cy < sel_start_pos) cy--;
|
|
} else {
|
|
cx++;
|
|
}
|
|
|
|
while (cx <= cy) {
|
|
vncterm_toggle_marked_cell (vt, cx);
|
|
cx++;
|
|
}
|
|
|
|
sel_end_pos = pos;
|
|
}
|
|
}
|
|
|
|
} else if (vt->mark_active) {
|
|
vt->mark_active = 0;
|
|
|
|
if (sel_start_pos > sel_end_pos) {
|
|
int tmp = sel_start_pos - 1;
|
|
sel_start_pos = sel_end_pos;
|
|
sel_end_pos = tmp;
|
|
}
|
|
|
|
int len = sel_end_pos - sel_start_pos + 1;
|
|
|
|
if (vt->selection) free (vt->selection);
|
|
vt->selection = (unicode *)malloc (len*sizeof (unicode));
|
|
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);
|
|
|
|
while (sel_start_pos <= sel_end_pos) {
|
|
vncterm_toggle_marked_cell (vt, sel_start_pos++);
|
|
}
|
|
|
|
}
|
|
|
|
rfbDefaultPtrAddEvent (buttonMask, x, y, cl);
|
|
}
|
|
|
|
static int client_count = 0;
|
|
static int client_connected = 0;
|
|
static int last_client = 1;
|
|
static time_t last_time = 0;
|
|
|
|
void
|
|
client_gone (rfbClientPtr client)
|
|
{
|
|
client_count--;
|
|
|
|
last_time = time (NULL);
|
|
|
|
if (client_count <= 0) {
|
|
last_client = 1;
|
|
}
|
|
}
|
|
|
|
/* libvncserver callback for when a new client connects */
|
|
enum rfbNewClientAction
|
|
new_client (rfbClientPtr client)
|
|
{
|
|
client->clientGoneHook = client_gone;
|
|
client_count++;
|
|
|
|
last_time = time (NULL);
|
|
|
|
last_client = 0;
|
|
client_connected = 1;
|
|
|
|
return RFB_CLIENT_ACCEPT;
|
|
}
|
|
|
|
static char *vncticket = NULL;
|
|
|
|
vncTerm *
|
|
create_vncterm (int argc, char** argv, int maxx, int maxy)
|
|
{
|
|
int i;
|
|
|
|
rfbScreenInfoPtr screen = rfbGetScreen (&argc, argv, maxx, maxy, 8, 1, 1);
|
|
screen->frameBuffer=(char*)calloc(maxx*maxy, 1);
|
|
|
|
char **passwds = calloc(sizeof(char**), 2);
|
|
|
|
vncTerm *vt = (vncTerm *)calloc (sizeof(vncTerm), 1);
|
|
|
|
rfbColourMap *cmap =&screen->colourMap;
|
|
cmap->data.bytes = malloc (16*3);
|
|
for(i=0;i<16;i++) {
|
|
cmap->data.bytes[i*3 + 0] = default_red[color_table[i]];
|
|
cmap->data.bytes[i*3 + 1] = default_grn[color_table[i]];
|
|
cmap->data.bytes[i*3 + 2] = default_blu[color_table[i]];
|
|
}
|
|
cmap->count = 16;
|
|
cmap->is16 = FALSE;
|
|
screen->serverFormat.trueColour = FALSE;
|
|
|
|
screen->kbdAddEvent = vncterm_kbd_event;
|
|
|
|
screen->setXCutText = vncterm_set_xcut_text;
|
|
|
|
screen->ptrAddEvent = vncterm_pointer_event;
|
|
|
|
screen->desktopName = "VNC Command Terminal";
|
|
|
|
screen->newClientHook = new_client;
|
|
|
|
vt->maxx = screen->width;
|
|
vt->maxy = screen->height;
|
|
|
|
vt->width = vt->maxx / 8;
|
|
vt->height = vt->maxy / 16;
|
|
|
|
vt->total_height = vt->height * 20;
|
|
vt->scroll_height = 0;
|
|
vt->y_base = 0;
|
|
vt->y_displ = 0;
|
|
|
|
vt->region_top = 0;
|
|
vt->region_bottom = vt->height;
|
|
|
|
vt->g0enc = LAT1_MAP;
|
|
vt->g1enc = GRAF_MAP;
|
|
vt->cur_enc = vt->g0enc;
|
|
vt->charset = 0;
|
|
|
|
/* default text attributes */
|
|
vt->default_attrib.bold = 0;
|
|
vt->default_attrib.uline = 0;
|
|
vt->default_attrib.blink = 0;
|
|
vt->default_attrib.invers = 0;
|
|
vt->default_attrib.unvisible = 0;
|
|
vt->default_attrib.fgcol = 7;
|
|
vt->default_attrib.bgcol = 0;
|
|
|
|
vt->cur_attrib = vt->default_attrib;
|
|
|
|
vt->cells = (TextCell *)calloc (sizeof (TextCell), vt->width*vt->total_height);
|
|
|
|
for (i = 0; i < vt->width*vt->total_height; i++) {
|
|
vt->cells[i].ch = ' ';
|
|
vt->cells[i].attrib = vt->default_attrib;
|
|
}
|
|
|
|
vt->altcells = (TextCell *)calloc (sizeof (TextCell), vt->width*vt->height);
|
|
|
|
vt->screen = screen;
|
|
|
|
screen->screenData = (void*)vt;
|
|
|
|
//screen->autoPort = 1;
|
|
|
|
if (vncticket) {
|
|
passwds[0] = vncticket;
|
|
passwds[1] = NULL;
|
|
|
|
screen->authPasswdData = (void *)passwds;
|
|
screen->passwordCheck = rfbCheckPasswordByList;
|
|
} else {
|
|
rfbRegisterSecurityHandler(&VncSecurityHandlerVencrypt);
|
|
}
|
|
|
|
rfbInitServer(screen);
|
|
|
|
return vt;
|
|
}
|
|
|
|
int
|
|
main (int argc, char** argv)
|
|
{
|
|
int i;
|
|
char **cmdargv = NULL;
|
|
char *command = "/bin/bash"; // execute normal shell as default
|
|
int fontfd;
|
|
struct stat sb;
|
|
int pid;
|
|
int master;
|
|
char ptyname[1024];
|
|
fd_set fs, fs1;
|
|
struct timeval tv, tv1;
|
|
time_t elapsed, cur_time;
|
|
struct winsize dimensions;
|
|
unsigned long width = 0;
|
|
unsigned long height = 0;
|
|
|
|
if (gnutls_global_init () < 0) {
|
|
fprintf(stderr, "gnutls_global_init failed\n");
|
|
exit(-1);
|
|
}
|
|
|
|
if (gnutls_dh_params_init (&dh_params) < 0) {
|
|
fprintf(stderr, "gnutls_dh_params_init failed\n");
|
|
exit(-1);
|
|
}
|
|
|
|
if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) {
|
|
fprintf(stderr, "gnutls_dh_params_init failed\n");
|
|
exit(-1);
|
|
}
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if (!strcmp (argv[i], "-c")) {
|
|
command = argv[i+1];
|
|
cmdargv = &argv[i+1];
|
|
argc = i;
|
|
argv[i] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if (!strcmp (argv[i], "-timeout")) {
|
|
CHECK_ARGC (argc, argv, i);
|
|
idle_timeout = atoi(argv[i+1]);
|
|
rfbPurgeArguments(&argc, &i, 2, argv); i--;
|
|
} else if (!strcmp (argv[i], "-authpath")) {
|
|
CHECK_ARGC (argc, argv, i);
|
|
auth_path = argv[i+1];
|
|
rfbPurgeArguments(&argc, &i, 2, argv); i--;
|
|
} else if (!strcmp (argv[i], "-perm")) {
|
|
CHECK_ARGC (argc, argv, i);
|
|
auth_perm = argv[i+1];
|
|
rfbPurgeArguments(&argc, &i, 2, argv); i--;
|
|
} else if (!strcmp (argv[i], "-width")) {
|
|
CHECK_ARGC (argc, argv, i);
|
|
errno = 0;
|
|
width = strtoul(argv[i+1], NULL, 10);
|
|
if (errno == 0 && width >= 16 && width < 0xFFFF) {
|
|
screen_width = width;
|
|
}
|
|
rfbPurgeArguments(&argc, &i, 2, argv); i--;
|
|
} else if (!strcmp (argv[i], "-height")) {
|
|
CHECK_ARGC (argc, argv, i);
|
|
errno = 0;
|
|
height = strtoul(argv[i+1], NULL, 10);
|
|
if (errno == 0 && height >= 32 && height < 0xFFFF) {
|
|
screen_height = height;
|
|
}
|
|
rfbPurgeArguments(&argc, &i, 2, argv); i--;
|
|
} else if (!strcmp (argv[i], "-notls")) {
|
|
rfbPurgeArguments(&argc, &i, 1, argv); i--;
|
|
if ((vncticket = getenv("PVE_VNC_TICKET")) == NULL) {
|
|
fprintf(stderr, "missing env PVE_VNC_TICKET (-notls)\n");
|
|
exit(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
unsetenv("PVE_VNC_TICKET"); // do not expose this to child
|
|
|
|
#ifdef DEBUG
|
|
rfbLogEnable (1);
|
|
gnutls_global_set_log_level(10);
|
|
gnutls_global_set_log_function(vnc_debug_gnutls_log);
|
|
#else
|
|
rfbLogEnable (0);
|
|
#endif
|
|
|
|
// mmap font file
|
|
fontfd = open(FONTFILE, O_RDONLY);
|
|
if (fontfd == -1) {
|
|
perror("Error opening Fontfile 'FONTFILE'");
|
|
exit (-1);
|
|
}
|
|
if (fstat(fontfd, &sb) == -1) {
|
|
perror("Stat on 'FONTFILE' failed");
|
|
exit (-1);
|
|
}
|
|
fontdata = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fontfd, 0);
|
|
if (fontdata == MAP_FAILED) {
|
|
perror("Could not mmap 'FONTFILE'");
|
|
exit (-1);
|
|
}
|
|
|
|
close(fontfd);
|
|
vncTerm *vt = create_vncterm (argc, argv, screen_width, screen_height);
|
|
|
|
setlocale(LC_ALL, ""); // set from environment
|
|
|
|
char *ctype = setlocale (LC_CTYPE, NULL); // query LC_CTYPE
|
|
|
|
// fixme: ist there a standard way to detect utf8 mode ?
|
|
if (strcasestr (ctype, ".utf-8")||strcasestr (ctype, ".utf8")) {
|
|
vt->utf8 = 1;
|
|
}
|
|
|
|
dimensions.ws_col = vt->width;
|
|
dimensions.ws_row = vt->height;
|
|
|
|
setenv ("TERM", TERM, 1);
|
|
|
|
pid = forkpty (&master, ptyname, NULL, &dimensions);
|
|
if(!pid) {
|
|
|
|
// install default signal handlers
|
|
signal (SIGQUIT, SIG_DFL);
|
|
signal (SIGTERM, SIG_DFL);
|
|
signal (SIGINT, SIG_DFL);
|
|
|
|
if (cmdargv) {
|
|
execvp (command, cmdargv);
|
|
} else {
|
|
execlp (command, command, NULL);
|
|
}
|
|
perror ("Error: exec failed\n");
|
|
exit (-1); // should not be reached
|
|
} else if (pid == -1) {
|
|
perror ("Error: fork failed\n");
|
|
exit (-1);
|
|
}
|
|
|
|
FD_ZERO (&fs);
|
|
FD_SET (master, &fs);
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 5000; /* 5 ms */
|
|
|
|
last_time = time (NULL);
|
|
|
|
int count = 0;
|
|
while (1) {
|
|
count ++;
|
|
tv1 = tv;
|
|
fs1 = fs;
|
|
|
|
cur_time = time (NULL);
|
|
|
|
elapsed = cur_time - last_time;
|
|
//printf ("Elapsed %ld\n", elapsed);
|
|
|
|
if (last_client) {
|
|
if (client_connected) {
|
|
if (idle_timeout && (elapsed >= idle_timeout)) {
|
|
break;
|
|
}
|
|
} else {
|
|
// wait at least 20 seconds for initial connect
|
|
if (idle_timeout && (elapsed >= (idle_timeout > 20 ? idle_timeout : 20))) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// exit after 30 minutes idle time
|
|
if (elapsed >= 30*60) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (!vt->mark_active) {
|
|
|
|
int num_fds = select (master+1, &fs1, NULL, NULL, &tv1);
|
|
if (num_fds >= 0) {
|
|
if (FD_ISSET (master, &fs1)) {
|
|
char buffer[1024];
|
|
int c;
|
|
while ((c = read (master, buffer, 1024)) == -1) {
|
|
if (errno != EAGAIN) break;
|
|
}
|
|
if (c == -1) break;
|
|
vncterm_puts (vt, buffer, c);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
rfbScreenCleanup(vt->screen);
|
|
|
|
kill (pid, 9);
|
|
int status;
|
|
waitpid(pid, &status, 0);
|
|
|
|
munmap(fontdata, sb.st_size);
|
|
exit (0);
|
|
}
|