Created
December 22, 2017 21:27
-
-
Save osnr/23eb05b4e0bcd335c06361c4fabadd6f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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.") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
it doesnt leak