浏览代码

CocoaSpice: refactor cursor drawing into display

There was a misunderstanding previouly that the input code and
cursor drawing code should reside in the same class. This led to
one CocoaSpice class handling both the SPICE input and cursor
channel. The problem is that SPICE protocol specifies a 1-to-1
mapping of cursor channel and display channel (sharing the same
channel id). To implement support for multiple displays, we
would have to follow this convention.

Additionally, there was some akwardness in forcing CSInput to
comply with the UTMRenderSource protocol because it shared the
viewport size with CSDisplay. "Outside" code would have to
syncronize the display size.

As part of the refactor, we also tried to improve the code style
by first moving as many ivars to properties as possible and by
also renaming some methods and inits.
osy 4 年之前
父节点
当前提交
23dde8cfaa

+ 21 - 3
CocoaSpice/CSDisplayMetal.h

@@ -32,11 +32,29 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, readonly, assign) NSInteger monitorID;
 @property (nonatomic, readonly, assign) NSInteger monitorID;
 @property (nonatomic, assign) CGSize displaySize;
 @property (nonatomic, assign) CGSize displaySize;
 @property (nonatomic, readonly) UTMScreenshot *screenshot;
 @property (nonatomic, readonly) UTMScreenshot *screenshot;
-
-- (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID;
-- (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID;
+@property (nonatomic, assign) BOOL inhibitCursor;
+@property (nonatomic) CGSize cursorSize;
+
+// properties from UTMRenderSource
+@property (nonatomic, readonly) BOOL cursorVisible;
+@property (nonatomic) CGPoint cursorOrigin;
+@property (nonatomic) CGPoint viewportOrigin;
+@property (nonatomic) CGFloat viewportScale;
+@property (nonatomic, readonly) dispatch_semaphore_t drawLock;
+@property (nonatomic, nullable) id<MTLDevice> device;
+@property (nonatomic, nullable, readonly) id<MTLTexture> displayTexture;
+@property (nonatomic, nullable, readonly) id<MTLTexture> cursorTexture;
+@property (nonatomic, readonly) NSUInteger displayNumVertices;
+@property (nonatomic, readonly) NSUInteger cursorNumVertices;
+@property (nonatomic, nullable, readonly) id<MTLBuffer> displayVertices;
+@property (nonatomic, nullable, readonly) id<MTLBuffer> cursorVertices;
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID;
 - (void)updateVisibleAreaWithRect:(CGRect)rect;
 - (void)updateVisibleAreaWithRect:(CGRect)rect;
 - (void)requestResolution:(CGRect)bounds;
 - (void)requestResolution:(CGRect)bounds;
+- (void)forceCursorPosition:(CGPoint)pos;
 
 
 @end
 @end
 
 

+ 261 - 51
CocoaSpice/CSDisplayMetal.m

@@ -36,6 +36,19 @@
 @property (nonatomic, readwrite, assign) NSInteger monitorID;
 @property (nonatomic, readwrite, assign) NSInteger monitorID;
 @property (nonatomic, nullable) SpiceDisplayChannel *display;
 @property (nonatomic, nullable) SpiceDisplayChannel *display;
 @property (nonatomic, nullable) SpiceMainChannel *main;
 @property (nonatomic, nullable) SpiceMainChannel *main;
+@property (nonatomic, nullable) SpiceCursorChannel *cursor;
+@property (nonatomic, readwrite) CGPoint cursorHotspot;
+@property (nonatomic, readwrite) BOOL cursorHidden;
+@property (nonatomic, readwrite) BOOL hasCursor;
+
+// UTMRenderSource properties
+@property (nonatomic, readwrite) dispatch_semaphore_t drawLock;
+@property (nonatomic, nullable, readwrite) id<MTLTexture> displayTexture;
+@property (nonatomic, nullable, readwrite) id<MTLTexture> cursorTexture;
+@property (nonatomic, readwrite) NSUInteger displayNumVertices;
+@property (nonatomic, readwrite) NSUInteger cursorNumVertices;
+@property (nonatomic, nullable, readwrite) id<MTLBuffer> displayVertices;
+@property (nonatomic, nullable, readwrite) id<MTLBuffer> cursorVertices;
 
 
 @end
 @end
 
 
@@ -47,13 +60,11 @@
     CGRect                  _canvasArea;
     CGRect                  _canvasArea;
     CGRect                  _visibleArea;
     CGRect                  _visibleArea;
     GWeakRef                _overlay_weak_ref;
     GWeakRef                _overlay_weak_ref;
-    id<MTLDevice>           _device;
-    id<MTLTexture>          _texture;
-    id<MTLBuffer>           _vertices;
-    NSUInteger              _numVertices;
-    dispatch_semaphore_t    _drawLock;
+    CGPoint                 _mouse_guest;
 }
 }
 
 
+#pragma mark - Display events
+
 static void cs_primary_create(SpiceChannel *channel, gint format,
 static void cs_primary_create(SpiceChannel *channel, gint format,
                            gint width, gint height, gint stride,
                            gint width, gint height, gint stride,
                            gint shmid, gpointer imgdata, gpointer data) {
                            gint shmid, gpointer imgdata, gpointer data) {
@@ -159,6 +170,90 @@ whole:
     self.ready = YES;
     self.ready = YES;
 }
 }
 
 
+#pragma mark - Cursor events
+
+static void cs_update_mouse_mode(SpiceChannel *channel, gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+    enum SpiceMouseMode mouse_mode;
+    
+    g_object_get(channel, "mouse-mode", &mouse_mode, NULL);
+    DISPLAY_DEBUG(self, "mouse mode %u", mouse_mode);
+    
+    if (mouse_mode == SPICE_MOUSE_MODE_SERVER) {
+        self->_mouse_guest.x = -1;
+        self->_mouse_guest.y = -1;
+    }
+}
+
+static void cs_cursor_invalidate(CSDisplayMetal *self)
+{
+    // We implement two different textures so invalidate is not needed
+}
+
+static void cs_cursor_set(SpiceCursorChannel *channel,
+                          G_GNUC_UNUSED GParamSpec *pspec,
+                          gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+    SpiceCursorShape *cursor_shape;
+    
+    g_object_get(G_OBJECT(channel), "cursor", &cursor_shape, NULL);
+    if (G_UNLIKELY(cursor_shape == NULL || cursor_shape->data == NULL)) {
+        if (cursor_shape != NULL) {
+            g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
+        }
+        return;
+    }
+    
+    cs_cursor_invalidate(self);
+    
+    CGPoint hotspot = CGPointMake(cursor_shape->hot_spot_x, cursor_shape->hot_spot_y);
+    CGSize newSize = CGSizeMake(cursor_shape->width, cursor_shape->height);
+    if (!CGSizeEqualToSize(newSize, self.cursorSize) || !CGPointEqualToPoint(hotspot, self.cursorHotspot)) {
+        [self rebuildCursorWithSize:newSize center:hotspot];
+    }
+    [self drawCursor:cursor_shape->data];
+    self.cursorHidden = NO;
+    cs_cursor_invalidate(self);
+}
+
+static void cs_cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+    
+    cs_cursor_invalidate(self); // old pointer buffer
+    
+    self->_mouse_guest.x = x;
+    self->_mouse_guest.y = y;
+    
+    cs_cursor_invalidate(self); // new pointer buffer
+    
+    /* apparently we have to restore cursor when "cursor_move" */
+    if (self.hasCursor) {
+        self.cursorHidden = NO;
+    }
+}
+
+static void cs_cursor_hide(SpiceCursorChannel *channel, gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+    
+    self.cursorHidden = YES;
+    cs_cursor_invalidate(self);
+}
+
+static void cs_cursor_reset(SpiceCursorChannel *channel, gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+    
+    DISPLAY_DEBUG(self, "%s",  __FUNCTION__);
+    [self destroyCursor];
+    cs_cursor_invalidate(self);
+}
+
+#pragma mark - Channel events
+
 static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) {
 static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) {
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     gint channel_id;
     gint channel_id;
@@ -194,8 +289,35 @@ static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data
         return;
         return;
     }
     }
     
     
