11#import "RDPSessionView.h"
13#import "RDPKeyboard.h"
15#include <winpr/wtypes.h>
16#include <freerdp/locale/keyboard.h>
18#import <Metal/Metal.h>
19#import <QuartzCore/CAMetalLayer.h>
23@interface RDPSessionDisplayLinkTarget : NSObject
28- (void)displayLinkDidFire:(CADisplayLink *)displayLink;
34 id<MTLDevice> _metalDevice;
35 id<MTLCommandQueue> _commandQueue;
36 id<MTLRenderPipelineState> _pipelineState;
37 id<MTLTexture> _desktopTexture;
38 CADisplayLink *_displayLink;
39 RDPSessionDisplayLinkTarget *_displayLinkTarget;
40 CGRect _pendingDirtyRect;
41 BOOL _hasPendingDirtyRect;
43 UIImageView *_cursorImageView;
44 CGPoint _cursorPosition;
45 CGPoint _cursorHotspot;
48- (void)configureMetal;
49- (void)configureDesktopTexture;
50- (void)scheduleRender;
51- (void)renderDisplayLink:(CADisplayLink *)displayLink;
54@implementation RDPSessionDisplayLinkTarget
64- (void)displayLinkDidFire:(CADisplayLink *)displayLink
66 [_view renderDisplayLink:displayLink];
80 return [CAMetalLayer class];
86 [
self configureDesktopTexture];
87 [
self setNeedsDisplayInRemoteRect:[
self bounds]];
95 [
self setContentScaleFactor:1.0f];
97 [
self setBackgroundColor:[UIColor blackColor]];
98 [
self setClipsToBounds:YES];
100 _pendingDirtyRect = CGRectNull;
101 _hasPendingDirtyRect = NO;
102 _drawableSize = CGSizeZero;
103 _cursorPosition = CGPointZero;
104 _cursorHotspot = CGPointZero;
105 _cursorImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
106 [_cursorImageView setUserInteractionEnabled:NO];
107 [_cursorImageView setContentMode:UIViewContentModeTopLeft];
108 [[_cursorImageView layer] setMagnificationFilter:kCAFilterNearest];
109 [[_cursorImageView layer] setMinificationFilter:kCAFilterNearest];
110 [_cursorImageView setHidden:YES];
111 [
self addSubview:_cursorImageView];
112 [
self configureMetal];
115- (void)updateRemoteCursorFrame
117 UIImage *image = [_cursorImageView image];
121 [_cursorImageView setFrame:CGRectMake(_cursorPosition.x - _cursorHotspot.x,
122 _cursorPosition.y - _cursorHotspot.y, [image size].width,
123 [image size].height)];
124 [
self bringSubviewToFront:_cursorImageView];
127- (void)setRemoteCursor:(
RDPCursor *)cursor
129 if (!cursor || ![cursor image])
131 [
self hideRemoteCursor];
135 _cursorHotspot = [cursor hotspot];
136 [_cursorImageView setImage:[cursor image]];
137 [_cursorImageView setHidden:NO];
138 [
self updateRemoteCursorFrame];
141- (void)setRemoteCursorPosition:(CGPoint)position
143 _cursorPosition = position;
144 [
self updateRemoteCursorFrame];
147- (void)showRemoteCursor
149 if ([_cursorImageView image])
150 [_cursorImageView setHidden:NO];
153- (void)hideRemoteCursor
155 [_cursorImageView setHidden:YES];
158- (void)setDefaultRemoteCursor
160 [
self setRemoteCursor:[
RDPCursor defaultCursor]];
163- (void)configureMetal
165 _metalDevice = [MTLCreateSystemDefaultDevice() retain];
169 CAMetalLayer *metalLayer = (CAMetalLayer *)[
self layer];
170 [metalLayer setDevice:_metalDevice];
171 [metalLayer setPixelFormat:MTLPixelFormatBGRA8Unorm];
172 [metalLayer setFramebufferOnly:YES];
173 [metalLayer setOpaque:YES];
174 [metalLayer setContentsScale:1.0f];
176 _commandQueue = [_metalDevice newCommandQueue];
177 NSString *shaderSource =
178 @"#include <metal_stdlib>\n"
179 "using namespace metal;\n"
180 "struct VertexOut { float4 position [[position]]; float2 texCoord; };\n"
181 "vertex VertexOut desktopVertex(uint vertexID [[vertex_id]]) {\n"
182 " const float2 positions[4] = { float2(-1.0, 1.0), float2(-1.0, -1.0), "
183 "float2(1.0, 1.0), float2(1.0, -1.0) };\n"
184 " const float2 texCoords[4] = { float2(0.0, 0.0), float2(0.0, 1.0), "
185 "float2(1.0, 0.0), float2(1.0, 1.0) };\n"
186 " VertexOut out; out.position = float4(positions[vertexID], 0.0, 1.0); "
187 "out.texCoord = texCoords[vertexID]; return out;\n"
189 "fragment float4 desktopFragment(VertexOut in [[stage_in]], "
190 "texture2d<float> desktop [[texture(0)]]) {\n"
191 " constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);\n"
192 " float4 color = desktop.sample(textureSampler, in.texCoord);\n"
193 " return float4(color.rgb, 1.0);\n"
196 NSError *error = nil;
197 id<MTLLibrary> library = [_metalDevice newLibraryWithSource:shaderSource
202 NSLog(
@"Failed to create iFreeRDP Metal shader library: %@", error);
206 id<MTLFunction> vertexFunction = [library newFunctionWithName:@"desktopVertex"];
207 id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"desktopFragment"];
208 MTLRenderPipelineDescriptor *descriptor =
209 [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
210 [descriptor setVertexFunction:vertexFunction];
211 [descriptor setFragmentFunction:fragmentFunction];
212 [[descriptor colorAttachments][0] setPixelFormat:MTLPixelFormatBGRA8Unorm];
213 _pipelineState = [_metalDevice newRenderPipelineStateWithDescriptor:descriptor error:&error];
214 [vertexFunction release];
215 [fragmentFunction release];
220 NSLog(
@"Failed to create iFreeRDP Metal pipeline: %@", error);
224 _displayLinkTarget = [[RDPSessionDisplayLinkTarget alloc] initWithView:self];
225 _displayLink = [[CADisplayLink displayLinkWithTarget:_displayLinkTarget
226 selector:@selector(displayLinkDidFire:)] retain];
227 [_displayLink setPreferredFramesPerSecond:60];
228 [_displayLink setPaused:YES];
229 [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
232- (void)configureDesktopTexture
234 [_desktopTexture release];
235 _desktopTexture = nil;
237 CGContextRef bitmapContext = (_session != nil) ? [_session bitmapContext] : nil;
238 if (!_metalDevice || !bitmapContext)
241 NSUInteger width = CGBitmapContextGetWidth(bitmapContext);
242 NSUInteger height = CGBitmapContextGetHeight(bitmapContext);
243 if ((width == 0) || (height == 0))
246 MTLTextureDescriptor *descriptor =
247 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
251 [descriptor setUsage:MTLTextureUsageShaderRead];
252 [descriptor setStorageMode:MTLStorageModeShared];
253 _desktopTexture = [_metalDevice newTextureWithDescriptor:descriptor];
256- (void)prepareForBitmapContextChange
258 [_displayLink setPaused:YES];
259 _hasPendingDirtyRect = NO;
260 _pendingDirtyRect = CGRectNull;
261 [_desktopTexture release];
262 _desktopTexture = nil;
265- (void)setNeedsDisplayInRemoteRect:(CGRect)rect
267 if (!_desktopTexture || CGRectIsNull(rect) || CGRectIsEmpty(rect))
270 CGRect textureBounds = CGRectMake(0.0, 0.0, [_desktopTexture width], [_desktopTexture height]);
271 rect = CGRectIntersection(CGRectIntegral(rect), textureBounds);
272 if (CGRectIsNull(rect) || CGRectIsEmpty(rect))
275 _pendingDirtyRect = _hasPendingDirtyRect ? CGRectUnion(_pendingDirtyRect, rect) : rect;
276 _hasPendingDirtyRect = YES;
277 [
self scheduleRender];
280- (void)scheduleRender
282 if ([
self window] && _displayLink && _hasPendingDirtyRect)
283 [_displayLink setPaused:NO];
286- (void)didMoveToWindow
288 [
super didMoveToWindow];
289 [
self scheduleRender];
292- (void)layoutSubviews
294 [
super layoutSubviews];
295 CAMetalLayer *metalLayer = (CAMetalLayer *)[
self layer];
296 CGSize size = [
self bounds].size;
297 CGSize drawableSize = CGSizeMake(MAX(1.0, size.width), MAX(1.0, size.height));
298 if (CGSizeEqualToSize(drawableSize, _drawableSize))
301 _drawableSize = drawableSize;
302 [metalLayer setDrawableSize:drawableSize];
305 [
self setNeedsDisplayInRemoteRect:CGRectMake(0, 0, [_desktopTexture width],
306 [_desktopTexture height])];
309- (void)renderDisplayLink:(CADisplayLink *)displayLink
312 if (!_hasPendingDirtyRect || !_desktopTexture || !_pipelineState)
314 [_displayLink setPaused:YES];
318 CGContextRef bitmapContext = (_session != nil) ? [_session bitmapContext] : nil;
319 BYTE *source = bitmapContext ? CGBitmapContextGetData(bitmapContext) : nullptr;
322 [_displayLink setPaused:YES];
326 CGRect dirtyRect = _pendingDirtyRect;
327 _pendingDirtyRect = CGRectNull;
328 _hasPendingDirtyRect = NO;
329 NSUInteger x = (NSUInteger)CGRectGetMinX(dirtyRect);
330 NSUInteger y = (NSUInteger)CGRectGetMinY(dirtyRect);
331 NSUInteger width = (NSUInteger)CGRectGetWidth(dirtyRect);
332 NSUInteger height = (NSUInteger)CGRectGetHeight(dirtyRect);
333 NSUInteger bytesPerRow = CGBitmapContextGetBytesPerRow(bitmapContext);
334 const NSUInteger bytesPerPixel = 4;
335 MTLRegion region = MTLRegionMake2D(x, y, width, height);
336 [_desktopTexture replaceRegion:region
338 withBytes:source + (y * bytesPerRow) + (x * bytesPerPixel)
339 bytesPerRow:bytesPerRow];
341 id<CAMetalDrawable> drawable = [(CAMetalLayer *)[
self layer] nextDrawable];
344 _pendingDirtyRect = dirtyRect;
345 _hasPendingDirtyRect = YES;
346 [_displayLink setPaused:YES];
350 MTLRenderPassDescriptor *pass = [MTLRenderPassDescriptor renderPassDescriptor];
351 [[pass colorAttachments][0] setTexture:[drawable texture]];
352 [[pass colorAttachments][0] setLoadAction:MTLLoadActionClear];
353 [[pass colorAttachments][0] setStoreAction:MTLStoreActionStore];
354 [[pass colorAttachments][0] setClearColor:MTLClearColorMake(0.0, 0.0, 0.0, 1.0)];
356 id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
357 id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:pass];
358 [encoder setRenderPipelineState:_pipelineState];
359 [encoder setFragmentTexture:_desktopTexture atIndex:0];
360 [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
361 [encoder endEncoding];
362 [commandBuffer presentDrawable:drawable];
363 [commandBuffer commit];
365 if (!_hasPendingDirtyRect)
366 [_displayLink setPaused:YES];
369static int VKFromHIDUsage(UIKeyboardHIDUsage usage)
371 if (usage >= UIKeyboardHIDUsageKeyboardA && usage <= UIKeyboardHIDUsageKeyboardZ)
372 return VK_KEY_A + (usage - UIKeyboardHIDUsageKeyboardA);
373 if (usage >= UIKeyboardHIDUsageKeyboard1 && usage <= UIKeyboardHIDUsageKeyboard9)
374 return VK_KEY_1 + (usage - UIKeyboardHIDUsageKeyboard1);
375 if (usage >= UIKeyboardHIDUsageKeyboardF1 && usage <= UIKeyboardHIDUsageKeyboardF12)
376 return VK_F1 + (usage - UIKeyboardHIDUsageKeyboardF1);
380 case UIKeyboardHIDUsageKeyboard0:
382 case UIKeyboardHIDUsageKeyboardReturnOrEnter:
384 case UIKeyboardHIDUsageKeyboardEscape:
386 case UIKeyboardHIDUsageKeyboardDeleteOrBackspace:
388 case UIKeyboardHIDUsageKeyboardTab:
390 case UIKeyboardHIDUsageKeyboardSpacebar:
392 case UIKeyboardHIDUsageKeyboardHyphen:
394 case UIKeyboardHIDUsageKeyboardEqualSign:
396 case UIKeyboardHIDUsageKeyboardOpenBracket:
398 case UIKeyboardHIDUsageKeyboardCloseBracket:
400 case UIKeyboardHIDUsageKeyboardBackslash:
402 case UIKeyboardHIDUsageKeyboardSemicolon:
404 case UIKeyboardHIDUsageKeyboardQuote:
406 case UIKeyboardHIDUsageKeyboardGraveAccentAndTilde:
408 case UIKeyboardHIDUsageKeyboardComma:
410 case UIKeyboardHIDUsageKeyboardPeriod:
411 return VK_OEM_PERIOD;
412 case UIKeyboardHIDUsageKeyboardSlash:
414 case UIKeyboardHIDUsageKeyboardCapsLock:
416 case UIKeyboardHIDUsageKeyboardRightArrow:
417 return VK_RIGHT | KBDEXT;
418 case UIKeyboardHIDUsageKeyboardLeftArrow:
419 return VK_LEFT | KBDEXT;
420 case UIKeyboardHIDUsageKeyboardDownArrow:
421 return VK_DOWN | KBDEXT;
422 case UIKeyboardHIDUsageKeyboardUpArrow:
423 return VK_UP | KBDEXT;
424 case UIKeyboardHIDUsageKeyboardInsert:
425 return VK_INSERT | KBDEXT;
426 case UIKeyboardHIDUsageKeyboardHome:
427 return VK_HOME | KBDEXT;
428 case UIKeyboardHIDUsageKeyboardPageUp:
429 return VK_PRIOR | KBDEXT;
430 case UIKeyboardHIDUsageKeyboardDeleteForward:
431 return VK_DELETE | KBDEXT;
432 case UIKeyboardHIDUsageKeyboardEnd:
433 return VK_END | KBDEXT;
434 case UIKeyboardHIDUsageKeyboardPageDown:
435 return VK_NEXT | KBDEXT;
436 case UIKeyboardHIDUsageKeyboardLeftControl:
438 case UIKeyboardHIDUsageKeyboardLeftShift:
440 case UIKeyboardHIDUsageKeyboardLeftAlt:
442 case UIKeyboardHIDUsageKeyboardLeftGUI:
443 return VK_LWIN | KBDEXT;
444 case UIKeyboardHIDUsageKeyboardRightControl:
445 return VK_RCONTROL | KBDEXT;
446 case UIKeyboardHIDUsageKeyboardRightShift:
448 case UIKeyboardHIDUsageKeyboardRightAlt:
449 return VK_RMENU | KBDEXT;
450 case UIKeyboardHIDUsageKeyboardRightGUI:
451 return VK_RWIN | KBDEXT;
457- (BOOL)canBecomeFirstResponder
459 return _hardwareKeyboardActive;
462- (void)handlePresses:(NSSet<UIPress *> *)presses up:(BOOL)up
464 for (UIPress *press in presses)
466 UIKey *key = press.key;
470 int vk = VKFromHIDUsage(key.keyCode);
472 [[
RDPKeyboard getSharedRDPKeyboard] sendVirtualKey:vk up:up];
476- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
478 if (_hardwareKeyboardActive)
479 [
self handlePresses:presses up:NO];
481 [
super pressesBegan:presses withEvent:event];
484- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
486 if (_hardwareKeyboardActive)
487 [
self handlePresses:presses up:YES];
489 [
super pressesEnded:presses withEvent:event];
494 [_displayLinkTarget clearView];
495 [_displayLink invalidate];
496 [_displayLink release];
497 [_displayLinkTarget release];
498 [_desktopTexture release];
499 [_pipelineState release];
500 [_commandQueue release];
501 [_metalDevice release];
502 [_cursorImageView release];