Last active
September 21, 2019 10:07
-
-
Save bpyamasinn/b2b255403d09c55f9e799456565bc616 to your computer and use it in GitHub Desktop.
Mac のキーボードを有効 / 無効を切り替える実装
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
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