diff --git a/submodules/Display/Source/TransformImageNode.swift b/submodules/Display/Source/TransformImageNode.swift index 23e600c96d..4e06c3ab48 100644 --- a/submodules/Display/Source/TransformImageNode.swift +++ b/submodules/Display/Source/TransformImageNode.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit import AVFoundation +import UIKitRuntimeUtils public struct TransformImageNodeContentAnimations: OptionSet { public var rawValue: Int32 @@ -28,61 +29,21 @@ open class TransformImageNode: ASDisplayNode { private var overlayColor: UIColor? private var overlayNode: ASDisplayNode? - private var captureProtectedContentLayer: CaptureProtectedContentLayer? - public var captureProtected: Bool = false { didSet { if self.captureProtected != oldValue { - if self.captureProtected { - if self.captureProtectedContentLayer == nil { - let captureProtectedContentLayer = CaptureProtectedContentLayer() - self.captureProtectedContentLayer = captureProtectedContentLayer - if #available(iOS 13.0, *) { - captureProtectedContentLayer.preventsCapture = true - captureProtectedContentLayer.preventsDisplaySleepDuringVideoPlayback = false - } - captureProtectedContentLayer.frame = self.bounds - self.layer.addSublayer(captureProtectedContentLayer) - var hasImage = false - if let image = self.image { - hasImage = true - if let cmSampleBuffer = image.cmSampleBuffer { - captureProtectedContentLayer.enqueue(cmSampleBuffer) - } - } - if hasImage { - Queue.mainQueue().after(0.1) { - self.contents = nil - } - } else { - self.contents = nil - } - } - } else if let captureProtectedContentLayer = self.captureProtectedContentLayer { - self.captureProtectedContentLayer = nil - captureProtectedContentLayer.removeFromSuperlayer() - self.contents = self.image?.cgImage + if self.isNodeLoaded { + setLayerDisableScreenshots(self.layer, self.captureProtected) } } } } - open override var bounds: CGRect { - didSet { - if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { - captureProtectedContentLayer.frame = super.bounds - } - } - } - open override var frame: CGRect { didSet { if let overlayNode = self.overlayNode { overlayNode.frame = self.bounds } - if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { - captureProtectedContentLayer.frame = super.bounds - } } } @@ -96,6 +57,9 @@ open class TransformImageNode: ASDisplayNode { if #available(iOSApplicationExtension 11.0, iOS 11.0, *), !self.isLayerBacked { self.view.accessibilityIgnoresInvertColors = true } + if self.captureProtected { + setLayerDisableScreenshots(self.layer, self.captureProtected) + } } public func reset() { @@ -138,30 +102,24 @@ open class TransformImageNode: ASDisplayNode { strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } } else if strongSelf.contentAnimations.contains(.subsequentUpdates) { - if let _ = strongSelf.captureProtectedContentLayer { - } else { - let tempLayer = CALayer() - tempLayer.frame = strongSelf.bounds - tempLayer.contentsGravity = strongSelf.layer.contentsGravity - tempLayer.contents = strongSelf.contents - strongSelf.layer.addSublayer(tempLayer) - tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in - tempLayer?.removeFromSuperlayer() - }) + let tempLayer = CALayer() + if strongSelf.captureProtected { + setLayerDisableScreenshots(tempLayer, strongSelf.captureProtected) } + tempLayer.frame = strongSelf.bounds + tempLayer.contentsGravity = strongSelf.layer.contentsGravity + tempLayer.contents = strongSelf.contents + strongSelf.layer.addSublayer(tempLayer) + tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in + tempLayer?.removeFromSuperlayer() + }) } var imageUpdate: UIImage? if let (transform, arguments, image) = next { strongSelf.currentTransform = transform strongSelf.currentArguments = arguments - if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer { - if let cmSampleBuffer = image?.cmSampleBuffer { - captureProtectedContentLayer.enqueue(cmSampleBuffer) - } - } else { - strongSelf.contents = image?.cgImage - } + strongSelf.contents = image?.cgImage strongSelf.image = image imageUpdate = image } @@ -198,13 +156,7 @@ open class TransformImageNode: ASDisplayNode { return } if let image = updatedImage { - if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer { - if let cmSampleBuffer = image.cmSampleBuffer { - captureProtectedContentLayer.enqueue(cmSampleBuffer) - } - } else { - strongSelf.contents = image.cgImage - } + strongSelf.contents = image.cgImage strongSelf.image = image strongSelf.currentArguments = arguments if let _ = strongSelf.overlayColor { @@ -277,24 +229,6 @@ open class TransformImageNode: ASDisplayNode { } } -public class CaptureProtectedContentLayer: AVSampleBufferDisplayLayer { - override public func action(forKey event: String) -> CAAction? { - return nullAction - } - - override public init() { - super.init() - } - - override public init(layer: Any) { - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - open class TransformImageView: UIView { public var imageUpdated: ((UIImage?) -> Void)? public var contentAnimations: TransformImageNodeContentAnimations = [] @@ -305,50 +239,21 @@ open class TransformImageView: UIView { private var argumentsPromise = ValuePromise(ignoreRepeated: true) public private(set) var image: UIImage? - private var captureProtectedContentLayer: CaptureProtectedContentLayer? - private var overlayColor: UIColor? private var overlayView: UIView? - - open override var bounds: CGRect { - didSet { - if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { - captureProtectedContentLayer.frame = super.bounds - } - } - } open override var frame: CGRect { didSet { if let overlayView = self.overlayView { overlayView.frame = self.bounds } - if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { - captureProtectedContentLayer.frame = super.bounds - } } } public var captureProtected: Bool = false { didSet { if self.captureProtected != oldValue { - if self.captureProtected { - if self.captureProtectedContentLayer == nil { - let captureProtectedContentLayer = CaptureProtectedContentLayer() - captureProtectedContentLayer.frame = self.bounds - self.layer.addSublayer(captureProtectedContentLayer) - if let image = self.image { - if let cmSampleBuffer = image.cmSampleBuffer { - captureProtectedContentLayer.enqueue(cmSampleBuffer) - } - } - self.layer.contents = nil - } - } else if let captureProtectedContentLayer = self.captureProtectedContentLayer { - self.captureProtectedContentLayer = nil - captureProtectedContentLayer.removeFromSuperlayer() - self.layer.contents = self.image?.cgImage - } + setLayerDisableScreenshots(self.layer, self.captureProtected) } } } @@ -375,7 +280,6 @@ open class TransformImageView: UIView { self.currentTransform = nil self.layer.contents = nil self.image = nil - self.captureProtectedContentLayer?.flushAndRemoveImage() } public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) { @@ -410,30 +314,24 @@ open class TransformImageView: UIView { strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } } else if strongSelf.contentAnimations.contains(.subsequentUpdates) { - if let _ = strongSelf.captureProtectedContentLayer { - } else { - let tempLayer = CALayer() - tempLayer.frame = strongSelf.bounds - tempLayer.contentsGravity = strongSelf.layer.contentsGravity - tempLayer.contents = strongSelf.layer.contents - strongSelf.layer.addSublayer(tempLayer) - tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in - tempLayer?.removeFromSuperlayer() - }) + let tempLayer = CALayer() + if strongSelf.captureProtected { + setLayerDisableScreenshots(tempLayer, strongSelf.captureProtected) } + tempLayer.frame = strongSelf.bounds + tempLayer.contentsGravity = strongSelf.layer.contentsGravity + tempLayer.contents = strongSelf.layer.contents + strongSelf.layer.addSublayer(tempLayer) + tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in + tempLayer?.removeFromSuperlayer() + }) } var imageUpdate: UIImage? if let (transform, arguments, image) = next { strongSelf.currentTransform = transform strongSelf.currentArguments = arguments - if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer { - if let cmSampleBuffer = image?.cmSampleBuffer { - captureProtectedContentLayer.enqueue(cmSampleBuffer) - } - } else { - strongSelf.layer.contents = image?.cgImage - } + strongSelf.layer.contents = image?.cgImage strongSelf.image = image imageUpdate = image } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 5f837beadf..03e7d7c4ff 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -336,7 +336,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.translateToLanguage = translateToLanguage self.peerIsCopyProtected = peerIsCopyProtected self.isSecret = isSecret - self.imageNode.captureProtected = message.isCopyProtected() || peerIsCopyProtected || isSecret + self.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.isCopyProtected() || peerIsCopyProtected || isSecret self.footerContentNode.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 4ba75cba0a..54e5bf7578 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -415,6 +415,7 @@ swift_library( "//submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen", "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode", "//submodules/TelegramUI/Components/Chat/ChatQrCodeScreen", + "//submodules/UIKitRuntimeUtils", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 330aed1c88..cee900da81 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1574,7 +1574,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } if let updateImageSignal = updateImageSignal { - strongSelf.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.isCopyProtected() || isExtendedMedia + strongSelf.imageNode.captureProtected = message.isCopyProtected() || isExtendedMedia strongSelf.imageNode.setSignal(updateImageSignal(synchronousLoads, false), attemptSynchronously: synchronousLoads) var imageDimensions: CGSize? diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 9cf1f30511..7c9f016a75 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -38,6 +38,7 @@ import ManagedDiceAnimationNode import ChatMessageTransitionNode import ChatLoadingNode import ChatRecentActionsController +import UIKitRuntimeUtils final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -82,13 +83,10 @@ private struct ChatControllerNodeDerivedLayoutState { } class HistoryNodeContainer: ASDisplayNode { - private(set) var secretContainer: UIView? - public var isSecret: Bool = false { + var isSecret: Bool { didSet { if self.isSecret != oldValue { - if self.isNodeLoaded { - (self.view as? UITextField)?.isSecureTextEntry = self.isSecret - } + setLayerDisableScreenshots(self.layer, self.isSecret) } } } @@ -98,35 +96,9 @@ class HistoryNodeContainer: ASDisplayNode { super.init() - self.setViewBlock { - let captureProtectedView = UITextField(frame: CGRect()) - captureProtectedView.isSecureTextEntry = self.isSecret - self.secretContainer = captureProtectedView.subviews.first - return captureProtectedView + if self.isSecret { + setLayerDisableScreenshots(self.layer, self.isSecret) } - - let _ = self.view - } - - override func addSubnode(_ subnode: ASDisplayNode) { - if let secretContainer = self.secretContainer { - secretContainer.addSubnode(subnode) - } else { - super.addSubnode(subnode) - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let secretContainer = self.secretContainer { - return secretContainer.hitTest(point, with: event) - } else { - return super.hitTest(point, with: event) - } - } - - func updateSize(size: CGSize, transition: ContainedViewLayoutTransition) { - /*if let secretContainer = self.secretContainer { - }*/ } } @@ -612,8 +584,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { //self.historyScrollingArea = SparseDiscreteScrollingArea() //self.historyNode.historyScrollingArea = self.historyScrollingArea - //self.historyNodeContainer = HistoryNodeContainer(isSecret: chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat) - self.historyNodeContainer = ASDisplayNode() + self.historyNodeContainer = HistoryNodeContainer(isSecret: chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat) self.historyNodeContainer.addSubnode(self.historyNode) @@ -1671,10 +1642,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateBounds(node: self.historyNodeContainer, bounds: contentBounds) transition.updatePosition(node: self.historyNodeContainer, position: contentBounds.center) - if let historyNodeContainer = self.historyNodeContainer as? HistoryNodeContainer { - historyNodeContainer.updateSize(size: contentBounds.size, transition: transition) - } - transition.updateBounds(node: self.historyNode, bounds: CGRect(origin: CGPoint(), size: contentBounds.size)) transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0)) if let blurredHistoryNode = self.blurredHistoryNode { diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h index 55ea375d7e..224d579d42 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h @@ -29,3 +29,6 @@ UIView * _Nullable getPortalViewSourceView(UIView * _Nonnull portalView); NSObject * _Nullable makeBlurFilter(); NSObject * _Nullable makeLuminanceToAlphaFilter(); + +void setLayerDisableScreenshots(CALayer * _Nonnull layer, bool disableScreenshots); +void setLayerContentsMaskMode(CALayer * _Nonnull layer, bool maskMode); diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m index f2d50d65ad..717c584044 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m @@ -231,3 +231,48 @@ NSObject * _Nullable makeBlurFilter() { NSObject * _Nullable makeLuminanceToAlphaFilter() { return [(id)NSClassFromString(@"CAFilter") filterWithName:@"luminanceToAlpha"]; } + +void setLayerDisableScreenshots(CALayer * _Nonnull layer, bool disableScreenshots) { + static UITextField *textField = nil; + static UIView *secureView = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + textField = [[UITextField alloc] init]; + for (UIView *subview in textField.subviews) { + if ([NSStringFromClass([subview class]) containsString:@"TextLayoutCanvasView"]) { + secureView = subview; + break; + } + } + }); + if (secureView == nil) { + return; + } + + CALayer *previousLayer = secureView.layer; + [secureView setValue:layer forKey:@"layer"]; + if (disableScreenshots) { + textField.secureTextEntry = false; + textField.secureTextEntry = true; + } else { + textField.secureTextEntry = true; + textField.secureTextEntry = false; + } + [secureView setValue:previousLayer forKey:@"layer"]; +} + +void setLayerContentsMaskMode(CALayer * _Nonnull layer, bool maskMode) { + static NSString *key = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + key = [@"contents" stringByAppendingString:@"Swizzle"]; + }); + if (key == nil) { + return; + } + if (maskMode) { + [layer setValue:@"AAAA" forKey:key]; + } else { + [layer setValue:@"RGBA" forKey:key]; + } +}