突出显示鼠标光标下的 NSWindow
Highlight NSWindow under mouse cursor
由于这是相当多的代码,如果有一个示例项目可以帮助您更好地理解当前的问题,我制作了一个简单的示例项目,您可以在 GitHub 此处找到它:https://github.com/dehlen/Whosebug
我想实现一些与 macOS 屏幕截图工具非常相似的功能。当鼠标悬停在 window 上时,应该突出显示 window。但是,我遇到的问题只是突出显示了用户可见的 window 部分。
这是该功能的屏幕截图:
我目前的实现是这样的:
我当前的实现执行以下操作:
1.获取屏幕上所有 windows 可见的列表
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 { [=10=]["kCGWindowLayer"] as! Int == 0 }
.map { Window(
frame: CGRect(x: ([=10=]["kCGWindowBounds"] as! [String: Any])["X"] as! CGFloat,
y: ([=10=]["kCGWindowBounds"] as! [String: Any])["Y"] as! CGFloat,
width: ([=10=]["kCGWindowBounds"] as! [String: Any])["Width"] as! CGFloat,
height: ([=10=]["kCGWindowBounds"] as! [String: Any])["Height"] as! CGFloat),
applicationName: [=10=]["kCGWindowOwnerName"] as! String)}
}
2。获取鼠标位置
private func registerMouseEvents() {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
self.mouseLocation = NSEvent.mouseLocation
return [=11=]
}
NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { _ in
self.mouseLocation = NSEvent.mouseLocation
}
}
3。高亮当前鼠标位置的window:
static func window(at point: CGPoint) -> Window? {
// TODO: only if frontmost
let list = all()
return list.filter { [=12=].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)
}
}
如您所见,光标下方的 window 已突出显示,但突出显示 window 绘制在可能相交的其他 windows 上方。
可能的解决方案
我可以遍历列表中可用的 windows,只找到不与其他 windows 重叠的矩形,只为这部分而不是整个 window 绘制高亮矩形。
我在问自己这是否是解决此问题的更优雅、更高效的解决方案。也许我可以用绘制的 HighlightWindow 的 window 级别来解决这个问题?或者我可以利用 Apple 的任何 API 来获得所需的行为吗?
我不习惯 Swift,抱歉,但在我看来,自然的解决方案是使用 - orderWindow:relativeTo:
。在 ObjC 中(在显示高亮 window 之后添加):
[highlightWindow orderWindow:NSWindowAbove relativeTo:window];
并让 window 服务器处理隐藏隐藏部分的所有细节。当然,当用户在 on-screen 周围移动东西时,将突出显示 window 保持在目标 window 的正上方会产生不同的麻烦,但是...
我弄乱了你的代码,@Ted 是正确的。 NSWindow.order(_:relativeTo)
正是您所需要的。
为什么 NSWindow.level 不起作用:
使用 NSWindow.level
对您不起作用,因为普通 windows(如您屏幕截图中的那些)都具有 window 级别 0
,或 .normal
。如果您只是将 window 级别调整为例如“1”,您的突出显示视图将出现在 所有 和另一个 windows 之上。相反,如果您将它设置为“-1”,您的突出显示视图将显示在 下方 所有正常 windows 和桌面上方。
使用NSWindow.order(_: relativeTo)
引入的问题
没有很好的解决方案就没有警告吗?为了使用此方法,您必须将 window 级别设置为 0
,以便它可以在其他 windows 之间分层。但是,这将导致在 WindowList.window(at: mouseLocation)
方法中选择突出显示 window。当它被选中时,您的 if-statement 将其删除,因为它认为它是主要的 window。这会导致闪烁。 (下面的 TLDR 中包含对此的修复)
此外,如果您尝试突出 window 而不是 的水平 0
,您将 运行 成为问题.要解决此类问题,您需要找到要突出显示的 window 的 window 级别,并将突出显示 window 设置为该级别。 (我的代码没有包含对这个问题的修复)
除了上述问题之外,您还需要考虑当用户将鼠标悬停在背景上 window 并在不移动鼠标的情况下单击它时会发生什么。会发生什么是背景 window 将变成前面.. 没有 移动突出显示 window。一个可能的解决方法是更新点击事件的高亮 window。
最后,我注意到您每次用户移动鼠标时都会创建一个新的 HighlightWindowController
+ window。如果您只是在鼠标移动时改变已经存在的 HighlightWindowController
的框架(而不是创建一个),系统可能会稍微轻一些。要隐藏它,您可以调用 NSWindowController.close()
函数,甚至将框架设置为 {0,0,0,0}(不确定第二个想法)。
TLDR;给我们一些代码
这是我所做的。
1. 更改您的 window 结构以包含一个 window 数字:
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. 在您的 window 列表功能中,即 static func all() -> [Window]
,包括 window 号码:
refNumber: [=11=]["kCGWindowNumber"] as! Int
3. 在你的 window 高亮函数中,在 highlightWindowController?.showWindow(nil)
之后,相对于你的 window 命令 window正在突出显示!
highlightWindowController!.window!.order(.above, relativeTo: window.windowNumber)
4. 在高亮控制器中,确保将 window 级别设置回正常:
window.level = .normal
5. window 现在会闪烁,为防止这种情况,请更新您的视图控制器 if-statement:
if let window = WindowList.window(at: mouseLocation) {
if !window.isCapture {
highlight(window: window)
}
} else {
removeHighlight()
}
祝你好运,玩得开心 swifting!
编辑:
我忘记说了,我的 swift 版本是 4.2(还没有升级)所以语法可能略有不同。
由于这是相当多的代码,如果有一个示例项目可以帮助您更好地理解当前的问题,我制作了一个简单的示例项目,您可以在 GitHub 此处找到它:https://github.com/dehlen/Whosebug
我想实现一些与 macOS 屏幕截图工具非常相似的功能。当鼠标悬停在 window 上时,应该突出显示 window。但是,我遇到的问题只是突出显示了用户可见的 window 部分。
这是该功能的屏幕截图:
我目前的实现是这样的:
我当前的实现执行以下操作:
1.获取屏幕上所有 windows 可见的列表
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 { [=10=]["kCGWindowLayer"] as! Int == 0 }
.map { Window(
frame: CGRect(x: ([=10=]["kCGWindowBounds"] as! [String: Any])["X"] as! CGFloat,
y: ([=10=]["kCGWindowBounds"] as! [String: Any])["Y"] as! CGFloat,
width: ([=10=]["kCGWindowBounds"] as! [String: Any])["Width"] as! CGFloat,
height: ([=10=]["kCGWindowBounds"] as! [String: Any])["Height"] as! CGFloat),
applicationName: [=10=]["kCGWindowOwnerName"] as! String)}
}
2。获取鼠标位置
private func registerMouseEvents() {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
self.mouseLocation = NSEvent.mouseLocation
return [=11=]
}
NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { _ in
self.mouseLocation = NSEvent.mouseLocation
}
}
3。高亮当前鼠标位置的window:
static func window(at point: CGPoint) -> Window? {
// TODO: only if frontmost
let list = all()
return list.filter { [=12=].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)
}
}
如您所见,光标下方的 window 已突出显示,但突出显示 window 绘制在可能相交的其他 windows 上方。
可能的解决方案 我可以遍历列表中可用的 windows,只找到不与其他 windows 重叠的矩形,只为这部分而不是整个 window 绘制高亮矩形。
我在问自己这是否是解决此问题的更优雅、更高效的解决方案。也许我可以用绘制的 HighlightWindow 的 window 级别来解决这个问题?或者我可以利用 Apple 的任何 API 来获得所需的行为吗?
我不习惯 Swift,抱歉,但在我看来,自然的解决方案是使用 - orderWindow:relativeTo:
。在 ObjC 中(在显示高亮 window 之后添加):
[highlightWindow orderWindow:NSWindowAbove relativeTo:window];
并让 window 服务器处理隐藏隐藏部分的所有细节。当然,当用户在 on-screen 周围移动东西时,将突出显示 window 保持在目标 window 的正上方会产生不同的麻烦,但是...
我弄乱了你的代码,@Ted 是正确的。 NSWindow.order(_:relativeTo)
正是您所需要的。
为什么 NSWindow.level 不起作用:
使用 NSWindow.level
对您不起作用,因为普通 windows(如您屏幕截图中的那些)都具有 window 级别 0
,或 .normal
。如果您只是将 window 级别调整为例如“1”,您的突出显示视图将出现在 所有 和另一个 windows 之上。相反,如果您将它设置为“-1”,您的突出显示视图将显示在 下方 所有正常 windows 和桌面上方。
使用NSWindow.order(_: relativeTo)
引入的问题没有很好的解决方案就没有警告吗?为了使用此方法,您必须将 window 级别设置为 0
,以便它可以在其他 windows 之间分层。但是,这将导致在 WindowList.window(at: mouseLocation)
方法中选择突出显示 window。当它被选中时,您的 if-statement 将其删除,因为它认为它是主要的 window。这会导致闪烁。 (下面的 TLDR 中包含对此的修复)
此外,如果您尝试突出 window 而不是 的水平 0
,您将 运行 成为问题.要解决此类问题,您需要找到要突出显示的 window 的 window 级别,并将突出显示 window 设置为该级别。 (我的代码没有包含对这个问题的修复)
除了上述问题之外,您还需要考虑当用户将鼠标悬停在背景上 window 并在不移动鼠标的情况下单击它时会发生什么。会发生什么是背景 window 将变成前面.. 没有 移动突出显示 window。一个可能的解决方法是更新点击事件的高亮 window。
最后,我注意到您每次用户移动鼠标时都会创建一个新的 HighlightWindowController
+ window。如果您只是在鼠标移动时改变已经存在的 HighlightWindowController
的框架(而不是创建一个),系统可能会稍微轻一些。要隐藏它,您可以调用 NSWindowController.close()
函数,甚至将框架设置为 {0,0,0,0}(不确定第二个想法)。
TLDR;给我们一些代码
这是我所做的。
1. 更改您的 window 结构以包含一个 window 数字:
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. 在您的 window 列表功能中,即 static func all() -> [Window]
,包括 window 号码:
refNumber: [=11=]["kCGWindowNumber"] as! Int
3. 在你的 window 高亮函数中,在 highlightWindowController?.showWindow(nil)
之后,相对于你的 window 命令 window正在突出显示!
highlightWindowController!.window!.order(.above, relativeTo: window.windowNumber)
4. 在高亮控制器中,确保将 window 级别设置回正常:
window.level = .normal
5. window 现在会闪烁,为防止这种情况,请更新您的视图控制器 if-statement:
if let window = WindowList.window(at: mouseLocation) {
if !window.isCapture {
highlight(window: window)
}
} else {
removeHighlight()
}
祝你好运,玩得开心 swifting!
编辑:
我忘记说了,我的 swift 版本是 4.2(还没有升级)所以语法可能略有不同。