You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			175 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			175 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Python
		
	
"""Eventloop hook for OS X
 | 
						|
 | 
						|
Calls NSApp / CoreFoundation APIs via ctypes.
 | 
						|
"""
 | 
						|
 | 
						|
# cribbed heavily from IPython.terminal.pt_inputhooks.osx
 | 
						|
# obj-c boilerplate from appnope, used under BSD 2-clause
 | 
						|
 | 
						|
import ctypes
 | 
						|
import ctypes.util
 | 
						|
from threading import Event
 | 
						|
 | 
						|
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc"))  # type:ignore[arg-type]
 | 
						|
 | 
						|
void_p = ctypes.c_void_p
 | 
						|
 | 
						|
objc.objc_getClass.restype = void_p
 | 
						|
objc.sel_registerName.restype = void_p
 | 
						|
objc.objc_msgSend.restype = void_p
 | 
						|
 | 
						|
msg = objc.objc_msgSend
 | 
						|
 | 
						|
 | 
						|
def _utf8(s):
 | 
						|
    """ensure utf8 bytes"""
 | 
						|
    if not isinstance(s, bytes):
 | 
						|
        s = s.encode("utf8")
 | 
						|
    return s
 | 
						|
 | 
						|
 | 
						|
def n(name):
 | 
						|
    """create a selector name (for ObjC methods)"""
 | 
						|
    return objc.sel_registerName(_utf8(name))
 | 
						|
 | 
						|
 | 
						|
def C(classname):
 | 
						|
    """get an ObjC Class by name"""
 | 
						|
    return objc.objc_getClass(_utf8(classname))
 | 
						|
 | 
						|
 | 
						|
# end obj-c boilerplate from appnope
 | 
						|
 | 
						|
# CoreFoundation C-API calls we will use:
 | 
						|
CoreFoundation = ctypes.cdll.LoadLibrary(
 | 
						|
    ctypes.util.find_library("CoreFoundation")  # type:ignore[arg-type]
 | 
						|
)
 | 
						|
 | 
						|
CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent
 | 
						|
CFAbsoluteTimeGetCurrent.restype = ctypes.c_double
 | 
						|
 | 
						|
CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent
 | 
						|
CFRunLoopGetCurrent.restype = void_p
 | 
						|
 | 
						|
CFRunLoopGetMain = CoreFoundation.CFRunLoopGetMain
 | 
						|
CFRunLoopGetMain.restype = void_p
 | 
						|
 | 
						|
CFRunLoopStop = CoreFoundation.CFRunLoopStop
 | 
						|
CFRunLoopStop.restype = None
 | 
						|
CFRunLoopStop.argtypes = [void_p]
 | 
						|
 | 
						|
CFRunLoopTimerCreate = CoreFoundation.CFRunLoopTimerCreate
 | 
						|
CFRunLoopTimerCreate.restype = void_p
 | 
						|
CFRunLoopTimerCreate.argtypes = [
 | 
						|
    void_p,  # allocator (NULL)
 | 
						|
    ctypes.c_double,  # fireDate
 | 
						|
    ctypes.c_double,  # interval
 | 
						|
    ctypes.c_int,  # flags (0)
 | 
						|
    ctypes.c_int,  # order (0)
 | 
						|
    void_p,  # callout
 | 
						|
    void_p,  # context
 | 
						|
]
 | 
						|
 | 
						|
CFRunLoopAddTimer = CoreFoundation.CFRunLoopAddTimer
 | 
						|
CFRunLoopAddTimer.restype = None
 | 
						|
CFRunLoopAddTimer.argtypes = [void_p, void_p, void_p]
 | 
						|
 | 
						|
kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, "kCFRunLoopCommonModes")
 | 
						|
 | 
						|
 | 
						|
def _NSApp():
 | 
						|
    """Return the global NSApplication instance (NSApp)"""
 | 
						|
    objc.objc_msgSend.argtypes = [void_p, void_p]
 | 
						|
    return msg(C("NSApplication"), n("sharedApplication"))
 | 
						|
 | 
						|
 | 
						|
def _wake(NSApp):
 | 
						|
    """Wake the Application"""
 | 
						|
    objc.objc_msgSend.argtypes = [
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
        void_p,
 | 
						|
    ]
 | 
						|
    event = msg(
 | 
						|
        C("NSEvent"),
 | 
						|
        n(
 | 
						|
            "otherEventWithType:location:modifierFlags:"
 | 
						|
            "timestamp:windowNumber:context:subtype:data1:data2:"
 | 
						|
        ),
 | 
						|
        15,  # Type
 | 
						|
        0,  # location
 | 
						|
        0,  # flags
 | 
						|
        0,  # timestamp
 | 
						|
        0,  # window
 | 
						|
        None,  # context
 | 
						|
        0,  # subtype
 | 
						|
        0,  # data1
 | 
						|
        0,  # data2
 | 
						|
    )
 | 
						|
    objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p]
 | 
						|
    msg(NSApp, n("postEvent:atStart:"), void_p(event), True)
 | 
						|
 | 
						|
 | 
						|
_triggered = Event()
 | 
						|
 | 
						|
 | 
						|
def stop(timer=None, loop=None):
 | 
						|
    """Callback to fire when there's input to be read"""
 | 
						|
    _triggered.set()
 | 
						|
    NSApp = _NSApp()
 | 
						|
    # if NSApp is not running, stop CFRunLoop directly,
 | 
						|
    # otherwise stop and wake NSApp
 | 
						|
    objc.objc_msgSend.argtypes = [void_p, void_p]
 | 
						|
    if msg(NSApp, n("isRunning")):
 | 
						|
        objc.objc_msgSend.argtypes = [void_p, void_p, void_p]
 | 
						|
        msg(NSApp, n("stop:"), NSApp)
 | 
						|
        _wake(NSApp)
 | 
						|
    else:
 | 
						|
        CFRunLoopStop(CFRunLoopGetCurrent())
 | 
						|
 | 
						|
 | 
						|
_c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p)
 | 
						|
_c_stop_callback = _c_callback_func_type(stop)
 | 
						|
 | 
						|
 | 
						|
def _stop_after(delay):
 | 
						|
    """Register callback to stop eventloop after a delay"""
 | 
						|
    timer = CFRunLoopTimerCreate(
 | 
						|
        None,  # allocator
 | 
						|
        CFAbsoluteTimeGetCurrent() + delay,  # fireDate
 | 
						|
        0,  # interval
 | 
						|
        0,  # flags
 | 
						|
        0,  # order
 | 
						|
        _c_stop_callback,
 | 
						|
        None,
 | 
						|
    )
 | 
						|
    CFRunLoopAddTimer(
 | 
						|
        CFRunLoopGetMain(),
 | 
						|
        timer,
 | 
						|
        kCFRunLoopCommonModes,
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def mainloop(duration=1):
 | 
						|
    """run the Cocoa eventloop for the specified duration (seconds)"""
 | 
						|
 | 
						|
    _triggered.clear()
 | 
						|
    NSApp = _NSApp()
 | 
						|
    _stop_after(duration)
 | 
						|
    objc.objc_msgSend.argtypes = [void_p, void_p]
 | 
						|
    msg(NSApp, n("run"))
 | 
						|
    if not _triggered.is_set():
 | 
						|
        # app closed without firing callback,
 | 
						|
        # probably due to last window being closed.
 | 
						|
        # Run the loop manually in this case,
 | 
						|
        # since there may be events still to process (ipython/ipython#9734)
 | 
						|
        CoreFoundation.CFRunLoopRun()
 |