Skip to content

Instantly share code, notes, and snippets.

@bpyamasinn
Last active September 21, 2019 10:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bpyamasinn/b2b255403d09c55f9e799456565bc616 to your computer and use it in GitHub Desktop.
Save bpyamasinn/b2b255403d09c55f9e799456565bc616 to your computer and use it in GitHub Desktop.
Mac のキーボードを有効 / 無効を切り替える実装
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let keyEvent = Event()
lazy var statusItem: NSStatusItem = {
return NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
}()
func applicationDidFinishLaunching(_ aNotification: Notification) {
setupStatusItem()
keyEvent.mouseTracking()
}
func applicationWillTerminate(_ aNotification: Notification) { }
private func setupStatusItem() {
statusItem.button?.title = "有効"
statusItem.menu = {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Quit", action: #selector(onQuit), keyEquivalent: "q"))
return menu
}()
}
@objc func onQuit() {
NSApplication.shared.terminate(nil)
}
@objc func updateStatusItemButton(title: String) {
statusItem.title = title
}
}
class Event: NSObject {
private var runLoopSource: CFRunLoopSource?
private var shouldChangeState: Bool = true
override init() {
let options = NSDictionary(
object: kCFBooleanTrue,
forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
) as CFDictionary
AXIsProcessTrustedWithOptions(options)
}
func mouseTracking() {
guard let eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .listenOnly,
eventsOfInterest: CGEventMask((1 << CGEventType.mouseMoved.rawValue)),
callback: { (proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, pointer: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? in
if let pointer = pointer, [.mouseMoved].contains(type) {
let `self` = Unmanaged<Event>.fromOpaque(pointer).takeUnretainedValue()
self.changeKeyboardStateIfNeeded(point: NSEvent.mouseLocation)
}
return Unmanaged.passRetained(event)
},
userInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque())
) else {
print("failed to create event tap")
exit(1)
}
let mouseMoveRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), mouseMoveRunLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
}
private func enableKeyboard() {
guard let runLoopSource = runLoopSource else { return }
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
CFRunLoopSourceInvalidate(runLoopSource)
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
self.runLoopSource = nil
appDelegate.updateStatusItemButton(title: "有効")
}
private func disableKeyboard() {
guard runLoopSource == nil else { return }
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
guard let eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask((1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.flagsChanged.rawValue)),
callback: { (proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? in
if [.keyDown, .flagsChanged].contains(type) {
let keyCode: Int64 = 99999 // this keycode does not exist.
event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
}
return Unmanaged.passRetained(event)
},
userInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque())
) else {
print("failed to create event tap")
exit(1)
}
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
appDelegate.updateStatusItemButton(title: "無効")
}
private func changeKeyboardStateIfNeeded(point: NSPoint) {
guard let main = NSScreen.main else { return }
let eventSize: CGFloat = 200
if main.frame.height - eventSize < point.y && main.frame.width - eventSize < point.x {
if shouldChangeState {
shouldChangeState = false
runLoopSource == nil ? disableKeyboard() : enableKeyboard()
}
} else {
shouldChangeState = true
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment