mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add layer screenshort protection
This commit is contained in:
parent
c3df326987
commit
5dd156f2fc
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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": [],
|
||||
|
@ -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?
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user