+    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
+        gpointer cursor_shape;
+        if (channel_id != self.channelID) {
+            return;
+        }
+        self.cursor = SPICE_CURSOR_CHANNEL(channel);
+        g_signal_connect(channel, "notify::cursor",
+                         G_CALLBACK(cs_cursor_set), GLIB_OBJC_RETAIN(self));
+        g_signal_connect(channel, "cursor-move",
+                         G_CALLBACK(cs_cursor_move), GLIB_OBJC_RETAIN(self));
+        g_signal_connect(channel, "cursor-hide",
+                         G_CALLBACK(cs_cursor_hide), GLIB_OBJC_RETAIN(self));
+        g_signal_connect(channel, "cursor-reset",
+                         G_CALLBACK(cs_cursor_reset), GLIB_OBJC_RETAIN(self));
+        spice_channel_connect(channel);
+        
+        g_object_get(G_OBJECT(channel), "cursor", &cursor_shape, NULL);
+        if (cursor_shape != NULL) {
+            g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
+            cs_cursor_set(self.cursor, NULL, (__bridge void *)self);
+        }
+        return;
+    }
+    
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
         self.main = SPICE_MAIN_CHANNEL(channel);
         self.main = SPICE_MAIN_CHANNEL(channel);
+        g_signal_connect(channel, "main-mouse-update",
+                         G_CALLBACK(cs_update_mouse_mode), GLIB_OBJC_RETAIN(self));
+        cs_update_mouse_mode(channel, data);
         return;
         return;
     }
     }
 }
 }
@@ -223,17 +345,31 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         return;
         return;
     }
     }
     
     
+    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
+        if (channel_id != self.channelID) {
+            return;
+        }
+        self.cursor = NULL;
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_set), GLIB_OBJC_RELEASE(self));
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_move), GLIB_OBJC_RELEASE(self));
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_hide), GLIB_OBJC_RELEASE(self));
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_reset), GLIB_OBJC_RELEASE(self));
+        return;
+    }
+    
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        self.main = NULL;
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_update_mouse_mode), GLIB_OBJC_RELEASE(self));
+        return;
+    }
+    
     return;
     return;
 }
 }
 
 
 - (void)setDevice:(id<MTLDevice>)device {
 - (void)setDevice:(id<MTLDevice>)device {
     _device = device;
     _device = device;
-    [self rebuildTexture];
-    [self rebuildVertices];
-}
-
-- (id<MTLDevice>)device {
-    return _device;
+    [self rebuildDisplayTexture];
+    [self rebuildDisplayVertices];
 }
 }
 
 
 - (UTMScreenshot *)screenshot {
 - (UTMScreenshot *)screenshot {
@@ -265,29 +401,16 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     }
     }
 }
 }
 
 
-@synthesize drawLock = _drawLock;
-@synthesize texture = _texture;
-@synthesize numVertices = _numVertices;
-@synthesize vertices = _vertices;
-@synthesize viewportOrigin;
-@synthesize viewportScale;
+#pragma mark - Methods
 
 
-- (id)init {
-    self = [super init];
-    if (self) {
-        _drawLock = dispatch_semaphore_create(1);
-        self.viewportScale = 1.0f;
-        self.viewportOrigin = CGPointMake(0, 0);
-    }
-    return self;
-}
-
-- (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
-    self = [self init];
-    if (self) {
+- (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
+    if (self = [super init]) {
         GList *list;
         GList *list;
         GList *it;
         GList *it;
         
         
+        self.drawLock = dispatch_semaphore_create(1);
+        self.viewportScale = 1.0f;
+        self.viewportOrigin = CGPointMake(0, 0);
         self.channelID = channelID;
         self.channelID = channelID;
         self.monitorID = monitorID;
         self.monitorID = monitorID;
         self.session = session;
         self.session = session;
@@ -314,7 +437,7 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     return self;
     return self;
 }
 }
 
 
-- (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID {
+- (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID {
     return [self initWithSession:session channelID:channelID monitorID:0];
     return [self initWithSession:session channelID:channelID monitorID:0];
 }
 }
 
 
@@ -322,6 +445,9 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     if (self.display) {
     if (self.display) {
         cs_channel_destroy(self.session, SPICE_CHANNEL(self.display), (__bridge void *)self);
         cs_channel_destroy(self.session, SPICE_CHANNEL(self.display), (__bridge void *)self);
     }
     }
+    if (_cursor) {
+        cs_channel_destroy(self.session, SPICE_CHANNEL(_cursor), (__bridge void *)self);
+    }
     UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
     UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
     g_signal_handlers_disconnect_by_func(self.session, G_CALLBACK(cs_channel_new), GLIB_OBJC_RELEASE(self));
     g_signal_handlers_disconnect_by_func(self.session, G_CALLBACK(cs_channel_new), GLIB_OBJC_RELEASE(self));
     g_signal_handlers_disconnect_by_func(self.session, G_CALLBACK(cs_channel_destroy), GLIB_OBJC_RELEASE(self));
     g_signal_handlers_disconnect_by_func(self.session, G_CALLBACK(cs_channel_destroy), GLIB_OBJC_RELEASE(self));
@@ -339,12 +465,12 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         _visibleArea = visible;
         _visibleArea = visible;
     }
     }
     self.displaySize = _visibleArea.size;
     self.displaySize = _visibleArea.size;
-    [self rebuildTexture];
-    [self rebuildVertices];
+    [self rebuildDisplayTexture];
+    [self rebuildDisplayVertices];
 }
 }
 
 
