Home > Mobile >  Draw shape around cursor on macOS using Swift [duplicate]
Draw shape around cursor on macOS using Swift [duplicate]

Time:09-18

I'm pretty new to macOS and swift development and I have been watching a few tutorials to figure this out. I also watched a udemy course that worked on 18 macOS projects which got me nowhere

All I want to do is a macOS menu bar app that will add a cursor highlight that should look something like:

cursor highlight

I could get the cursor changed to an image doing the following

import SpriteKit

class CursorView: SKView {
    override func resetCursorRects() {
        if let targetImage = NSImage(named: "cursor") {
            let cursor = NSCursor(image: targetImage, 
                                  hotSpot: CGPoint(x: targetImage.size.width / 2, 
                                                   y: targetImage.size.height / 2))
            addCursorRect(frame, cursor: cursor)
        }
    }
}

Three things wrong with this:

  1. SKView is a class from SpriteKit and I don't think I should use that for my use-case
  2. This calls the addCursorRect that add the changes to a window frame (I need to all the time regardless of the frame)
  3. I can't have 100's of images for each style I would set in the future for the highlight color, size, or opacity

So, I'm here trying to understand how I can do this for a menu bar app that should be available on all screens and achieve a highlight as should in the above picture

Not sure if this matters but I'm using storyboard and don't mind switching to SwiftUI

I could really use some help from the community on this. Thank you

CodePudding user response:

You can annotate the system mouse by drawing something around it. This can be done by

  • adding an CGEvent tap to capture mouse events
  • drawing the annotation around the cursor using a custom window

Here is a simple example application

import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var mouseTap: CFMachPort?
    private var window: NSWindow = {
        // Create the SwiftUI view that provides the window contents.
        let contentView = Circle()
            .stroke(lineWidth: 2)
            .foregroundColor(.blue)
            .frame(width: 30, height: 30)
            .padding(2)

        // Create the window and set the content view.
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 34, height: 34),
            styleMask: [.borderless],
            backing: .buffered,
            defer: false
        )
        window.contentView = NSHostingView(rootView: contentView)
        window.backgroundColor = .clear
        window.level = NSWindow.Level.statusBar
        window.makeKeyAndOrderFront(nil)
        return window
    }()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        if let tap = createMouseTap() {
            if CGEvent.tapIsEnabled(tap: tap) {
                let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
                CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes)
                mouseTap = tap
            } else {
                print("tap not enabled")
                mouseTap = nil
            }
        } else {
            print("tap not enabled")
        }
    }

    func createMouseTap() -> CFMachPort? {
        withUnsafeMutableBytes(of: &window) { pointer in
            CGEvent.tapCreate(
                tap: .cgSessionEventTap,
                place: .headInsertEventTap,
                options: CGEventTapOptions.listenOnly,
                eventsOfInterest: (1 << CGEventType.mouseMoved.rawValue | 1 << CGEventType.leftMouseDragged.rawValue),
                callback: mouseMoved,
                userInfo: pointer.baseAddress
            )
        }
    }
}

func mouseMoved(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, context: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
    let window = context!.assumingMemoryBound(to: NSWindow.self).pointee
    // using CGPoint SIMD extension from https://gist.github.com/Dev1an/7973cee9d960479b35b705f88b7f38c4
    window.setFrameOrigin(event.unflippedLocation - 17)
    return nil
}

Drawback

Note that this implementation does require the user to allow the keyboard input option in "Universal Access" in "System Preferences".

  • Related