# -*- coding: utf-8 -*- # Copied from https://github.com/alberthier/corefoundationasyncio/blob/5061b9b7daa8bcd40d54d58432d84dcc0a339ca6/corefoundationasyncio.py # This module is copied, rather than simply installed, because the PyPI version of this module depends on *all* of PyObjC even though it only needs pyobjc-framework-Cocoa. # There's an open PR to fix this: httpsV//github.com/alberthier/corefoundationasyncio/pull/3 import asyncio import sys import threading from CoreFoundation import ( CFRunLoopGetCurrent, CFRunLoopTimerCreate, CFRunLoopAddTimer, CFRunLoopRemoveTimer, CFAbsoluteTimeGetCurrent, CFFileDescriptorCreate, CFFileDescriptorIsValid, CFFileDescriptorEnableCallBacks, CFFileDescriptorDisableCallBacks, CFFileDescriptorCreateRunLoopSource, CFRunLoopAddSource, CFRunLoopRemoveSource, kCFAllocatorDefault, kCFRunLoopDefaultMode, kCFRunLoopCommonModes, kCFFileDescriptorReadCallBack, kCFFileDescriptorWriteCallBack ) import PyObjCTools.AppHelper class _TimerHandle(asyncio.TimerHandle): def __init__(self, when, callback, args, loop, context): super().__init__(when, callback, args, loop, context) self.cf_runloop_timer = None class _FDEntry: def __init__(self): self.cf_fd = None self.cf_source = None self.callbacks = {} class CoreFoundationEventLoop(asyncio.SelectorEventLoop): """ Event loop based on CoreFoundation's CFRunLoop. This allows integration of Cocoa GUI apps with asyncio """ def __init__(self, console_app = False, *eventloop_args): self._console_app = console_app self._eventloop_args = eventloop_args self._registered_fds = {} self._runloop = CFRunLoopGetCurrent() super().__init__() # Running and stopping the event loop. def run_forever(self): """Run until stop() is called.""" self._check_closed() if self.is_running(): raise RuntimeError('This event loop is already running') if asyncio._get_running_loop() is not None: raise RuntimeError('Cannot run the event loop while another loop is running') self._set_coroutine_origin_tracking(self._debug) self._thread_id = threading.get_ident() old_agen_hooks = sys.get_asyncgen_hooks() sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook, finalizer=self._asyncgen_finalizer_hook) try: asyncio._set_running_loop(self) if self._console_app: PyObjCTools.AppHelper.runConsoleEventLoop(*self._eventloop_args) else: PyObjCTools.AppHelper.runEventLoop(*self._eventloop_args) finally: self._thread_id = None asyncio._set_running_loop(None) self._set_coroutine_origin_tracking(False) sys.set_asyncgen_hooks(*old_agen_hooks) def _process_events(self, event_list): raise NotImplementedError("Not available in this implementation") def _run_once(self): raise NotImplementedError("Not available in this implementation") def stop(self): PyObjCTools.AppHelper.stopEventLoop() # Methods scheduling callbacks. All these return Handles. def call_at(self, when, callback, *args, context=None): self._check_closed() if self._debug: self._check_thread() self._check_callback(callback, 'call_at') timerHandle = _TimerHandle(when, callback, args, self, context) if timerHandle._source_traceback: del timerHandle._source_traceback[-1] self._add_callback(timerHandle) return timerHandle def _call_soon(self, callback, args, context): handle = asyncio.Handle(callback, args, self, context) self._add_callback(handle) return handle def _add_callback(self, handle): assert isinstance(handle, asyncio.Handle), 'A Handle is required here' is_timer = isinstance(handle, _TimerHandle) if handle.cancelled(): return def ontimeout(cf_timer, info): if not handle.cancelled(): handle._run() when = handle.when() if is_timer else self.time() cf_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, when, 0, 0, 0, ontimeout, None) CFRunLoopAddTimer(self._runloop, cf_timer, kCFRunLoopCommonModes) if is_timer: handle.cf_runloop_timer = cf_timer handle._scheduled = True def _timer_handle_cancelled(self, handle): CFRunLoopRemoveTimer(self._runloop, handle.cf_runloop_timer, kCFRunLoopCommonModes) def time(self): return CFAbsoluteTimeGetCurrent() # Ready-based callback registration methods. # The add_*() methods return None. # The remove_*() methods return True if something was removed, # False if there was nothing to delete. def _register_fd(self, fd, event, callback, args): entry = self._registered_fds.get(fd) if entry is not None: CFFileDescriptorEnableCallBacks(entry.cf_fd, event) entry.callbacks[event] = (callback, args) else: def fd_callback(file_desc, callback_types, entry): if callback_types & kCFFileDescriptorReadCallBack: (callback, args) = entry.callbacks[kCFFileDescriptorReadCallBack] callback(*args) if callback_types & kCFFileDescriptorWriteCallBack: (callback, args) = entry.callbacks[kCFFileDescriptorWriteCallBack] callback(*args) if CFFileDescriptorIsValid(file_desc): CFFileDescriptorEnableCallBacks(file_desc, callback_types) entry = _FDEntry() entry.cf_fd = CFFileDescriptorCreate(kCFAllocatorDefault, fd, 0, fd_callback, entry) CFFileDescriptorEnableCallBacks(entry.cf_fd, event) entry.callbacks[event] = (callback, args) entry.cf_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, entry.cf_fd, 0) self._registered_fds[fd] = entry CFRunLoopAddSource(self._runloop, entry.cf_source, kCFRunLoopDefaultMode) def _unregister_fd(self, fd, event): entry = self._registered_fds.pop(fd, None) if entry is None: return False cb = entry.callbacks.pop(event, None) if cb is None: return False if len(entry.callbacks) != 0: CFFileDescriptorDisableCallBacks(entry.cf_fd, event) else: CFRunLoopRemoveSource(self._runloop, entry.cf_source, kCFRunLoopDefaultMode) return True def _add_reader(self, fd, callback, *args): self._check_closed() self._register_fd(fd, kCFFileDescriptorReadCallBack, callback, args) def _remove_reader(self, fd): if self.is_closed(): return False return self._unregister_fd(fd, kCFFileDescriptorReadCallBack) def _add_writer(self, fd, callback, *args): self._check_closed() self._register_fd(fd, kCFFileDescriptorWriteCallBack, callback, args) def _remove_writer(self, fd): if self.is_closed(): return False return self._unregister_fd(fd, kCFFileDescriptorWriteCallBack)