Add layer screenshort protection

This commit is contained in:
Isaac 2023-11-23 16:00:15 +04:00
parent c3df326987
commit 5dd156f2fc
7 changed files with 87 additions and 173 deletions

View File

@ -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<TransformImageArguments>(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
}

View File

@ -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)
}

View File

@ -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": [],

View File

@ -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?

View File

@ -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 {

View File

@ -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);

View File

@ -231,3 +231,48 @@ NSObject * _Nullable makeBlurFilter() {
NSObject * _Nullable makeLuminanceToAlphaFilter() {
return [(id<GraphicsFilterProtocol>)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];
}
}