FBSimulatorControl/Framebuffer/FBFramebuffer.m (143 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "FBFramebuffer.h" #import <CoreSimulator/SimDeviceIOProtocol-Protocol.h> #import <xpc/xpc.h> #import <IOSurface/IOSurface.h> #import <FBControlCore/FBControlCore.h> #import <CoreSimulator/SimDevice.h> #import <SimulatorKit/SimDeviceIOPortConsumer-Protocol.h> #import <SimulatorKit/SimDeviceIOPortDescriptorState-Protocol.h> #import <SimulatorKit/SimDeviceIOPortInterface-Protocol.h> #import <SimulatorKit/SimDisplayDescriptorState-Protocol.h> #import <SimulatorKit/SimDisplayIOSurfaceRenderable-Protocol.h> #import <SimulatorKit/SimDisplayRenderable-Protocol.h> #import <IOSurface/IOSurfaceObjC.h> #import "FBSimulator+Private.h" #import "FBSimulatorError.h" @interface FBFramebuffer () @property (nonatomic, strong, readonly) NSMapTable<id<FBFramebufferConsumer>, NSUUID *> *consumers; @property (nonatomic, strong, readonly) id<FBControlCoreLogger> logger; @end @interface FBFramebuffer_Legacy : FBFramebuffer @property (nonatomic, strong, readonly) id<SimDisplayIOSurfaceRenderable, SimDisplayRenderable> surface; - (instancetype)initWithSurface:(id<SimDisplayIOSurfaceRenderable, SimDisplayRenderable>)surface logger:(id<FBControlCoreLogger>)logger; @end @implementation FBFramebuffer #pragma mark Initializers + (instancetype)mainScreenSurfaceForSimulator:(FBSimulator *)simulator logger:(id<FBControlCoreLogger>)logger error:(NSError **)error; { id<SimDeviceIOProtocol> ioClient = simulator.device.io; for (id<SimDeviceIOPortInterface> port in ioClient.ioPorts) { if (![port conformsToProtocol:@protocol(SimDeviceIOPortInterface)]) { continue; } id<SimDisplayIOSurfaceRenderable, SimDisplayRenderable> descriptor = [port descriptor]; if (![descriptor conformsToProtocol:@protocol(SimDisplayRenderable)]) { continue; } if (![descriptor conformsToProtocol:@protocol(SimDisplayIOSurfaceRenderable)]) { continue; } if (![descriptor respondsToSelector:@selector(state)]) { [logger logFormat:@"SimDisplay %@ does not have a state, cannot determine if it is the main display", descriptor]; continue; } id<SimDisplayDescriptorState> descriptorState = [descriptor performSelector:@selector(state)]; unsigned short displayClass = descriptorState.displayClass; if (displayClass != 0) { [logger logFormat:@"SimDisplay Class is '%d' which is not the main display '0'", displayClass]; continue; } return [[FBFramebuffer_Legacy alloc] initWithSurface:descriptor logger:logger]; } return [[FBSimulatorError describeFormat:@"Could not find the Main Screen Surface for Clients %@ in %@", [FBCollectionInformation oneLineDescriptionFromArray:ioClient.ioPorts], ioClient] fail:error]; } - (instancetype)initWithLogger:(id<FBControlCoreLogger>)logger { if (!self) { return nil; } _consumers = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsCopyIn]; _logger = logger; return self; } #pragma mark Public Methods - (nullable IOSurface *)attachConsumer:(id<FBFramebufferConsumer>)consumer onQueue:(dispatch_queue_t)queue { // Don't attach the same consumer twice NSAssert(![self isConsumerAttached:consumer], @"Cannot re-attach the same consumer %@", consumer); NSUUID *consumerUUID = NSUUID.UUID; // Attempt to return the surface synchronously (if supported). IOSurface *surface = [self extractImmediatelyAvailableSurface]; // Register the consumer. [self.consumers setObject:consumerUUID forKey:consumer]; [self registerConsumer:consumer uuid:consumerUUID queue:queue]; return surface; } - (void)detachConsumer:(id<FBFramebufferConsumer>)consumer { NSUUID *uuid = [self.consumers objectForKey:consumer]; if (!uuid) { return;; } [self.consumers removeObjectForKey:consumer]; [self unregisterConsumer:consumer uuid:uuid]; } - (BOOL)isConsumerAttached:(id<FBFramebufferConsumer>)consumer { for (id<FBFramebufferConsumer> existing_consumer in self.consumers.keyEnumerator) { if (existing_consumer == consumer) { return true; } } return false; } #pragma mark Private - (IOSurface *)extractImmediatelyAvailableSurface { return nil; } - (void)registerConsumer:(id<FBFramebufferConsumer>)consumer uuid:(NSUUID *)uuid queue:(dispatch_queue_t)queue { NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); } - (void)unregisterConsumer:(id<FBFramebufferConsumer>)consumer uuid:(NSUUID *)uuid { NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); } @end @implementation FBFramebuffer_Legacy - (instancetype)initWithSurface:(id<SimDisplayIOSurfaceRenderable, SimDisplayRenderable>)surface logger:(id<FBControlCoreLogger>)logger { self = [super initWithLogger:logger]; if (!self) { return nil; } _surface = surface; return self; } - (IOSurface *)extractImmediatelyAvailableSurface { return self.surface.ioSurface; } - (void)registerConsumer:(id<FBFramebufferConsumer>)consumer uuid:(NSUUID *)uuid queue:(dispatch_queue_t)queue { [self.surface registerCallbackWithUUID:uuid ioSurfaceChangeCallback:^(IOSurface *surface) { dispatch_async(queue, ^{ [consumer didChangeIOSurface:surface]; }); }]; [self.surface registerCallbackWithUUID:uuid damageRectanglesCallback:^(NSArray<NSValue *> *frames) { dispatch_async(queue, ^{ for (NSValue *value in frames) { [consumer didReceiveDamageRect:value.rectValue]; } }); }]; } - (void)unregisterConsumer:(id<FBFramebufferConsumer>)consumer uuid:(NSUUID *)uuid { [self.surface unregisterIOSurfaceChangeCallbackWithUUID:uuid]; [self.surface unregisterDamageRectanglesCallbackWithUUID:uuid]; } @end