osx.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. """Inputhook for OS X
  2. Calls NSApp / CoreFoundation APIs via ctypes.
  3. """
  4. # obj-c boilerplate from appnope, used under BSD 2-clause
  5. import ctypes
  6. import ctypes.util
  7. from threading import Event
  8. objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) # type: ignore
  9. void_p = ctypes.c_void_p
  10. objc.objc_getClass.restype = void_p
  11. objc.sel_registerName.restype = void_p
  12. objc.objc_msgSend.restype = void_p
  13. objc.objc_msgSend.argtypes = [void_p, void_p]
  14. msg = objc.objc_msgSend
  15. def _utf8(s):
  16. """ensure utf8 bytes"""
  17. if not isinstance(s, bytes):
  18. s = s.encode('utf8')
  19. return s
  20. def n(name):
  21. """create a selector name (for ObjC methods)"""
  22. return objc.sel_registerName(_utf8(name))
  23. def C(classname):
  24. """get an ObjC Class by name"""
  25. return objc.objc_getClass(_utf8(classname))
  26. # end obj-c boilerplate from appnope
  27. # CoreFoundation C-API calls we will use:
  28. CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) # type: ignore
  29. CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate
  30. CFFileDescriptorCreate.restype = void_p
  31. CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p, void_p]
  32. CFFileDescriptorGetNativeDescriptor = CoreFoundation.CFFileDescriptorGetNativeDescriptor
  33. CFFileDescriptorGetNativeDescriptor.restype = ctypes.c_int
  34. CFFileDescriptorGetNativeDescriptor.argtypes = [void_p]
  35. CFFileDescriptorEnableCallBacks = CoreFoundation.CFFileDescriptorEnableCallBacks
  36. CFFileDescriptorEnableCallBacks.restype = None
  37. CFFileDescriptorEnableCallBacks.argtypes = [void_p, ctypes.c_ulong]
  38. CFFileDescriptorCreateRunLoopSource = CoreFoundation.CFFileDescriptorCreateRunLoopSource
  39. CFFileDescriptorCreateRunLoopSource.restype = void_p
  40. CFFileDescriptorCreateRunLoopSource.argtypes = [void_p, void_p, void_p]
  41. CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent
  42. CFRunLoopGetCurrent.restype = void_p
  43. CFRunLoopAddSource = CoreFoundation.CFRunLoopAddSource
  44. CFRunLoopAddSource.restype = None
  45. CFRunLoopAddSource.argtypes = [void_p, void_p, void_p]
  46. CFRelease = CoreFoundation.CFRelease
  47. CFRelease.restype = None
  48. CFRelease.argtypes = [void_p]
  49. CFFileDescriptorInvalidate = CoreFoundation.CFFileDescriptorInvalidate
  50. CFFileDescriptorInvalidate.restype = None
  51. CFFileDescriptorInvalidate.argtypes = [void_p]
  52. # From CFFileDescriptor.h
  53. kCFFileDescriptorReadCallBack = 1
  54. kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes')
  55. def _NSApp():
  56. """Return the global NSApplication instance (NSApp)"""
  57. objc.objc_msgSend.argtypes = [void_p, void_p]
  58. return msg(C('NSApplication'), n('sharedApplication'))
  59. def _wake(NSApp):
  60. """Wake the Application"""
  61. objc.objc_msgSend.argtypes = [
  62. void_p,
  63. void_p,
  64. void_p,
  65. void_p,
  66. void_p,
  67. void_p,
  68. void_p,
  69. void_p,
  70. void_p,
  71. void_p,
  72. void_p,
  73. ]
  74. event = msg(
  75. C("NSEvent"),
  76. n(
  77. "otherEventWithType:location:modifierFlags:"
  78. "timestamp:windowNumber:context:subtype:data1:data2:"
  79. ),
  80. 15, # Type
  81. 0, # location
  82. 0, # flags
  83. 0, # timestamp
  84. 0, # window
  85. None, # context
  86. 0, # subtype
  87. 0, # data1
  88. 0, # data2
  89. )
  90. objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p]
  91. msg(NSApp, n('postEvent:atStart:'), void_p(event), True)
  92. def _input_callback(fdref, flags, info):
  93. """Callback to fire when there's input to be read"""
  94. CFFileDescriptorInvalidate(fdref)
  95. CFRelease(fdref)
  96. NSApp = _NSApp()
  97. objc.objc_msgSend.argtypes = [void_p, void_p, void_p]
  98. msg(NSApp, n('stop:'), NSApp)
  99. _wake(NSApp)
  100. _c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p)
  101. _c_input_callback = _c_callback_func_type(_input_callback)
  102. def _stop_on_read(fd):
  103. """Register callback to stop eventloop when there's data on fd"""
  104. fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None)
  105. CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack)
  106. source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0)
  107. loop = CFRunLoopGetCurrent()
  108. CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes)
  109. CFRelease(source)
  110. def inputhook(context):
  111. """Inputhook for Cocoa (NSApp)"""
  112. NSApp = _NSApp()
  113. _stop_on_read(context.fileno())
  114. objc.objc_msgSend.argtypes = [void_p, void_p]
  115. msg(NSApp, n('run'))