Skip to content

Instantly share code, notes, and snippets.

@osnr
Created December 22, 2017 21:27
Show Gist options
  • Select an option

  • Save osnr/23eb05b4e0bcd335c06361c4fabadd6f to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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