-- (void)rebuildTexture {
-    if (CGRectIsEmpty(_canvasArea) || !_device) {
+- (void)rebuildDisplayTexture {
+    if (CGRectIsEmpty(_canvasArea) || !self.device) {
         return;
         return;
     }
     }
     MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
     MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
@@ -352,13 +478,13 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     textureDescriptor.pixelFormat = (_canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? MTLPixelFormatBGRA8Unorm : (MTLPixelFormat)43;// FIXME: MTLPixelFormatBGR5A1Unorm is supposed to be available.
     textureDescriptor.pixelFormat = (_canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? MTLPixelFormatBGRA8Unorm : (MTLPixelFormat)43;// FIXME: MTLPixelFormatBGR5A1Unorm is supposed to be available.
     textureDescriptor.width = _visibleArea.size.width;
     textureDescriptor.width = _visibleArea.size.width;
     textureDescriptor.height = _visibleArea.size.height;
     textureDescriptor.height = _visibleArea.size.height;
-    dispatch_semaphore_wait(_drawLock, DISPATCH_TIME_FOREVER);
-    _texture = [_device newTextureWithDescriptor:textureDescriptor];
-    dispatch_semaphore_signal(_drawLock);
+    dispatch_semaphore_wait(self.drawLock, DISPATCH_TIME_FOREVER);
+    self.displayTexture = [self.device newTextureWithDescriptor:textureDescriptor];
+    dispatch_semaphore_signal(self.drawLock);
     [self drawRegion:_visibleArea];
     [self drawRegion:_visibleArea];
 }
 }
 
 
-- (void)rebuildVertices {
+- (void)rebuildDisplayVertices {
     // We flip the y-coordinates because pixman renders flipped
     // We flip the y-coordinates because pixman renders flipped
     UTMVertex quadVertices[] =
     UTMVertex quadVertices[] =
     {
     {
@@ -372,15 +498,15 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         { {  _visibleArea.size.width/2,  -_visibleArea.size.height/2 },  { 1.f, 1.f } },
         { {  _visibleArea.size.width/2,  -_visibleArea.size.height/2 },  { 1.f, 1.f } },
     };
     };
     
     
-    dispatch_semaphore_wait(_drawLock, DISPATCH_TIME_FOREVER);
+    dispatch_semaphore_wait(self.drawLock, DISPATCH_TIME_FOREVER);
     // Create our vertex buffer, and initialize it with our quadVertices array
     // Create our vertex buffer, and initialize it with our quadVertices array
-    _vertices = [_device newBufferWithBytes:quadVertices
-                                     length:sizeof(quadVertices)
-                                    options:MTLResourceStorageModeShared];
+    self.displayVertices = [self.device newBufferWithBytes:quadVertices
+                                                    length:sizeof(quadVertices)
+                                                   options:MTLResourceStorageModeShared];
 
 
     // Calculate the number of vertices by dividing the byte length by the size of each vertex
     // Calculate the number of vertices by dividing the byte length by the size of each vertex
-    _numVertices = sizeof(quadVertices) / sizeof(UTMVertex);
-    dispatch_semaphore_signal(_drawLock);
+    self.displayNumVertices = sizeof(quadVertices) / sizeof(UTMVertex);
+    dispatch_semaphore_signal(self.drawLock);
 }
 }
 
 
 - (void)drawRegion:(CGRect)rect {
 - (void)drawRegion:(CGRect)rect {
@@ -390,14 +516,14 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         { rect.origin.x-_visibleArea.origin.x, rect.origin.y-_visibleArea.origin.y, 0 }, // MTLOrigin
         { rect.origin.x-_visibleArea.origin.x, rect.origin.y-_visibleArea.origin.y, 0 }, // MTLOrigin
         { rect.size.width, rect.size.height, 1} // MTLSize
         { rect.size.width, rect.size.height, 1} // MTLSize
     };
     };
-    dispatch_semaphore_wait(_drawLock, DISPATCH_TIME_FOREVER);
+    dispatch_semaphore_wait(self.drawLock, DISPATCH_TIME_FOREVER);
     if (_canvasData != NULL) { // canvas may be destroyed by this time
     if (_canvasData != NULL) { // canvas may be destroyed by this time
-        [_texture replaceRegion:region
-                    mipmapLevel:0
-                      withBytes:(const char *)_canvasData + (NSUInteger)(rect.origin.y*_canvasStride + rect.origin.x*pixelSize)
-                    bytesPerRow:_canvasStride];
+        [self.displayTexture replaceRegion:region
+                               mipmapLevel:0
+                                 withBytes:(const char *)_canvasData + (NSUInteger)(rect.origin.y*_canvasStride + rect.origin.x*pixelSize)
+                               bytesPerRow:_canvasStride];
     }
     }
-    dispatch_semaphore_signal(_drawLock);
+    dispatch_semaphore_signal(self.drawLock);
 }
 }
 
 
 - (BOOL)visible {
 - (BOOL)visible {
@@ -420,4 +546,88 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     spice_main_channel_send_monitor_config(self.main);
     spice_main_channel_send_monitor_config(self.main);
 }
 }
 
 
+#pragma mark - Cursor drawing
+
+- (void)rebuildCursorWithSize:(CGSize)size center:(CGPoint)hotspot {
+    // hotspot is the offset in buffer for the center of the pointer
+    if (!self.device) {
+        UTMLog(@"MTL device not ready for cursor draw");
+        return;
+    }
+    dispatch_semaphore_wait(self.drawLock, DISPATCH_TIME_FOREVER);
+    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
+    // don't worry that that components are reversed, we fix it in shaders
+    textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
+    textureDescriptor.width = size.width;
+    textureDescriptor.height = size.height;
+    self.cursorTexture = [self.device newTextureWithDescriptor:textureDescriptor];
+
+    // We flip the y-coordinates because pixman renders flipped
+    UTMVertex quadVertices[] =
+    {
+     // Pixel positions, Texture coordinates
+     { { -hotspot.x + size.width, hotspot.y               },  { 1.f, 0.f } },
+     { { -hotspot.x             , hotspot.y               },  { 0.f, 0.f } },
+     { { -hotspot.x             , hotspot.y - size.height },  { 0.f, 1.f } },
+     
+     { { -hotspot.x + size.width, hotspot.y               },  { 1.f, 0.f } },
+     { { -hotspot.x             , hotspot.y - size.height },  { 0.f, 1.f } },
+     { { -hotspot.x + size.width, hotspot.y - size.height },  { 1.f, 1.f } },
+    };
+
+    // Create our vertex buffer, and initialize it with our quadVertices array
+    self.cursorVertices = [self.device newBufferWithBytes:quadVertices
+                                                   length:sizeof(quadVertices)
+                                                  options:MTLResourceStorageModeShared];
+
+    // Calculate the number of vertices by dividing the byte length by the size of each vertex
+    self.cursorNumVertices = sizeof(quadVertices) / sizeof(UTMVertex);
+    self.cursorSize = size;
+    self.cursorHotspot = hotspot;
+    self.hasCursor = YES;
+    dispatch_semaphore_signal(self.drawLock);
+}
+
+- (void)destroyCursor {
+    dispatch_semaphore_wait(self.drawLock, DISPATCH_TIME_FOREVER);
+    self.cursorNumVertices = 0;
+    self.cursorVertices = nil;
+    self.cursorTexture = nil;
+    self.cursorSize = CGSizeZero;
+    self.cursorHotspot = CGPointZero;
+    self.hasCursor = NO;
+    dispatch_semaphore_signal(self.drawLock);
+}
+
+- (void)drawCursor:(const void *)buffer {
+    const NSInteger pixelSize = 4;
+    MTLRegion region = {
+        { 0, 0 }, // MTLOrigin
+        { self.cursorSize.width, self.cursorSize.height, 1} // MTLSize
+    };
+    dispatch_semaphore_wait(self.drawLock, DISPATCH_TIME_FOREVER);
+    [self.cursorTexture replaceRegion:region
+                          mipmapLevel:0
+                            withBytes:buffer
+                          bytesPerRow:self.cursorSize.width*pixelSize];
+    dispatch_semaphore_signal(self.drawLock);
+}
+
+- (BOOL)cursorVisible {
+    return !self.inhibitCursor && self.hasCursor && !self.cursorHidden;
+}
+
+- (CGPoint)cursorOrigin {
+    CGPoint point = _mouse_guest;
+    point.x -= self.displaySize.width/2;
+    point.y -= self.displaySize.height/2;
+    point.x *= self.viewportScale;
+    point.y *= self.viewportScale;
+    return point;
+}
+
+- (void)forceCursorPosition:(CGPoint)pos {
+    _mouse_guest = pos;
+}
+
 @end
 @end

+ 3 - 7
CocoaSpice/CSInput.h

@@ -40,17 +40,13 @@ typedef NS_ENUM(NSInteger, CSInputScroll) {
 
 
 NS_ASSUME_NONNULL_BEGIN
 NS_ASSUME_NONNULL_BEGIN
 
 
-@interface CSInput : NSObject <UTMRenderSource>
+@interface CSInput : NSObject
 
 
 @property (nonatomic, readonly, nullable) SpiceSession *session;
 @property (nonatomic, readonly, nullable) SpiceSession *session;
 @property (nonatomic, readonly, assign) NSInteger channelID;
 @property (nonatomic, readonly, assign) NSInteger channelID;
 @property (nonatomic, readonly, assign) NSInteger monitorID;
 @property (nonatomic, readonly, assign) NSInteger monitorID;
 @property (nonatomic, readonly, assign) BOOL serverModeCursor;
 @property (nonatomic, readonly, assign) BOOL serverModeCursor;
-@property (nonatomic, readonly, assign) BOOL hasCursor;
 @property (nonatomic, assign) BOOL disableInputs;
 @property (nonatomic, assign) BOOL disableInputs;
-@property (nonatomic, readonly) CGSize cursorSize;
-@property (nonatomic, assign) CGSize displaySize;
-@property (nonatomic, assign) BOOL inhibitCursor;
 
 
 - (void)sendKey:(CSInputKey)type code:(int)scancode;
 - (void)sendKey:(CSInputKey)type code:(int)scancode;
 - (void)sendPause:(CSInputKey)type;
 - (void)sendPause:(CSInputKey)type;
@@ -60,9 +56,9 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)sendMouseScroll:(CSInputScroll)type button:(CSInputButton)button dy:(CGFloat)dy;
 - (void)sendMouseScroll:(CSInputScroll)type button:(CSInputButton)button dy:(CGFloat)dy;
 - (void)sendMouseButton:(CSInputButton)button pressed:(BOOL)pressed point:(CGPoint)point;
 - (void)sendMouseButton:(CSInputButton)button pressed:(BOOL)pressed point:(CGPoint)point;
 - (void)requestMouseMode:(BOOL)server;
 - (void)requestMouseMode:(BOOL)server;
-- (void)forceCursorPosition:(CGPoint)pos;
 
 
-- (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID;
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID NS_DESIGNATED_INITIALIZER;
 
 
 @end
 @end
 
 

+ 10 - 244
CocoaSpice/CSInput.m

@@ -27,115 +27,18 @@
 @property (nonatomic, readwrite, nullable) SpiceSession *session;
 @property (nonatomic, readwrite, nullable) SpiceSession *session;
 @property (nonatomic, readwrite, assign) NSInteger channelID;
 @property (nonatomic, readwrite, assign) NSInteger channelID;
 @property (nonatomic, readwrite, assign) NSInteger monitorID;
 @property (nonatomic, readwrite, assign) NSInteger monitorID;
-@property (nonatomic, readwrite, assign) BOOL serverModeCursor;
-@property (nonatomic, readwrite, assign) BOOL hasCursor;
-@property (nonatomic, readwrite) CGSize cursorSize;
-@property (nonatomic, readwrite) CGPoint cursorHotspot;
 @property (nonatomic, nullable) SpiceMainChannel *main;
 @property (nonatomic, nullable) SpiceMainChannel *main;
 @property (nonatomic, nullable) SpiceInputsChannel *inputs;
 @property (nonatomic, nullable) SpiceInputsChannel *inputs;
 
 
 @end
 @end
 
 
 @implementation CSInput {
 @implementation CSInput {
-    SpiceCursorChannel      *_cursor;
-    
-    CGPoint                 _mouse_guest;
     CGFloat                 _scroll_delta_y;
     CGFloat                 _scroll_delta_y;
     
     
     uint32_t                _key_state[512 / 32];
     uint32_t                _key_state[512 / 32];
-    
-    // Drawing cursor
-    id<MTLDevice>           _device;
-    id<MTLTexture>          _texture;
-    id<MTLBuffer>           _vertices;
-    NSUInteger              _numVertices;
-    dispatch_semaphore_t    _drawLock;
-    BOOL                    _cursorHidden;
-}
-
-#pragma mark - glib events
-
-static void cs_update_mouse_mode(SpiceChannel *channel, gpointer data)
-{
-    CSInput *self = (__bridge CSInput *)data;
-    enum SpiceMouseMode mouse_mode;
-    
-    g_object_get(channel, "mouse-mode", &mouse_mode, NULL);
-    DISPLAY_DEBUG(self, "mouse mode %u", mouse_mode);
-    
-    self.serverModeCursor = (mouse_mode == SPICE_MOUSE_MODE_SERVER);
-    
-    if (self.serverModeCursor) {
-        self->_mouse_guest.x = -1;
-        self->_mouse_guest.y = -1;
-    }
-}
-
-static void cs_cursor_invalidate(CSInput *self)
-{
-    // We implement two different textures so invalidate is not needed
-}
-
-static void cs_cursor_set(SpiceCursorChannel *channel,
-                          G_GNUC_UNUSED GParamSpec *pspec,
-                          gpointer data)
-{
-    CSInput *self = (__bridge CSInput *)data;
-    SpiceCursorShape *cursor_shape;
-    
-    g_object_get(G_OBJECT(channel), "cursor", &cursor_shape, NULL);
-    if (G_UNLIKELY(cursor_shape == NULL || cursor_shape->data == NULL)) {
-        if (cursor_shape != NULL) {
-            g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
-        }
-        return;
-    }
-    
-    cs_cursor_invalidate(self);
-    
-    CGPoint hotspot = CGPointMake(cursor_shape->hot_spot_x, cursor_shape->hot_spot_y);
-    CGSize newSize = CGSizeMake(cursor_shape->width, cursor_shape->height);
-    if (!CGSizeEqualToSize(newSize, self.cursorSize) || !CGPointEqualToPoint(hotspot, self.cursorHotspot)) {
-        [self rebuildTexture:newSize center:hotspot];
-    }
-    [self drawCursor:cursor_shape->data];
-    self->_cursorHidden = NO;
-    cs_cursor_invalidate(self);
 }
 }
 
 
-static void cs_cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
-{
-    CSInput *self = (__bridge CSInput *)data;
-    
-    cs_cursor_invalidate(self); // old pointer buffer
-    
-    self->_mouse_guest.x = x;
-    self->_mouse_guest.y = y;
-    
-    cs_cursor_invalidate(self); // new pointer buffer
-    
-    /* apparently we have to restore cursor when "cursor_move" */
-    if (self.hasCursor) {
-        self->_cursorHidden = NO;
-    }
-}
-
-static void cs_cursor_hide(SpiceCursorChannel *channel, gpointer data)
-{
-    CSInput *self = (__bridge CSInput *)data;
-    
-    self->_cursorHidden = YES;
-    cs_cursor_invalidate(self);
-}
-
-static void cs_cursor_reset(SpiceCursorChannel *channel, gpointer data)
-{
-    CSInput *self = (__bridge CSInput *)data;
-    
-    DISPLAY_DEBUG(self, "%s",  __FUNCTION__);
-    [self destroyTexture];
-    cs_cursor_invalidate(self);
-}
+#pragma mark - Channel events
 
 
 static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
 static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
 {
 {
@@ -145,32 +48,6 @@ static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data
     g_object_get(channel, "channel-id", &chid, NULL);
     g_object_get(channel, "channel-id", &chid, NULL);
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
         self.main = SPICE_MAIN_CHANNEL(channel);
         self.main = SPICE_MAIN_CHANNEL(channel);
-        g_signal_connect(channel, "main-mouse-update",
-                         G_CALLBACK(cs_update_mouse_mode), GLIB_OBJC_RETAIN(self));
-        cs_update_mouse_mode(channel, data);
-        return;
-    }
-    
-    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
-        gpointer cursor_shape;
-        if (chid != self.channelID)
-            return;
-        self->_cursor = SPICE_CURSOR_CHANNEL(channel);
-        g_signal_connect(channel, "notify::cursor",
-                         G_CALLBACK(cs_cursor_set), GLIB_OBJC_RETAIN(self));
-        g_signal_connect(channel, "cursor-move",
-                         G_CALLBACK(cs_cursor_move), GLIB_OBJC_RETAIN(self));
-        g_signal_connect(channel, "cursor-hide",
-                         G_CALLBACK(cs_cursor_hide), GLIB_OBJC_RETAIN(self));
-        g_signal_connect(channel, "cursor-reset",
-                         G_CALLBACK(cs_cursor_reset), GLIB_OBJC_RETAIN(self));
-        spice_channel_connect(channel);
-        
-        g_object_get(G_OBJECT(channel), "cursor", &cursor_shape, NULL);
-        if (cursor_shape != NULL) {
-            g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
-            cs_cursor_set(self->_cursor, NULL, (__bridge void *)self);
-        }
         return;
         return;
     }
     }
     
     
@@ -189,22 +66,8 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     g_object_get(channel, "channel-id", &chid, NULL);
     g_object_get(channel, "channel-id", &chid, NULL);
     DISPLAY_DEBUG(self, "channel_destroy %d", chid);
     DISPLAY_DEBUG(self, "channel_destroy %d", chid);
     
     
-    [self destroyTexture];
-    
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
         self.main = NULL;
         self.main = NULL;
-        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_update_mouse_mode), GLIB_OBJC_RELEASE(self));
-        return;
-    }
-    
-    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
-        if (chid != self.channelID)
-            return;
-        self->_cursor = NULL;
-        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_set), GLIB_OBJC_RELEASE(self));
-        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_move), GLIB_OBJC_RELEASE(self));
-        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_hide), GLIB_OBJC_RELEASE(self));
-        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_reset), GLIB_OBJC_RELEASE(self));
         return;
         return;
     }
     }
     
     
@@ -216,6 +79,14 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     return;
     return;
 }
 }
 
 
+#pragma mark - Properties
+
+- (BOOL)serverModeCursor {
+    enum SpiceMouseMode mouse_mode;
+    g_object_get(self.main, "mouse-mode", &mouse_mode, NULL);
+    return (mouse_mode == SPICE_MOUSE_MODE_SERVER);
+}
+
 #pragma mark - Key handling
 #pragma mark - Key handling
 
 
 - (void)sendPause:(CSInputKey)type {
 - (void)sendPause:(CSInputKey)type {
@@ -405,24 +276,10 @@ static int cs_button_to_spice(CSInputButton button)
     }
     }
 }
 }
 
 
-- (void)forceCursorPosition:(CGPoint)pos {
-    _mouse_guest = pos;
-}
-
 #pragma mark - Initializers
 #pragma mark - Initializers
 
 
