Выделите NSWindow под курсором мыши - PullRequest
2 голосов
/ 08 июля 2019

Так как это довольно много кода, и, вероятно, это поможет, если есть пример проекта, где вы можете лучше понять текущую проблему, я сделал простой пример проекта, который вы можете найти на 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?Или есть какой-нибудь API от Apple, который я мог бы использовать, чтобы получить желаемое поведение?

Ответы [ 2 ]

3 голосов
/ 08 июля 2019

Я не привык к Swift, извините, но мне кажется, что естественным решением этой проблемы было бы использование - orderWindow:relativeTo:.В ObjC это будет (добавлено сразу после того, как будет показано окно выделения):

[highlightWindow orderWindow:NSWindowAbove relativeTo:window];

И пусть оконный сервер обрабатывает все детали скрытия скрытых частей.Конечно, это создает другую головную боль - держать окно выделения прямо над целевым окном, когда пользователи перемещают объекты по экрану, но ...

2 голосов
/ 08 июля 2019

Я возился с вашим кодом, и @Ted верен NSWindow.order(_:relativeTo) - это именно то, что вам нужно.

Почему NSWindow.level не будет работать:

Использование NSWindow.level не будет работать для вас, потому что все обычные окна (как на скриншоте) имеют уровень окна 0 или .normal. Если вы просто отрегулируете уровень окна, скажем, например, «1», ваш вид подсветки будет отображаться над всеми другими окнами. Напротив, если вы установите его на «-1», ваше выделение будет отображаться ниже всех обычных окон и над рабочим столом.

Проблемы, которые будут введены с помощью NSWindow.order (_ :lativeTo)

Нет хорошего решения без предостережений, верно? Чтобы использовать этот метод, вам нужно установить уровень окна на 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()
    }

Желаю удачи и веселья!

Edit:

Я забыл упомянуть, моя swift версия 4.2 (еще не обновлена), поэтому синтаксис может немного отличаться.

...