用鼠标光标突出显示NSWindow

时间:2019-07-08 12:36:21

标签: swift macos cocoa appkit


由于这有很多代码,如果有一个示例项目可以更好地了解当前问题,它可能会有所帮助,我制作了一个简单的示例项目,您可以在GitHub上找到:https://github.com/dehlen/Stackoverflow


我想实现一些功能与macOS屏幕快照工具非常相似。当鼠标悬停在窗口上时,该窗口应突出显示。但是,我仅在突出显示用户可见的窗口部分时遇到了问题。

以下是该功能的屏幕截图: What it should look like

但是我当前的实现如下所示: What it looks like

我当前的实现如下:

1。获取屏幕上所有可见窗口的列表

static func all() -> [Window] {
        let options = CGWindowListOption(arrayLiteral: .excludeDesktopElements, .optionOnScreenOnly)
        let windowsListInfo = CGWindowListCopyWindowInfo(options, CGMainDisplayID()) //current window
        let infoList = windowsListInfo as! [[String: Any]]
        return infoList
            .filter { $0["kCGWindowLayer"] as! Int == 0 }
            .map { Window(
                frame: CGRect(x: ($0["kCGWindowBounds"] as! [String: Any])["X"] as! CGFloat,
                       y: ($0["kCGWindowBounds"] as! [String: Any])["Y"] as! CGFloat,
                       width: ($0["kCGWindowBounds"] as! [String: Any])["Width"] as! CGFloat,
                       height: ($0["kCGWindowBounds"] as! [String: Any])["Height"] as! CGFloat),
                applicationName: $0["kCGWindowOwnerName"] as! String)}
    }

2。获取鼠标位置

private func registerMouseEvents() {
        NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
            self.mouseLocation = NSEvent.mouseLocation
            return $0
        }
        NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { _ in
            self.mouseLocation = NSEvent.mouseLocation
        }
    }

3。突出显示当前鼠标位置处的窗口:

static func window(at point: CGPoint) -> Window? {
        // TODO: only if frontmost
        let list = all()
        return list.filter { $0.frame.contains(point) }.first
    }
var mouseLocation: NSPoint = NSEvent.mouseLocation {
        didSet {
            //TODO: don't highlight if its the same window
            if let window = WindowList.window(at: mouseLocation), !window.isCapture {
                highlight(window: window)
            } else {
                removeHighlight()
            }
        }
    }

 private func removeHighlight() {
        highlightWindowController?.close()
        highlightWindowController = nil
    }

    func highlight(window: Window) {
        removeHighlight()
        highlightWindowController = HighlightWindowController()
        highlightWindowController?.highlight(frame: window.frame, animate: false)
        highlightWindowController?.showWindow(nil)
    }

class HighlightWindowController: NSWindowController, NSWindowDelegate {
    // MARK: - Initializers
    init() {
        let bounds = NSRect(x: 0, y: 0, width: 100, height: 100)
        let window = NSWindow(contentRect: bounds, styleMask: .borderless, backing: .buffered, defer: true)
        window.isOpaque = false
        window.level = .screenSaver
        window.backgroundColor = NSColor.blue
        window.alphaValue = 0.2
        window.ignoresMouseEvents = true
        super.init(window: window)
        window.delegate = self
    }

    // MARK: - Public API
    func highlight(frame: CGRect, animate: Bool) {
        if animate {
            NSAnimationContext.current.duration = 0.1
        }
        let target = animate ? window?.animator() : window
        target?.setFrame(frame, display: false)
    }
}

如您所见,光标下方的窗口突出显示,但是突出显示窗口绘制在其他可能相交的窗口上方。

可能的解决方案 我可以遍历列表中的可用窗口,仅找到与其他窗口不重叠的矩形,以仅针对此部分而不是整个窗口绘制突出显示矩形。

