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