Skip to content

Instantly share code, notes, and snippets.

@osnr
Created December 22, 2017 21:27
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save osnr/23eb05b4e0bcd335c06361c4fabadd6f to your computer and use it in GitHub Desktop.
Save osnr/23eb05b4e0bcd335c06361c4fabadd6f to your computer and use it in GitHub Desktop.
//
// ScreenCapture.swift
// Screenotate
//
// Created by Omar Rizwan on 7/6/17.
// Copyright © 2017 Omar Rizwan. All rights reserved.
//
import Foundation
import Cocoa
class Tap {
var eventTap: CFMachPort!
var selfPtr: Unmanaged<Tap>!
init() {
// Catch all events.
let eventMask: CGEventMask = ~0
// Need to keep this pointer around for a while until we're sure of being done,
// or else Tap gets freed and the event tap has a dangling pointer to it (??)
selfPtr = Unmanaged.passRetained(self)
eventTap = CGEvent.tapCreate(
tap: CGEventTapLocation.cgSessionEventTap,
place: CGEventTapPlacement.headInsertEventTap,
options: CGEventTapOptions.defaultTap,
eventsOfInterest: eventMask,
callback: { proxy, type, event, refcon in
// Trick from https://stackoverflow.com/questions/33260808/how-to-use-instance-method-as-callback-for-function-which-takes-only-func-or-lit
let mySelf = Unmanaged<Tap>.fromOpaque(refcon!).takeUnretainedValue()
return mySelf.eventTapCallback(proxy: proxy, type: type, event: event, refcon: refcon)
},
userInfo: selfPtr.toOpaque())!
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
CFRunLoopRun()
}
func eventTapCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
if type == CGEventType.tapDisabledByUserInput {
return nil
}
// Need to convert CGEvent to NSEvent to analyze
// Touch Bar events for some reason.
if #available(OSX 10.12.2, *) {
if let cocoaEvent = NSEvent(cgEvent: event) {
if cocoaEvent.type == NSEvent.EventType.directTouch {
print("Touch bar touch")
}
}
}
switch type {
case .leftMouseDown:
print("Left mouse down")
break
case .keyDown:
print("Key down", event.getIntegerValueField(.keyboardEventKeycode))
break
default:
break
}
// print("event", type, type.rawValue, event)
// If you don't return the event, it will be suppressed!
return Unmanaged.passUnretained(event)
}
func done() {
CGEvent.tapEnable(tap: self.eventTap, enable: false)
// FIXME: Wait some random period of time and then manually free the
// event tap pointer?
// This whole thing is really weird and probably overthinking --
// stems from the odd unused construction of the Tap object at main.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let _ = self.selfPtr.autorelease()
}
}
}
// Ripped off from https://stackoverflow.com/questions/40144259/modify-accessibility-settings-on-macos-with-swift
// You need accessibility access to tap key events.
public func checkAccess() -> Bool{
//get the value for accesibility
let checkOptPrompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
//set the options: false means it wont ask
//true means it will popup and ask
let options = [checkOptPrompt: true]
//translate into boolean value
let accessEnabled = AXIsProcessTrustedWithOptions(options as CFDictionary?)
return accessEnabled
}
if checkAccess() {
// Eh. This is not good OOP
Tap()
} else {
print("Enable access in System Preferences, then rerun.")
}
@partyka1
Copy link

partyka1 commented Nov 6, 2020

it doesnt leak

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment