diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index 7c3dc32..e88c44a 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -62,6 +62,7 @@ from IPython.core.payload import PayloadManager
 from IPython.core.plugin import PluginManager
 from IPython.core.prefilter import PrefilterManager, ESC_MAGIC
 from IPython.core.profiledir import ProfileDir
+from IPython.core.pylabtools import pylab_activate
 from IPython.external.Itpl import ItplNS
 from IPython.utils import PyColorize
 from IPython.utils import io
@@ -2531,8 +2532,46 @@ class InteractiveShell(SingletonConfigurable, Magic):
     # Things related to GUI support and pylab
     #-------------------------------------------------------------------------
 
+    def enable_gui(self, gui=None):
+        raise NotImplementedError('Implement enable_gui in a subclass')
+
     def enable_pylab(self, gui=None, import_all=True):
-        raise NotImplementedError('Implement enable_pylab in a subclass')
+        """Activate pylab support at runtime.
+
+        This turns on support for matplotlib, preloads into the interactive
+        namespace all of numpy and pylab, and configures IPython to correctly
+        interact with the GUI event loop.  The GUI backend to be used can be
+        optionally selected with the optional :param:`gui` argument.
+
+        Parameters
+        ----------
+        gui : optional, string
+
+          If given, dictates the choice of matplotlib GUI backend to use
+          (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
+          'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
+          matplotlib (as dictated by the matplotlib build-time options plus the
+          user's matplotlibrc configuration file).  Note that not all backends
+          make sense in all contexts, for example a terminal ipython can't
+          display figures inline.
+        """
+
+        # We want to prevent the loading of pylab to pollute the user's
+        # namespace as shown by the %who* magics, so we execute the activation
+        # code in an empty namespace, and we update *both* user_ns and
+        # user_ns_hidden with this information.
+        ns = {}
+        try:
+            gui = pylab_activate(ns, gui, import_all, self)
+        except KeyError:
+            error("Backend %r not supported" % gui)
+            return
+        self.user_ns.update(ns)
+        self.user_ns_hidden.update(ns)
+        # Now we must activate the gui pylab wants to use, and fix %run to take
+        # plot updates into account
+        self.enable_gui(gui)
+        self.magic_run = self._pylab_magic_run
 
     #-------------------------------------------------------------------------
     # Utilities
diff --git a/IPython/core/magic.py b/IPython/core/magic.py
index e39d289..bb28845 100644
--- a/IPython/core/magic.py
+++ b/IPython/core/magic.py
@@ -50,7 +50,7 @@ from IPython.core.profiledir import ProfileDir
 from IPython.core.macro import Macro
 from IPython.core import magic_arguments, page
 from IPython.core.prefilter import ESC_MAGIC
-from IPython.lib.pylabtools import mpl_runner
+from IPython.core.pylabtools import mpl_runner
 from IPython.testing.skipdoctest import skip_doctest
 from IPython.utils import py3compat
 from IPython.utils.io import file_read, nlprint
@@ -3305,24 +3305,30 @@ Defaulting color scheme to 'NoColor'"""
 
         This magic replaces IPython's threaded shells that were activated
         using the (pylab/wthread/etc.) command line flags.  GUI toolkits
-        can now be enabled, disabled and changed at runtime and keyboard
+        can now be enabled at runtime and keyboard
         interrupts should work without any problems.  The following toolkits
-        are supported:  wxPython, PyQt4, PyGTK, and Tk::
+        are supported:  wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
 
             %gui wx      # enable wxPython event loop integration
             %gui qt4|qt  # enable PyQt4 event loop integration
             %gui gtk     # enable PyGTK event loop integration
             %gui tk      # enable Tk event loop integration
+            %gui OSX     # enable Cocoa event loop integration
+                         # (requires %matplotlib 1.1) 
             %gui         # disable all event loop integration
 
         WARNING:  after any of these has been called you can simply create
         an application object, but DO NOT start the event loop yourself, as
         we have already handled that.
         """
-        from IPython.lib.inputhook import enable_gui
         opts, arg = self.parse_options(parameter_s, '')
         if arg=='': arg = None
-        return enable_gui(arg)
+        try:
+            return self.enable_gui(arg)
+        except Exception as e:
+            # print simple error message, rather than traceback if we can't
+            # hook up the GUI
+            error(str(e))
 
     def magic_load_ext(self, module_str):
         """Load an IPython extension by its module name."""
