1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2010 Red Hat, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "config.h"
19
20 #ifdef HAVE_SYS_TYPES_H
21 #include <sys/types.h>
22 #endif
23
24 #include "spice-client.h"
25 #include "spice-common.h"
26
27 #include "spice-marshal.h"
28 #include "spice-channel-priv.h"
29 #include "spice-session-priv.h"
30 #include "channel-display-priv.h"
31 #include "decode.h"
32
33 /**
34 * SECTION:channel-display
35 * @short_description: remote display area
36 * @title: Display Channel
37 * @section_id:
38 * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
39 * @stability: Stable
40 * @include: spice-client.h
41 *
42 * A class that handles the rendering of the remote display and inform
43 * of its updates.
44 *
45 * The creation of the main graphic buffer is signaled with
46 * #SpiceDisplayChannel::display-primary-create.
47 *
48 * The update of regions is notified by
49 * #SpiceDisplayChannel::display-invalidate signals.
50 */
51
52 #define MONITORS_MAX 256
53
54 struct _SpiceDisplayChannelPrivate {
55 GHashTable *surfaces;
56 display_surface *primary;
57 display_cache *images;
58 display_cache *palettes;
59 SpiceImageCache image_cache;
60 SpicePaletteCache palette_cache;
61 SpiceImageSurfaces image_surfaces;
62 SpiceGlzDecoderWindow *glz_window;
63 display_stream **streams;
64 int nstreams;
65 gboolean mark;
66 guint mark_false_event_id;
67 GArray *monitors;
68 guint monitors_max;
69 gboolean enable_adaptive_streaming;
70 SpiceGlScanout scanout;
71 };
72
73 G_DEFINE_TYPE_WITH_PRIVATE(SpiceDisplayChannel, spice_display_channel, SPICE_TYPE_CHANNEL)
74
75 /* Properties */
76 enum {
77 PROP_0,
78 PROP_WIDTH,
79 PROP_HEIGHT,
80 PROP_MONITORS,
81 PROP_MONITORS_MAX,
82 PROP_GL_SCANOUT,
83 };
84
85 enum {
86 SPICE_DISPLAY_PRIMARY_CREATE,
87 SPICE_DISPLAY_PRIMARY_DESTROY,
88 SPICE_DISPLAY_INVALIDATE,
89 SPICE_DISPLAY_MARK,
90 SPICE_DISPLAY_GL_DRAW,
91 SPICE_DISPLAY_STREAMING_MODE,
92 SPICE_DISPLAY_OVERLAY,
93
94 SPICE_DISPLAY_LAST_SIGNAL,
95 };
96
97 static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
98
99 static void spice_display_channel_up(SpiceChannel *channel);
100 static void channel_set_handlers(SpiceChannelClass *klass);
101
102 static void clear_surfaces(SpiceChannel *channel, gboolean keep_primary);
103 static void clear_streams(SpiceChannel *channel);
104 static display_surface *find_surface(SpiceDisplayChannelPrivate *c, guint32 surface_id);
105 static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating);
106 static void spice_display_channel_set_capabilities(SpiceChannel *channel);
107 static void destroy_canvas(display_surface *surface);
108 static void display_stream_destroy(gpointer st);
109 static void display_session_mm_time_reset_cb(SpiceSession *session, gpointer data);
110 static SpiceGlScanout* spice_gl_scanout_copy(const SpiceGlScanout *scanout);
111
112 G_DEFINE_BOXED_TYPE(SpiceGlScanout, spice_gl_scanout,
113 (GBoxedCopyFunc)spice_gl_scanout_copy,
114 (GBoxedFreeFunc)spice_gl_scanout_free)
115
116 /* ------------------------------------------------------------------ */
117
118 static SpiceGlScanout*
spice_gl_scanout_copy(const SpiceGlScanout * scanout)119 spice_gl_scanout_copy(const SpiceGlScanout *scanout)
120 {
121 SpiceGlScanout *so = g_new(SpiceGlScanout, 1);
122
123 *so = *scanout;
124 so->fd = dup(so->fd);
125
126 return so;
127 }
128
129 /**
130 * spice_gl_scanout_free:
131 * @scanout: a #SpiceGlScanout
132 *
133 * Frees the @scanout.
134 *
135 * Since: 0.31
136 */
137 void
spice_gl_scanout_free(SpiceGlScanout * scanout)138 spice_gl_scanout_free(SpiceGlScanout *scanout)
139 {
140 close(scanout->fd);
141
142 g_free(scanout);
143 }
144
spice_display_channel_dispose(GObject * object)145 static void spice_display_channel_dispose(GObject *object)
146 {
147 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
148
149 if (c->mark_false_event_id != 0) {
150 g_source_remove(c->mark_false_event_id);
151 c->mark_false_event_id = 0;
152 }
153
154 if (c->scanout.fd >= 0) {
155 close(c->scanout.fd);
156 c->scanout.fd = -1;
157 }
158
159 if (G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose)
160 G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose(object);
161 }
162
spice_display_channel_finalize(GObject * object)163 static void spice_display_channel_finalize(GObject *object)
164 {
165 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
166
167 g_clear_pointer(&c->monitors, g_array_unref);
168 clear_surfaces(SPICE_CHANNEL(object), FALSE);
169 g_hash_table_unref(c->surfaces);
170 clear_streams(SPICE_CHANNEL(object));
171 g_clear_pointer(&c->palettes, cache_free);
172
173 if (G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize)
174 G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize(object);
175 }
176
spice_display_channel_constructed(GObject * object)177 static void spice_display_channel_constructed(GObject *object)
178 {
179 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
180 SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
181
182 g_return_if_fail(s != NULL);
183 spice_session_get_caches(s, &c->images, &c->glz_window);
184 c->palettes = cache_new(g_free);
185
186 g_return_if_fail(c->glz_window != NULL);
187 g_return_if_fail(c->images != NULL);
188 g_return_if_fail(c->palettes != NULL);
189
190 c->monitors = g_array_new(FALSE, TRUE, sizeof(SpiceDisplayMonitorConfig));
191 spice_g_signal_connect_object(s, "mm-time-reset",
192 G_CALLBACK(display_session_mm_time_reset_cb),
193 SPICE_CHANNEL(object), 0);
194
195 spice_display_channel_set_capabilities(SPICE_CHANNEL(object));
196
197 if (G_OBJECT_CLASS(spice_display_channel_parent_class)->constructed)
198 G_OBJECT_CLASS(spice_display_channel_parent_class)->constructed(object);
199 }
200
spice_display_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)201 static void spice_display_get_property(GObject *object,
202 guint prop_id,
203 GValue *value,
204 GParamSpec *pspec)
205 {
206 SpiceDisplayChannel *channel = SPICE_DISPLAY_CHANNEL(object);
207 SpiceDisplayChannelPrivate *c = channel->priv;
208
209 switch (prop_id) {
210 case PROP_WIDTH: {
211 g_value_set_uint(value, c->primary ? c->primary->width : 0);
212 break;
213 }
214 case PROP_HEIGHT: {
215 g_value_set_uint(value, c->primary ? c->primary->height : 0);
216 break;
217 }
218 case PROP_MONITORS: {
219 g_value_set_boxed(value, c->monitors);
220 break;
221 }
222 case PROP_MONITORS_MAX: {
223 g_value_set_uint(value, c->monitors_max);
224 break;
225 }
226 case PROP_GL_SCANOUT: {
227 g_value_set_static_boxed(value, spice_display_channel_get_gl_scanout(channel));
228 break;
229 }
230 default:
231 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
232 break;
233 }
234 }
235
spice_display_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)236 static void spice_display_set_property(GObject *object,
237 guint prop_id,
238 const GValue *value,
239 GParamSpec *pspec)
240 {
241 switch (prop_id) {
242 default:
243 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
244 break;
245 }
246 }
247
248 /* main or coroutine context */
spice_display_channel_reset(SpiceChannel * channel,gboolean migrating)249 static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating)
250 {
251 /* palettes, images, and glz_window are cleared in the session */
252 clear_streams(channel);
253 clear_surfaces(channel, TRUE);
254
255 SPICE_CHANNEL_CLASS(spice_display_channel_parent_class)->channel_reset(channel, migrating);
256 }
257
spice_display_channel_class_init(SpiceDisplayChannelClass * klass)258 static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass)
259 {
260 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
261 SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
262
263 gobject_class->finalize = spice_display_channel_finalize;
264 gobject_class->dispose = spice_display_channel_dispose;
265 gobject_class->get_property = spice_display_get_property;
266 gobject_class->set_property = spice_display_set_property;
267 gobject_class->constructed = spice_display_channel_constructed;
268
269 channel_class->channel_up = spice_display_channel_up;
270 channel_class->channel_reset = spice_display_channel_reset;
271
272 g_object_class_install_property
273 (gobject_class, PROP_HEIGHT,
274 g_param_spec_uint("height",
275 "Display height",
276 "The primary surface height",
277 0, G_MAXUINT, 0,
278 G_PARAM_READABLE |
279 G_PARAM_STATIC_STRINGS));
280
281 g_object_class_install_property
282 (gobject_class, PROP_WIDTH,
283 g_param_spec_uint("width",
284 "Display width",
285 "The primary surface width",
286 0, G_MAXUINT, 0,
287 G_PARAM_READABLE |
288 G_PARAM_STATIC_STRINGS));
289
290 /**
291 * SpiceDisplayChannel:monitors:
292 *
293 * Current monitors configuration.
294 *
295 * Since: 0.13
296 */
297 g_object_class_install_property
298 (gobject_class, PROP_MONITORS,
299 g_param_spec_boxed("monitors",
300 "Display monitors",
301 "The monitors configuration",
302 G_TYPE_ARRAY,
303 G_PARAM_READABLE |
304 G_PARAM_STATIC_STRINGS));
305
306 /**
307 * SpiceDisplayChannel:monitors-max:
308 *
309 * The maximum number of monitors the server or guest supports.
310 * May change during client lifetime, for instance guest may
311 * reboot or dynamically adjust this.
312 *
313 * Since: 0.13
314 */
315 g_object_class_install_property
316 (gobject_class, PROP_MONITORS_MAX,
317 g_param_spec_uint("monitors-max",
318 "Max display monitors",
319 "The current maximum number of monitors",
320 1, MONITORS_MAX, 1,
321 G_PARAM_READABLE |
322 G_PARAM_STATIC_STRINGS));
323
324 /**
325 * SpiceDisplayChannel:gl-scanout:
326 *
327 * The last #SpiceGlScanout received.
328 *
329 * Since: 0.31
330 */
331 g_object_class_install_property
332 (gobject_class, PROP_GL_SCANOUT,
333 g_param_spec_boxed("gl-scanout",
334 "GL scanout",
335 "GL scanout",
336 SPICE_TYPE_GL_SCANOUT,
337 G_PARAM_READABLE |
338 G_PARAM_STATIC_STRINGS));
339
340 /**
341 * SpiceDisplayChannel::display-primary-create:
342 * @display: the #SpiceDisplayChannel that emitted the signal
343 * @format: %SPICE_SURFACE_FMT_32_xRGB or %SPICE_SURFACE_FMT_16_555;
344 * @width: width resolution
345 * @height: height resolution
346 * @stride: the buffer stride ("width" padding)
347 * @shmid: identifier of the shared memory segment associated with
348 * the @imgdata, or -1 if not shm
349 * @imgdata: pointer to surface buffer
350 *
351 * The #SpiceDisplayChannel::display-primary-create signal
352 * provides main display buffer data.
353 **/
354 signals[SPICE_DISPLAY_PRIMARY_CREATE] =
355 g_signal_new("display-primary-create",
356 G_OBJECT_CLASS_TYPE(gobject_class),
357 G_SIGNAL_RUN_FIRST,
358 G_STRUCT_OFFSET(SpiceDisplayChannelClass,
359 display_primary_create),
360 NULL, NULL,
361 g_cclosure_user_marshal_VOID__INT_INT_INT_INT_INT_POINTER,
362 G_TYPE_NONE,
363 6,
364 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
365 G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER);
366
367 /**
368 * SpiceDisplayChannel::display-primary-destroy:
369 * @display: the #SpiceDisplayChannel that emitted the signal
370 *
371 * The #SpiceDisplayChannel::display-primary-destroy signal is
372 * emitted when the primary surface is freed and should not be
373 * accessed anymore.
374 **/
375 signals[SPICE_DISPLAY_PRIMARY_DESTROY] =
376 g_signal_new("display-primary-destroy",
377 G_OBJECT_CLASS_TYPE(gobject_class),
378 G_SIGNAL_RUN_FIRST,
379 G_STRUCT_OFFSET(SpiceDisplayChannelClass,
380 display_primary_destroy),
381 NULL, NULL,
382 g_cclosure_marshal_VOID__VOID,
383 G_TYPE_NONE,
384 0);
385
386 /**
387 * SpiceDisplayChannel::display-invalidate:
388 * @display: the #SpiceDisplayChannel that emitted the signal
389 * @x: x position
390 * @y: y position
391 * @width: width
392 * @height: height
393 *
394 * The #SpiceDisplayChannel::display-invalidate signal is emitted
395 * when the rectangular region x/y/w/h of the primary buffer is
396 * updated.
397 **/
398 signals[SPICE_DISPLAY_INVALIDATE] =
399 g_signal_new("display-invalidate",
400 G_OBJECT_CLASS_TYPE(gobject_class),
401 G_SIGNAL_RUN_FIRST,
402 G_STRUCT_OFFSET(SpiceDisplayChannelClass,
403 display_invalidate),
404 NULL, NULL,
405 g_cclosure_user_marshal_VOID__INT_INT_INT_INT,
406 G_TYPE_NONE,
407 4,
408 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
409
410 /**
411 * SpiceDisplayChannel::display-mark:
412 * @display: the #SpiceDisplayChannel that emitted the signal
413 * @mark: %TRUE when the display mark has been received
414 *
415 * The #SpiceDisplayChannel::display-mark signal is emitted when
416 * the %RED_DISPLAY_MARK command is received, and the display
417 * should be exposed.
418 **/
419 signals[SPICE_DISPLAY_MARK] =
420 g_signal_new("display-mark",
421 G_OBJECT_CLASS_TYPE(gobject_class),
422 G_SIGNAL_RUN_FIRST,
423 G_STRUCT_OFFSET(SpiceDisplayChannelClass,
424 display_mark),
425 NULL, NULL,
426 g_cclosure_marshal_VOID__INT,
427 G_TYPE_NONE,
428 1,
429 G_TYPE_INT);
430
431 /**
432 * SpiceDisplayChannel::gl-draw:
433 * @display: the #SpiceDisplayChannel that emitted the signal
434 * @x: x position
435 * @y: y position
436 * @width: width
437 * @height: height
438 *
439 * The #SpiceDisplayChannel::gl-draw signal is emitted when the
440 * rectangular region x/y/w/h of the GL scanout is updated and
441 * must be drawn. When the draw is finished, you must call
442 * spice_display_gl_draw_done() in order to release the GL
443 * resources.
444 *
445 * Since: 0.31
446 **/
447 signals[SPICE_DISPLAY_GL_DRAW] =
448 g_signal_new("gl-draw",
449 G_OBJECT_CLASS_TYPE(gobject_class),
450 0, 0, NULL, NULL,
451 g_cclosure_user_marshal_VOID__UINT_UINT_UINT_UINT,
452 G_TYPE_NONE,
453 4,
454 G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
455
456 /**
457 * SpiceDisplayChannel::streaming-mode:
458 * @display: the #SpiceDisplayChannel that emitted the signal
459 * @streaming_mode: %TRUE when it's streaming mode
460 *
461 * Return: handle for the display window if possible
462 *
463 * The #SpiceDisplayChannel::streaming-mode signal is emitted when
464 * spice server is working in streaming mode.
465 *
466 * Since: 0.35
467 *
468 * Deprecated: 0.36: use #SpiceDisplayChannel::gst-video-overlay
469 * instead
470 **/
471 signals[SPICE_DISPLAY_STREAMING_MODE] =
472 g_signal_new("streaming-mode",
473 G_OBJECT_CLASS_TYPE(gobject_class),
474 G_SIGNAL_DEPRECATED, 0,
475 NULL, NULL, NULL,
476 G_TYPE_POINTER,
477 1,
478 G_TYPE_BOOLEAN);
479
480 /**
481 * SpiceDisplayChannel::gst-video-overlay:
482 * @display: the #SpiceDisplayChannel that emitted the signal
483 * @pipeline: pointer to GStreamer's pipeline
484 *
485 * The #SpiceDisplayChannel::gst-video-overlay signal is emitted when
486 * pipeline is ready and can be passed to widget to register GStreamer
487 * overlay interface and other GStreamer callbacks.
488 *
489 * Returns: %TRUE if the overlay is being set
490 *
491 * Since: 0.36
492 **/
493 signals[SPICE_DISPLAY_OVERLAY] =
494 g_signal_new("gst-video-overlay",
495 G_OBJECT_CLASS_TYPE(gobject_class),
496 0, 0,
497 NULL, NULL,
498 g_cclosure_user_marshal_BOOLEAN__POINTER,
499 G_TYPE_BOOLEAN,
500 1,
501 GST_TYPE_PIPELINE);
502
503 channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
504 }
505
506 /**
507 * spice_display_get_primary:
508 * @channel: a #SpiceDisplayChannel
509 * @surface_id: a surface id
510 * @primary: a #SpiceDisplayPrimary
511 *
512 * Retrieve primary display surface @surface_id.
513 *
514 * Returns: %TRUE if the primary surface was found and its details
515 * collected in @primary.
516 *
517 * Deprecated: 0.35: use spice_display_channel_get_primary() instead.
518 */
spice_display_get_primary(SpiceChannel * channel,guint32 surface_id,SpiceDisplayPrimary * primary)519 gboolean spice_display_get_primary(SpiceChannel *channel, guint32 surface_id,
520 SpiceDisplayPrimary *primary)
521 {
522 return spice_display_channel_get_primary(channel, surface_id, primary);
523 }
524
525 /**
526 * spice_display_channel_get_primary:
527 * @channel: a #SpiceDisplayChannel
528 * @surface_id: a surface id
529 * @primary: a #SpiceDisplayPrimary
530 *
531 * Retrieve primary display surface @surface_id.
532 *
533 * Returns: %TRUE if the primary surface was found and its details
534 * collected in @primary.
535 *
536 * Since: 0.35
537 */
spice_display_channel_get_primary(SpiceChannel * channel,guint32 surface_id,SpiceDisplayPrimary * primary)538 gboolean spice_display_channel_get_primary(SpiceChannel *channel, guint32 surface_id,
539 SpiceDisplayPrimary *primary)
540 {
541 g_return_val_if_fail(SPICE_IS_DISPLAY_CHANNEL(channel), FALSE);
542 g_return_val_if_fail(primary != NULL, FALSE);
543
544 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
545 display_surface *surface = find_surface(c, surface_id);
546
547 if (surface == NULL)
548 return FALSE;
549
550 g_return_val_if_fail(surface->primary, FALSE);
551
552 primary->format = surface->format;
553 primary->width = surface->width;
554 primary->height = surface->height;
555 primary->stride = surface->stride;
556 primary->shmid = -1;
557 primary->data = surface->data;
558 primary->marked = c->mark;
559 CHANNEL_DEBUG(channel, "get primary %p", primary->data);
560
561 return TRUE;
562 }
563
564 /**
565 * spice_display_change_preferred_compression:
566 * @channel: a #SpiceDisplayChannel
567 * @compression: a #SpiceImageCompression
568 *
569 * Tells the spice server to change the preferred image compression
570 * for the @channel.
571 *
572 * Since: 0.31
573 * Deprecated: 0.35: use spice_display_channel_change_preferred_compression() instead.
574 */
spice_display_change_preferred_compression(SpiceChannel * channel,gint compression)575 void spice_display_change_preferred_compression(SpiceChannel *channel, gint compression)
576 {
577 spice_display_channel_change_preferred_compression(channel, compression);
578 }
579
580 /**
581 * spice_display_channel_change_preferred_compression:
582 * @channel: a #SpiceDisplayChannel
583 * @compression: a #SpiceImageCompression
584 *
585 * Tells the spice server to change the preferred image compression
586 * for the @channel.
587 *
588 * Since: 0.35
589 */
spice_display_channel_change_preferred_compression(SpiceChannel * channel,gint compression)590 void spice_display_channel_change_preferred_compression(SpiceChannel *channel, gint compression)
591 {
592 SpiceMsgOut *out;
593 SpiceMsgcDisplayPreferredCompression pref_comp_msg;
594
595 g_return_if_fail(SPICE_IS_DISPLAY_CHANNEL(channel));
596 g_return_if_fail(compression > SPICE_IMAGE_COMPRESSION_INVALID &&
597 compression < SPICE_IMAGE_COMPRESSION_ENUM_END);
598
599 if (!spice_channel_test_capability(channel, SPICE_DISPLAY_CAP_PREF_COMPRESSION)) {
600 CHANNEL_DEBUG(channel, "does not have capability to change the preferred compression");
601 return;
602 }
603
604 CHANNEL_DEBUG(channel, "changing preferred compression to %d", compression);
605
606 pref_comp_msg.image_compression = compression;
607 out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION);
608 out->marshallers->msgc_display_preferred_compression(out->marshaller, &pref_comp_msg);
609 spice_msg_out_send_internal(out);
610 }
611
spice_display_send_client_preferred_video_codecs(SpiceChannel * channel,const GArray * codecs)612 static void spice_display_send_client_preferred_video_codecs(SpiceChannel *channel,
613 const GArray *codecs)
614 {
615 guint i;
616 SpiceMsgOut *out;
617 SpiceMsgcDisplayPreferredVideoCodecType *msg;
618
619 msg = g_malloc0(sizeof(SpiceMsgcDisplayPreferredVideoCodecType) +
620 (sizeof(SpiceVideoCodecType) * codecs->len));
621 msg->num_of_codecs = codecs->len;
622 for (i = 0; i < codecs->len; i++) {
623 msg->codecs[i] = g_array_index(codecs, gint, i);
624 }
625
626 out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_PREFERRED_VIDEO_CODEC_TYPE);
627 out->marshallers->msgc_display_preferred_video_codec_type(out->marshaller, msg);
628 spice_msg_out_send_internal(out);
629 g_free(msg);
630 }
631
632 /**
633 * spice_display_change_preferred_video_codec_type:
634 * @channel: a #SpiceDisplayChannel
635 * @codec_type: a #SpiceVideoCodecType
636 *
637 * Tells the spice server to change the preferred video codec type for
638 * streaming in @channel. Application can set only one preferred video codec per
639 * display channel.
640 *
641 * Since: 0.34
642 * Deprecated: 0.35: use spice_display_channel_change_preferred_video_codec_type() instead.
643 */
spice_display_change_preferred_video_codec_type(SpiceChannel * channel,gint codec_type)644 void spice_display_change_preferred_video_codec_type(SpiceChannel *channel, gint codec_type)
645 {
646 spice_display_channel_change_preferred_video_codec_type(channel, codec_type);
647 }
648
649 /**
650 * spice_display_channel_change_preferred_video_codec_type:
651 * @channel: a #SpiceDisplayChannel
652 * @codec_type: a #SpiceVideoCodecType
653 *
654 * Tells the spice server to change the preferred video codec type for
655 * streaming in @channel. Application can set only one preferred video codec per
656 * display channel.
657 *
658 * Since: 0.35
659 */
spice_display_channel_change_preferred_video_codec_type(SpiceChannel * channel,gint codec_type)660 void spice_display_channel_change_preferred_video_codec_type(SpiceChannel *channel, gint codec_type)
661 {
662 GArray *codecs;
663
664 g_return_if_fail(SPICE_IS_DISPLAY_CHANNEL(channel));
665 g_return_if_fail(codec_type >= SPICE_VIDEO_CODEC_TYPE_MJPEG &&
666 codec_type < SPICE_VIDEO_CODEC_TYPE_ENUM_END);
667
668 if (!spice_channel_test_capability(channel, SPICE_DISPLAY_CAP_PREF_VIDEO_CODEC_TYPE)) {
669 CHANNEL_DEBUG(channel, "does not have capability to change the preferred video codec type");
670 return;
671 }
672
673 /* FIXME: We should detect video codecs that client machine can do hw
674 * decoding, store this information (as GArray) and send it to the server.
675 * This array can be rearranged to have @codec_type in the front (which is
676 * the preferred for the client side) */
677 CHANNEL_DEBUG(channel, "changing preferred video codec type to %s", gst_opts[codec_type].name);
678 codecs = g_array_new(FALSE, FALSE, sizeof(gint));
679 g_array_append_val(codecs, codec_type);
680 spice_display_send_client_preferred_video_codecs(channel, codecs);
681 g_array_unref(codecs);
682 }
683
684 /**
685 * spice_display_get_gl_scanout:
686 * @channel: a #SpiceDisplayChannel
687 *
688 * Retrieves the GL scanout if available
689 *
690 * Returns: the current GL scanout, or %NULL if none or not valid
691 *
692 * Since: 0.31
693 * Deprecated: 0.35: use spice_display_channel_get_gl_scanout() instead.
694 **/
695 const SpiceGlScanout *
spice_display_get_gl_scanout(SpiceDisplayChannel * channel)696 spice_display_get_gl_scanout(SpiceDisplayChannel *channel)
697 {
698 return spice_display_channel_get_gl_scanout(channel);
699 }
700
701 /**
702 * spice_display_channel_get_gl_scanout:
703 * @channel: a #SpiceDisplayChannel
704 *
705 * Retrieves the GL scanout if available
706 *
707 * Returns: the current GL scanout, or %NULL if none or not valid
708 *
709 * Since: 0.35
710 **/
711 const SpiceGlScanout *
spice_display_channel_get_gl_scanout(SpiceDisplayChannel * channel)712 spice_display_channel_get_gl_scanout(SpiceDisplayChannel *channel)
713 {
714 g_return_val_if_fail(SPICE_IS_DISPLAY_CHANNEL(channel), NULL);
715
716 return channel->priv->scanout.fd != -1 ? &channel->priv->scanout : NULL;
717 }
718
719 /* ------------------------------------------------------------------ */
720
image_put(SpiceImageCache * cache,uint64_t id,pixman_image_t * image)721 static void image_put(SpiceImageCache *cache, uint64_t id, pixman_image_t *image)
722 {
723 SpiceDisplayChannelPrivate *c =
724 SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
725
726 cache_add(c->images, id, pixman_image_ref(image));
727 }
728
729 typedef struct _WaitImageData
730 {
731 gboolean lossy;
732 SpiceImageCache *cache;
733 uint64_t id;
734 pixman_image_t *image;
735 } WaitImageData;
736
wait_image(gpointer data)737 static gboolean wait_image(gpointer data)
738 {
739 gboolean lossy;
740 WaitImageData *wait = data;
741 SpiceDisplayChannelPrivate *c =
742 SPICE_CONTAINEROF(wait->cache, SpiceDisplayChannelPrivate, image_cache);
743 pixman_image_t *image = cache_find_lossy(c->images, wait->id, &lossy);
744
745 if (!image || (lossy && !wait->lossy))
746 return FALSE;
747
748 wait->image = pixman_image_ref(image);
749
750 return TRUE;
751 }
752
image_get(SpiceImageCache * cache,uint64_t id)753 static pixman_image_t *image_get(SpiceImageCache *cache, uint64_t id)
754 {
755 WaitImageData wait = {
756 .lossy = TRUE,
757 .cache = cache,
758 .id = id,
759 .image = NULL
760 };
761 if (!g_coroutine_condition_wait(g_coroutine_self(), wait_image, &wait))
762 SPICE_DEBUG("wait image got cancelled");
763
764 return wait.image;
765 }
766
palette_put(SpicePaletteCache * cache,SpicePalette * palette)767 static void palette_put(SpicePaletteCache *cache, SpicePalette *palette)
768 {
769 SpiceDisplayChannelPrivate *c =
770 SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
771
772 cache_add(c->palettes, palette->unique,
773 g_memdup(palette, sizeof(SpicePalette) +
774 palette->num_ents * sizeof(palette->ents[0])));
775 }
776
palette_get(SpicePaletteCache * cache,uint64_t id)777 static SpicePalette *palette_get(SpicePaletteCache *cache, uint64_t id)
778 {
779 SpiceDisplayChannelPrivate *c =
780 SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
781
782 /* here the returned pointer is weak, no ref given to caller. it
783 * seems spice canvas usage is exclusively temporary, so it's ok.
784 * palette_release is a noop. */
785 return cache_find(c->palettes, id);
786 }
787
palette_remove(SpicePaletteCache * cache,uint64_t id)788 static void palette_remove(SpicePaletteCache *cache, uint64_t id)
789 {
790 SpiceDisplayChannelPrivate *c =
791 SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
792
793 cache_remove(c->palettes, id);
794 }
795
palette_release(SpicePaletteCache * cache,SpicePalette * palette)796 static void palette_release(SpicePaletteCache *cache, SpicePalette *palette)
797 {
798 /* there is no refcount of palette, see palette_get() */
799 }
800
image_put_lossy(SpiceImageCache * cache,uint64_t id,pixman_image_t * surface)801 static void image_put_lossy(SpiceImageCache *cache, uint64_t id,
802 pixman_image_t *surface)
803 {
804 SpiceDisplayChannelPrivate *c =
805 SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
806
807 #ifndef NDEBUG
808 g_warn_if_fail(cache_find(c->images, id) == NULL);
809 #endif
810
811 cache_add_lossy(c->images, id, pixman_image_ref(surface), TRUE);
812 }
813
image_replace_lossy(SpiceImageCache * cache,uint64_t id,pixman_image_t * surface)814 static void image_replace_lossy(SpiceImageCache *cache, uint64_t id,
815 pixman_image_t *surface)
816 {
817 SpiceDisplayChannelPrivate *c =
818 SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
819
820 cache_replace_lossy(c->images, id, pixman_image_ref(surface), FALSE);
821 }
822
image_get_lossless(SpiceImageCache * cache,uint64_t id)823 static pixman_image_t* image_get_lossless(SpiceImageCache *cache, uint64_t id)
824 {
825 WaitImageData wait = {
826 .lossy = FALSE,
827 .cache = cache,
828 .id = id,
829 .image = NULL
830 };
831 if (!g_coroutine_condition_wait(g_coroutine_self(), wait_image, &wait))
832 SPICE_DEBUG("wait lossless got cancelled");
833
834 return wait.image;
835 }
836
surfaces_get(SpiceImageSurfaces * surfaces,uint32_t surface_id)837 static SpiceCanvas *surfaces_get(SpiceImageSurfaces *surfaces,
838 uint32_t surface_id)
839 {
840 SpiceDisplayChannelPrivate *c =
841 SPICE_CONTAINEROF(surfaces, SpiceDisplayChannelPrivate, image_surfaces);
842
843 display_surface *s =
844 find_surface(c, surface_id);
845
846 return s ? s->canvas : NULL;
847 }
848
849 static SpiceImageCacheOps image_cache_ops = {
850 .put = image_put,
851 .get = image_get,
852
853 .put_lossy = image_put_lossy,
854 .replace_lossy = image_replace_lossy,
855 .get_lossless = image_get_lossless,
856 };
857
858 static SpicePaletteCacheOps palette_cache_ops = {
859 .put = palette_put,
860 .get = palette_get,
861 .release = palette_release,
862 };
863
864 static SpiceImageSurfacesOps image_surfaces_ops = {
865 .get = surfaces_get
866 };
867
spice_display_channel_set_capabilities(SpiceChannel * channel)868 static void spice_display_channel_set_capabilities(SpiceChannel *channel)
869 {
870 SpiceSession *s = spice_channel_get_session(channel);
871 guint i;
872
873 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_SIZED_STREAM);
874 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG);
875 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_COMPOSITE);
876 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_A8_SURFACE);
877 #ifdef USE_LZ4
878 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_LZ4_COMPRESSION);
879 #endif
880 if (SPICE_DISPLAY_CHANNEL(channel)->priv->enable_adaptive_streaming) {
881 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_STREAM_REPORT);
882 }
883 if (spice_session_get_gl_scanout_enabled(s)) {
884 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_GL_SCANOUT);
885 }
886 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_MULTI_CODEC);
887 #ifdef HAVE_BUILTIN_MJPEG
888 spice_channel_set_capability(channel, SPICE_DISPLAY_CAP_CODEC_MJPEG);
889 #endif
890 for (i = 1; i < G_N_ELEMENTS(gst_opts); i++) {
891 if (gstvideo_has_codec(i)) {
892 spice_channel_set_capability(channel, gst_opts[i].cap);
893 } else {
894 SPICE_DEBUG("GStreamer does not support the %s codec", gst_opts[i].name);
895 }
896 }
897 }
898
destroy_surface(gpointer data)899 static void destroy_surface(gpointer data)
900 {
901 display_surface *surface = data;
902
903 destroy_canvas(surface);
904 g_free(surface);
905 }
906
spice_display_channel_init(SpiceDisplayChannel * channel)907 static void spice_display_channel_init(SpiceDisplayChannel *channel)
908 {
909 SpiceDisplayChannelPrivate *c;
910
911 c = channel->priv = spice_display_channel_get_instance_private(channel);
912
913 c->surfaces = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, destroy_surface);
914 c->image_cache.ops = &image_cache_ops;
915 c->palette_cache.ops = &palette_cache_ops;
916 c->image_surfaces.ops = &image_surfaces_ops;
917 c->monitors_max = 1;
918 c->scanout.fd = -1;
919
920 if (g_getenv("SPICE_DISABLE_ADAPTIVE_STREAMING")) {
921 SPICE_DEBUG("adaptive video disabled");
922 c->enable_adaptive_streaming = FALSE;
923 } else {
924 c->enable_adaptive_streaming = TRUE;
925 }
926 }
927
928 /* ------------------------------------------------------------------ */
929
create_canvas(SpiceChannel * channel,display_surface * surface)930 static int create_canvas(SpiceChannel *channel, display_surface *surface)
931 {
932 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
933
934 if (surface->primary) {
935 if (c->primary) {
936 if (c->primary->width == surface->width &&
937 c->primary->height == surface->height) {
938 CHANNEL_DEBUG(channel, "Reusing existing primary surface");
939 return 0;
940 }
941
942 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
943
944 g_hash_table_remove(c->surfaces, GINT_TO_POINTER(c->primary->surface_id));
945 }
946
947 CHANNEL_DEBUG(channel, "Create primary canvas");
948 }
949
950 surface->data = g_malloc0(surface->size);
951
952 g_return_val_if_fail(c->glz_window, 0);
953 g_warn_if_fail(surface->canvas == NULL);
954 g_warn_if_fail(surface->glz_decoder == NULL);
955 g_warn_if_fail(surface->zlib_decoder == NULL);
956 g_warn_if_fail(surface->jpeg_decoder == NULL);
957
958 surface->glz_decoder = glz_decoder_new(c->glz_window);
959 surface->zlib_decoder = zlib_decoder_new();
960 surface->jpeg_decoder = jpeg_decoder_new();
961
962 surface->canvas = canvas_create_for_data(surface->width,
963 surface->height,
964 surface->format,
965 surface->data,
966 surface->stride,
967 &c->image_cache,
968 &c->palette_cache,
969 &c->image_surfaces,
970 surface->glz_decoder,
971 surface->jpeg_decoder,
972 surface->zlib_decoder);
973
974 g_return_val_if_fail(surface->canvas != NULL, 0);
975 g_hash_table_insert(c->surfaces, GINT_TO_POINTER(surface->surface_id), surface);
976
977 if (surface->primary) {
978 g_warn_if_fail(c->primary == NULL);
979 c->primary = surface;
980 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_CREATE], 0,
981 surface->format, surface->width, surface->height,
982 surface->stride, -1, surface->data);
983
984 if (!spice_channel_test_capability(channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
985 g_array_set_size(c->monitors, 1);
986 SpiceDisplayMonitorConfig *config = &g_array_index(c->monitors, SpiceDisplayMonitorConfig, 0);
987 config->x = config->y = 0;
988 config->width = surface->width;
989 config->height = surface->height;
990 g_coroutine_object_notify(G_OBJECT(channel), "monitors");
991 }
992 }
993
994 return 0;
995 }
996
destroy_canvas(display_surface * surface)997 static void destroy_canvas(display_surface *surface)
998 {
999 if (surface == NULL)
1000 return;
1001
1002 glz_decoder_destroy(surface->glz_decoder);
1003 zlib_decoder_destroy(surface->zlib_decoder);
1004 jpeg_decoder_destroy(surface->jpeg_decoder);
1005
1006 g_clear_pointer(&surface->data, g_free);
1007 g_clear_pointer(&surface->canvas, surface->canvas->ops->destroy);
1008 }
1009
find_surface(SpiceDisplayChannelPrivate * c,guint32 surface_id)1010 static display_surface *find_surface(SpiceDisplayChannelPrivate *c, guint32 surface_id)
1011 {
1012 if (c->primary && c->primary->surface_id == surface_id)
1013 return c->primary;
1014
1015 return g_hash_table_lookup(c->surfaces, GINT_TO_POINTER(surface_id));
1016 }
1017
1018 /* main or coroutine context */
clear_surfaces(SpiceChannel * channel,gboolean keep_primary)1019 static void clear_surfaces(SpiceChannel *channel, gboolean keep_primary)
1020 {
1021 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1022 GHashTableIter iter;
1023 display_surface *surface;
1024
1025 if (!keep_primary) {
1026 c->primary = NULL;
1027 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
1028 }
1029
1030 g_hash_table_iter_init(&iter, c->surfaces);
1031 while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&surface)) {
1032
1033 if (keep_primary && surface->primary) {
1034 CHANNEL_DEBUG(channel, "keeping existing primary surface, migration or reset");
1035 continue;
1036 }
1037
1038 g_hash_table_iter_remove(&iter);
1039 }
1040 }
1041
1042 /* coroutine context */
emit_invalidate(SpiceChannel * channel,SpiceRect * bbox)1043 static void emit_invalidate(SpiceChannel *channel, SpiceRect *bbox)
1044 {
1045 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
1046 bbox->left, bbox->top,
1047 bbox->right - bbox->left,
1048 bbox->bottom - bbox->top);
1049 }
1050
1051 /* ------------------------------------------------------------------ */
1052
1053 /* coroutine context */
spice_display_channel_up(SpiceChannel * channel)1054 static void spice_display_channel_up(SpiceChannel *channel)
1055 {
1056 SpiceMsgOut *out;
1057 SpiceSession *s = spice_channel_get_session(channel);
1058 SpiceMsgcDisplayInit init;
1059 int cache_size;
1060 int glz_window_size;
1061 SpiceImageCompression preferred_compression = SPICE_IMAGE_COMPRESSION_INVALID;
1062
1063 g_object_get(s,
1064 "cache-size", &cache_size,
1065 "glz-window-size", &glz_window_size,
1066 "preferred-compression", &preferred_compression,
1067 NULL);
1068 CHANNEL_DEBUG(channel, "%s: cache_size %d, glz_window_size %d (bytes)", __FUNCTION__,
1069 cache_size, glz_window_size);
1070 init.pixmap_cache_id = 1;
1071 init.glz_dictionary_id = 1;
1072 init.pixmap_cache_size = cache_size / 4; /* pixels */
1073 init.glz_dictionary_window_size = glz_window_size / 4; /* pixels */
1074 out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_INIT);
1075 out->marshallers->msgc_display_init(out->marshaller, &init);
1076 spice_msg_out_send_internal(out);
1077
1078 /* notify of existence of this monitor */
1079 g_coroutine_object_notify(G_OBJECT(channel), "monitors");
1080
1081 if (preferred_compression != SPICE_IMAGE_COMPRESSION_INVALID) {
1082 spice_display_channel_change_preferred_compression(channel, preferred_compression);
1083 }
1084 }
1085
1086 #define DRAW(type) { \
1087 display_surface *surface = \
1088 find_surface(SPICE_DISPLAY_CHANNEL(channel)->priv, \
1089 op->base.surface_id); \
1090 g_return_if_fail(surface != NULL); \
1091 surface->canvas->ops->draw_##type(surface->canvas, &op->base.box, \
1092 &op->base.clip, &op->data); \
1093 if (surface->primary) { \
1094 emit_invalidate(channel, &op->base.box); \
1095 } \
1096 }
1097
1098 /* coroutine context */
display_handle_mode(SpiceChannel * channel,SpiceMsgIn * in)1099 static void display_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
1100 {
1101 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1102 SpiceMsgDisplayMode *mode = spice_msg_in_parsed(in);
1103 display_surface *surface;
1104
1105 g_warn_if_fail(c->mark == FALSE);
1106
1107 surface = g_new0(display_surface, 1);
1108 surface->format = mode->bits == 32 ?
1109 SPICE_SURFACE_FMT_32_xRGB : SPICE_SURFACE_FMT_16_555;
1110 surface->width = mode->x_res;
1111 surface->height = mode->y_res;
1112 surface->stride = surface->width * 4;
1113 surface->size = surface->height * surface->stride;
1114 surface->primary = true;
1115 create_canvas(channel, surface);
1116 }
1117
1118 /* coroutine context */
display_handle_mark(SpiceChannel * channel,SpiceMsgIn * in)1119 static void display_handle_mark(SpiceChannel *channel, SpiceMsgIn *in)
1120 {
1121 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1122
1123 CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
1124 g_return_if_fail(c->primary != NULL);
1125 #ifdef EXTRA_CHECKS
1126 g_warn_if_fail(c->mark == FALSE);
1127 #endif
1128
1129 c->mark = TRUE;
1130 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, TRUE);
1131 }
1132
1133 /* coroutine context */
display_handle_reset(SpiceChannel * channel,SpiceMsgIn * in)1134 static void display_handle_reset(SpiceChannel *channel, SpiceMsgIn *in)
1135 {
1136 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1137 display_surface *surface = c->primary;
1138
1139 CHANNEL_DEBUG(channel, "%s: TODO detach_from_screen", __FUNCTION__);
1140
1141 if (surface != NULL)
1142 surface->canvas->ops->clear(surface->canvas);
1143
1144 cache_clear(c->palettes);
1145
1146 c->mark = FALSE;
1147 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, FALSE);
1148 }
1149
1150 /* coroutine context */
display_handle_copy_bits(SpiceChannel * channel,SpiceMsgIn * in)1151 static void display_handle_copy_bits(SpiceChannel *channel, SpiceMsgIn *in)
1152 {
1153 SpiceMsgDisplayCopyBits *op = spice_msg_in_parsed(in);
1154 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1155 display_surface *surface = find_surface(c, op->base.surface_id);
1156
1157 g_return_if_fail(surface != NULL);
1158 surface->canvas->ops->copy_bits(surface->canvas, &op->base.box,
1159 &op->base.clip, &op->src_pos);
1160 if (surface->primary) {
1161 emit_invalidate(channel, &op->base.box);
1162 }
1163 }
1164
1165 /* coroutine context */
display_handle_inv_list(SpiceChannel * channel,SpiceMsgIn * in)1166 static void display_handle_inv_list(SpiceChannel *channel, SpiceMsgIn *in)
1167 {
1168 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1169 SpiceResourceList *list = spice_msg_in_parsed(in);
1170 int i;
1171
1172 for (i = 0; i < list->count; i++) {
1173 guint64 id = list->resources[i].id;
1174
1175 switch (list->resources[i].type) {
1176 case SPICE_RES_TYPE_PIXMAP:
1177 if (!cache_remove(c->images, id))
1178 SPICE_DEBUG("fail to remove image %" G_GUINT64_FORMAT, id);
1179 break;
1180 default:
1181 g_return_if_reached();
1182 break;
1183 }
1184 }
1185 }
1186
1187 /* coroutine context */
display_handle_inv_pixmap_all(SpiceChannel * channel,SpiceMsgIn * in)1188 static void display_handle_inv_pixmap_all(SpiceChannel *channel, SpiceMsgIn *in)
1189 {
1190 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1191
1192 spice_channel_handle_wait_for_channels(channel, in);
1193 cache_clear(c->images);
1194 }
1195
1196 /* coroutine context */
display_handle_inv_palette(SpiceChannel * channel,SpiceMsgIn * in)1197 static void display_handle_inv_palette(SpiceChannel *channel, SpiceMsgIn *in)
1198 {
1199 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1200 SpiceMsgDisplayInvalOne* op = spice_msg_in_parsed(in);
1201
1202 palette_remove(&c->palette_cache, op->id);
1203 }
1204
1205 /* coroutine context */
display_handle_inv_palette_all(SpiceChannel * channel,SpiceMsgIn * in)1206 static void display_handle_inv_palette_all(SpiceChannel *channel, SpiceMsgIn *in)
1207 {
1208 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1209
1210 cache_clear(c->palettes);
1211 }
1212
1213 /* ------------------------------------------------------------------ */
1214
display_update_stream_region(display_stream * st)1215 static void display_update_stream_region(display_stream *st)
1216 {
1217 int i;
1218
1219 switch (st->clip.type) {
1220 case SPICE_CLIP_TYPE_RECTS:
1221 region_clear(&st->region);
1222 for (i = 0; i < st->clip.rects->num_rects; i++) {
1223 region_add(&st->region, &st->clip.rects->rects[i]);
1224 }
1225 st->have_region = true;
1226 break;
1227 case SPICE_CLIP_TYPE_NONE:
1228 default:
1229 st->have_region = false;
1230 break;
1231 }
1232 }
1233
report_invalid_stream(SpiceChannel * channel,uint32_t id)1234 static void report_invalid_stream(SpiceChannel *channel, uint32_t id)
1235 {
1236 if (spice_channel_test_capability(channel, SPICE_DISPLAY_CAP_STREAM_REPORT)) {
1237 SpiceMsgcDisplayStreamReport report;
1238 SpiceMsgOut *msg;
1239
1240 /* Send a special stream report (UINT_MAX dropped frames out of zero)
1241 * to indicate there is no such stream.
1242 */
1243 g_warning("notify the server that stream %u does not exist", id);
1244 memset(&report, 0, sizeof(report));
1245 report.stream_id = id;
1246 report.num_frames = 0;
1247 report.num_drops = UINT_MAX;
1248
1249 msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_DISPLAY_STREAM_REPORT);
1250 msg->marshallers->msgc_display_stream_report(msg->marshaller, &report);
1251 spice_msg_out_send(msg);
1252 }
1253 }
1254
get_stream_by_id(SpiceChannel * channel,uint32_t id)1255 static display_stream *get_stream_by_id(SpiceChannel *channel, uint32_t id)
1256 {
1257 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1258
1259 if (c != NULL && c->streams != NULL && id < c->nstreams &&
1260 c->streams[id] != NULL) {
1261 return c->streams[id];
1262 }
1263
1264 report_invalid_stream(channel, id);
1265 return NULL;
1266 }
1267
1268 /* coroutine context */
display_stream_create(SpiceChannel * channel,uint32_t id,uint32_t surface_id,uint32_t flags,uint32_t codec_type,const SpiceRect * dest,const SpiceClip * clip)1269 static display_stream *display_stream_create(SpiceChannel *channel,
1270 uint32_t id, uint32_t surface_id,
1271 uint32_t flags, uint32_t codec_type,
1272 const SpiceRect *dest, const SpiceClip *clip)
1273 {
1274 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1275 display_stream *st = g_new0(display_stream, 1);
1276
1277 st->id = id;
1278 st->flags = flags;
1279 st->dest = *dest;
1280 st->clip = *clip;
1281 st->surface = find_surface(c, surface_id);
1282 st->channel = channel;
1283 st->drops_seqs_stats_arr = g_array_new(FALSE, FALSE, sizeof(drops_sequence_stats));
1284
1285 region_init(&st->region);
1286 display_update_stream_region(st);
1287
1288 switch (codec_type) {
1289 #ifdef HAVE_BUILTIN_MJPEG
1290 case SPICE_VIDEO_CODEC_TYPE_MJPEG:
1291 st->video_decoder = create_mjpeg_decoder(codec_type, st);
1292 break;
1293 #endif
1294 default:
1295 st->video_decoder = create_gstreamer_decoder(codec_type, st);
1296 break;
1297 }
1298 if (st->video_decoder == NULL) {
1299 g_warning("could not create a video decoder for codec %u", codec_type);
1300 g_clear_pointer(&st, display_stream_destroy);
1301 }
1302 return st;
1303 }
1304
destroy_stream(SpiceChannel * channel,int id)1305 static void destroy_stream(SpiceChannel *channel, int id)
1306 {
1307 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1308
1309 g_return_if_fail(c != NULL);
1310 g_return_if_fail(c->streams != NULL);
1311 g_return_if_fail(c->nstreams > id);
1312
1313 g_clear_pointer(&c->streams[id], display_stream_destroy);
1314 }
1315
display_handle_stream_create(SpiceChannel * channel,SpiceMsgIn * in)1316 static void display_handle_stream_create(SpiceChannel *channel, SpiceMsgIn *in)
1317 {
1318 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1319 SpiceMsgDisplayStreamCreate *op = spice_msg_in_parsed(in);
1320
1321 CHANNEL_DEBUG(channel, "%s: id %u", __FUNCTION__, op->id);
1322
1323 if (op->id >= c->nstreams) {
1324 int n = c->nstreams;
1325 if (!c->nstreams) {
1326 c->nstreams = 1;
1327 }
1328 while (op->id >= c->nstreams) {
1329 c->nstreams *= 2;
1330 }
1331 c->streams = realloc(c->streams, c->nstreams * sizeof(c->streams[0]));
1332 memset(c->streams + n, 0, (c->nstreams - n) * sizeof(c->streams[0]));
1333 }
1334 g_return_if_fail(c->streams[op->id] == NULL);
1335
1336 c->streams[op->id] = display_stream_create(channel, op->id, op->surface_id,
1337 op->flags, op->codec_type,
1338 &op->dest, &op->clip);
1339 if (c->streams[op->id] == NULL) {
1340 g_warning("could not create the %u video stream", op->id);
1341 destroy_stream(channel, op->id);
1342 report_invalid_stream(channel, op->id);
1343 }
1344 }
1345
stream_get_dest(display_stream * st,SpiceMsgIn * frame_msg)1346 static const SpiceRect *stream_get_dest(display_stream *st, SpiceMsgIn *frame_msg)
1347 {
1348 if (frame_msg == NULL ||
1349 spice_msg_in_type(frame_msg) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
1350 return &st->dest;
1351 } else {
1352 SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(frame_msg);
1353
1354 return &op->dest;
1355 }
1356
1357 }
1358
spice_msg_in_frame_data(SpiceMsgIn * frame_msg,uint8_t ** data)1359 static uint32_t spice_msg_in_frame_data(SpiceMsgIn *frame_msg, uint8_t **data)
1360 {
1361 switch (spice_msg_in_type(frame_msg)) {
1362 case SPICE_MSG_DISPLAY_STREAM_DATA: {
1363 SpiceMsgDisplayStreamData *op = spice_msg_in_parsed(frame_msg);
1364 *data = op->data;
1365 return op->data_size;
1366 }
1367 case SPICE_MSG_DISPLAY_STREAM_DATA_SIZED: {
1368 SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(frame_msg);
1369 *data = op->data;
1370 return op->data_size;
1371 }
1372 default:
1373 *data = NULL;
1374 g_return_val_if_reached(0);
1375 }
1376 }
1377
1378 G_GNUC_INTERNAL
stream_get_time(display_stream * st)1379 guint32 stream_get_time(display_stream *st)
1380 {
1381 SpiceSession *session = spice_channel_get_session(st->channel);
1382 return session ? spice_session_get_mm_time(session) : 0;
1383 }
1384
1385 /* coroutine or main context */
1386 G_GNUC_INTERNAL
stream_dropped_frame_on_playback(display_stream * st)1387 void stream_dropped_frame_on_playback(display_stream *st)
1388 {
1389 st->num_drops_on_playback++;
1390 }
1391
1392 /* main context */
1393 G_GNUC_INTERNAL
stream_display_frame(display_stream * st,SpiceFrame * frame,uint32_t width,uint32_t height,int stride,uint8_t * data)1394 void stream_display_frame(display_stream *st, SpiceFrame *frame,
1395 uint32_t width, uint32_t height, int stride, uint8_t *data)
1396 {
1397 if (stride == SPICE_UNKNOWN_STRIDE) {
1398 stride = width * sizeof(uint32_t);
1399 }
1400 if (!(st->flags & SPICE_STREAM_FLAGS_TOP_DOWN)) {
1401 data += stride * (height - 1);
1402 stride = -stride;
1403 }
1404
1405 st->surface->canvas->ops->put_image(st->surface->canvas,
1406 &frame->dest, data,
1407 width, height, stride,
1408 st->have_region ? &st->region : NULL);
1409
1410 if (st->surface->primary) {
1411 g_signal_emit(st->channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
1412 frame->dest.left, frame->dest.top,
1413 frame->dest.right - frame->dest.left,
1414 frame->dest.bottom - frame->dest.top);
1415 }
1416 }
1417
1418 G_GNUC_INTERNAL
hand_pipeline_to_widget(display_stream * st,GstPipeline * pipeline)1419 gboolean hand_pipeline_to_widget(display_stream *st, GstPipeline *pipeline)
1420 {
1421 gboolean res = false;
1422
1423 if (st->surface->streaming_mode) {
1424 g_signal_emit(st->channel, signals[SPICE_DISPLAY_OVERLAY], 0,
1425 pipeline, &res);
1426 }
1427 return res;
1428 }
1429
1430 /* after a sequence of 3 drops, push a report to the server, even
1431 * if the report window is bigger */
1432 #define STREAM_REPORT_DROP_SEQ_LEN_LIMIT 3
1433
display_update_stream_report(SpiceDisplayChannel * channel,uint32_t stream_id,uint32_t frame_time,int32_t latency)1434 static void display_update_stream_report(SpiceDisplayChannel *channel, uint32_t stream_id,
1435 uint32_t frame_time, int32_t latency)
1436 {
1437 display_stream *st = get_stream_by_id(SPICE_CHANNEL(channel), stream_id);
1438 guint64 now;
1439
1440 g_return_if_fail(st != NULL);
1441 if (!st->report_is_active) {
1442 return;
1443 }
1444 now = g_get_monotonic_time();
1445
1446 if (st->report_num_frames == 0) {
1447 st->report_start_frame_time = frame_time;
1448 st->report_start_time = now;
1449 }
1450 st->report_num_frames++;
1451
1452 if (latency < 0) { // drop
1453 st->report_num_drops++;
1454 st->report_drops_seq_len++;
1455 } else {
1456 st->report_drops_seq_len = 0;
1457 }
1458
1459 if (st->report_num_frames >= st->report_max_window ||
1460 spice_mmtime_diff(now - st->report_start_time, st->report_timeout) >= 0 ||
1461 st->report_drops_seq_len >= STREAM_REPORT_DROP_SEQ_LEN_LIMIT) {
1462 SpiceMsgcDisplayStreamReport report;
1463 SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(channel));
1464 SpiceMsgOut *msg;
1465
1466 report.stream_id = stream_id;
1467 report.unique_id = st->report_id;
1468 report.start_frame_mm_time = st->report_start_frame_time;
1469 report.end_frame_mm_time = frame_time;
1470 report.num_frames = st->report_num_frames;
1471 report.num_drops = st-> report_num_drops;
1472 report.last_frame_delay = latency;
1473 if (spice_session_is_playback_active(session)) {
1474 report.audio_delay = spice_session_get_playback_latency(session);
1475 } else {
1476 report.audio_delay = UINT_MAX;
1477 }
1478
1479 msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_DISPLAY_STREAM_REPORT);
1480 msg->marshallers->msgc_display_stream_report(msg->marshaller, &report);
1481 spice_msg_out_send(msg);
1482
1483 st->report_start_time = 0;
1484 st->report_start_frame_time = 0;
1485 st->report_num_frames = 0;
1486 st->report_num_drops = 0;
1487 st->report_drops_seq_len = 0;
1488 }
1489 }
1490
1491 /*
1492 * Migration can occur between 2 spice-servers with different mm-times.
1493 * Then, the following cases can happen after migration completes:
1494 * (We refer to src/dst-time as the mm-times on the src/dst servers):
1495 *
1496 * (case 1) Frames with time ~= dst-time arrive to the client before the
1497 * playback-channel updates the session's mm-time (i.e., the mm_time
1498 * of the session is still based on the src-time).
1499 * (a) If src-time < dst-time:
1500 * display_stream_schedule schedules the next rendering to
1501 * ~(dst-time - src-time) milliseconds from now.
1502 * Since we assume monotonic mm_time, display_stream_schedule,
1503 * returns immediately when a rendering timeout
1504 * has already been set, and doesn't update the timeout,
1505 * even after the mm_time is updated.
1506 * When src-time << dst-time, a significant video frames loss will occur.
1507 * (b) If src-time > dst-time
1508 * Frames will be dropped till the mm-time will be updated.
1509 * (case 2) mm-time is synced with dst-time, but frames that were in the command
1510 * ring during migration still arrive (such frames hold src-time).
1511 * (a) If src-time < dst-time
1512 * The frames that hold src-time will be dropped, since their
1513 * mm_time < session-mm_time. But all the new frames that are generated in
1514 * the driver after migration, will be rendered appropriately.
1515 * (b) If src-time > dst-time
1516 * Similar consequences as in 1 (a)
1517 * case 2 is less likely, since at takes at least 20 frames till the dst-server re-identifies
1518 * the video stream and starts sending stream data
1519 *
1520 * display_session_mm_time_reset_cb handles case 1.a by notifying the
1521 * video decoders through their reschedule() method, and case 2.b is handled
1522 * directly by the video decoders in their queue_frame() method
1523 */
1524
1525 /* main context */
display_session_mm_time_reset_cb(SpiceSession * session,gpointer data)1526 static void display_session_mm_time_reset_cb(SpiceSession *session, gpointer data)
1527 {
1528 SpiceChannel *channel = data;
1529 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1530 guint i;
1531
1532 CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
1533
1534 for (i = 0; i < c->nstreams; i++) {
1535 display_stream *st;
1536
1537 if (c->streams[i] == NULL) {
1538 continue;
1539 }
1540 SPICE_DEBUG("%s: stream-id %u", __FUNCTION__, i);
1541 st = c->streams[i];
1542 st->video_decoder->reschedule(st->video_decoder);
1543 }
1544 }
1545
1546 #define STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT 5
1547
display_stream_stats_debug(display_stream * st)1548 static void display_stream_stats_debug(display_stream *st)
1549 {
1550 guint64 drops_duration_total = 0;
1551 guint32 i, num_out_frames;
1552 gdouble avg_late_time = 0.0;
1553
1554 if (st->num_input_frames == 0) {
1555 return;
1556 }
1557
1558 num_out_frames = st->num_input_frames - st->arrive_late_count - st->num_drops_on_playback;
1559
1560 if (st->arrive_late_count != 0) {
1561 avg_late_time = st->arrive_late_time / ((double)st->arrive_late_count);
1562 }
1563
1564 CHANNEL_DEBUG(st->channel,
1565 "%s: id=%u #in-frames=%u out/in=%.2f "
1566 "#drops-on-receive=%u avg-late-time(ms)=%.2f "
1567 "#drops-on-playback=%u",
1568 __FUNCTION__,
1569 st->id,
1570 st->num_input_frames,
1571 num_out_frames / (double)st->num_input_frames,
1572 st->arrive_late_count,
1573 avg_late_time,
1574 st->num_drops_on_playback);
1575
1576 if (st->num_drops_seqs) {
1577 CHANNEL_DEBUG(st->channel,
1578 "%s: #drops-sequences=%u ==>",
1579 __FUNCTION__,
1580 st->num_drops_seqs);
1581 }
1582
1583 for (i = 0; i < st->num_drops_seqs; i++) {
1584 drops_sequence_stats *stats = &g_array_index(st->drops_seqs_stats_arr,
1585 drops_sequence_stats,
1586 i);
1587 drops_duration_total += stats->duration;
1588 CHANNEL_DEBUG(st->channel,
1589 "%s: \t len=%u start-ms=%u duration-ms=%u",
1590 __FUNCTION__,
1591 stats->len,
1592 stats->start_mm_time - st->first_frame_mm_time,
1593 stats->duration);
1594 }
1595
1596 if (st->num_drops_seqs) {
1597 CHANNEL_DEBUG(st->channel,
1598 "%s: drops-total-duration=%"G_GUINT64_FORMAT" ==>",
1599 __FUNCTION__,
1600 drops_duration_total);
1601 }
1602 }
1603
1604
display_stream_stats_save(display_stream * st,guint32 server_mmtime,guint32 client_mmtime)1605 static void display_stream_stats_save(display_stream *st,
1606 guint32 server_mmtime,
1607 guint32 client_mmtime)
1608 {
1609 gint32 latency = server_mmtime - client_mmtime;
1610
1611 if (!st->num_input_frames) {
1612 st->first_frame_mm_time = server_mmtime;
1613 }
1614 st->num_input_frames++;
1615
1616 if (latency < 0) {
1617 CHANNEL_DEBUG(st->channel, "stream data too late by %u ms (ts: %u, mmtime: %u)",
1618 client_mmtime - server_mmtime, server_mmtime, client_mmtime);
1619 st->arrive_late_time += client_mmtime - server_mmtime;
1620 st->arrive_late_count++;
1621
1622 /* Late frames are counted as drops in the stats but aren't necessarily dropped - depends
1623 * on codec and decoder
1624 */
1625 if (!st->cur_drops_seq_stats.len) {
1626 st->cur_drops_seq_stats.start_mm_time = server_mmtime;
1627 }
1628 st->cur_drops_seq_stats.len++;
1629 st->playback_sync_drops_seq_len++;
1630 return;
1631 }
1632
1633 CHANNEL_DEBUG(st->channel, "video latency: %d", latency);
1634 if (st->cur_drops_seq_stats.len) {
1635 st->cur_drops_seq_stats.duration = server_mmtime -
1636 st->cur_drops_seq_stats.start_mm_time;
1637 g_array_append_val(st->drops_seqs_stats_arr, st->cur_drops_seq_stats);
1638 memset(&st->cur_drops_seq_stats, 0, sizeof(st->cur_drops_seq_stats));
1639 st->num_drops_seqs++;
1640 }
1641 st->playback_sync_drops_seq_len = 0;
1642 }
1643
spice_frame_new(display_stream * st,SpiceMsgIn * in,guint32 server_mmtime)1644 static SpiceFrame *spice_frame_new(display_stream *st,
1645 SpiceMsgIn *in,
1646 guint32 server_mmtime)
1647 {
1648 SpiceFrame *frame;
1649 guint8 *data_ptr;
1650 const SpiceRect *dest_rect = stream_get_dest(st, in);
1651 guint32 data_size = spice_msg_in_frame_data(in, &data_ptr);
1652
1653 frame = g_new(SpiceFrame, 1);
1654 frame->mm_time = server_mmtime;
1655 frame->dest = *dest_rect;
1656 frame->data = data_ptr;
1657 frame->size = data_size;
1658 frame->data_opaque = in;
1659 spice_msg_in_ref(in);
1660 frame->creation_time = g_get_monotonic_time();
1661 return frame;
1662 }
1663
1664 G_GNUC_INTERNAL
spice_frame_free(SpiceFrame * frame)1665 void spice_frame_free(SpiceFrame *frame)
1666 {
1667 if (frame == NULL) {
1668 return;
1669 }
1670
1671 spice_msg_in_unref(frame->data_opaque);
1672 g_free(frame);
1673 }
1674
1675 /* coroutine context */
display_handle_stream_data(SpiceChannel * channel,SpiceMsgIn * in)1676 static void display_handle_stream_data(SpiceChannel *channel, SpiceMsgIn *in)
1677 {
1678 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1679 SpiceStreamDataHeader *op = spice_msg_in_parsed(in);
1680 display_stream *st = get_stream_by_id(channel, op->id);
1681 guint32 mmtime;
1682 int32_t latency, latency_report;
1683 SpiceFrame *frame;
1684
1685 g_return_if_fail(st != NULL);
1686 mmtime = stream_get_time(st);
1687
1688 if (spice_msg_in_type(in) == SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
1689 CHANNEL_DEBUG(channel, "stream %u contains sized data", op->id);
1690 }
1691
1692 if (op->multi_media_time == 0) {
1693 g_critical("Received frame with invalid 0 timestamp! perhaps wrong graphic driver?");
1694 op->multi_media_time = mmtime + 100; /* workaround... */
1695 }
1696
1697 latency = latency_report = op->multi_media_time - mmtime;
1698 if (latency > 0) {
1699 SpiceSession *s = spice_channel_get_session(channel);
1700
1701 if (st->surface->streaming_mode && !spice_session_is_playback_active(s)) {
1702 CHANNEL_DEBUG(channel, "video latency: %d, set to 0 since there is no playback", latency);
1703 latency = 0;
1704 }
1705 }
1706 display_stream_stats_save(st, op->multi_media_time, mmtime);
1707
1708 /* Let the video decoder queue the frames so it can optimize their
1709 * decoding and best decide if/when to drop them when they are late,
1710 * taking into account the impact on later frames.
1711 */
1712 frame = spice_frame_new(st, in, op->multi_media_time);
1713 if (!st->video_decoder->queue_frame(st->video_decoder, frame, latency)) {
1714 destroy_stream(channel, op->id);
1715 report_invalid_stream(channel, op->id);
1716 return;
1717 }
1718
1719 if (c->enable_adaptive_streaming) {
1720 display_update_stream_report(SPICE_DISPLAY_CHANNEL(channel), op->id,
1721 op->multi_media_time, latency_report);
1722 if (st->playback_sync_drops_seq_len >= STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT) {
1723 spice_session_sync_playback_latency(spice_channel_get_session(channel));
1724 st->playback_sync_drops_seq_len = 0;
1725 }
1726 }
1727 }
1728
1729 /* coroutine context */
display_handle_stream_clip(SpiceChannel * channel,SpiceMsgIn * in)1730 static void display_handle_stream_clip(SpiceChannel *channel, SpiceMsgIn *in)
1731 {
1732 SpiceMsgDisplayStreamClip *op = spice_msg_in_parsed(in);
1733 display_stream *st = get_stream_by_id(channel, op->id);
1734
1735 g_return_if_fail(st != NULL);
1736
1737 st->clip = op->clip;
1738 display_update_stream_region(st);
1739 }
1740
display_stream_destroy(gpointer st_pointer)1741 static void display_stream_destroy(gpointer st_pointer)
1742 {
1743 display_stream *st = st_pointer;
1744
1745 display_stream_stats_debug(st);
1746 g_array_free(st->drops_seqs_stats_arr, TRUE);
1747
1748 if (st->video_decoder) {
1749 st->video_decoder->destroy(st->video_decoder);
1750 }
1751
1752 g_free(st);
1753 }
1754
clear_streams(SpiceChannel * channel)1755 static void clear_streams(SpiceChannel *channel)
1756 {
1757 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1758 int i;
1759
1760 for (i = 0; i < c->nstreams; i++) {
1761 destroy_stream(channel, i);
1762 }
1763 g_clear_pointer(&c->streams, g_free);
1764 c->nstreams = 0;
1765 }
1766
1767 /* coroutine context */
display_handle_stream_destroy(SpiceChannel * channel,SpiceMsgIn * in)1768 static void display_handle_stream_destroy(SpiceChannel *channel, SpiceMsgIn *in)
1769 {
1770 SpiceMsgDisplayStreamDestroy *op = spice_msg_in_parsed(in);
1771
1772 g_return_if_fail(op != NULL);
1773 CHANNEL_DEBUG(channel, "%s: id %u", __FUNCTION__, op->id);
1774 destroy_stream(channel, op->id);
1775 }
1776
1777 /* coroutine context */
display_handle_stream_destroy_all(SpiceChannel * channel,SpiceMsgIn * in)1778 static void display_handle_stream_destroy_all(SpiceChannel *channel, SpiceMsgIn *in)
1779 {
1780 clear_streams(channel);
1781 }
1782
1783 /* coroutine context */
display_handle_stream_activate_report(SpiceChannel * channel,SpiceMsgIn * in)1784 static void display_handle_stream_activate_report(SpiceChannel *channel, SpiceMsgIn *in)
1785 {
1786 SpiceMsgDisplayStreamActivateReport *op = spice_msg_in_parsed(in);
1787 display_stream *st = get_stream_by_id(channel, op->stream_id);
1788
1789 g_return_if_fail(st != NULL);
1790 st->report_is_active = TRUE;
1791 st->report_id = op->unique_id;
1792 st->report_max_window = op->max_window_size;
1793 st->report_timeout = op->timeout_ms * 1000;
1794 st->report_start_time = 0;
1795 st->report_start_frame_time = 0;
1796 st->report_num_frames = 0;
1797 st->report_num_drops = 0;
1798 st->report_drops_seq_len = 0;
1799 }
1800
1801 /* ------------------------------------------------------------------ */
1802
1803 /* coroutine context */
display_handle_draw_fill(SpiceChannel * channel,SpiceMsgIn * in)1804 static void display_handle_draw_fill(SpiceChannel *channel, SpiceMsgIn *in)
1805 {
1806 SpiceMsgDisplayDrawFill *op = spice_msg_in_parsed(in);
1807 DRAW(fill);
1808 }
1809
1810 /* coroutine context */
display_handle_draw_opaque(SpiceChannel * channel,SpiceMsgIn * in)1811 static void display_handle_draw_opaque(SpiceChannel *channel, SpiceMsgIn *in)
1812 {
1813 SpiceMsgDisplayDrawOpaque *op = spice_msg_in_parsed(in);
1814 DRAW(opaque);
1815 }
1816
1817 /* coroutine context */
display_handle_draw_copy(SpiceChannel * channel,SpiceMsgIn * in)1818 static void display_handle_draw_copy(SpiceChannel *channel, SpiceMsgIn *in)
1819 {
1820 SpiceMsgDisplayDrawCopy *op = spice_msg_in_parsed(in);
1821 DRAW(copy);
1822 }
1823
1824 /* coroutine context */
display_handle_draw_blend(SpiceChannel * channel,SpiceMsgIn * in)1825 static void display_handle_draw_blend(SpiceChannel *channel, SpiceMsgIn *in)
1826 {
1827 SpiceMsgDisplayDrawBlend *op = spice_msg_in_parsed(in);
1828 DRAW(blend);
1829 }
1830
1831 /* coroutine context */
display_handle_draw_blackness(SpiceChannel * channel,SpiceMsgIn * in)1832 static void display_handle_draw_blackness(SpiceChannel *channel, SpiceMsgIn *in)
1833 {
1834 SpiceMsgDisplayDrawBlackness *op = spice_msg_in_parsed(in);
1835 DRAW(blackness);
1836 }
1837
display_handle_draw_whiteness(SpiceChannel * channel,SpiceMsgIn * in)1838 static void display_handle_draw_whiteness(SpiceChannel *channel, SpiceMsgIn *in)
1839 {
1840 SpiceMsgDisplayDrawWhiteness *op = spice_msg_in_parsed(in);
1841 DRAW(whiteness);
1842 }
1843
1844 /* coroutine context */
display_handle_draw_invers(SpiceChannel * channel,SpiceMsgIn * in)1845 static void display_handle_draw_invers(SpiceChannel *channel, SpiceMsgIn *in)
1846 {
1847 SpiceMsgDisplayDrawInvers *op = spice_msg_in_parsed(in);
1848 DRAW(invers);
1849 }
1850
1851 /* coroutine context */
display_handle_draw_rop3(SpiceChannel * channel,SpiceMsgIn * in)1852 static void display_handle_draw_rop3(SpiceChannel *channel, SpiceMsgIn *in)
1853 {
1854 SpiceMsgDisplayDrawRop3 *op = spice_msg_in_parsed(in);
1855 DRAW(rop3);
1856 }
1857
1858 /* coroutine context */
display_handle_draw_stroke(SpiceChannel * channel,SpiceMsgIn * in)1859 static void display_handle_draw_stroke(SpiceChannel *channel, SpiceMsgIn *in)
1860 {
1861 SpiceMsgDisplayDrawStroke *op = spice_msg_in_parsed(in);
1862 DRAW(stroke);
1863 }
1864
1865 /* coroutine context */
display_handle_draw_text(SpiceChannel * channel,SpiceMsgIn * in)1866 static void display_handle_draw_text(SpiceChannel *channel, SpiceMsgIn *in)
1867 {
1868 SpiceMsgDisplayDrawText *op = spice_msg_in_parsed(in);
1869 DRAW(text);
1870 }
1871
1872 /* coroutine context */
display_handle_draw_transparent(SpiceChannel * channel,SpiceMsgIn * in)1873 static void display_handle_draw_transparent(SpiceChannel *channel, SpiceMsgIn *in)
1874 {
1875 SpiceMsgDisplayDrawTransparent *op = spice_msg_in_parsed(in);
1876 DRAW(transparent);
1877 }
1878
1879 /* coroutine context */
display_handle_draw_alpha_blend(SpiceChannel * channel,SpiceMsgIn * in)1880 static void display_handle_draw_alpha_blend(SpiceChannel *channel, SpiceMsgIn *in)
1881 {
1882 SpiceMsgDisplayDrawAlphaBlend *op = spice_msg_in_parsed(in);
1883 DRAW(alpha_blend);
1884 }
1885
1886 /* coroutine context */
display_handle_draw_composite(SpiceChannel * channel,SpiceMsgIn * in)1887 static void display_handle_draw_composite(SpiceChannel *channel, SpiceMsgIn *in)
1888 {
1889 SpiceMsgDisplayDrawComposite *op = spice_msg_in_parsed(in);
1890 DRAW(composite);
1891 }
1892
1893 /* coroutine context */
display_handle_surface_create(SpiceChannel * channel,SpiceMsgIn * in)1894 static void display_handle_surface_create(SpiceChannel *channel, SpiceMsgIn *in)
1895 {
1896 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1897 SpiceMsgSurfaceCreate *create = spice_msg_in_parsed(in);
1898 display_surface *surface = g_new0(display_surface, 1);
1899
1900 surface->surface_id = create->surface_id;
1901 surface->format = create->format;
1902 surface->width = create->width;
1903 surface->height = create->height;
1904 surface->stride = create->width * 4;
1905 surface->size = surface->height * surface->stride;
1906 surface->streaming_mode = !!(create->flags & SPICE_SURFACE_FLAGS_STREAMING_MODE);
1907
1908 if (create->flags & SPICE_SURFACE_FLAGS_PRIMARY) {
1909 SPICE_DEBUG("surface flags: %x", create->flags);
1910 surface->primary = true;
1911 create_canvas(channel, surface);
1912 if (c->mark_false_event_id != 0) {
1913 g_source_remove(c->mark_false_event_id);
1914 c->mark_false_event_id = FALSE;
1915 }
1916 } else {
1917 surface->primary = false;
1918 create_canvas(channel, surface);
1919 }
1920 }
1921
display_mark_false(gpointer data)1922 static gboolean display_mark_false(gpointer data)
1923 {
1924 SpiceChannel *channel = data;
1925 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1926
1927 c->mark = FALSE;
1928 g_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, FALSE);
1929
1930 c->mark_false_event_id = 0;
1931 return FALSE;
1932 }
1933
1934 /* coroutine context */
display_handle_surface_destroy(SpiceChannel * channel,SpiceMsgIn * in)1935 static void display_handle_surface_destroy(SpiceChannel *channel, SpiceMsgIn *in)
1936 {
1937 SpiceMsgSurfaceDestroy *destroy = spice_msg_in_parsed(in);
1938 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1939 display_surface *surface;
1940
1941 g_return_if_fail(destroy != NULL);
1942
1943 surface = find_surface(c, destroy->surface_id);
1944 if (surface == NULL) {
1945 /* this is not a problem in spicec, it happens as well and returns.. */
1946 /* g_warn_if_reached(); */
1947 return;
1948 }
1949 if (surface->primary) {
1950 int id = spice_channel_get_channel_id(channel);
1951 CHANNEL_DEBUG(channel, "%d: FIXME primary destroy, but is display really disabled?", id);
1952 /* this is done with a timeout in spicec as well, it's *ugly* */
1953 if (id != 0 && c->mark_false_event_id == 0) {
1954 c->mark_false_event_id = g_timeout_add_seconds(1, display_mark_false, channel);
1955 }
1956 c->primary = NULL;
1957 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
1958 }
1959
1960 g_hash_table_remove(c->surfaces, GINT_TO_POINTER(surface->surface_id));
1961 }
1962
1963 #define CLAMP_CHECK(x, low, high) (((x) > (high)) ? TRUE : (((x) < (low)) ? TRUE : FALSE))
1964
1965 /* coroutine context */
display_handle_monitors_config(SpiceChannel * channel,SpiceMsgIn * in)1966 static void display_handle_monitors_config(SpiceChannel *channel, SpiceMsgIn *in)
1967 {
1968 SpiceMsgDisplayMonitorsConfig *config = spice_msg_in_parsed(in);
1969 SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
1970 guint i;
1971
1972 g_return_if_fail(config != NULL);
1973
1974 if (config->count == 0) {
1975 CHANNEL_DEBUG(channel, "received empty monitor config");
1976 return;
1977 }
1978
1979 CHANNEL_DEBUG(channel, "received new monitors config from guest: n: %d/%d", config->count, config->max_allowed);
1980
1981 c->monitors_max = config->max_allowed;
1982 if (CLAMP_CHECK(c->monitors_max, 1, MONITORS_MAX)) {
1983 g_warning("MonitorConfig max_allowed is not within permitted range, clamping");
1984 c->monitors_max = CLAMP(c->monitors_max, 1, MONITORS_MAX);
1985 }
1986
1987 if (CLAMP_CHECK(config->count, 1, c->monitors_max)) {
1988 g_warning("MonitorConfig count is not within permitted range, clamping");
1989 config->count = CLAMP(config->count, 1, c->monitors_max);
1990 }
1991
1992 c->monitors = g_array_set_size(c->monitors, config->count);
1993
1994 for (i = 0; i < config->count; i++) {
1995 SpiceDisplayMonitorConfig *mc = &g_array_index(c->monitors, SpiceDisplayMonitorConfig, i);
1996 SpiceHead *head = &config->heads[i];
1997 CHANNEL_DEBUG(channel, "monitor id: %u, surface id: %u, +%u+%u-%ux%u",
1998 head->monitor_id, head->surface_id,
1999 head->x, head->y, head->width, head->height);
2000 mc->id = head->monitor_id;
2001 mc->surface_id = head->surface_id;
2002 mc->x = head->x;
2003 mc->y = head->y;
2004 mc->width = head->width;
2005 mc->height = head->height;
2006 }
2007
2008 g_coroutine_object_notify(G_OBJECT(channel), "monitors");
2009 }
2010
2011
2012 #ifdef G_OS_UNIX
2013 /* coroutine context */
display_handle_gl_scanout_unix(SpiceChannel * channel,SpiceMsgIn * in)2014 static void display_handle_gl_scanout_unix(SpiceChannel *channel, SpiceMsgIn *in)
2015 {
2016 SpiceDisplayChannel *display = SPICE_DISPLAY_CHANNEL(channel);
2017 SpiceDisplayChannelPrivate *c = display->priv;
2018 SpiceMsgDisplayGlScanoutUnix *scanout = spice_msg_in_parsed(in);
2019
2020 scanout->drm_dma_buf_fd = -1;
2021 if (scanout->drm_fourcc_format != 0) {
2022 scanout->drm_dma_buf_fd = spice_channel_unix_read_fd(channel);
2023 CHANNEL_DEBUG(channel, "gl scanout fd: %d", scanout->drm_dma_buf_fd);
2024 }
2025
2026 c->scanout.y0top = scanout->flags & SPICE_GL_SCANOUT_FLAGS_Y0TOP;
2027 if (c->scanout.fd >= 0)
2028 close(c->scanout.fd);
2029 c->scanout.fd = scanout->drm_dma_buf_fd;
2030 c->scanout.width = scanout->width;
2031 c->scanout.height = scanout->height;
2032 c->scanout.stride = scanout->stride;
2033 c->scanout.format = scanout->drm_fourcc_format;
2034
2035 g_coroutine_object_notify(G_OBJECT(channel), "gl-scanout");
2036 }
2037 #endif
2038
2039 /* coroutine context */
display_handle_gl_draw(SpiceChannel * channel,SpiceMsgIn * in)2040 static void display_handle_gl_draw(SpiceChannel *channel, SpiceMsgIn *in)
2041 {
2042 SpiceMsgDisplayGlDraw *draw = spice_msg_in_parsed(in);
2043
2044 CHANNEL_DEBUG(channel, "gl draw %ux%u+%u+%u",
2045 draw->w, draw->h, draw->x, draw->y);
2046
2047 g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_GL_DRAW], 0,
2048 draw->x, draw->y,
2049 draw->w, draw->h);
2050 }
2051
2052 /**
2053 * spice_display_gl_draw_done:
2054 * @channel: a #SpiceDisplayChannel
2055 *
2056 * After a SpiceDisplayChannel::gl-draw is emitted, the client should
2057 * draw the current display with the current GL scanout, and must
2058 * release the GL resource with a call to spice_display_gl_draw_done()
2059 * (failing to do so for each gl-draw may result in a frozen display).
2060 *
2061 * Since: 0.31
2062 * Deprecated: 0.35: use spice_display_channel_gl_draw_done() instead.
2063 **/
spice_display_gl_draw_done(SpiceDisplayChannel * display)2064 void spice_display_gl_draw_done(SpiceDisplayChannel *display)
2065 {
2066 spice_display_channel_gl_draw_done(display);
2067 }
2068
2069 /**
2070 * spice_display_channel_gl_draw_done:
2071 * @channel: a #SpiceDisplayChannel
2072 *
2073 * After a SpiceDisplayChannel::gl-draw is emitted, the client should
2074 * draw the current display with the current GL scanout, and must
2075 * release the GL resource with a call to spice_display_gl_draw_done()
2076 * (failing to do so for each gl-draw may result in a frozen display).
2077 *
2078 * Since: 0.35
2079 **/
spice_display_channel_gl_draw_done(SpiceDisplayChannel * display)2080 void spice_display_channel_gl_draw_done(SpiceDisplayChannel *display)
2081 {
2082 SpiceChannel *channel;
2083 SpiceMsgOut *out;
2084
2085 g_return_if_fail(SPICE_IS_DISPLAY_CHANNEL(display));
2086 channel = SPICE_CHANNEL(display);
2087
2088 out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_GL_DRAW_DONE);
2089 out->marshallers->msgc_display_gl_draw_done(out->marshaller, NULL);
2090 spice_msg_out_send_internal(out);
2091 }
2092
channel_set_handlers(SpiceChannelClass * klass)2093 static void channel_set_handlers(SpiceChannelClass *klass)
2094 {
2095 static const spice_msg_handler handlers[] = {
2096 [ SPICE_MSG_DISPLAY_MODE ] = display_handle_mode,
2097 [ SPICE_MSG_DISPLAY_MARK ] = display_handle_mark,
2098 [ SPICE_MSG_DISPLAY_RESET ] = display_handle_reset,
2099 [ SPICE_MSG_DISPLAY_COPY_BITS ] = display_handle_copy_bits,
2100 [ SPICE_MSG_DISPLAY_INVAL_LIST ] = display_handle_inv_list,
2101 [ SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS ] = display_handle_inv_pixmap_all,
2102 [ SPICE_MSG_DISPLAY_INVAL_PALETTE ] = display_handle_inv_palette,
2103 [ SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES ] = display_handle_inv_palette_all,
2104
2105 [ SPICE_MSG_DISPLAY_STREAM_CREATE ] = display_handle_stream_create,
2106 [ SPICE_MSG_DISPLAY_STREAM_DATA ] = display_handle_stream_data,
2107 [ SPICE_MSG_DISPLAY_STREAM_CLIP ] = display_handle_stream_clip,
2108 [ SPICE_MSG_DISPLAY_STREAM_DESTROY ] = display_handle_stream_destroy,
2109 [ SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL ] = display_handle_stream_destroy_all,
2110 [ SPICE_MSG_DISPLAY_STREAM_DATA_SIZED ] = display_handle_stream_data,
2111 [ SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT ] = display_handle_stream_activate_report,
2112
2113 [ SPICE_MSG_DISPLAY_DRAW_FILL ] = display_handle_draw_fill,
2114 [ SPICE_MSG_DISPLAY_DRAW_OPAQUE ] = display_handle_draw_opaque,
2115 [ SPICE_MSG_DISPLAY_DRAW_COPY ] = display_handle_draw_copy,
2116 [ SPICE_MSG_DISPLAY_DRAW_BLEND ] = display_handle_draw_blend,
2117 [ SPICE_MSG_DISPLAY_DRAW_BLACKNESS ] = display_handle_draw_blackness,
2118 [ SPICE_MSG_DISPLAY_DRAW_WHITENESS ] = display_handle_draw_whiteness,
2119 [ SPICE_MSG_DISPLAY_DRAW_INVERS ] = display_handle_draw_invers,
2120 [ SPICE_MSG_DISPLAY_DRAW_ROP3 ] = display_handle_draw_rop3,
2121 [ SPICE_MSG_DISPLAY_DRAW_STROKE ] = display_handle_draw_stroke,
2122 [ SPICE_MSG_DISPLAY_DRAW_TEXT ] = display_handle_draw_text,
2123 [ SPICE_MSG_DISPLAY_DRAW_TRANSPARENT ] = display_handle_draw_transparent,
2124 [ SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND ] = display_handle_draw_alpha_blend,
2125 [ SPICE_MSG_DISPLAY_DRAW_COMPOSITE ] = display_handle_draw_composite,
2126
2127 [ SPICE_MSG_DISPLAY_SURFACE_CREATE ] = display_handle_surface_create,
2128 [ SPICE_MSG_DISPLAY_SURFACE_DESTROY ] = display_handle_surface_destroy,
2129
2130 [ SPICE_MSG_DISPLAY_MONITORS_CONFIG ] = display_handle_monitors_config,
2131 #ifdef G_OS_UNIX
2132 [ SPICE_MSG_DISPLAY_GL_SCANOUT_UNIX ] = display_handle_gl_scanout_unix,
2133 #endif
2134 [ SPICE_MSG_DISPLAY_GL_DRAW ] = display_handle_gl_draw,
2135 };
2136
2137 spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
2138 }
2139