import AppKit final class OverlayWindowController: NSWindowController { private let statusView = StatusView() // Fully transparent background; no visual effect overlay init(serviceChecker: ServiceChecker, pfManager: PortForwardManager? = nil) { let style: NSWindow.StyleMask = [.borderless] let window = OverlayWindow(contentRect: .zero, styleMask: style, backing: .buffered, defer: false) super.init(window: window) window.isReleasedWhenClosed = false window.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1) window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle] window.hasShadow = false window.backgroundColor = .clear window.isOpaque = false statusView.translatesAutoresizingMaskIntoConstraints = false let container = TransparentContainerView() container.wantsLayer = true container.translatesAutoresizingMaskIntoConstraints = false container.postsFrameChangedNotifications = true container.layer?.backgroundColor = NSColor.clear.cgColor container.addSubview(statusView) window.contentView = container NSLayoutConstraint.activate([ statusView.centerXAnchor.constraint(equalTo: container.centerXAnchor), statusView.centerYAnchor.constraint(equalTo: container.centerYAnchor) ]) statusView.bindTo(serviceChecker: serviceChecker) // Initial placement recomputeFrameAndVisibility() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func showIfMenuBarVisible() { recomputeFrameAndVisibility() } // Show the overlay explicitly, regardless of whether the menu bar is auto-hidden. // Positions the overlay at the top of the visible frame. func showOverlayNow() { guard let screen = NSScreen.main else { return } let frame = screen.frame let visible = screen.visibleFrame let statusThickness = NSStatusBar.system.thickness let overlayHeight = statusThickness + 6.0 let y = visible.maxY - overlayHeight let rect = NSRect(x: frame.minX, y: y, width: frame.width, height: overlayHeight) window?.setFrame(rect, display: true) window?.orderFrontRegardless() window?.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1) dlog("Overlay window forced visible at top of visible frame") } func hideOverlay() { window?.orderOut(nil) } func recomputeFrameAndVisibility() { guard let screen = NSScreen.main else { dlog("No main screen detected; ordering window out") window?.orderOut(nil); return } let frame = screen.frame let visible = screen.visibleFrame let menuBarHeight = frame.maxY - visible.maxY // 0 if auto-hidden or full-screen space if menuBarHeight <= 0.5 { // treat as hidden/no menu bar dlog("Menu bar not visible (height≈0). Hiding overlay window.") window?.orderOut(nil) return } // Make the overlay a bit taller for better readability than the status bar thickness let statusThickness = NSStatusBar.system.thickness let overlayHeight = statusThickness + 6.0 let y = visible.maxY - overlayHeight let rect = NSRect(x: frame.minX, y: y, width: frame.width, height: overlayHeight) dlog("Computed overlay frame: x=\(rect.origin.x), y=\(rect.origin.y), w=\(rect.size.width), h=\(rect.size.height) | menuBarHeight=\(menuBarHeight), statusBarThickness=\(statusThickness), overlayHeight=\(overlayHeight)") window?.setFrame(rect, display: true) window?.orderFrontRegardless() window?.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1) dlog("Overlay window ordered front") } func showContextMenu() { guard let window = window, window.isVisible else { return } let menu = NSMenu(title: "Prole Tools") menu.addItem(withTitle: "Prole Tools — System Status", action: nil, keyEquivalent: "") menu.addItem(.separator()) menu.addItem(withTitle: "Refresh Now", action: #selector(refreshNow), keyEquivalent: "r").target = self menu.addItem(withTitle: "Restart Port Forwards", action: #selector(resetPortForwards), keyEquivalent: "").target = self menu.addItem(.separator()) menu.addItem(withTitle: "Quit Prole Tools", action: #selector(quit), keyEquivalent: "q").target = self let point = NSPoint(x: window.frame.midX, y: window.frame.minY) menu.popUp(positioning: nil, at: point, in: nil) } @objc private func refreshNow() { NotificationCenter.default.post(name: ServiceChecker.forceRefreshNotification, object: nil) } @objc private func quit() { dlog("Quit requested via context menu; terminating app") NSApp.terminate(nil) } @objc private func resetPortForwards() { _ = PFScriptBridge.restart() } } final class OverlayWindow: NSWindow { override var canBecomeKey: Bool { return false } override var canBecomeMain: Bool { return false } // Allow interaction with the window even if it's not key override var acceptsMouseMovedEvents: Bool { get { return true } set { } } override func mouseDown(with event: NSEvent) { dlog("OverlayWindow: mouseDown at \(event.locationInWindow)") super.mouseDown(with: event) } override func sendEvent(_ event: NSEvent) { if event.type == .leftMouseDown { dlog("OverlayWindow: sendEvent .leftMouseDown at \(event.locationInWindow)") // Fallback: Manually check if the click is in the maximize button area if let contentView = self.contentView, let statusView = contentView.subviews.first(where: { $0 is StatusView }) as? StatusView { let pointInStatusView = statusView.convert(event.locationInWindow, from: nil) if let hitView = statusView.hitTest(pointInStatusView), hitView.toolTip == "Show Main Window" { dlog("OverlayWindow: Manual hit detected for maximize button via sendEvent") NotificationCenter.default.post(name: NSNotification.Name("ProleStatus.showMainWindow"), object: nil) return // Intercepted } } } super.sendEvent(event) } override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) isOpaque = false backgroundColor = .clear // Default to NOT click-through so we can intercept clicks on the maximize button. // TransparentContainerView.hitTest handles passing through clicks for non-button areas. ignoresMouseEvents = false acceptsMouseMovedEvents = true isMovableByWindowBackground = false // Ensure the window level is high and it doesn't ignore mouse events level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1) dlog("OverlayWindow initialized (borderless, transparent, level: \(level.rawValue))") } } // Transparent container view that passes through mouse clicks everywhere // while still allowing mouse-moved events for subviews that add tracking areas. final class TransparentContainerView: NSView { override func hitTest(_ point: NSPoint) -> NSView? { let view = super.hitTest(point) dlog("TransparentContainerView: hitTest at \(point) -> \(view?.description ?? "nil")") return view } }