From a711e50f99398357504fabca16750cf331e12927 2013-11-28 19:15:03 From: Min RK Date: 2013-11-28 19:15:03 Subject: [PATCH] Merge pull request #4453 from minrk/appnap Play nice with App Nap --- diff --git a/IPython/external/appnope/__init__.py b/IPython/external/appnope/__init__.py new file mode 100644 index 0000000..014645d --- /dev/null +++ b/IPython/external/appnope/__init__.py @@ -0,0 +1,15 @@ + +try: + from appnope import * +except ImportError: + __version__ = '0.0.5' + import sys + import platform + from distutils.version import LooseVersion as V + + if sys.platform != "darwin" or V(platform.mac_ver()[0]) < V("10.9"): + from _dummy import * + else: + from ._nope import * + + del sys, platform, V diff --git a/IPython/external/appnope/_dummy.py b/IPython/external/appnope/_dummy.py new file mode 100644 index 0000000..a55ec5b --- /dev/null +++ b/IPython/external/appnope/_dummy.py @@ -0,0 +1,30 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2013 Min RK +# +# Distributed under the terms of the 2-clause BSD License. +#----------------------------------------------------------------------------- + +from contextlib import contextmanager + +def beginActivityWithOptions(options, reason=""): + return + +def endActivity(activity): + return + +def nope(): + return + +def nap(): + return + + +@contextmanager +def nope_scope( + options=0, + reason="Because Reasons" + ): + yield + +def napping_allowed(): + return True \ No newline at end of file diff --git a/IPython/external/appnope/_nope.py b/IPython/external/appnope/_nope.py new file mode 100644 index 0000000..70a3493 --- /dev/null +++ b/IPython/external/appnope/_nope.py @@ -0,0 +1,126 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2013 Min RK +# +# Distributed under the terms of the 2-clause BSD License. +#----------------------------------------------------------------------------- + +from contextlib import contextmanager + +import ctypes +import ctypes.util + +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) + +void_p = ctypes.c_void_p +ull = ctypes.c_uint64 + +objc.objc_getClass.restype = void_p +objc.sel_registerName.restype = void_p +objc.objc_msgSend.restype = void_p +objc.objc_msgSend.argtypes = [void_p, 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 methods)""" + return objc.sel_registerName(_utf8(name)) + +def C(classname): + """get an ObjC Class by name""" + return objc.objc_getClass(_utf8(classname)) + +# constants from Foundation + +NSActivityIdleDisplaySleepDisabled = (1 << 40) +NSActivityIdleSystemSleepDisabled = (1 << 20) +NSActivitySuddenTerminationDisabled = (1 << 14) +NSActivityAutomaticTerminationDisabled = (1 << 15) +NSActivityUserInitiated = (0x00FFFFFF | NSActivityIdleSystemSleepDisabled) +NSActivityUserInitiatedAllowingIdleSystemSleep = (NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled) +NSActivityBackground = 0x000000FF +NSActivityLatencyCritical = 0xFF00000000 + +def beginActivityWithOptions(options, reason=""): + """Wrapper for: + + [ [ NSProcessInfo processInfo] + beginActivityWithOptions: (uint64)options + reason: (str)reason + ] + """ + NSProcessInfo = C('NSProcessInfo') + NSString = C('NSString') + + reason = msg(NSString, n("stringWithUTF8String:"), _utf8(reason)) + info = msg(NSProcessInfo, n('processInfo')) + activity = msg(info, + n('beginActivityWithOptions:reason:'), + ull(options), + void_p(reason) + ) + return activity + +def endActivity(activity): + """end a process activity assertion""" + NSProcessInfo = C('NSProcessInfo') + info = msg(NSProcessInfo, n('processInfo')) + msg(info, n("endActivity:"), void_p(activity)) + +_theactivity = None + +def nope(): + """disable App Nap by setting NSActivityUserInitiatedAllowingIdleSystemSleep""" + global _theactivity + _theactivity = beginActivityWithOptions( + NSActivityUserInitiatedAllowingIdleSystemSleep, + "Because Reasons" + ) + +def nap(): + """end the caffeinated state started by `nope`""" + global _theactivity + if _theactivity is not None: + endActivity(_theactivity) + _theactivity = None + +def napping_allowed(): + """is napping allowed?""" + return _theactivity is None + +@contextmanager +def nope_scope( + options=NSActivityUserInitiatedAllowingIdleSystemSleep, + reason="Because Reasons" + ): + """context manager for beginActivityWithOptions. + + Within this context, App Nap will be disabled. + """ + activity = beginActivityWithOptions(options, reason) + try: + yield + finally: + endActivity(activity) + +__all__ = [ + "NSActivityIdleDisplaySleepDisabled", + "NSActivityIdleSystemSleepDisabled", + "NSActivitySuddenTerminationDisabled", + "NSActivityAutomaticTerminationDisabled", + "NSActivityUserInitiated", + "NSActivityUserInitiatedAllowingIdleSystemSleep", + "NSActivityBackground", + "NSActivityLatencyCritical", + "beginActivityWithOptions", + "endActivity", + "nope", + "nap", + "napping_allowed", + "nope_scope", +] diff --git a/IPython/kernel/zmq/eventloops.py b/IPython/kernel/zmq/eventloops.py index 0335075..f3a437c 100644 --- a/IPython/kernel/zmq/eventloops.py +++ b/IPython/kernel/zmq/eventloops.py @@ -16,10 +16,10 @@ import sys -# System library imports. +# System library imports import zmq -# Local imports. +# Local imports from IPython.config.application import Application from IPython.utils import io @@ -28,18 +28,40 @@ from IPython.utils import io # Eventloops for integrating the Kernel into different GUIs #------------------------------------------------------------------------------ +def _on_os_x_10_9(): + import platform + from distutils.version import LooseVersion as V + return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') + +def _notify_stream_qt(kernel, stream): + + from IPython.external.qt_for_kernel import QtCore + + if _on_os_x_10_9() and kernel._darwin_app_nap: + from IPython.external.appnope import nope_scope as context + else: + from IPython.core.interactiveshell import no_op_context as context + + def process_stream_events(): + while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN: + with context(): + kernel.do_one_iteration() + + fd = stream.getsockopt(zmq.FD) + notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) + notifier.activated.connect(process_stream_events) + def loop_qt4(kernel): """Start a kernel with PyQt4 event loop integration.""" - from IPython.external.qt_for_kernel import QtCore from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 kernel.app = get_app_qt4([" "]) kernel.app.setQuitOnLastWindowClosed(False) - kernel.timer = QtCore.QTimer() - kernel.timer.timeout.connect(kernel.do_one_iteration) - # Units for the timer are in milliseconds - kernel.timer.start(1000*kernel._poll_interval) + + for s in kernel.shell_streams: + _notify_stream_qt(kernel, s) + start_event_loop_qt4(kernel.app) @@ -48,6 +70,12 @@ def loop_wx(kernel): import wx from IPython.lib.guisupport import start_event_loop_wx + + if _on_os_x_10_9() and kernel._darwin_app_nap: + # we don't hook up App Nap contexts for Wx, + # just disable it outright. + from IPython.external.appnope import nope + nope() doi = kernel.do_one_iteration # Wx uses milliseconds diff --git a/IPython/kernel/zmq/ipkernel.py b/IPython/kernel/zmq/ipkernel.py index 2a71dcf..15840bb 100755 --- a/IPython/kernel/zmq/ipkernel.py +++ b/IPython/kernel/zmq/ipkernel.py @@ -32,7 +32,7 @@ from IPython.utils.py3compat import builtin_mod, unicode_type, string_types from IPython.utils.jsonutil import json_clean from IPython.utils.traitlets import ( Any, Instance, Float, Dict, List, Set, Integer, Unicode, - Type + Type, Bool, ) from .serialize import serialize_object, unpack_apply_message @@ -91,9 +91,15 @@ class Kernel(Configurable): def _ident_default(self): return unicode_type(uuid.uuid4()) - # Private interface + _darwin_app_nap = Bool(True, config=True, + help="""Whether to use appnope for compatiblity with OS X App Nap. + + Only affects OS X >= 10.9. + """ + ) + # Time to sleep after flushing the stdout/err buffers in each execute # cycle. While this introduces a hard limit on the minimal latency of the # execute cycle, it helps prevent output synchronization problems for