]>
Commit | Line | Data |
---|---|---|
4ef70811 DM |
1 | /* |
2 | ||
e9120d9a | 3 | Copyright (C) 2013 - 2021 Proxmox Server Solutions GmbH |
4ef70811 DM |
4 | |
5 | Copyright: spiceterm is under GNU GPL, the GNU General Public License. | |
6 | ||
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. | |
10 | ||
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. | |
15 | ||
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 | |
19 | 02111-1307, USA. | |
20 | ||
21 | Author: Dietmar Maurer <dietmar@proxmox.com> | |
22 | ||
23 | Note: qlx drawing code is copied from spice-server test code. | |
24 | ||
25 | */ | |
26 | ||
cc04455b DM |
27 | #include <stdlib.h> |
28 | #include <math.h> | |
29 | #include <string.h> | |
30 | #include <stdio.h> | |
31 | #include <unistd.h> | |
32 | #include <signal.h> | |
33 | #include <wait.h> | |
34 | #include <sys/select.h> | |
35 | #include <sys/types.h> | |
36 | #include <getopt.h> | |
37 | ||
38 | #include <spice.h> | |
474762d8 | 39 | #include <spice/enums.h> |
cc04455b DM |
40 | #include <spice/macros.h> |
41 | #include <spice/qxl_dev.h> | |
60b9ef63 | 42 | #include <spice/vd_agent.h> |
cc04455b | 43 | |
e3759a34 DM |
44 | #include "glyphs.h" |
45 | ||
46 | #include "spiceterm.h" | |
cc04455b | 47 | |
a0579497 | 48 | static int debug = 0; |
e9120d9a | 49 | |
a0579497 DM |
50 | #define DPRINTF(x, format, ...) { \ |
51 | if (x <= debug) { \ | |
52 | printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \ | |
53 | } \ | |
54 | } | |
55 | ||
cc04455b DM |
56 | #define MEM_SLOT_GROUP_ID 0 |
57 | ||
5508c4fd DC |
58 | |
59 | extern unsigned char color_table[]; | |
60 | ||
65007123 DM |
61 | /* these colours are from linux kernel drivers/char/vt.c */ |
62 | /* the default colour table, for VGA+ colour systems */ | |
63 | int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa, | |
64 | 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff}; | |
65 | int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa, | |
66 | 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff}; | |
67 | int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa, | |
68 | 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff}; | |
69 | ||
cc04455b DM |
70 | /* Parts cribbed from spice-display.h/.c/qxl.c */ |
71 | ||
d0df10ce | 72 | typedef struct __attribute__ ((__packed__)) SimpleSpiceUpdate { |
e70e45a9 | 73 | QXLCommandExt ext; // needs to be first member |
cc04455b DM |
74 | QXLDrawable drawable; |
75 | QXLImage image; | |
76 | uint8_t *bitmap; | |
e70e45a9 | 77 | int cache_id; // do not free bitmap if cache_id != 0 |
cc04455b DM |
78 | } SimpleSpiceUpdate; |
79 | ||
e9120d9a | 80 | static void |
64bc7a2f | 81 | spice_screen_destroy_update(SimpleSpiceUpdate *update) |
cc04455b DM |
82 | { |
83 | if (!update) { | |
84 | return; | |
85 | } | |
86 | if (update->drawable.clip.type != SPICE_CLIP_TYPE_NONE) { | |
87 | uint8_t *ptr = (uint8_t*)update->drawable.clip.data; | |
88 | free(ptr); | |
89 | } | |
e70e45a9 DM |
90 | if (update->bitmap && !update->cache_id) { |
91 | g_free(update->bitmap); | |
92 | } | |
93 | ||
9f9c8d83 | 94 | g_free(update); |
cc04455b DM |
95 | } |
96 | ||
e9120d9a | 97 | static void |
c783f726 DM |
98 | release_qxl_command_ext(QXLCommandExt *ext) |
99 | { | |
100 | g_assert(ext != NULL); | |
101 | ||
102 | switch (ext->cmd.type) { | |
e9120d9a TL |
103 | case QXL_CMD_DRAW: |
104 | spice_screen_destroy_update((void*)ext); | |
105 | break; | |
106 | case QXL_CMD_SURFACE: | |
107 | free(ext); | |
108 | break; | |
109 | case QXL_CMD_CURSOR: { | |
110 | QXLCursorCmd *cmd = (QXLCursorCmd *)(unsigned long)ext->cmd.data; | |
111 | if (cmd->type == QXL_CURSOR_SET) { | |
112 | free(cmd); | |
113 | } | |
114 | free(ext); | |
115 | break; | |
c783f726 | 116 | } |
e9120d9a TL |
117 | default: |
118 | abort(); | |
c783f726 DM |
119 | } |
120 | } | |
121 | ||
e9120d9a | 122 | static void |
c783f726 DM |
123 | release_resource(QXLInstance *qin, struct QXLReleaseInfoExt release_info) |
124 | { | |
125 | QXLCommandExt *ext = (QXLCommandExt*)(unsigned long)release_info.info->id; | |
126 | ||
127 | g_assert(release_info.group_id == MEM_SLOT_GROUP_ID); | |
128 | release_qxl_command_ext(ext); | |
129 | } | |
130 | ||
e9a6b86c | 131 | static int unique = 0x0ffff + 1; |
cc04455b | 132 | |
e9120d9a | 133 | static void |
64bc7a2f | 134 | set_cmd(QXLCommandExt *ext, uint32_t type, QXLPHYSICAL data) |
cc04455b DM |
135 | { |
136 | ext->cmd.type = type; | |
137 | ext->cmd.data = data; | |
138 | ext->cmd.padding = 0; | |
139 | ext->group_id = MEM_SLOT_GROUP_ID; | |
140 | ext->flags = 0; | |
141 | } | |
142 | ||
e9120d9a | 143 | static void |
64bc7a2f | 144 | simple_set_release_info(QXLReleaseInfo *info, intptr_t ptr) |
cc04455b DM |
145 | { |
146 | info->id = ptr; | |
147 | //info->group_id = MEM_SLOT_GROUP_ID; | |
148 | } | |
149 | ||
64bc7a2f | 150 | /* Note: push_command/get_command are called from different threads */ |
7b4a7650 | 151 | |
e9120d9a | 152 | static void |
64bc7a2f | 153 | push_command(SpiceScreen *spice_screen, QXLCommandExt *ext) |
7b4a7650 | 154 | { |
30930d41 DM |
155 | int need_wakeup = 1; |
156 | ||
b52b9534 | 157 | g_mutex_lock(&spice_screen->command_mutex); |
7b4a7650 | 158 | |
64bc7a2f | 159 | while (spice_screen->commands_end - spice_screen->commands_start >= COMMANDS_SIZE) { |
b52b9534 | 160 | g_cond_wait(&spice_screen->command_cond, &spice_screen->command_mutex); |
7b4a7650 | 161 | } |
7b4a7650 | 162 | |
64bc7a2f DM |
163 | g_assert(spice_screen->commands_end - spice_screen->commands_start < COMMANDS_SIZE); |
164 | ||
30930d41 DM |
165 | if ((spice_screen->commands_end - spice_screen->commands_start) > 0) { |
166 | need_wakeup = 0; | |
167 | } | |
168 | ||
64bc7a2f DM |
169 | spice_screen->commands[spice_screen->commands_end % COMMANDS_SIZE] = ext; |
170 | spice_screen->commands_end++; | |
171 | ||
30930d41 | 172 | if (need_wakeup) { |
b52b9534 DM |
173 | spice_qxl_wakeup(&spice_screen->qxl_instance); |
174 | //spice_screen->qxl_worker->wakeup(spice_screen->qxl_worker); | |
30930d41 DM |
175 | } |
176 | ||
b52b9534 | 177 | g_mutex_unlock(&spice_screen->command_mutex); |
64bc7a2f | 178 | |
7b4a7650 DM |
179 | } |
180 | ||
9f9c8d83 | 181 | /* bitmap are freed, so they must be allocated with g_malloc */ |
64bc7a2f | 182 | static SimpleSpiceUpdate * |
e9120d9a TL |
183 | spice_screen_update_from_bitmap_cmd( |
184 | uint32_t surface_id, | |
185 | QXLRect bbox, | |
186 | uint8_t *bitmap, | |
187 | int cache_id | |
188 | ) { | |
cc04455b DM |
189 | SimpleSpiceUpdate *update; |
190 | QXLDrawable *drawable; | |
191 | QXLImage *image; | |
192 | uint32_t bw, bh; | |
193 | ||
194 | bh = bbox.bottom - bbox.top; | |
195 | bw = bbox.right - bbox.left; | |
196 | ||
9f9c8d83 | 197 | update = g_new0(SimpleSpiceUpdate, 1); |
cc04455b DM |
198 | update->bitmap = bitmap; |
199 | drawable = &update->drawable; | |
200 | image = &update->image; | |
201 | ||
202 | drawable->surface_id = surface_id; | |
203 | ||
204 | drawable->bbox = bbox; | |
4219b121 | 205 | drawable->clip.type = SPICE_CLIP_TYPE_NONE; |
cc04455b DM |
206 | drawable->effect = QXL_EFFECT_OPAQUE; |
207 | simple_set_release_info(&drawable->release_info, (intptr_t)update); | |
208 | drawable->type = QXL_DRAW_COPY; | |
209 | drawable->surfaces_dest[0] = -1; | |
210 | drawable->surfaces_dest[1] = -1; | |
211 | drawable->surfaces_dest[2] = -1; | |
212 | ||
213 | drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT; | |
214 | drawable->u.copy.src_bitmap = (intptr_t)image; | |
215 | drawable->u.copy.src_area.right = bw; | |
216 | drawable->u.copy.src_area.bottom = bh; | |
217 | ||
e9a6b86c DM |
218 | if (cache_id) { |
219 | QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, cache_id); | |
62e6a86b | 220 | image->descriptor.flags = SPICE_IMAGE_FLAGS_CACHE_ME; |
e70e45a9 | 221 | update->cache_id = cache_id; |
e9a6b86c DM |
222 | } else { |
223 | QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, ++unique); | |
224 | } | |
cc04455b DM |
225 | image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP; |
226 | image->bitmap.flags = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN; | |
227 | image->bitmap.stride = bw * 4; | |
228 | image->descriptor.width = image->bitmap.x = bw; | |
229 | image->descriptor.height = image->bitmap.y = bh; | |
230 | image->bitmap.data = (intptr_t)bitmap; | |
231 | image->bitmap.palette = 0; | |
232 | image->bitmap.format = SPICE_BITMAP_FMT_32BIT; | |
233 | ||
234 | set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable); | |
235 | ||
236 | return update; | |
237 | } | |
238 | ||
64bc7a2f | 239 | static SimpleSpiceUpdate * |
e9120d9a TL |
240 | spice_screen_draw_char_cmd( |
241 | SpiceScreen *spice_screen, | |
242 | int x, | |
243 | int y, | |
244 | int c, | |
245 | int fg, | |
246 | int bg, | |
247 | gboolean uline | |
248 | ) { | |
e70e45a9 | 249 | uint8_t *bitmap = NULL; |
cc04455b | 250 | QXLRect bbox; |
e9a6b86c | 251 | int cache_id = 0; |
e70e45a9 | 252 | CachedImage *ce; |
e9a6b86c | 253 | |
b1f67c20 | 254 | if (!uline && c < 256) { |
e9a6b86c | 255 | cache_id = ((fg << 12) | (bg << 8) | (c & 255)) & 0x0ffff; |
e70e45a9 DM |
256 | if ((ce = (CachedImage *)g_hash_table_lookup(spice_screen->image_cache, &cache_id))) { |
257 | bitmap = ce->bitmap; | |
258 | } | |
e9a6b86c | 259 | } |
cc04455b | 260 | |
e9120d9a TL |
261 | int bw = 8, bh = 16; |
262 | int left = x * bw, top = y * bh; | |
cc04455b | 263 | |
e70e45a9 | 264 | if (!bitmap) { |
e9120d9a TL |
265 | uint8_t *dst = bitmap = g_malloc(bw * bh * 4); |
266 | ||
e70e45a9 DM |
267 | unsigned char *data = vt_font_data + c*16; |
268 | unsigned char d = *data; | |
269 | ||
270 | g_assert(fg >= 0 && fg < 16); | |
271 | g_assert(bg >= 0 && bg < 16); | |
272 | ||
5508c4fd DC |
273 | unsigned char fgc_red = default_red[color_table[fg]]; |
274 | unsigned char fgc_blue = default_blu[color_table[fg]]; | |
275 | unsigned char fgc_green = default_grn[color_table[fg]]; | |
276 | unsigned char bgc_red = default_red[color_table[bg]]; | |
277 | unsigned char bgc_blue = default_blu[color_table[bg]]; | |
278 | unsigned char bgc_green = default_grn[color_table[bg]]; | |
e70e45a9 | 279 | |
e9120d9a | 280 | for (int j = 0; j < 16; j++) { |
e70e45a9 | 281 | gboolean ul = (j == 14) && uline; |
e9120d9a | 282 | for (int i = 0; i < 8; i++) { |
e70e45a9 DM |
283 | if (i == 0) { |
284 | d=*data; | |
285 | data++; | |
286 | } | |
287 | if (ul || d&0x80) { | |
e9120d9a | 288 | *(dst+0) = fgc_blue; |
e70e45a9 DM |
289 | *(dst+1) = fgc_green; |
290 | *(dst+2) = fgc_red; | |
291 | *(dst+3) = 0; | |
292 | } else { | |
e9120d9a | 293 | *(dst+0) = bgc_blue; |
e70e45a9 DM |
294 | *(dst+1) = bgc_green; |
295 | *(dst+2) = bgc_red; | |
296 | *(dst+3) = 0; | |
297 | } | |
e9120d9a | 298 | d <<= 1; |
e70e45a9 | 299 | dst += 4; |
9462ba15 | 300 | } |
9462ba15 | 301 | } |
ffeedb04 | 302 | |
e9120d9a TL |
303 | if (cache_id != 0) { |
304 | ce = g_new(CachedImage, 1); | |
305 | ce->cache_id = cache_id; | |
306 | ce->bitmap = bitmap; | |
307 | g_hash_table_insert(spice_screen->image_cache, &ce->cache_id, ce); | |
308 | } | |
cc04455b DM |
309 | } |
310 | ||
311 | bbox.left = left; bbox.top = top; | |
312 | bbox.right = left + bw; bbox.bottom = top + bh; | |
9462ba15 | 313 | |
e9a6b86c | 314 | return spice_screen_update_from_bitmap_cmd(0, bbox, bitmap, cache_id); |
cc04455b DM |
315 | } |
316 | ||
e9120d9a TL |
317 | void |
318 | spice_screen_scroll( | |
319 | SpiceScreen *spice_screen, | |
320 | int x1, | |
321 | int y1, | |
322 | int x2, | |
323 | int y2, | |
324 | int src_x, | |
325 | int src_y | |
326 | ) { | |
7b4a7650 DM |
327 | SimpleSpiceUpdate *update; |
328 | QXLDrawable *drawable; | |
329 | QXLRect bbox; | |
330 | ||
64bc7a2f | 331 | int surface_id = 0; |
7b4a7650 DM |
332 | |
333 | update = g_new0(SimpleSpiceUpdate, 1); | |
334 | drawable = &update->drawable; | |
335 | ||
336 | bbox.left = x1; | |
337 | bbox.top = y1; | |
338 | bbox.right = x2; | |
339 | bbox.bottom = y2; | |
340 | ||
341 | drawable->surface_id = surface_id; | |
342 | ||
343 | drawable->bbox = bbox; | |
344 | drawable->clip.type = SPICE_CLIP_TYPE_NONE; | |
345 | drawable->effect = QXL_EFFECT_OPAQUE; | |
346 | simple_set_release_info(&drawable->release_info, (intptr_t)update); | |
347 | drawable->type = QXL_COPY_BITS; | |
348 | drawable->surfaces_dest[0] = -1; | |
349 | drawable->surfaces_dest[1] = -1; | |
350 | drawable->surfaces_dest[2] = -1; | |
351 | ||
352 | drawable->u.copy_bits.src_pos.x = src_x; | |
353 | drawable->u.copy_bits.src_pos.y = src_y; | |
354 | ||
355 | set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable); | |
356 | ||
64bc7a2f | 357 | push_command(spice_screen, &update->ext); |
7b4a7650 DM |
358 | } |
359 | ||
e9120d9a | 360 | void |
64bc7a2f | 361 | spice_screen_clear(SpiceScreen *spice_screen, int x1, int y1, int x2, int y2) |
7b4a7650 DM |
362 | { |
363 | SimpleSpiceUpdate *update; | |
364 | QXLDrawable *drawable; | |
365 | QXLRect bbox; | |
366 | ||
64bc7a2f | 367 | int surface_id = 0; |
7b4a7650 DM |
368 | |
369 | update = g_new0(SimpleSpiceUpdate, 1); | |
370 | drawable = &update->drawable; | |
371 | ||
372 | bbox.left = x1; | |
373 | bbox.top = y1; | |
374 | bbox.right = x2; | |
375 | bbox.bottom = y2; | |
376 | ||
377 | drawable->surface_id = surface_id; | |
378 | ||
379 | drawable->bbox = bbox; | |
380 | drawable->clip.type = SPICE_CLIP_TYPE_NONE; | |
381 | drawable->effect = QXL_EFFECT_OPAQUE; | |
382 | simple_set_release_info(&drawable->release_info, (intptr_t)update); | |
383 | drawable->type = QXL_DRAW_BLACKNESS; | |
384 | drawable->surfaces_dest[0] = -1; | |
385 | drawable->surfaces_dest[1] = -1; | |
386 | drawable->surfaces_dest[2] = -1; | |
387 | ||
388 | set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable); | |
389 | ||
64bc7a2f | 390 | push_command(spice_screen, &update->ext); |
7b4a7650 DM |
391 | } |
392 | ||
e9120d9a TL |
393 | static void |
394 | create_primary_surface( | |
395 | SpiceScreen *spice_screen, | |
396 | uint32_t width, | |
397 | uint32_t height | |
398 | ) { | |
cc04455b DM |
399 | QXLDevSurfaceCreate surface = { 0, }; |
400 | ||
cc04455b DM |
401 | g_assert(height > 0); |
402 | g_assert(width > 0); | |
403 | ||
ead260d7 DM |
404 | if (height > MAX_HEIGHT) |
405 | height = MAX_HEIGHT; | |
406 | ||
407 | if (width > MAX_WIDTH) | |
408 | width = MAX_WIDTH; | |
409 | ||
cc04455b | 410 | surface.format = SPICE_SURFACE_FMT_32_xRGB; |
64bc7a2f DM |
411 | surface.width = spice_screen->primary_width = width; |
412 | surface.height = spice_screen->primary_height = height; | |
cc04455b DM |
413 | surface.stride = -width * 4; /* negative? */ |
414 | surface.mouse_mode = TRUE; /* unused by red_worker */ | |
415 | surface.flags = 0; | |
416 | surface.type = 0; /* unused by red_worker */ | |
417 | surface.position = 0; /* unused by red_worker */ | |
64bc7a2f | 418 | surface.mem = (uint64_t)&spice_screen->primary_surface; |
cc04455b DM |
419 | surface.group_id = MEM_SLOT_GROUP_ID; |
420 | ||
64bc7a2f DM |
421 | spice_screen->width = width; |
422 | spice_screen->height = height; | |
cc04455b | 423 | |
6508981f DM |
424 | spice_screen->cursor_set = 0; |
425 | ||
b52b9534 | 426 | spice_qxl_create_primary_surface(&spice_screen->qxl_instance, 0, &surface); |
cc04455b DM |
427 | } |
428 | ||
429 | QXLDevMemSlot slot = { | |
64bc7a2f DM |
430 | .slot_group_id = MEM_SLOT_GROUP_ID, |
431 | .slot_id = 0, | |
432 | .generation = 0, | |
433 | .virt_start = 0, | |
434 | .virt_end = ~0, | |
435 | .addr_delta = 0, | |
436 | .qxl_ram_size = ~0, | |
cc04455b DM |
437 | }; |
438 | ||
e9120d9a | 439 | static void |
64bc7a2f | 440 | attache_worker(QXLInstance *qin, QXLWorker *_qxl_worker) |
cc04455b | 441 | { |
64bc7a2f | 442 | SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance); |
cc04455b | 443 | |
64bc7a2f DM |
444 | if (spice_screen->qxl_worker) { |
445 | g_assert_not_reached(); | |
cc04455b | 446 | } |
e9120d9a | 447 | |
64bc7a2f | 448 | spice_screen->qxl_worker = _qxl_worker; |
b52b9534 | 449 | spice_qxl_add_memslot(&spice_screen->qxl_instance, &slot); |
8f53ae81 | 450 | create_primary_surface(spice_screen, spice_screen->width, spice_screen->height); |
b52b9534 | 451 | spice_server_vm_start(spice_screen->server); |
cc04455b DM |
452 | } |
453 | ||
e9120d9a | 454 | static void |
64bc7a2f | 455 | set_compression_level(QXLInstance *qin, int level) |
cc04455b | 456 | { |
64bc7a2f | 457 | /* not used */ |
cc04455b DM |
458 | } |
459 | ||
e9120d9a | 460 | static void |
64bc7a2f | 461 | set_mm_time(QXLInstance *qin, uint32_t mm_time) |
cc04455b | 462 | { |
64bc7a2f | 463 | /* not used */ |
cc04455b | 464 | } |
64bc7a2f | 465 | |
e9120d9a | 466 | static void |
64bc7a2f | 467 | get_init_info(QXLInstance *qin, QXLDevInitInfo *info) |
cc04455b DM |
468 | { |
469 | memset(info, 0, sizeof(*info)); | |
470 | info->num_memslots = 1; | |
471 | info->num_memslots_groups = 1; | |
472 | info->memslot_id_bits = 1; | |
473 | info->memslot_gen_bits = 1; | |
aae93060 | 474 | info->n_surfaces = 1; |
cc04455b DM |
475 | } |
476 | ||
64bc7a2f | 477 | /* called from spice_server thread (i.e. red_worker thread) */ |
e9120d9a | 478 | static int |
64bc7a2f | 479 | get_command(QXLInstance *qin, struct QXLCommandExt *ext) |
cc04455b | 480 | { |
64bc7a2f | 481 | SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance); |
63a34f43 DM |
482 | int res = FALSE; |
483 | ||
b52b9534 | 484 | g_mutex_lock(&spice_screen->command_mutex); |
e9120d9a | 485 | |
64bc7a2f | 486 | if ((spice_screen->commands_end - spice_screen->commands_start) == 0) { |
63a34f43 DM |
487 | res = FALSE; |
488 | goto ret; | |
cc04455b | 489 | } |
63a34f43 | 490 | |
64bc7a2f DM |
491 | *ext = *spice_screen->commands[spice_screen->commands_start % COMMANDS_SIZE]; |
492 | g_assert(spice_screen->commands_start < spice_screen->commands_end); | |
493 | spice_screen->commands_start++; | |
b52b9534 | 494 | g_cond_signal(&spice_screen->command_cond); |
63a34f43 DM |
495 | |
496 | res = TRUE; | |
497 | ||
498 | ret: | |
b52b9534 | 499 | g_mutex_unlock(&spice_screen->command_mutex); |
63a34f43 | 500 | return res; |
cc04455b DM |
501 | } |
502 | ||
c783f726 | 503 | void |
e9120d9a | 504 | discard_pending_commands(SpiceScreen *spice_screen) |
c783f726 DM |
505 | { |
506 | int pos; | |
507 | ||
b52b9534 | 508 | g_mutex_lock(&spice_screen->command_mutex); |
c783f726 DM |
509 | for (pos = spice_screen->commands_start; pos < spice_screen->commands_end; pos++) { |
510 | release_qxl_command_ext(spice_screen->commands[pos % COMMANDS_SIZE]); | |
511 | } | |
512 | spice_screen->commands_start = spice_screen->commands_end; | |
b52b9534 | 513 | g_mutex_unlock(&spice_screen->command_mutex); |
c783f726 DM |
514 | } |
515 | ||
e9120d9a | 516 | static int |
64bc7a2f | 517 | req_cmd_notification(QXLInstance *qin) |
cc04455b | 518 | { |
64bc7a2f DM |
519 | //SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance); |
520 | //spice_screen->core->timer_start(spice_screen->wakeup_timer, spice_screen->wakeup_ms); | |
abc13312 | 521 | |
cc04455b DM |
522 | return TRUE; |
523 | } | |
f6cb554c | 524 | |
64bc7a2f DM |
525 | #define CURSOR_WIDTH 8 |
526 | #define CURSOR_HEIGHT 16 | |
cc04455b DM |
527 | |
528 | static struct { | |
529 | QXLCursor cursor; | |
530 | uint8_t data[CURSOR_WIDTH * CURSOR_HEIGHT * 4]; // 32bit per pixel | |
531 | } cursor; | |
532 | ||
e9120d9a | 533 | static void |
64bc7a2f | 534 | cursor_init() |
cc04455b DM |
535 | { |
536 | cursor.cursor.header.unique = 0; | |
537 | cursor.cursor.header.type = SPICE_CURSOR_TYPE_COLOR32; | |
538 | cursor.cursor.header.width = CURSOR_WIDTH; | |
539 | cursor.cursor.header.height = CURSOR_HEIGHT; | |
540 | cursor.cursor.header.hot_spot_x = 0; | |
541 | cursor.cursor.header.hot_spot_y = 0; | |
542 | cursor.cursor.data_size = CURSOR_WIDTH * CURSOR_HEIGHT * 4; | |
543 | ||
544 | // X drivers addes it to the cursor size because it could be | |
545 | // cursor data information or another cursor related stuffs. | |
546 | // Otherwise, the code will break in client/cursor.cpp side, | |
547 | // that expect the data_size plus cursor information. | |
548 | // Blame cursor protocol for this. :-) | |
549 | cursor.cursor.data_size += 128; | |
550 | cursor.cursor.chunk.data_size = cursor.cursor.data_size; | |
551 | cursor.cursor.chunk.prev_chunk = cursor.cursor.chunk.next_chunk = 0; | |
552 | } | |
553 | ||
e9120d9a | 554 | static int |
64bc7a2f | 555 | get_cursor_command(QXLInstance *qin, struct QXLCommandExt *ext) |
cc04455b | 556 | { |
6508981f | 557 | SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance); |
e9120d9a | 558 | |
cc04455b DM |
559 | QXLCursorCmd *cursor_cmd; |
560 | QXLCommandExt *cmd; | |
561 | ||
e9120d9a | 562 | if (spice_screen->cursor_set) |
6508981f | 563 | return FALSE; |
e9120d9a | 564 | |
6508981f | 565 | spice_screen->cursor_set = 1; |
e9120d9a | 566 | |
cc04455b DM |
567 | cmd = calloc(sizeof(QXLCommandExt), 1); |
568 | cursor_cmd = calloc(sizeof(QXLCursorCmd), 1); | |
569 | ||
570 | cursor_cmd->release_info.id = (unsigned long)cmd; | |
571 | ||
622c777f DM |
572 | cursor_cmd->type = QXL_CURSOR_SET; |
573 | cursor_cmd->u.set.position.x = 0; | |
574 | cursor_cmd->u.set.position.y = 0; | |
575 | cursor_cmd->u.set.visible = TRUE; | |
576 | cursor_cmd->u.set.shape = (unsigned long)&cursor; | |
577 | // white rect as cursor | |
578 | memset(cursor.data, 255, sizeof(cursor.data)); | |
cc04455b DM |
579 | |
580 | cmd->cmd.data = (unsigned long)cursor_cmd; | |
581 | cmd->cmd.type = QXL_CMD_CURSOR; | |
582 | cmd->group_id = MEM_SLOT_GROUP_ID; | |
622c777f | 583 | cmd->flags = 0; |
cc04455b | 584 | *ext = *cmd; |
64bc7a2f | 585 | |
cc04455b DM |
586 | return TRUE; |
587 | } | |
588 | ||
e9120d9a | 589 | static int |
64bc7a2f | 590 | req_cursor_notification(QXLInstance *qin) |
cc04455b | 591 | { |
64bc7a2f DM |
592 | /* not used */ |
593 | ||
cc04455b DM |
594 | return TRUE; |
595 | } | |
596 | ||
e9120d9a | 597 | static void |
64bc7a2f | 598 | notify_update(QXLInstance *qin, uint32_t update_id) |
cc04455b | 599 | { |
64bc7a2f | 600 | /* not used */ |
cc04455b DM |
601 | } |
602 | ||
e9120d9a | 603 | static int |
64bc7a2f | 604 | flush_resources(QXLInstance *qin) |
cc04455b | 605 | { |
64bc7a2f DM |
606 | /* not used */ |
607 | ||
cc04455b DM |
608 | return TRUE; |
609 | } | |
610 | ||
e9120d9a | 611 | static int |
64bc7a2f | 612 | client_monitors_config(QXLInstance *qin, VDAgentMonitorsConfig *monitors_config) |
cc04455b | 613 | { |
64bc7a2f DM |
614 | /* not used */ |
615 | ||
cc04455b DM |
616 | return 0; |
617 | } | |
618 | ||
e9120d9a TL |
619 | static void |
620 | set_client_capabilities( | |
621 | QXLInstance *qin, | |
622 | uint8_t client_present, | |
623 | uint8_t caps[58] | |
624 | ) { | |
64bc7a2f | 625 | SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance); |
cc04455b | 626 | |
a3fd8291 | 627 | DPRINTF(1, "present %d caps %d", client_present, caps[0]); |
64bc7a2f DM |
628 | |
629 | if (spice_screen->on_client_connected && client_present) { | |
630 | spice_screen->on_client_connected(spice_screen); | |
cc04455b | 631 | } |
64bc7a2f DM |
632 | if (spice_screen->on_client_disconnected && !client_present) { |
633 | spice_screen->on_client_disconnected(spice_screen); | |
cc04455b DM |
634 | } |
635 | } | |
636 | ||
6faab5d5 DM |
637 | static int client_count = 0; |
638 | ||
e9120d9a | 639 | static void |
64bc7a2f | 640 | client_connected(SpiceScreen *spice_screen) |
6faab5d5 | 641 | { |
6faab5d5 | 642 | client_count++; |
a0579497 | 643 | |
a3fd8291 | 644 | DPRINTF(1, "client_count = %d", client_count); |
6faab5d5 DM |
645 | } |
646 | ||
e9120d9a | 647 | static void |
64bc7a2f | 648 | client_disconnected(SpiceScreen *spice_screen) |
e9120d9a | 649 | { |
6faab5d5 DM |
650 | if (client_count > 0) { |
651 | client_count--; | |
a3fd8291 | 652 | DPRINTF(1, "client_count = %d", client_count); |
6faab5d5 DM |
653 | exit(0); // fixme: cleanup? |
654 | } | |
655 | } | |
656 | ||
e9120d9a | 657 | static void |
64bc7a2f | 658 | do_conn_timeout(void *opaque) |
f6cb554c | 659 | { |
64bc7a2f | 660 | // SpiceScreen *spice_screen = opaque; |
f6cb554c DM |
661 | |
662 | if (client_count <= 0) { | |
a0579497 | 663 | printf("connection timeout - stopping server\n"); |
f6cb554c DM |
664 | exit (0); // fixme: cleanup? |
665 | } | |
666 | } | |
667 | ||
c783f726 | 668 | |
cc04455b DM |
669 | QXLInterface display_sif = { |
670 | .base = { | |
671 | .type = SPICE_INTERFACE_QXL, | |
64bc7a2f | 672 | .description = "spiceterm display server", |
cc04455b DM |
673 | .major_version = SPICE_INTERFACE_QXL_MAJOR, |
674 | .minor_version = SPICE_INTERFACE_QXL_MINOR | |
675 | }, | |
676 | .attache_worker = attache_worker, | |
677 | .set_compression_level = set_compression_level, | |
678 | .set_mm_time = set_mm_time, | |
679 | .get_init_info = get_init_info, | |
680 | ||
681 | /* the callbacks below are called from spice server thread context */ | |
682 | .get_command = get_command, | |
683 | .req_cmd_notification = req_cmd_notification, | |
684 | .release_resource = release_resource, | |
685 | .get_cursor_command = get_cursor_command, | |
686 | .req_cursor_notification = req_cursor_notification, | |
687 | .notify_update = notify_update, | |
688 | .flush_resources = flush_resources, | |
689 | .client_monitors_config = client_monitors_config, | |
690 | .set_client_capabilities = set_client_capabilities, | |
691 | }; | |
692 | ||
cc04455b | 693 | |
e9120d9a TL |
694 | void |
695 | spice_screen_draw_char( | |
696 | SpiceScreen *spice_screen, | |
697 | int x, | |
698 | int y, | |
699 | gunichar2 ch, | |
700 | TextAttributes attrib | |
701 | ) { | |
22e5ba02 DM |
702 | int fg, bg; |
703 | ||
1d11aa12 | 704 | int invers; |
22e5ba02 | 705 | if (attrib.invers) { |
1d11aa12 DM |
706 | invers = attrib.selected ? 0 : 1; |
707 | } else { | |
708 | invers = attrib.selected ? 1 : 0; | |
e9120d9a | 709 | } |
1d11aa12 DM |
710 | |
711 | if (invers) { | |
22e5ba02 DM |
712 | bg = attrib.fgcol; |
713 | fg = attrib.bgcol; | |
714 | } else { | |
715 | bg = attrib.bgcol; | |
716 | fg = attrib.fgcol; | |
717 | } | |
718 | ||
719 | if (attrib.bold) { | |
720 | fg += 8; | |
721 | } | |
722 | ||
723 | // unsuported attributes = (attrib.blink || attrib.unvisible) | |
724 | ||
abc13312 DM |
725 | int c = vt_fontmap[ch]; |
726 | ||
22e5ba02 | 727 | SimpleSpiceUpdate *update; |
b1f67c20 | 728 | update = spice_screen_draw_char_cmd(spice_screen, x, y, c, fg, bg, attrib.uline); |
64bc7a2f | 729 | push_command(spice_screen, &update->ext); |
22e5ba02 DM |
730 | } |
731 | ||
64bc7a2f | 732 | SpiceScreen * |
e9120d9a TL |
733 | spice_screen_new( |
734 | SpiceCoreInterface *core, | |
735 | uint32_t width, | |
736 | uint32_t height, | |
737 | SpiceTermOptions *opts | |
738 | ) { | |
64bc7a2f | 739 | SpiceScreen *spice_screen = g_new0(SpiceScreen, 1); |
cc04455b | 740 | SpiceServer* server = spice_server_new(); |
1631c5a9 DM |
741 | char *x509_key_file = "/etc/pve/local/pve-ssl.key"; |
742 | char *x509_cert_file = "/etc/pve/local/pve-ssl.pem"; | |
743 | char *x509_cacert_file = "/etc/pve/pve-root-ca.pem"; | |
744 | char *x509_key_password = NULL; | |
745 | char *x509_dh_file = NULL; | |
9d53e332 | 746 | char *tls_ciphers = "HIGH"; |
1631c5a9 | 747 | |
8f53ae81 DM |
748 | spice_screen->width = width; |
749 | spice_screen->height = height; | |
750 | ||
b52b9534 DM |
751 | g_cond_init(&spice_screen->command_cond); |
752 | g_mutex_init(&spice_screen->command_mutex); | |
63a34f43 | 753 | |
64bc7a2f DM |
754 | spice_screen->on_client_connected = client_connected, |
755 | spice_screen->on_client_disconnected = client_disconnected, | |
6faab5d5 | 756 | |
64bc7a2f DM |
757 | spice_screen->qxl_instance.base.sif = &display_sif.base; |
758 | spice_screen->qxl_instance.id = 0; | |
cc04455b | 759 | |
64bc7a2f DM |
760 | spice_screen->core = core; |
761 | spice_screen->server = server; | |
762 | ||
68c2b067 DM |
763 | if (opts->addr) { |
764 | printf("listening on '%s:%d' (TLS)\n", opts->addr, opts->port); | |
765 | spice_server_set_addr(server, opts->addr, 0); | |
766 | } else { | |
767 | printf("listening on '*:%d' (TLS)\n", opts->port); | |
768 | } | |
769 | ||
1631c5a9 DM |
770 | // spice_server_set_port(spice_server, port); |
771 | //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF); | |
772 | ||
e9120d9a TL |
773 | spice_server_set_tls( |
774 | server, opts->port, | |
775 | x509_cacert_file, | |
776 | x509_cert_file, | |
777 | x509_key_file, | |
778 | x509_key_password, | |
779 | x509_dh_file, | |
780 | tls_ciphers | |
781 | ); | |
1631c5a9 | 782 | |
68c2b067 | 783 | if (opts->noauth) { |
1631c5a9 | 784 | spice_server_set_noauth(server); |
68c2b067 | 785 | } else { |
16ca49da TL |
786 | char *ticket = getenv("SPICE_TICKET"); |
787 | if (ticket) { | |
788 | spice_server_set_ticket(server, ticket, 300, 0, 0); | |
68c2b067 | 789 | } |
1631c5a9 | 790 | } |
64bc7a2f | 791 | |
120ce266 DC |
792 | spice_server_set_exit_on_disconnect(server, 1); |
793 | ||
1d7f2da4 DM |
794 | int res = spice_server_init(server, core); |
795 | if (res != 0) { | |
796 | g_error("spice_server_init failed, res = %d\n", res); | |
797 | } | |
cc04455b DM |
798 | |
799 | cursor_init(); | |
f6cb554c | 800 | |
ecc34bb2 DM |
801 | if (opts->timeout > 0) { |
802 | spice_screen->conn_timeout_timer = core->timer_add(do_conn_timeout, spice_screen); | |
803 | spice_screen->core->timer_start(spice_screen->conn_timeout_timer, opts->timeout*1000); | |
804 | } | |
f6cb554c | 805 | |
38e3e912 DM |
806 | spice_server_add_interface(spice_screen->server, &spice_screen->qxl_instance.base); |
807 | ||
64bc7a2f | 808 | return spice_screen; |
cc04455b | 809 | } |
c783f726 | 810 | |
e9120d9a TL |
811 | void |
812 | spice_screen_resize( | |
813 | SpiceScreen *spice_screen, | |
814 | uint32_t width, | |
815 | uint32_t height | |
816 | ) { | |
c783f726 DM |
817 | if (spice_screen->width == width && spice_screen->height == height) { |
818 | return; | |
819 | } | |
820 | ||
821 | discard_pending_commands(spice_screen); | |
822 | ||
b52b9534 | 823 | spice_qxl_destroy_primary_surface(&spice_screen->qxl_instance, 0); |
e9120d9a | 824 | |
c783f726 DM |
825 | create_primary_surface(spice_screen, width, height); |
826 | ||
827 | spice_screen_clear(spice_screen, 0, 0, width, height); | |
828 | } |