feat: show outline of windows hidden for privacy reasons (closes #5172)

This commit is contained in:
lwouis committed 2026-01-07 19:16:14 +01:00
1 parent b8ba0f0888
commit 1090acf3a5
2 files changed
+86

No files matched your search

+26
View File
@@ -198,6 +198,15 @@ extension NSImage {
static func initCopy(_ name: String) -> NSImage {
return NSImage(named: name)!.copy() as! NSImage
}
func tinted(_ color: NSColor) -> NSImage {
NSImage(size: size, flipped: false) { rect in
color.set()
rect.fill()
self.draw(in: rect, from: NSRect(origin: .zero, size: self.size), operation: .destinationIn, fraction: 1.0)
return true
}
}
}
extension CGImage {
@@ -214,6 +223,23 @@ extension CGImage {
func size() -> NSSize {
return NSSize(width: width, height: height)
}
func iFullyTransparent() -> Bool {
guard ![.none, .noneSkipFirst, .noneSkipLast].contains(alphaInfo),
let provider = dataProvider, let data = provider.data, let ptr = CFDataGetBytePtr(data)
else { return false }
// Assumes: kCGImageAlphaPremultipliedFirst | kCGImageByteOrder32Little
// Layout: [B, G, R, A]
let length = CFDataGetLength(data)
var i = 3
while i < length {
if ptr[i] != 0 {
return false
}
i += 4
}
return true
}
}
extension CVPixelBuffer {
@@ -3,6 +3,9 @@ import Cocoa
/// this is a lightweight view which displays an image using its CALayer
/// it is an alternative to NSImageView, which doesn't have internal complexities and performance costs
class LightImageView: NSView {
private var outlineLayer: CAShapeLayer!
private var handRaisedLayer: CALayer!
required init?(coder: NSCoder) {
fatalError("Class only supports programmatic initialization")
}
@@ -25,12 +28,67 @@ class LightImageView: NSView {
"contentsScale": NSNull()
]
layerContentsRedrawPolicy = .never
setupOutlineLayer()
setupHandRaisedLayer()
}
private func setupOutlineLayer() {
outlineLayer = CAShapeLayer()
outlineLayer.name = "outline"
outlineLayer.fillColor = nil
outlineLayer.lineWidth = 1.0
outlineLayer.lineDashPattern = [4, 3]
outlineLayer.lineCap = .round
outlineLayer.lineJoin = .round
outlineLayer.isHidden = true
layer!.addSublayer(outlineLayer)
}
private func setupHandRaisedLayer() {
guard #available(macOS 11.0, *) else { return }
handRaisedLayer = CALayer()
handRaisedLayer.contentsGravity = .resizeAspect
handRaisedLayer.isHidden = true
layer!.addSublayer(handRaisedLayer)
}
private func updateOutlineLayer(_ fullyTransparent: Bool) {
outlineLayer.isHidden = !fullyTransparent
guard fullyTransparent else { return }
let inset: CGFloat = 0.5
let radius: CGFloat = 8.0
let bounds = outlineLayer.superlayer!.bounds
outlineLayer.frame = bounds
let rect = bounds.insetBy(dx: inset, dy: inset)
let path = CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)
outlineLayer.path = path
outlineLayer.strokeColor = Appearance.fontColor.cgColor
}
private func updateHandRaisedLayer(_ fullyTransparent: Bool) {
guard #available(macOS 11.0, *) else { return }
handRaisedLayer.isHidden = !fullyTransparent
guard fullyTransparent else { return }
// we want the icon to be large but not too large, and readable at small sizes
let minSize = min(Appearance.iconSize, bounds.width * 0.9, bounds.height * 0.9)
let maxSize = Appearance.iconSize * 3
let ratio = min(bounds.width / ThumbnailView.maxThumbnailWidth(), bounds.height / ThumbnailView.maxThumbnailHeight())
let pointSize = max(minSize, maxSize * ratio)
let config = NSImage.SymbolConfiguration(pointSize: pointSize, weight: .regular)
guard let image = NSImage(systemSymbolName: "hand.raised.fill", accessibilityDescription: nil)?.withSymbolConfiguration(config) else { return }
handRaisedLayer.contents = image.tinted(Appearance.fontColor)
handRaisedLayer.bounds = CGRect(x: 0, y: 0, width: pointSize, height: pointSize)
handRaisedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
}
func updateContents(_ caLayerContents: CALayerContents, _ size: NSSize) {
var fullyTransparent = false
switch caLayerContents {
case .cgImage(let image?):
layer!.contents = image
fullyTransparent = image.iFullyTransparent()
case .pixelBuffer(let pixelBuffer?):
layer!.contents = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue()
default: break
@@ -38,6 +96,8 @@ class LightImageView: NSView {
if frame.size != size {
frame.size = size
}
updateOutlineLayer(fullyTransparent)
updateHandRaisedLayer(fullyTransparent)
}
func releaseImage() {