xref: /kvmtool/ui/vnc.c (revision a4d8c55eb29a7344befcec02a7834c7cbeedcaa1)
1 #include "kvm/vnc.h"
2 
3 #include "kvm/framebuffer.h"
4 #include "kvm/i8042.h"
5 #include "kvm/vesa.h"
6 
7 #include <linux/types.h>
8 #include <rfb/keysym.h>
9 #include <rfb/rfb.h>
10 #include <pthread.h>
11 #include <linux/err.h>
12 
13 #define VESA_QUEUE_SIZE		128
14 #define VESA_IRQ		14
15 
16 /*
17  * This "6000" value is pretty much the result of experimentation
18  * It seems that around this value, things update pretty smoothly
19  */
20 #define VESA_UPDATE_TIME	6000
21 
22 /*
23  * We can map the letters and numbers without a fuss,
24  * but the other characters not so much.
25  */
26 static char letters[26] = {
27 	0x1c, 0x32, 0x21, 0x23, 0x24, /* a-e */
28 	0x2b, 0x34, 0x33, 0x43, 0x3b, /* f-j */
29 	0x42, 0x4b, 0x3a, 0x31, 0x44, /* k-o */
30 	0x4d, 0x15, 0x2d, 0x1b, 0x2c, /* p-t */
31 	0x3c, 0x2a, 0x1d, 0x22, 0x35, /* u-y */
32 	0x1a,
33 };
34 
35 static rfbScreenInfoPtr server;
36 static char num[10] = {
37 	0x45, 0x16, 0x1e, 0x26, 0x2e, 0x23, 0x36, 0x3d, 0x3e, 0x46,
38 };
39 
40 /*
41  * This is called when the VNC server receives a key event
42  * The reason this function is such a beast is that we have
43  * to convert from ASCII characters (which is what VNC gets)
44  * to PC keyboard scancodes, which is what Linux expects to
45  * get from its keyboard. ASCII and the scancode set don't
46  * really seem to mesh in any good way beyond some basics with
47  * the letters and numbers.
48  */
kbd_handle_key(rfbBool down,rfbKeySym key,rfbClientPtr cl)49 static void kbd_handle_key(rfbBool down, rfbKeySym key, rfbClientPtr cl)
50 {
51 	char tosend = 0;
52 
53 	if (key >= 0x41 && key <= 0x5a)
54 		key += 0x20; /* convert to lowercase */
55 
56 	if (key >= 0x61 && key <= 0x7a) /* a-z */
57 		tosend = letters[key - 0x61];
58 
59 	if (key >= 0x30 && key <= 0x39)
60 		tosend = num[key - 0x30];
61 
62 	switch (key) {
63 	case XK_Insert:		kbd_queue(0xe0);	tosend = 0x70;	break;
64 	case XK_Delete:		kbd_queue(0xe0);	tosend = 0x71;	break;
65 	case XK_Up:		kbd_queue(0xe0);	tosend = 0x75;	break;
66 	case XK_Down:		kbd_queue(0xe0);	tosend = 0x72;	break;
67 	case XK_Left:		kbd_queue(0xe0);	tosend = 0x6b;	break;
68 	case XK_Right:		kbd_queue(0xe0);	tosend = 0x74;	break;
69 	case XK_Page_Up:	kbd_queue(0xe0);	tosend = 0x7d;	break;
70 	case XK_Page_Down:	kbd_queue(0xe0);	tosend = 0x7a;	break;
71 	case XK_Home:		kbd_queue(0xe0);	tosend = 0x6c;	break;
72 	case XK_BackSpace:	tosend = 0x66;		break;
73 	case XK_Tab:		tosend = 0x0d;		break;
74 	case XK_Return:		tosend = 0x5a;		break;
75 	case XK_Escape:		tosend = 0x76;		break;
76 	case XK_End:		tosend = 0x69;		break;
77 	case XK_Shift_L:	tosend = 0x12;		break;
78 	case XK_Shift_R:	tosend = 0x59;		break;
79 	case XK_Control_R:	kbd_queue(0xe0);
80 	case XK_Control_L:	tosend = 0x14;		break;
81 	case XK_Alt_R:		kbd_queue(0xe0);
82 	case XK_Alt_L:		tosend = 0x11;		break;
83 	case XK_quoteleft:	tosend = 0x0e;		break;
84 	case XK_minus:		tosend = 0x4e;		break;
85 	case XK_equal:		tosend = 0x55;		break;
86 	case XK_bracketleft:	tosend = 0x54;		break;
87 	case XK_bracketright:	tosend = 0x5b;		break;
88 	case XK_backslash:	tosend = 0x5d;		break;
89 	case XK_Caps_Lock:	tosend = 0x58;		break;
90 	case XK_semicolon:	tosend = 0x4c;		break;
91 	case XK_quoteright:	tosend = 0x52;		break;
92 	case XK_comma:		tosend = 0x41;		break;
93 	case XK_period:		tosend = 0x49;		break;
94 	case XK_slash:		tosend = 0x4a;		break;
95 	case XK_space:		tosend = 0x29;		break;
96 
97 	/*
98 	 * This is where I handle the shifted characters.
99 	 * They don't really map nicely the way A-Z maps to a-z,
100 	 * so I'm doing it manually
101 	 */
102 	case XK_exclam:		tosend = 0x16;		break;
103 	case XK_quotedbl:	tosend = 0x52;		break;
104 	case XK_numbersign:	tosend = 0x26;		break;
105 	case XK_dollar:		tosend = 0x25;		break;
106 	case XK_percent:	tosend = 0x2e;		break;
107 	case XK_ampersand:	tosend = 0x3d;		break;
108 	case XK_parenleft:	tosend = 0x46;		break;
109 	case XK_parenright:	tosend = 0x45;		break;
110 	case XK_asterisk:	tosend = 0x3e;		break;
111 	case XK_plus:		tosend = 0x55;		break;
112 	case XK_colon:		tosend = 0x4c;		break;
113 	case XK_less:		tosend = 0x41;		break;
114 	case XK_greater:	tosend = 0x49;		break;
115 	case XK_question:	tosend = 0x4a;		break;
116 	case XK_at:		tosend = 0x1e;		break;
117 	case XK_asciicircum:	tosend = 0x36;		break;
118 	case XK_underscore:	tosend = 0x4e;		break;
119 	case XK_braceleft:	tosend = 0x54;		break;
120 	case XK_braceright:	tosend = 0x5b;		break;
121 	case XK_bar:		tosend = 0x5d;		break;
122 	case XK_asciitilde:	tosend = 0x0e;		break;
123 	default:		break;
124 	}
125 
126 	/*
127 	 * If this is a "key up" event (the user has released the key, we
128 	 * need to send 0xf0 first.
129 	 */
130 	if (!down && tosend != 0x0)
131 		kbd_queue(0xf0);
132 
133 	if (tosend)
134 		kbd_queue(tosend);
135 }
136 
137 /* The previous X and Y coordinates of the mouse */
138 static int xlast, ylast = -1;
139 
140 /*
141  * This function is called by the VNC server whenever a mouse event occurs.
142  */
kbd_handle_ptr(int buttonMask,int x,int y,rfbClientPtr cl)143 static void kbd_handle_ptr(int buttonMask, int x, int y, rfbClientPtr cl)
144 {
145 	int dx, dy;
146 	char b1 = 0x8;
147 
148 	/* The VNC mask and the PS/2 button encoding are the same */
149 	b1 |= buttonMask;
150 
151 	if (xlast >= 0 && ylast >= 0) {
152 		/* The PS/2 mouse sends deltas, not absolutes */
153 		dx = x - xlast;
154 		dy = ylast - y;
155 
156 		/* Set overflow bits if needed */
157 		if (dy > 255)
158 			b1 |= 0x80;
159 		if (dx > 255)
160 			b1 |= 0x40;
161 
162 		/* Set negative bits if needed */
163 		if (dy < 0)
164 			b1 |= 0x20;
165 		if (dx < 0)
166 			b1 |= 0x10;
167 
168 		mouse_queue(b1);
169 		mouse_queue(dx);
170 		mouse_queue(dy);
171 	}
172 
173 	xlast = x;
174 	ylast = y;
175 	rfbDefaultPtrAddEvent(buttonMask, x, y, cl);
176 }
177 
vnc__thread(void * p)178 static void *vnc__thread(void *p)
179 {
180 	struct framebuffer *fb = p;
181 	/*
182 	 * Make a fake argc and argv because the getscreen function
183 	 * seems to want it.
184 	 */
185 	char argv[1][1] = {{0}};
186 	int argc = 1;
187 
188 	kvm__set_thread_name("kvm-vnc-worker");
189 
190 	server = rfbGetScreen(&argc, (char **) argv, fb->width, fb->height, 8, 3, 4);
191 	server->frameBuffer		= fb->mem;
192 	server->alwaysShared		= TRUE;
193 	server->kbdAddEvent		= kbd_handle_key;
194 	server->ptrAddEvent		= kbd_handle_ptr;
195 	rfbInitServer(server);
196 
197 	while (rfbIsActive(server)) {
198 		rfbMarkRectAsModified(server, 0, 0, fb->width, fb->height);
199 		rfbProcessEvents(server, server->deferUpdateTime * VESA_UPDATE_TIME);
200 	}
201 	return NULL;
202 }
203 
vnc__start(struct framebuffer * fb)204 static int vnc__start(struct framebuffer *fb)
205 {
206 	pthread_t thread;
207 
208 	if (pthread_create(&thread, NULL, vnc__thread, fb) != 0)
209 		return -1;
210 
211 	return 0;
212 }
213 
vnc__stop(struct framebuffer * fb)214 static int vnc__stop(struct framebuffer *fb)
215 {
216 	rfbShutdownServer(server, TRUE);
217 
218 	return 0;
219 }
220 
221 static struct fb_target_operations vnc_ops = {
222 	.start	= vnc__start,
223 	.stop	= vnc__stop,
224 };
225 
vnc__init(struct kvm * kvm)226 int vnc__init(struct kvm *kvm)
227 {
228 	struct framebuffer *fb;
229 
230 	if (!kvm->cfg.vnc)
231 		return 0;
232 
233 	fb = vesa__init(kvm);
234 	if (IS_ERR(fb)) {
235 		pr_err("vesa__init() failed with error %ld\n", PTR_ERR(fb));
236 		return PTR_ERR(fb);
237 	}
238 
239 	return fb__attach(fb, &vnc_ops);
240 }
241 dev_init(vnc__init);
242 
vnc__exit(struct kvm * kvm)243 int vnc__exit(struct kvm *kvm)
244 {
245 	if (kvm->cfg.vnc)
246 		return vnc__stop(NULL);
247 
248 	return 0;
249 }
250 dev_exit(vnc__exit);
251