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:
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:
- SKView is a class from SpriteKit and I don't think I should use that for my use-case
- This calls the
addCursorRect
that add the changes to a window frame (I need to all the time regardless of the frame) - 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".