我在问自己,这是否将是解决此问题的更优雅,更高效的解决方案。也许我可以用绘制的HighlightWindow的窗口级别解决此问题?还是我可以利用Apple提供的任何API来获得所需的行为?

2 个答案:

答案 0 :(得分:3)

抱歉,我不习惯使用Swift,但是对我来说,自然的解决方案是使用 print(filename) NameError: global name 'filename' is not defined 。在ObjC中会被添加(在高亮显示窗口之后添加):

- orderWindow:relativeTo:

然后让窗口服务器处理隐藏模糊部分的所有详细信息。当然,当用户在屏幕上四处移动内容时,将高亮窗口保持在目标窗口的正上方会产生不同的头痛,但是...

答案 1 :(得分:2)

我弄乱了您的代码,@ Ted是正确的。 NSWindow.order(_:relativeTo)正是您需要的。

为什么NSWindow.level无法工作:

使用NSWindow.level对您不起作用,因为普通窗口(如屏幕快照中的窗口)都具有0.normal的窗口级别。如果仅将窗口级别调整为例如“ 1”,则突出显示视图将出现在所有其他窗口的 all 上方。相反,如果将其设置为“ -1”,突出显示视图将出现在所有普通窗口的下方和桌面上方。

使用NSWindow.order(_:relativeTo)引入的问题

没有警告,没有伟大的解决方案了吗?为了使用此方法,您必须将窗口级别设置为0,以便可以在其他窗口之间进行分层。但是,这将导致在WindowList.window(at: mouseLocation)方法中选择突出显示窗口。当选中它时,您的if语句会删除它,因为它认为它是主窗口。这将导致闪烁。 (下面的TLDR中包含此问题的修复程序)

此外,如果您尝试突出显示没有级别为0的窗口,则会遇到问题。要解决此类问题,您需要找到要突出显示的窗口的窗口级别,并将突出显示窗口设置为该级别。 (我的代码未包含针对此问题的修复程序)

除上述问题外,您还需要考虑当用户将鼠标悬停在背景窗口上并在不移动鼠标的情况下单击它时会发生什么。将会发生的情况是背景窗口将变为最前面。移动突出显示窗口。可能的解决方法是在点击事件上更新突出显示窗口。

最后,我注意到您每次用户移动鼠标时都会创建一个新的HighlightWindowController +窗口。如果您仅更改鼠标移动中已经存在的HighlightWindowController的框架(而不是创建鼠标),则对系统的影响可能会更小。要隐藏它,您可以调用NSWindowController.close()函数,甚至可以将框架设置为{0,0,0,0}(不确定第二个想法)。

TLDR;给我们看一些代码

这就是我所做的。

1。。更改窗口结构以包括一个窗口号:

struct Window {
    let frame: CGRect
    let applicationName: String
    let windowNumber: Int

    init(frame: CGRect, applicationName: String, refNumber: Int) {
        self.frame = frame.flippedScreenBounds
        self.applicationName = applicationName
        self.windowNumber = refNumber
    }

    var isCapture: Bool {
        return applicationName.caseInsensitiveCompare("Capture") == .orderedSame
    }
}

2。。在窗口列表功能(即static func all() -> [Window])中,包含窗口号:

refNumber: $0["kCGWindowNumber"] as! Int

3。。在窗口突出显示功能中,在highlightWindowController?.showWindow(nil)之后,相对于要突出显示的窗口对窗口进行排序!

highlightWindowController!.window!.order(.above, relativeTo: window.windowNumber)

4。。请确保在高亮控制器中将窗口级别设置回正常:

window.level = .normal

5。。现在,窗口将闪烁,为避免这种情况,请更新视图控制器的if语句:

    if let window = WindowList.window(at: mouseLocation) {
        if !window.isCapture {
            highlight(window: window)
        }
    } else {
        removeHighlight()
    }

祝你好运,玩得开心!

编辑:

我忘了提及,我的快速版本是4.2(尚未升级),因此语法可能稍有不同。