-- (id)init {
+- (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
     self = [super init];
     self = [super init];
-    if (self) {
-        _drawLock = dispatch_semaphore_create(1);
-        self.viewportScale = 1.0f;
-        self.viewportOrigin = CGPointMake(0, 0);
-    }
-    return self;
-}
-
-- (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
-    self = [self init];
     if (self) {
     if (self) {
         GList *list;
         GList *list;
         GList *it;
         GList *it;
@@ -454,9 +311,6 @@ static int cs_button_to_spice(CSInputButton button)
 }
 }
 
 
 - (void)dealloc {
 - (void)dealloc {
-    if (_cursor) {
-        cs_channel_destroy(self.session, SPICE_CHANNEL(_cursor), (__bridge void *)self);
-    }
     if (self.main) {
     if (self.main) {
         cs_channel_destroy(self.session, SPICE_CHANNEL(self.main), (__bridge void *)self);
         cs_channel_destroy(self.session, SPICE_CHANNEL(self.main), (__bridge void *)self);
     }
     }
@@ -467,92 +321,4 @@ static int cs_button_to_spice(CSInputButton button)
     self.session = NULL;
     self.session = NULL;
 }
 }
 
 
-#pragma mark - Drawing Cursor
-
-@synthesize device = _device;
-@synthesize drawLock = _drawLock;
-@synthesize texture = _texture;
-@synthesize numVertices = _numVertices;
-@synthesize vertices = _vertices;
-@synthesize viewportOrigin;
-@synthesize viewportScale;
-
-- (void)rebuildTexture:(CGSize)size center:(CGPoint)hotspot {
-    // hotspot is the offset in buffer for the center of the pointer
-    if (!_device) {
-        UTMLog(@"MTL device not ready for cursor draw");
-        return;
-    }
-    dispatch_semaphore_wait(_drawLock, DISPATCH_TIME_FOREVER);
-    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
-    // don't worry that that components are reversed, we fix it in shaders
-    textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
-    textureDescriptor.width = size.width;
-    textureDescriptor.height = size.height;
-    _texture = [_device newTextureWithDescriptor:textureDescriptor];
-
-    // We flip the y-coordinates because pixman renders flipped
-    UTMVertex quadVertices[] =
-    {
-     // Pixel positions, Texture coordinates
-     { { -hotspot.x + size.width, hotspot.y               },  { 1.f, 0.f } },
-     { { -hotspot.x             , hotspot.y               },  { 0.f, 0.f } },
-     { { -hotspot.x             , hotspot.y - size.height },  { 0.f, 1.f } },
-     
-     { { -hotspot.x + size.width, hotspot.y               },  { 1.f, 0.f } },
-     { { -hotspot.x             , hotspot.y - size.height },  { 0.f, 1.f } },
-     { { -hotspot.x + size.width, hotspot.y - size.height },  { 1.f, 1.f } },
-    };
-
-    // Create our vertex buffer, and initialize it with our quadVertices array
-    _vertices = [_device newBufferWithBytes:quadVertices
-                                    length:sizeof(quadVertices)
-                                   options:MTLResourceStorageModeShared];
-
-    // Calculate the number of vertices by dividing the byte length by the size of each vertex
-    _numVertices = sizeof(quadVertices) / sizeof(UTMVertex);
-    self.cursorSize = size;
-    self.cursorHotspot = hotspot;
-    self.hasCursor = YES;
-    dispatch_semaphore_signal(_drawLock);
-}
-
-- (void)destroyTexture {
-    dispatch_semaphore_wait(_drawLock, DISPATCH_TIME_FOREVER);
-    _numVertices = 0;
-    _vertices = nil;
-    _texture = nil;
-    self.cursorSize = CGSizeZero;
-    self.cursorHotspot = CGPointZero;
-    self.hasCursor = NO;
-    dispatch_semaphore_signal(_drawLock);
-}
-
-- (void)drawCursor:(const void *)buffer {
-    const NSInteger pixelSize = 4;
-    MTLRegion region = {
-        { 0, 0 }, // MTLOrigin
-        { self.cursorSize.width, self.cursorSize.height, 1} // MTLSize
-    };
-    dispatch_semaphore_wait(_drawLock, DISPATCH_TIME_FOREVER);
-    [_texture replaceRegion:region
-                mipmapLevel:0
-                  withBytes:buffer
-                bytesPerRow:self.cursorSize.width*pixelSize];
-    dispatch_semaphore_signal(_drawLock);
-}
-
-- (BOOL)visible {
-    return !self.inhibitCursor && self.hasCursor && !_cursorHidden;
-}
-
-- (CGPoint)viewportOrigin {
-    CGPoint point = _mouse_guest;
-    point.x -= self.displaySize.width/2;
-    point.y -= self.displaySize.height/2;
-    point.x *= self.viewportScale;
-    point.y *= self.viewportScale;
-    return point;
-}
-
 @end
 @end

+ 0 - 20
Managers/UTMSpiceIO.m

@@ -39,7 +39,6 @@ typedef void (^connectionCallback_t)(BOOL success, NSString * _Nullable msg);
 @property (nonatomic, nullable) CSSession *session;
 @property (nonatomic, nullable) CSSession *session;
 @property (nonatomic, nullable, copy) NSURL *sharedDirectory;
 @property (nonatomic, nullable, copy) NSURL *sharedDirectory;
 @property (nonatomic) NSInteger port;
 @property (nonatomic) NSInteger port;
-@property (nonatomic) BOOL hasObservers;
 @property (nonatomic) BOOL dynamicResolutionSupported;
 @property (nonatomic) BOOL dynamicResolutionSupported;
 
 
 @end
 @end
@@ -85,15 +84,6 @@ typedef void (^connectionCallback_t)(BOOL success, NSString * _Nullable msg);
     }
     }
 }
 }
 
 
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
-    // make sure the CSDisplay properties are synced with the CSInput
-    if ([keyPath isEqualToString:@"primaryDisplay.viewportScale"]) {
-        self.primaryInput.viewportScale = self.primaryDisplay.viewportScale;
-    } else if ([keyPath isEqualToString:@"primaryDisplay.displaySize"]) {
-        self.primaryInput.displaySize = self.primaryDisplay.displaySize;
-    }
-}
-
 #pragma mark - UTMInputOutput
 #pragma mark - UTMInputOutput
 
 
 - (BOOL)startWithError:(NSError **)err {
 - (BOOL)startWithError:(NSError **)err {
@@ -141,11 +131,6 @@ typedef void (^connectionCallback_t)(BOOL success, NSString * _Nullable msg);
 
 
 - (void)disconnect {
 - (void)disconnect {
     @synchronized (self) {
     @synchronized (self) {
-        if (self.hasObservers) {
-            [self removeObserver:self forKeyPath:@"primaryDisplay.viewportScale"];
-            [self removeObserver:self forKeyPath:@"primaryDisplay.displaySize"];
-            self.hasObservers = NO;
-        }
         [self.spiceConnection disconnect];
         [self.spiceConnection disconnect];
         self.spiceConnection.delegate = nil;
         self.spiceConnection.delegate = nil;
         self.spiceConnection = nil;
         self.spiceConnection = nil;
@@ -207,11 +192,6 @@ typedef void (^connectionCallback_t)(BOOL success, NSString * _Nullable msg);
         _primaryInput = input;
         _primaryInput = input;
         _delegate.vmDisplay = display;
         _delegate.vmDisplay = display;
         _delegate.vmInput = input;
         _delegate.vmInput = input;
-        @synchronized (self) {
-            [self addObserver:self forKeyPath:@"primaryDisplay.viewportScale" options:0 context:nil];
-            [self addObserver:self forKeyPath:@"primaryDisplay.displaySize" options:0 context:nil];
-            self.hasObservers = YES;
-        }
         if (self.connectionCallback) {
         if (self.connectionCallback) {
             self.connectionCallback(YES, nil);
             self.connectionCallback(YES, nil);
             self.connectionCallback = nil;
             self.connectionCallback = nil;

+ 3 - 2
Platform/iOS/Display/VMCursor.m

@@ -16,6 +16,7 @@
 
 
 #import "VMCursor.h"
 #import "VMCursor.h"
 #import "VMDisplayMetalViewController+Touch.h"
 #import "VMDisplayMetalViewController+Touch.h"
+#import "CSDisplayMetal.h"
 
 
 @implementation VMCursor {
 @implementation VMCursor {
     CGPoint _start;
     CGPoint _start;
@@ -46,8 +47,8 @@
 
 
 - (CGRect)bounds {
 - (CGRect)bounds {
     CGRect bounds = CGRectZero;
     CGRect bounds = CGRectZero;
-    bounds.size.width = MAX(1, _controller.vmInput.cursorSize.width);
-    bounds.size.height = MAX(1, _controller.vmInput.cursorSize.height);
+    bounds.size.width = MAX(1, _controller.vmDisplay.cursorSize.width);
+    bounds.size.height = MAX(1, _controller.vmDisplay.cursorSize.height);
     return bounds;
     return bounds;
 }
 }
 
 

+ 2 - 2
Platform/iOS/Display/VMDisplayMetalViewController+Touch.m

@@ -338,7 +338,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
     translated = [self clipCursorToDisplay:translated];
     translated = [self clipCursorToDisplay:translated];
     if (!self.vmInput.serverModeCursor) {
     if (!self.vmInput.serverModeCursor) {
         [self.vmInput sendMouseMotion:self.mouseButtonDown point:translated];
         [self.vmInput sendMouseMotion:self.mouseButtonDown point:translated];
-        [self.vmInput forceCursorPosition:translated]; // required to show cursor on screen
+        [self.vmDisplay forceCursorPosition:translated]; // required to show cursor on screen
     } else {
     } else {
         UTMLog(@"Warning: ignored mouse set (%f, %f) while mouse is in server mode", translated.x, translated.y);
         UTMLog(@"Warning: ignored mouse set (%f, %f) while mouse is in server mode", translated.x, translated.y);
     }
     }
@@ -571,7 +571,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
 - (BOOL)switchMouseType:(VMMouseType)type {
 - (BOOL)switchMouseType:(VMMouseType)type {
     BOOL shouldHideCursor = (type == VMMouseTypeAbsoluteHideCursor);
     BOOL shouldHideCursor = (type == VMMouseTypeAbsoluteHideCursor);
     BOOL shouldUseServerMouse = (type == VMMouseTypeRelative);
     BOOL shouldUseServerMouse = (type == VMMouseTypeRelative);
-    self.vmInput.inhibitCursor = shouldHideCursor;
+    self.vmDisplay.inhibitCursor = shouldHideCursor;
     if (shouldUseServerMouse != self.vmInput.serverModeCursor) {
     if (shouldUseServerMouse != self.vmInput.serverModeCursor) {
         UTMLog(@"Switching mouse mode to server:%d for type:%ld", shouldUseServerMouse, type);
         UTMLog(@"Switching mouse mode to server:%d for type:%ld", shouldUseServerMouse, type);
         [self.vm requestInputTablet:!shouldUseServerMouse];
         [self.vm requestInputTablet:!shouldUseServerMouse];

+ 2 - 4
Platform/iOS/Display/VMDisplayMetalViewController.m

@@ -63,8 +63,7 @@
     
     
     // Initialize our renderer with the view size
     // Initialize our renderer with the view size
     [_renderer mtkView:self.mtkView drawableSizeWillChange:self.mtkView.drawableSize];
     [_renderer mtkView:self.mtkView drawableSizeWillChange:self.mtkView.drawableSize];
-    _renderer.sourceScreen = self.vmDisplay;
-    _renderer.sourceCursor = self.vmInput;
+    _renderer.source = self.vmDisplay;
     
     
     [_renderer changeUpscaler:self.vmConfiguration.displayUpscalerValue
     [_renderer changeUpscaler:self.vmConfiguration.displayUpscalerValue
                    downscaler:self.vmConfiguration.displayDownscalerValue];
                    downscaler:self.vmConfiguration.displayDownscalerValue];
@@ -121,8 +120,7 @@
                 self.placeholderImageView.hidden = YES;
                 self.placeholderImageView.hidden = YES;
                 self.mtkView.hidden = NO;
                 self.mtkView.hidden = NO;
             } completion:nil];
             } completion:nil];
-            self->_renderer.sourceScreen = self.vmDisplay;
-            self->_renderer.sourceCursor = self.vmInput;
+            self->_renderer.source = self.vmDisplay;
             [self displayResize:self.view.bounds.size];
             [self displayResize:self.view.bounds.size];
             if (self.vmConfiguration.shareClipboardEnabled) {
             if (self.vmConfiguration.shareClipboardEnabled) {
                 [[UTMPasteboard generalPasteboard] requestPollingModeForObject:self];
                 [[UTMPasteboard generalPasteboard] requestPollingModeForObject:self];

+ 2 - 3
Platform/macOS/Display/VMDisplayMetalWindowController.swift

@@ -84,8 +84,7 @@ class VMDisplayMetalWindowController: VMDisplayWindowController, UTMSpiceIODeleg
     override func enterLive() {
     override func enterLive() {
         metalView.isHidden = false
         metalView.isHidden = false
         screenshotView.isHidden = true
         screenshotView.isHidden = true
-        renderer!.sourceScreen = vmDisplay
-        renderer!.sourceCursor = vmInput
+        renderer!.source = vmDisplay
         displaySizeObserver = observe(\.vmDisplay!.displaySize, options: [.initial, .new]) { (_, change) in
         displaySizeObserver = observe(\.vmDisplay!.displaySize, options: [.initial, .new]) { (_, change) in
             guard let size = change.newValue else { return }
             guard let size = change.newValue else { return }
             self.displaySizeDidChange(size: size)
             self.displaySizeDidChange(size: size)
@@ -282,7 +281,7 @@ extension VMDisplayMetalWindowController: VMMetalViewInputDelegate {
         let point = CGPoint(x: newX, y: newY)
         let point = CGPoint(x: newX, y: newY)
         logger.debug("move cursor: cocoa (\(absolutePoint.x), \(absolutePoint.y)), native (\(newX), \(newY))")
         logger.debug("move cursor: cocoa (\(absolutePoint.x), \(absolutePoint.y)), native (\(newX), \(newY))")
         vmInput?.sendMouseMotion(button, point: point)
         vmInput?.sendMouseMotion(button, point: point)
-        vmInput?.forceCursorPosition(point) // required to show cursor on screen
+        vmDisplay?.forceCursorPosition(point) // required to show cursor on screen
     }
     }
     
     
     func mouseMove(relativePoint: CGPoint, button: CSInputButton) {
     func mouseMove(relativePoint: CGPoint, button: CSInputButton) {

+ 11 - 7
Renderer/UTMRenderSource.h

@@ -21,14 +21,18 @@ NS_ASSUME_NONNULL_BEGIN
 
 
 @protocol UTMRenderSource <NSObject>
 @protocol UTMRenderSource <NSObject>
 
 
-@property (nonatomic, readonly) BOOL visible;
-@property (nonatomic, assign) CGPoint viewportOrigin;
-@property (nonatomic, assign) CGFloat viewportScale;
+@property (nonatomic, readonly) BOOL cursorVisible;
+@property (nonatomic) CGPoint cursorOrigin;
+@property (nonatomic) CGPoint viewportOrigin;
+@property (nonatomic) CGFloat viewportScale;
 @property (nonatomic, readonly) dispatch_semaphore_t drawLock;
 @property (nonatomic, readonly) dispatch_semaphore_t drawLock;
-@property (nonatomic, nullable, strong) id<MTLDevice> device;
-@property (nonatomic, nullable, readonly) id<MTLTexture> texture;
-@property (nonatomic, readonly) NSUInteger numVertices;
-@property (nonatomic, nullable, readonly) id<MTLBuffer> vertices;
+@property (nonatomic, nullable) id<MTLDevice> device;
+@property (nonatomic, nullable, readonly) id<MTLTexture> displayTexture;
+@property (nonatomic, nullable, readonly) id<MTLTexture> cursorTexture;
+@property (nonatomic, readonly) NSUInteger displayNumVertices;
+@property (nonatomic, readonly) NSUInteger cursorNumVertices;
+@property (nonatomic, nullable, readonly) id<MTLBuffer> displayVertices;
+@property (nonatomic, nullable, readonly) id<MTLBuffer> cursorVertices;
 
 
 @end
 @end
 
 

+ 1 - 2
Renderer/UTMRenderer.h

@@ -14,8 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
 // Our platform independent renderer class
 // Our platform independent renderer class
 @interface UTMRenderer : NSObject<MTKViewDelegate>
 @interface UTMRenderer : NSObject<MTKViewDelegate>
 
 
-@property (nonatomic, weak, nullable) id<UTMRenderSource> sourceScreen;
-@property (nonatomic, weak, nullable) id<UTMRenderSource> sourceCursor;
+@property (nonatomic, weak, nullable) id<UTMRenderSource> source;
 
 
 - (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView;
 - (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView;
 - (void)changeUpscaler:(MTLSamplerMinMagFilter)upscaler downscaler:(MTLSamplerMinMagFilter)downscaler;
 - (void)changeUpscaler:(MTLSamplerMinMagFilter)upscaler downscaler:(MTLSamplerMinMagFilter)downscaler;

+ 73 - 97
Renderer/UTMRenderer.m

@@ -34,14 +34,9 @@ Implementation of renderer class which performs Metal setup and per frame render
     id<MTLSamplerState> _sampler;
     id<MTLSamplerState> _sampler;
 }
 }
 
 
-- (void)setSourceScreen:(id<UTMRenderSource>)source {
+- (void)setSource:(id<UTMRenderSource>)source {
     source.device = _device;
     source.device = _device;
-    _sourceScreen = source;
-}
-
-- (void)setSourceCursor:(id<UTMRenderSource>)source {
-    source.device = _device;
-    _sourceCursor = source;
+    _source = source;
 }
 }
 
 
 /// Initialize with the MetalKit view from which we'll obtain our Metal device
 /// Initialize with the MetalKit view from which we'll obtain our Metal device
@@ -154,7 +149,8 @@ static matrix_float4x4 matrix_scale_translate(CGFloat scale, CGPoint translate)
 /// Called whenever the view needs to render a frame
 /// Called whenever the view needs to render a frame
 - (void)drawInMTKView:(nonnull MTKView *)view
 - (void)drawInMTKView:(nonnull MTKView *)view
 {
 {
-    if (view.hidden) {
+    id<UTMRenderSource> source = self.source;
+    if (view.hidden || !source) {
         return;
         return;
     }
     }
 
 
@@ -167,96 +163,81 @@ static matrix_float4x4 matrix_scale_translate(CGFloat scale, CGPoint translate)
 
 
     if(renderPassDescriptor != nil)
     if(renderPassDescriptor != nil)
     {
     {
-        BOOL screenDrawn = NO;
-        __weak dispatch_semaphore_t screenLock = nil;
-        __weak dispatch_semaphore_t cursorLock = nil;
-
         // Create a render command encoder so we can render into something
         // Create a render command encoder so we can render into something
         id<MTLRenderCommandEncoder> renderEncoder =
         id<MTLRenderCommandEncoder> renderEncoder =
         [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
         [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
         renderEncoder.label = @"MyRenderEncoder";
         renderEncoder.label = @"MyRenderEncoder";
         
         
-        if (self.sourceScreen) {
-            // Lock screen updates
-            bool hasAlpha = NO;
-            screenLock = self.sourceScreen.drawLock;
-            dispatch_semaphore_wait(screenLock, DISPATCH_TIME_FOREVER);
-            
-            if (self.sourceScreen.visible) {
-                // Render the screen first
-                
-                matrix_float4x4 transform = matrix_scale_translate(self.sourceScreen.viewportScale,
-                                                                   self.sourceScreen.viewportOrigin);
-
-                [renderEncoder setRenderPipelineState:_pipelineState];
-
-                [renderEncoder setVertexBuffer:self.sourceScreen.vertices
-                                        offset:0
-                                      atIndex:UTMVertexInputIndexVertices];
-
-                [renderEncoder setVertexBytes:&_viewportSize
-                                       length:sizeof(_viewportSize)
-                                      atIndex:UTMVertexInputIndexViewportSize];
-
-                [renderEncoder setVertexBytes:&transform
-                                       length:sizeof(transform)
-                                      atIndex:UTMVertexInputIndexTransform];
-
-                [renderEncoder setVertexBytes:&hasAlpha
-                                       length:sizeof(hasAlpha)
-                                      atIndex:UTMVertexInputIndexHasAlpha];
-
-                // Set the texture object.  The UTMTextureIndexBaseColor enum value corresponds
-                ///  to the 'colorMap' argument in our 'samplingShader' function because its
-                //   texture attribute qualifier also uses UTMTextureIndexBaseColor for its index
-                [renderEncoder setFragmentTexture:self.sourceScreen.texture
-                                          atIndex:UTMTextureIndexBaseColor];
-                
-                [renderEncoder setFragmentSamplerState:_sampler
-                                               atIndex:UTMSamplerIndexTexture];
-
-                // Draw the vertices of our triangles
-                [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
-                                  vertexStart:0
-                                  vertexCount:self.sourceScreen.numVertices];
-                
-                screenDrawn = YES;
-            }
-        }
+        // Lock screen updates
+        dispatch_semaphore_t drawLock = source.drawLock;
+        dispatch_semaphore_wait(drawLock, DISPATCH_TIME_FOREVER);
+        
+        // Render the screen first
+        
+        bool hasAlpha = NO;
+        matrix_float4x4 transform = matrix_scale_translate(source.viewportScale,
+                                                           source.viewportOrigin);
+
+        [renderEncoder setRenderPipelineState:_pipelineState];
 
 
-        if (screenDrawn && self.sourceCursor) {
-            // Lock cursor updates
+        [renderEncoder setVertexBuffer:source.displayVertices
+                                offset:0
+                              atIndex:UTMVertexInputIndexVertices];
+
+        [renderEncoder setVertexBytes:&_viewportSize
+                               length:sizeof(_viewportSize)
+                              atIndex:UTMVertexInputIndexViewportSize];
+
+        [renderEncoder setVertexBytes:&transform
+                               length:sizeof(transform)
+                              atIndex:UTMVertexInputIndexTransform];
+
+        [renderEncoder setVertexBytes:&hasAlpha
+                               length:sizeof(hasAlpha)
+                              atIndex:UTMVertexInputIndexHasAlpha];
+
+        // Set the texture object.  The UTMTextureIndexBaseColor enum value corresponds
+        ///  to the 'colorMap' argument in our 'samplingShader' function because its
+        //   texture attribute qualifier also uses UTMTextureIndexBaseColor for its index
+        [renderEncoder setFragmentTexture:source.displayTexture
+                                  atIndex:UTMTextureIndexBaseColor];
+        
+        [renderEncoder setFragmentSamplerState:_sampler
+                                       atIndex:UTMSamplerIndexTexture];
+
+        // Draw the vertices of our triangles
+        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
+                          vertexStart:0
+                          vertexCount:source.displayNumVertices];
+        
+        // Draw cursor
+        if (source.cursorVisible) {
+            // Next render the cursor
             bool hasAlpha = YES;
             bool hasAlpha = YES;
-            cursorLock = self.sourceCursor.drawLock;
-            dispatch_semaphore_wait(cursorLock, DISPATCH_TIME_FOREVER);
-            
-            if (self.sourceCursor.visible) {
-                // Next render the cursor
-                matrix_float4x4 transform = matrix_scale_translate(self.sourceScreen.viewportScale,
-                                                                   CGPointMake(self.sourceScreen.viewportOrigin.x +
-                                                                               self.sourceCursor.viewportOrigin.x,
-                                                                               self.sourceScreen.viewportOrigin.y +
-                                                                               self.sourceCursor.viewportOrigin.y));
-                [renderEncoder setVertexBuffer:self.sourceCursor.vertices
-                                        offset:0
-                                      atIndex:UTMVertexInputIndexVertices];
-                [renderEncoder setVertexBytes:&_viewportSize
-                                       length:sizeof(_viewportSize)
-                                      atIndex:UTMVertexInputIndexViewportSize];
-                [renderEncoder setVertexBytes:&transform
-                                     length:sizeof(transform)
-                                    atIndex:UTMVertexInputIndexTransform];
-                [renderEncoder setVertexBytes:&hasAlpha
-                                     length:sizeof(hasAlpha)
-                                    atIndex:UTMVertexInputIndexHasAlpha];
-                [renderEncoder setFragmentTexture:self.sourceCursor.texture
-                                          atIndex:UTMTextureIndexBaseColor];
-                [renderEncoder setFragmentSamplerState:_sampler
-                                               atIndex:UTMSamplerIndexTexture];
-                [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
-                                  vertexStart:0
-                                  vertexCount:self.sourceCursor.numVertices];
-            }
+            matrix_float4x4 transform = matrix_scale_translate(source.viewportScale,
+                                                               CGPointMake(source.viewportOrigin.x +
+                                                                           source.cursorOrigin.x,
+                                                                           source.viewportOrigin.y +
+                                                                           source.cursorOrigin.y));
+            [renderEncoder setVertexBuffer:source.cursorVertices
+                                    offset:0
+                                  atIndex:UTMVertexInputIndexVertices];
+            [renderEncoder setVertexBytes:&_viewportSize
+                                   length:sizeof(_viewportSize)
+                                  atIndex:UTMVertexInputIndexViewportSize];
+            [renderEncoder setVertexBytes:&transform
+                                 length:sizeof(transform)
+                                atIndex:UTMVertexInputIndexTransform];
+            [renderEncoder setVertexBytes:&hasAlpha
+                                 length:sizeof(hasAlpha)
+                                atIndex:UTMVertexInputIndexHasAlpha];
+            [renderEncoder setFragmentTexture:source.cursorTexture
+                                      atIndex:UTMTextureIndexBaseColor];
+            [renderEncoder setFragmentSamplerState:_sampler
+                                           atIndex:UTMSamplerIndexTexture];
+            [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
+                              vertexStart:0
+                              vertexCount:source.cursorNumVertices];
         }
         }
 
 
         [renderEncoder endEncoding];
         [renderEncoder endEncoding];
@@ -268,12 +249,7 @@ static matrix_float4x4 matrix_scale_translate(CGFloat scale, CGPoint translate)
         [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
         [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
             // GPU work is complete
             // GPU work is complete
             // Signal the semaphore to start the CPU work
             // Signal the semaphore to start the CPU work
-            if (screenLock) {
-                dispatch_semaphore_signal(screenLock);
-            }
-            if (cursorLock) {
-                dispatch_semaphore_signal(cursorLock);
-            }
+            dispatch_semaphore_signal(drawLock);
         }];
         }];
     }
     }