_eventloop_macos.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. """Eventloop hook for OS X
  2. Calls NSApp / CoreFoundation APIs via ctypes.
  3. """
  4. # cribbed heavily from IPython.terminal.pt_inputhooks.osx
  5. # obj-c boilerplate from appnope, used under BSD 2-clause
  6. import ctypes
  7. import ctypes.util
  8. from threading import Event
  9. objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
  10. void_p = ctypes.c_void_p
  11. objc.objc_getClass.restype = void_p
  12. objc.sel_registerName.restype = void_p
  13. objc.objc_msgSend.restype = void_p
  14. objc.objc_msgSend.argtypes = [void_p, void_p]
  15. msg = objc.objc_msgSend
  16. def _utf8(s):
  17. """ensure utf8 bytes"""
  18. if not isinstance(s, bytes):
  19. s = s.encode('utf8')
  20. return s
  21. def n(name):
  22. """create a selector name (for ObjC methods)"""
  23. return objc.sel_registerName(_utf8(name))
  24. def C(classname):
  25. """get an ObjC Class by name"""
  26. return objc.objc_getClass(_utf8(classname))
  27. # end obj-c boilerplate from appnope
  28. # CoreFoundation C-API calls we will use:
  29. CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
  30. CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent
  31. CFAbsoluteTimeGetCurrent.restype = ctypes.c_double
  32. CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent
  33. CFRunLoopGetCurrent.restype = void_p
  34. CFRunLoopGetMain = CoreFoundation.CFRunLoopGetMain
  35. CFRunLoopGetMain.restype = void_p
  36. CFRunLoopStop = CoreFoundation.CFRunLoopStop
  37. CFRunLoopStop.restype = None
  38. CFRunLoopStop.argtypes = [void_p]
  39. CFRunLoopTimerCreate = CoreFoundation.CFRunLoopTimerCreate
  40. CFRunLoopTimerCreate.restype = void_p
  41. CFRunLoopTimerCreate.argtypes = [
  42. void_p, # allocator (NULL)
  43. ctypes.c_double, # fireDate
  44. ctypes.c_double, # interval
  45. ctypes.c_int, # flags (0)
  46. ctypes.c_int, # order (0)
  47. void_p, # callout
  48. void_p, # context
  49. ]
  50. CFRunLoopAddTimer = CoreFoundation.CFRunLoopAddTimer
  51. CFRunLoopAddTimer.restype = None
  52. CFRunLoopAddTimer.argtypes = [ void_p, void_p, void_p ]
  53. kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes')
  54. def _NSApp():
  55. """Return the global NSApplication instance (NSApp)"""
  56. return msg(C('NSApplication'), n('sharedApplication'))
  57. def _wake(NSApp):
  58. """Wake the Application"""
  59. event = msg(C('NSEvent'),
  60. n('otherEventWithType:location:modifierFlags:'
  61. 'timestamp:windowNumber:context:subtype:data1:data2:'),
  62. 15, # Type
  63. 0, # location
  64. 0, # flags
  65. 0, # timestamp
  66. 0, # window
  67. None, # context
  68. 0, # subtype
  69. 0, # data1
  70. 0, # data2
  71. )
  72. msg(NSApp, n('postEvent:atStart:'), void_p(event), True)
  73. _triggered = Event()
  74. def stop(timer=None, loop=None):
  75. """Callback to fire when there's input to be read"""
  76. _triggered.set()
  77. NSApp = _NSApp()
  78. # if NSApp is not running, stop CFRunLoop directly,
  79. # otherwise stop and wake NSApp
  80. if msg(NSApp, n('isRunning')):
  81. msg(NSApp, n('stop:'), NSApp)
  82. _wake(NSApp)
  83. else:
  84. CFRunLoopStop(CFRunLoopGetCurrent())
  85. _c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p)
  86. _c_stop_callback = _c_callback_func_type(stop)
  87. def _stop_after(delay):
  88. """Register callback to stop eventloop after a delay"""
  89. timer = CFRunLoopTimerCreate(
  90. None, # allocator
  91. CFAbsoluteTimeGetCurrent() + delay, # fireDate
  92. 0, # interval
  93. 0, # flags
  94. 0, # order
  95. _c_stop_callback,
  96. None,
  97. )
  98. CFRunLoopAddTimer(
  99. CFRunLoopGetMain(),
  100. timer,
  101. kCFRunLoopCommonModes,
  102. )
  103. def mainloop(duration=1):
  104. """run the Cocoa eventloop for the specified duration (seconds)"""
  105. _triggered.clear()
  106. NSApp = _NSApp()
  107. _stop_after(duration)
  108. msg(NSApp, n('run'))
  109. if not _triggered.is_set():
  110. # app closed without firing callback,
  111. # probably due to last window being closed.
  112. # Run the loop manually in this case,
  113. # since there may be events still to process (ipython/ipython#9734)
  114. CoreFoundation.CFRunLoopRun()