@@ -3416,9 +3422,9 @@ Defaulting color scheme to 'NoColor'"""
         Parameters
         ----------
         guiname : optional
-          One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk', 'osx' or
-          'tk').  If given, the corresponding Matplotlib backend is used,
-          otherwise matplotlib's default (which you can override in your
+          One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk',
+          'osx' or 'tk').  If given, the corresponding Matplotlib backend is
+          used, otherwise matplotlib's default (which you can override in your
           matplotlib config file) is used.
 
         Examples
@@ -3449,7 +3455,7 @@ Defaulting color scheme to 'NoColor'"""
         else:
             import_all_status = True
 
-        self.shell.enable_pylab(s,import_all=import_all_status)
+        self.shell.enable_pylab(s, import_all=import_all_status)
 
     def magic_tb(self, s):
         """Print the last traceback with the currently active exception mode.
diff --git a/IPython/lib/pylabtools.py b/IPython/core/pylabtools.py
similarity index 91%
rename from IPython/lib/pylabtools.py
rename to IPython/core/pylabtools.py
index 1187d55..c48b722 100644
--- a/IPython/lib/pylabtools.py
+++ b/IPython/core/pylabtools.py
@@ -232,7 +232,8 @@ def activate_matplotlib(backend):
     # For this, we wrap it into a decorator which adds a 'called' flag.
     pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
 
-def import_pylab(user_ns, backend, import_all=True, shell=None):
+
+def import_pylab(user_ns, import_all=True):
     """Import the standard pylab symbols into user_ns."""
 
     # Import numpy as np/pyplot as plt are conventions we're trying to
@@ -246,53 +247,57 @@ def import_pylab(user_ns, backend, import_all=True, shell=None):
           )
     exec s in user_ns
 
-    if shell is not None:
-        # All local executions are done in a fresh namespace and we then update
-        # the set of 'hidden' keys so these variables don't show up in %who
-        # (which is meant to show only what the user has manually defined).
-        ns = {}
-        exec s in ns
-        # If using our svg payload backend, register the post-execution
-        # function that will pick up the results for display.  This can only be
-        # done with access to the real shell object.
-        #
-        from IPython.zmq.pylab.backend_inline import InlineBackend
-
-        cfg = InlineBackend.instance(config=shell.config)
-        cfg.shell = shell
-        if cfg not in shell.configurables:
-            shell.configurables.append(cfg)
-
-        if backend == backends['inline']:
-            from IPython.zmq.pylab.backend_inline import flush_figures
-            from matplotlib import pyplot
-            shell.register_post_execute(flush_figures)
-            # load inline_rc
-            pyplot.rcParams.update(cfg.rc)
-            
-            # Add 'figsize' to pyplot and to the user's namespace
-            user_ns['figsize'] = pyplot.figsize = figsize
-            ns['figsize'] = figsize
-        
-        # Setup the default figure format
-        fmt = cfg.figure_format
-        select_figure_format(shell, fmt)
-
-        # The old pastefig function has been replaced by display
-        from IPython.core.display import display
-        # Add display and display_png to the user's namespace
-        ns['display'] = user_ns['display'] = display
-        ns['getfigs'] = user_ns['getfigs'] = getfigs
-
     if import_all:
         s = ("from matplotlib.pylab import *\n"
              "from numpy import *\n")
         exec s in user_ns
-        if shell is not None:
-            exec s in ns
 
-    # Update the set of hidden variables with anything we've done here.
-    shell.user_ns_hidden.update(ns)
+
+def configure_shell(shell, backend, user_ns=None):
+    """Configure an IPython shell object for matplotlib use.
+
+    Parameters
+    ----------
+    shell : InteractiveShell instance
+      If None, this function returns immediately.
+
+    user_ns : dict
+      A namespace where all configured variables will be placed.  If not given,
+      the `user_ns` attribute of the shell object is used.
+    """
+    if shell is None:
+        return
+
+    user_ns = shell.user_ns if user_ns is None else user_ns
+    
+    # If using our svg payload backend, register the post-execution
+    # function that will pick up the results for display.  This can only be
+    # done with access to the real shell object.
+    from IPython.zmq.pylab.backend_inline import InlineBackend
+
+    cfg = InlineBackend.instance(config=shell.config)
+    cfg.shell = shell
+    if cfg not in shell.configurables:
+        shell.configurables.append(cfg)
+
+    if backend == backends['inline']:
+        from IPython.zmq.pylab.backend_inline import flush_figures
+        from matplotlib import pyplot
+        shell.register_post_execute(flush_figures)
+        # load inline_rc
+        pyplot.rcParams.update(cfg.rc)
+        # Add 'figsize' to pyplot and to the user's namespace
+        user_ns['figsize'] = pyplot.figsize = figsize
+
+    # Setup the default figure format
+    fmt = cfg.figure_format
+    select_figure_format(shell, fmt)
+
+    # The old pastefig function has been replaced by display
+    from IPython.core.display import display
+    # Add display and getfigs to the user's namespace
+    user_ns['display'] = display
+    user_ns['getfigs'] = getfigs
 
 
 def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
@@ -318,8 +323,8 @@ def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
     """
     gui, backend = find_gui_and_backend(gui)
     activate_matplotlib(backend)
-    import_pylab(user_ns, backend, import_all, shell)
-
+    import_pylab(user_ns, import_all)
+    configure_shell(shell, backend, user_ns)
     print """
 Welcome to pylab, a matplotlib-based Python environment [backend: %s].
 For more information, type 'help(pylab)'.""" % backend
diff --git a/IPython/lib/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py
similarity index 92%
rename from IPython/lib/tests/test_pylabtools.py
rename to IPython/core/tests/test_pylabtools.py
index c64e01f..3c12523 100644
--- a/IPython/lib/tests/test_pylabtools.py
+++ b/IPython/core/tests/test_pylabtools.py
@@ -57,6 +57,7 @@ def test_figure_to_svg():
 
 def test_import_pylab():
     ip = get_ipython()
-    pt.import_pylab(ip.user_ns, 'inline', import_all=False, shell=ip)
-    nt.assert_true('plt' in ip.user_ns)
-    nt.assert_equal(ip.user_ns['np'], np)
+    ns = {}
+    pt.import_pylab(ns, import_all=False)
+    nt.assert_true('plt' in ns)
+    nt.assert_equal(ns['np'], np)
diff --git a/IPython/frontend/terminal/interactiveshell.py b/IPython/frontend/terminal/interactiveshell.py
index 7196ca7..7c37282 100644
--- a/IPython/frontend/terminal/interactiveshell.py
+++ b/IPython/frontend/terminal/interactiveshell.py
@@ -29,8 +29,7 @@ except:
 from IPython.core.error import TryNext
 from IPython.core.usage import interactive_usage, default_banner
 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
-from IPython.lib.inputhook import enable_gui
-from IPython.lib.pylabtools import pylab_activate
+from IPython.core.pylabtools import pylab_activate
 from IPython.testing.skipdoctest import skip_doctest
 from IPython.utils import py3compat
 from IPython.utils.terminal import toggle_set_term_title, set_term_title
@@ -520,40 +519,9 @@ class TerminalInteractiveShell(InteractiveShell):
     # Things related to GUI support and pylab
     #-------------------------------------------------------------------------
 
-    def enable_pylab(self, gui=None, import_all=True):
-        """Activate pylab support at runtime.
-
-        This turns on support for matplotlib, preloads into the interactive
-        namespace all of numpy and pylab, and configures IPython to correcdtly
-        interact with the GUI event loop.  The GUI backend to be used can be
-        optionally selected with the optional :param:`gui` argument.
-
-        Parameters
-        ----------
-        gui : optional, string
-
-          If given, dictates the choice of matplotlib GUI backend to use
-          (should be one of IPython's supported backends, 'tk', 'qt', 'wx' or
-          'gtk'), otherwise we use the default chosen by matplotlib (as
-          dictated by the matplotlib build-time options plus the user's
-          matplotlibrc configuration file).
-        """
-        # We want to prevent the loading of pylab to pollute the user's
-        # namespace as shown by the %who* magics, so we execute the activation
-        # code in an empty namespace, and we update *both* user_ns and
-        # user_ns_hidden with this information.
-        ns = {}
-        try:
-            gui = pylab_activate(ns, gui, import_all)
-        except KeyError:
-            error("Backend %r not supported" % gui)
-            return
-        self.user_ns.update(ns)
-        self.user_ns_hidden.update(ns)
-        # Now we must activate the gui pylab wants to use, and fix %run to take
-        # plot updates into account
+    def enable_gui(self, gui=None):
+        from IPython.lib.inputhook import enable_gui
         enable_gui(gui)
-        self.magic_run = self._pylab_magic_run
 
     #-------------------------------------------------------------------------
     # Things related to exiting
diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py
index 453de87..5536073 100755
--- a/IPython/zmq/ipkernel.py
+++ b/IPython/zmq/ipkernel.py
@@ -679,10 +679,11 @@ loop_map = {
     'wx' : loop_wx,
     'tk' : loop_tk,
     'gtk': loop_gtk,
+    None : None,
 }
 
 def enable_gui(gui, kernel=None):
-    """Enable integration with a give GUI"""
+    """Enable integration with a given GUI"""
     if kernel is None:
         kernel = IPKernelApp.instance().kernel
     if gui not in loop_map:
diff --git a/IPython/zmq/pylab/backend_inline.py b/IPython/zmq/pylab/backend_inline.py
index 7bf9b1f..e806588 100644
--- a/IPython/zmq/pylab/backend_inline.py
+++ b/IPython/zmq/pylab/backend_inline.py
@@ -16,7 +16,7 @@ from matplotlib._pylab_helpers import Gcf
 # Local imports.
 from IPython.config.configurable import SingletonConfigurable
 from IPython.core.displaypub import publish_display_data
-from IPython.lib.pylabtools import print_figure, select_figure_format
+from IPython.core.pylabtools import print_figure, select_figure_format
 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, CBool
 from IPython.utils.warn import warn
 
diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py
index 942ea43..0c292cb 100644
--- a/IPython/zmq/zmqshell.py
+++ b/IPython/zmq/zmqshell.py
@@ -390,78 +390,9 @@ class ZMQInteractiveShell(InteractiveShell):
         }
         self.payload_manager.write_payload(payload)
 
-    def magic_gui(self, parameter_s=''):
-        """Enable or disable IPython GUI event loop integration.
-
-        %gui [GUINAME]
-
-        This magic replaces IPython's threaded shells that were activated
-        using the (pylab/wthread/etc.) command line flags.  GUI toolkits
-        can now be enabled at runtime and keyboard
-        interrupts should work without any problems.  The following toolkits
-        are supported:  wxPython, PyQt4, PyGTK, Cocoa, and Tk::
-
-            %gui wx      # enable wxPython event loop integration
-            %gui qt4|qt  # enable PyQt4 event loop integration
-            %gui gtk     # enable PyGTK event loop integration
-            %gui OSX     # enable Cocoa event loop integration (requires matplotlib 1.1)
-            %gui tk      # enable Tk event loop integration
-
-        WARNING:  after any of these has been called you can simply create
-        an application object, but DO NOT start the event loop yourself, as
-        we have already handled that.
-        """
-        from IPython.zmq.ipkernel import enable_gui
-        opts, arg = self.parse_options(parameter_s, '')
-        if arg=='': arg = None
-        try:
-            enable_gui(arg)
-        except Exception as e:
-            # print simple error message, rather than traceback if we can't
-            # hook up the GUI
-            error(str(e))
-
-    def enable_pylab(self, gui=None, import_all=True):
-        """Activate pylab support at runtime.
-
-        This turns on support for matplotlib, preloads into the interactive
-        namespace all of numpy and pylab, and configures IPython to correcdtly
-        interact with the GUI event loop.  The GUI backend to be used can be
-        optionally selected with the optional :param:`gui` argument.
-
-        Parameters
-        ----------
-        gui : optional, string [default: inline]
-
-          If given, dictates the choice of matplotlib GUI backend to use
-          (should be one of IPython's supported backends, 'inline', 'qt', 'osx',
-          'tk', or 'gtk'), otherwise we use the default chosen by matplotlib
-          (as dictated by the matplotlib build-time options plus the user's
-          matplotlibrc configuration file).
-        """
+    def enable_gui(self, gui=None):
         from IPython.zmq.ipkernel import enable_gui
-        # We want to prevent the loading of pylab to pollute the user's
-        # namespace as shown by the %who* magics, so we execute the activation
-        # code in an empty namespace, and we update *both* user_ns and
-        # user_ns_hidden with this information.
-        ns = {}
-        try:
-            gui = pylabtools.pylab_activate(ns, gui, import_all, self)
-        except KeyError:
-            error("Backend %r not supported" % gui)
-            return
-        self.user_ns.update(ns)
-        self.user_ns_hidden.update(ns)
-        # Now we must activate the gui pylab wants to use, and fix %run to take
-        # plot updates into account
-        try:
-            enable_gui(gui)
-        except Exception as e:
-            # print simple error message, rather than traceback if we can't
-            # hook up the GUI
-            error(str(e))
-        self.magic_run = self._pylab_magic_run
-
+        enable_gui(gui)
 
     # A few magics that are adapted to the specifics of using pexpect and a
     # remote terminal
@@ -567,7 +498,6 @@ class ZMQInteractiveShell(InteractiveShell):
         except Exception as e:
             error("Could not start qtconsole: %r" % e)
             return
-        
 
     def set_next_input(self, text):
         """Send the specified text to the frontend to be presented at the next
@@ -578,4 +508,5 @@ class ZMQInteractiveShell(InteractiveShell):
         )
         self.payload_manager.write_payload(payload)
 
+
 InteractiveShellABC.register(ZMQInteractiveShell)