diff --git a/CMakeLists.txt b/CMakeLists.txt
index f4547e5da0..b8dc1c38c7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -761,6 +761,8 @@ include( ${SWIG_USE_FILE} )
 # pybind11 is header-only, so include the subdir
 add_subdirectory(thirdparty/pybind11)
 
+include_directories( SYSTEM ${PYBIND11_INCLUDE_DIR} )
+
 set( PythonInterp_FIND_VERSION 3.6 )
 set( PythonLibs_FIND_VERSION 3.6 )
 
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index b9ce2ec584..7fabf84ea3 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -19,7 +19,6 @@ include_directories(
     ${INC_AFTER}
     )
 
-
 if( NOT APPLE )     # windows and linux use openssl under curl
     find_package( OpenSSL REQUIRED )
     include_directories( SYSTEM ${OPENSSL_INCLUDE_DIR} )
@@ -484,6 +483,7 @@ target_link_libraries( common
     kimath
     kiplatform
     gal
+    pybind11::embed
     ${Boost_LIBRARIES}
     ${CURL_LIBRARIES}
     ${OPENSSL_LIBRARIES}        # empty on Apple
diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp
index 357be95462..b63a997e6a 100644
--- a/common/eda_base_frame.cpp
+++ b/common/eda_base_frame.cpp
@@ -102,28 +102,24 @@ BEGIN_EVENT_TABLE( EDA_BASE_FRAME, wxFrame )
     EVT_SYS_COLOUR_CHANGED( EDA_BASE_FRAME::onSystemColorChange )
 END_EVENT_TABLE()
 
-EDA_BASE_FRAME::EDA_BASE_FRAME( wxWindow* aParent, FRAME_T aFrameType,
-                                const wxString& aTitle, const wxPoint& aPos, const wxSize& aSize,
-                                long aStyle, const wxString& aFrameName, KIWAY* aKiway ) :
-        wxFrame( aParent, wxID_ANY, aTitle, aPos, aSize, aStyle, aFrameName ),
-        TOOLS_HOLDER(),
-        KIWAY_HOLDER( aKiway, KIWAY_HOLDER::FRAME ),
-        m_ident( aFrameType ),
-        m_maximizeByDefault( false ),
-        m_infoBar( nullptr ),
-        m_settingsManager( nullptr ),
-        m_fileHistory( nullptr ),
-        m_hasAutoSave( false ),
-        m_autoSaveState( false ),
-        m_autoSaveInterval(-1 ),
-        m_undoRedoCountMax( DEFAULT_MAX_UNDO_ITEMS ),
-        m_userUnits( EDA_UNITS::MILLIMETRES ),
-        m_isClosing( false ),
-        m_isNonUserClose( false )
+
+void EDA_BASE_FRAME::commonInit( FRAME_T aFrameType )
 {
-    m_autoSaveTimer = new wxTimer( this, ID_AUTO_SAVE_TIMER );
-    m_mruPath       = PATHS::GetDefaultUserProjectsPath();
-    m_frameSize     = defaultSize( aFrameType );
+    m_ident             = aFrameType;
+    m_maximizeByDefault = false;
+    m_infoBar           = nullptr;
+    m_settingsManager   = nullptr;
+    m_fileHistory       = nullptr;
+    m_hasAutoSave       = false;
+    m_autoSaveState     = false;
+    m_autoSaveInterval  = -1;
+    m_undoRedoCountMax  = DEFAULT_MAX_UNDO_ITEMS;
+    m_userUnits         = EDA_UNITS::MILLIMETRES;
+    m_isClosing         = false;
+    m_isNonUserClose    = false;
+    m_autoSaveTimer     = new wxTimer( this, ID_AUTO_SAVE_TIMER );
+    m_mruPath           = PATHS::GetDefaultUserProjectsPath();
+    m_frameSize         = defaultSize( aFrameType );
 
     m_auimgr.SetArtProvider( new WX_AUI_DOCK_ART() );
 
@@ -143,6 +139,25 @@ EDA_BASE_FRAME::EDA_BASE_FRAME( wxWindow* aParent, FRAME_T aFrameType,
     Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( EDA_BASE_FRAME::windowClosing ) );
 
     initExitKey();
+
+}
+
+EDA_BASE_FRAME::EDA_BASE_FRAME( FRAME_T aFrameType, KIWAY* aKiway ) :
+        wxFrame(),
+        TOOLS_HOLDER(),
+        KIWAY_HOLDER( aKiway, KIWAY_HOLDER::FRAME )
+{
+    commonInit( aFrameType );
+}
+
+EDA_BASE_FRAME::EDA_BASE_FRAME( wxWindow* aParent, FRAME_T aFrameType,
+                                const wxString& aTitle, const wxPoint& aPos, const wxSize& aSize,
+                                long aStyle, const wxString& aFrameName, KIWAY* aKiway ) :
+        wxFrame( aParent, wxID_ANY, aTitle, aPos, aSize, aStyle, aFrameName ),
+        TOOLS_HOLDER(),
+        KIWAY_HOLDER( aKiway, KIWAY_HOLDER::FRAME )
+{
+    commonInit( aFrameType );
 }
 
 
diff --git a/common/eda_dde.cpp b/common/eda_dde.cpp
index 6dad2603e2..f7d59a481f 100644
--- a/common/eda_dde.cpp
+++ b/common/eda_dde.cpp
@@ -27,7 +27,7 @@
 #include <thread>
 
 #include <eda_dde.h>
-#include <eda_draw_frame.h>
+#include <kiway_player.h>
 #include <id.h>
 
 #include <wx/wx.h>
@@ -45,7 +45,7 @@ static char client_ipc_buffer[IPC_BUF_SIZE];
 
 /* Function to initialize a server socket
  */
-void EDA_DRAW_FRAME::CreateServer( int service, bool local )
+void KIWAY_PLAYER::CreateServer( int service, bool local )
 {
     wxIPV4address addr;
 
@@ -67,7 +67,7 @@ void EDA_DRAW_FRAME::CreateServer( int service, bool local )
 
 /* Function called on every client request.
  */
-void EDA_DRAW_FRAME::OnSockRequest( wxSocketEvent& evt )
+void KIWAY_PLAYER::OnSockRequest( wxSocketEvent& evt )
 {
     size_t        len;
     wxSocketBase* sock = evt.GetSocket();
@@ -99,7 +99,7 @@ void EDA_DRAW_FRAME::OnSockRequest( wxSocketEvent& evt )
 
 /* Function called when a connection is requested by a client.
  */
-void EDA_DRAW_FRAME::OnSockRequestServer( wxSocketEvent& evt )
+void KIWAY_PLAYER::OnSockRequestServer( wxSocketEvent& evt )
 {
     wxSocketBase*   socket;
     wxSocketServer* server = (wxSocketServer*) evt.GetSocket();
diff --git a/common/eda_item.cpp b/common/eda_item.cpp
index c1fb070186..fcfdc0e368 100644
--- a/common/eda_item.cpp
+++ b/common/eda_item.cpp
@@ -23,7 +23,6 @@
  */
 
 #include <algorithm>
-#include <pybind11/pybind11.h>
 
 #include <bitmaps.h>
 #include <eda_item.h>
diff --git a/common/kiway.cpp b/common/kiway.cpp
index 2783c899cd..18a7a3baad 100644
--- a/common/kiway.cpp
+++ b/common/kiway.cpp
@@ -264,9 +264,9 @@ KIFACE*  KIWAY::KiFACE( FACE_T aFaceId, bool doLoad )
         }
         else
         {
-            KIFACE_GETTER_FUNC* getter = (KIFACE_GETTER_FUNC*) addr;
+            KIFACE_GETTER_FUNC* ki_getter = (KIFACE_GETTER_FUNC*) addr;
 
-            KIFACE* kiface = getter( &m_kiface_version[aFaceId], KIFACE_VERSION, m_program );
+            KIFACE* kiface = ki_getter( &m_kiface_version[aFaceId], KIFACE_VERSION, m_program );
 
             // KIFACE_GETTER_FUNC function comment (API) says the non-NULL is unconditional.
             wxASSERT_MSG( kiface,
diff --git a/common/kiway_player.cpp b/common/kiway_player.cpp
index fc1720b916..4889bd12f7 100644
--- a/common/kiway_player.cpp
+++ b/common/kiway_player.cpp
@@ -47,7 +47,8 @@ KIWAY_PLAYER::KIWAY_PLAYER( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType
     m_modal( false ),
     m_modal_loop( nullptr ),
     m_modal_resultant_parent( nullptr ),
-    m_modal_ret_val( false )
+    m_modal_ret_val( false ),
+    m_socketServer( nullptr )
 {
 }
 
@@ -59,11 +60,11 @@ KIWAY_PLAYER::KIWAY_PLAYER( wxWindow* aParent, wxWindowID aId, const wxString& a
     m_modal( false ),
     m_modal_loop( nullptr ),
     m_modal_resultant_parent( nullptr ),
-    m_modal_ret_val( false )
+    m_modal_ret_val( false ),
+    m_socketServer( nullptr )
 {
 }
 
-
 KIWAY_PLAYER::~KIWAY_PLAYER() throw() {}
 
 
diff --git a/common/single_top.cpp b/common/single_top.cpp
index a3d0af274c..30da289073 100644
--- a/common/single_top.cpp
+++ b/common/single_top.cpp
@@ -295,12 +295,12 @@ bool PGM_SINGLE_TOP::OnPgmInit()
     // i.e. they are single part link images so don't need to load a *.kiface.
 
     // Get the getter, it is statically linked into this binary image.
-    KIFACE_GETTER_FUNC* getter = &KIFACE_GETTER;
+    KIFACE_GETTER_FUNC* ki_getter = &KIFACE_GETTER;
 
     int  kiface_version;
 
     // Get the KIFACE.
-    KIFACE* kiface = getter( &kiface_version, KIFACE_VERSION, this );
+    KIFACE* kiface = ki_getter( &kiface_version, KIFACE_VERSION, this );
 
     // Trick the KIWAY into thinking it loaded a KIFACE, by recording the KIFACE
     // in the KIWAY.  It needs to be there for KIWAY::OnKiwayEnd() anyways.
diff --git a/include/eda_base_frame.h b/include/eda_base_frame.h
index 982e4d0269..9cf79dfe3f 100644
--- a/include/eda_base_frame.h
+++ b/include/eda_base_frame.h
@@ -106,6 +106,8 @@ public:
                     const wxPoint& aPos, const wxSize& aSize, long aStyle,
                     const wxString& aFrameName, KIWAY* aKiway );
 
+    EDA_BASE_FRAME( FRAME_T aFrameType, KIWAY* aKiway );
+
     ~EDA_BASE_FRAME();
 
     /**
@@ -650,6 +652,11 @@ private:
      */
     void windowClosing( wxCloseEvent& event );
 
+    /**
+     * Collect common initialization functions used in all CTORs
+     */
+    void commonInit( FRAME_T aFrameType );
+
     wxWindow* findQuasiModalDialog();
 
     /**
diff --git a/include/eda_dde.h b/include/eda_dde.h
index e9a7a2ffda..365dade51e 100644
--- a/include/eda_dde.h
+++ b/include/eda_dde.h
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2007-2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
- * Copyright (C) 1992-2014 KiCad Developers, see CHANGELOG.TXT for contributors.
+ * Copyright (C) 1992-2021 KiCad Developers, see CHANGELOG.TXT for contributors.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -42,6 +42,9 @@
 ///< Eeschema listens on this port for commands from Pcbnew
 #define KICAD_SCH_PORT_SERVICE_NUMBER   4243
 
+///< Scripting window listens for commands for other apps
+#define KICAD_PY_PORT_SERVICE_NUMBER    4244
+
 
 #define MSG_TO_PCB                      KICAD_PCB_PORT_SERVICE_NUMBER
 #define MSG_TO_SCH                      KICAD_SCH_PORT_SERVICE_NUMBER
diff --git a/include/eda_draw_frame.h b/include/eda_draw_frame.h
index d29e19e0fa..4bdbe0c18f 100644
--- a/include/eda_draw_frame.h
+++ b/include/eda_draw_frame.h
@@ -191,14 +191,6 @@ public:
      */
     virtual BASE_SCREEN* GetScreen() const  { return m_currentScreen; }
 
-    /**
-     * Execute a remote command sent via socket (to port KICAD_PCB_PORT_SERVICE_NUMBER,
-     * currently 4242).
-     *
-     * Subclasses should override to implement actual command handlers.
-     */
-    virtual void ExecuteRemoteCommand( const char* cmdline ){}
-
     void EraseMsgBox();
 
     void ReCreateMenuBar() override { }
@@ -346,11 +338,6 @@ public:
      */
     virtual void DisplayGridMsg();
 
-    /* interprocess communication */
-    void CreateServer( int service, bool local = true );
-    void OnSockRequest( wxSocketEvent& evt );
-    void OnSockRequestServer( wxSocketEvent& evt );
-
     void LoadSettings( APP_SETTINGS_BASE* aCfg ) override;
     void SaveSettings( APP_SETTINGS_BASE* aCfg ) override;
 
diff --git a/include/kiway_player.h b/include/kiway_player.h
index bd3b1c3c6c..4b4b04a81a 100644
--- a/include/kiway_player.h
+++ b/include/kiway_player.h
@@ -32,7 +32,6 @@
 #include <kiway_holder.h>
 #include <eda_base_frame.h>
 
-
 class KIWAY;
 class PROJECT;
 struct KIFACE;
@@ -43,6 +42,9 @@ class KIWAY_EXPRESS;
 #define WX_EVENT_LOOP      wxGUIEventLoop
 class WX_EVENT_LOOP;
 
+class wxSocketServer;
+class wxSocketBase;
+
 
 /**
  * A wxFrame capable of the OpenProjectFiles function, meaning it can load a portion of
@@ -179,6 +181,20 @@ public:
 
     void DismissModal( bool aRetVal, const wxString& aResult = wxEmptyString );
 
+    /* interprocess communication */
+    void CreateServer( int service, bool local = true );
+    void OnSockRequest( wxSocketEvent& evt );
+    void OnSockRequestServer( wxSocketEvent& evt );
+
+    /**
+     * Execute a remote command sent via socket (to port KICAD_PCB_PORT_SERVICE_NUMBER,
+     * currently 4242).
+     *
+     * Subclasses should override to implement actual command handlers.
+     */
+    virtual void ExecuteRemoteCommand( const char* cmdline ){}
+
+
 protected:
 
     /// event handler, routes to derivative specific virtual KiwayMailIn()
@@ -198,6 +214,9 @@ protected:
     wxString        m_modal_string;
     bool            m_modal_ret_val;    // true if a selection was made
 
+    wxSocketServer*             m_socketServer;
+    std::vector<wxSocketBase*>  m_sockets;         ///< interprocess communication
+
 #ifndef SWIG
     DECLARE_EVENT_TABLE()
 #endif
diff --git a/include/pgm_base.h b/include/pgm_base.h
index c99af07af7..e016d5c627 100644
--- a/include/pgm_base.h
+++ b/include/pgm_base.h
@@ -38,6 +38,7 @@
 #include <wx/filename.h>
 #include <wx/gdicmn.h>
 
+#include <pybind11/embed.h>
 
 class wxSingleInstanceChecker;
 class wxApp;
@@ -329,6 +330,7 @@ protected:
 
     /// Flag to indicate if the environment variable overwrite warning dialog should be shown.
     bool            m_show_env_var_dialog;
+
 };
 
 
diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp
index 65102a8298..2166e33e21 100644
--- a/pcbnew/pcb_edit_frame.cpp
+++ b/pcbnew/pcb_edit_frame.cpp
@@ -1299,6 +1299,10 @@ void PCB_EDIT_FRAME::ScriptingConsoleEnableDisable()
     {
         frame = Kiway().Player( FRAME_PYTHON, true, this );
 
+        // If we received an error in the CTOR due to Python-ness, don't crash
+        if( !frame )
+            return;
+
         if( !frame->IsVisible() )
             frame->Show( true );
 
diff --git a/scripting/CMakeLists.txt b/scripting/CMakeLists.txt
index d7d12585d9..f1a0576bfa 100644
--- a/scripting/CMakeLists.txt
+++ b/scripting/CMakeLists.txt
@@ -7,8 +7,6 @@ add_library( scripting STATIC
     ${KIPYTHON_SRCS}
 )
 
-add_subdirectory( pybind11 )
-    
 target_link_libraries( scripting
     ${wxWidgets_LIBRARIES}      # wxLogDebug, wxASSERT
     ${Boost_LIBRARIES}          # Because of the OPT types
@@ -32,6 +30,7 @@ target_include_directories( scripting PRIVATE
 # Setup the KIFACE
 add_library( scripting_kiface MODULE
     kicad_scripting_main.cpp
+    kipython_frame.cpp
     ${KIPYTHON_SRCS}
     )
 
@@ -39,7 +38,7 @@ set_source_files_properties( kicad_scripting_main.cpp PROPERTIES
     # The KIFACE is in kicad_scripting_main.cpp, export it:
     COMPILE_DEFINITIONS     "BUILD_KIWAY_DLL;COMPILING_DLL"
     )
-    
+
 target_include_directories( scripting_kiface PRIVATE
     ${PROJECT_SOURCE_DIR}/bitmaps_png/include
     ${PROJECT_SOURCE_DIR}/include
diff --git a/scripting/kicad_pyshell/__init__.py b/scripting/kicad_pyshell/__init__.py
index 62831e1f76..813c02edb7 100644
--- a/scripting/kicad_pyshell/__init__.py
+++ b/scripting/kicad_pyshell/__init__.py
@@ -15,17 +15,16 @@ import wx
 import sys
 import os
 
-from wx.py import crust, editor, version, dispatcher
-from wx.py.editor import EditorNotebook
+from wx.py import crust, version, dispatcher
+
+from .kicad_pyeditor import KiCadEditorNotebookFrame, KiCadEditorNotebook
 
 import pcbnew
 
-INTRO = "KiCad - Python Shell"
-
-
-class KiCadPyShell(editor.EditorNotebookFrame):
-
-    """The KiCad Pythonshell based on wxPyShell"""
+class KiCadPyShell(KiCadEditorNotebookFrame):
+   
+    def __init__(self, parent):
+        KiCadEditorNotebookFrame.__init__(self, parent)
 
     def _setup_startup(self):
         """Initialise the startup script."""
@@ -58,7 +57,7 @@ class KiCadPyShell(editor.EditorNotebookFrame):
 
         Called automatically by base class during init.
         """
-        self.notebook = EditorNotebook(parent=self)
+        self.notebook = KiCadEditorNotebook(parent=self.parent)
         intro = 'Py %s' % version.VERSION
         import imp
         import builtins
@@ -81,11 +80,6 @@ class KiCadPyShell(editor.EditorNotebookFrame):
         self.autoSaveHistory = False
         self.LoadSettings()
 
-        # in case of wxPhoenix we may need to create a wxApp first and store it
-        # to prevent removal by garbage collector
-        if 'phoenix' in wx.PlatformInfo:
-            self.theApp = wx.GetApp() or wx.App()
-
         self.crust = crust.Crust(parent=self.notebook,
                                  intro=intro, locals=namespace,
                                  rootLabel="locals()",
@@ -94,9 +88,9 @@ class KiCadPyShell(editor.EditorNotebookFrame):
 
         self.shell = self.crust.shell
         # Override the filling so that status messages go to the status bar.
-        self.crust.filling.tree.setStatusText = self.SetStatusText
+        self.crust.filling.tree.setStatusText = self.parent.SetStatusText
         # Override the shell so that status messages go to the status bar.
-        self.shell.setStatusText = self.SetStatusText
+        self.shell.setStatusText = self.parent.SetStatusText
         # Fix a problem with the sash shrinking to nothing.
         self.crust.filling.SetSashPosition(200)
         self.notebook.AddPage(page=self.crust, text='*Shell*', select=True)
@@ -115,7 +109,7 @@ class KiCadPyShell(editor.EditorNotebookFrame):
                "wxPython Version: %s\n" % wx.VERSION_STRING + \
                ("\t(%s)\n" % ", ".join(wx.PlatformInfo[1:]))
 
-        dialog = wx.MessageDialog(self, text, title,
+        dialog = wx.MessageDialog(self.parent, text, title,
                                   wx.OK | wx.ICON_INFORMATION)
         dialog.ShowModal()
         dialog.Destroy()
@@ -127,7 +121,7 @@ class KiCadPyShell(editor.EditorNotebookFrame):
     def LoadSettings(self):
         """Load settings for the shell."""
         if self.config is not None:
-            editor.EditorNotebookFrame.LoadSettings(self, self.config)
+            KiCadEditorNotebookFrame.LoadSettings(self.parent, self.config)
             self.autoSaveSettings = \
                 self.config.ReadBool('Options/AutoSaveSettings', False)
             self.execStartupScript = \
@@ -150,7 +144,7 @@ class KiCadPyShell(editor.EditorNotebookFrame):
             self.config.WriteBool('Options/AutoSaveSettings',
                                   self.autoSaveSettings)
             if self.autoSaveSettings or force:
-                editor.EditorNotebookFrame.SaveSettings(self, self.config)
+                KiCadEditorNotebookFrame.SaveSettings(self, self.config)
 
                 self.config.WriteBool('Options/AutoSaveHistory',
                                       self.autoSaveHistory)
@@ -209,7 +203,7 @@ class KiCadPyShell(editor.EditorNotebookFrame):
                     d.Destroy()
 
 
-def makePcbnewShellWindow(parent=None):
+def makePcbnewShellWindow(parentid):
     """
     Create a new Shell Window and return its handle.
 
@@ -219,6 +213,6 @@ def makePcbnewShellWindow(parent=None):
     Returns:
     The handle to the new window.
     """
-    pyshell = KiCadPyShell(parent, id=-1, title=INTRO)
-    pyshell.Show()
-    return pyshell
+    
+    parent = wx.FindWindowById( parentid )
+    return KiCadPyShell(parent)
diff --git a/scripting/kicad_pyshell/kicad_pyeditor.py b/scripting/kicad_pyshell/kicad_pyeditor.py
new file mode 100644
index 0000000000..d61c02a82e
--- /dev/null
+++ b/scripting/kicad_pyshell/kicad_pyeditor.py
@@ -0,0 +1,1059 @@
+'''
+Provides the backend for a basic python editor in KiCad.
+
+This takes most code from PyShell/PyCrust but adapts it to the KiCad
+environment where the Python doesn't create a frame but instead hooks 
+into the existing KIWAY_PLAYER
+
+Original PyCrust code used from 
+https://github.com/wxWidgets/Phoenix/tree/master/wx/py
+'''
+
+import wx
+
+from wx.py import crust, version, dispatcher
+from wx.py.editor import Editor
+from wx.py.buffer import Buffer
+
+ID_NEW = wx.ID_NEW
+ID_OPEN = wx.ID_OPEN
+ID_REVERT = wx.ID_REVERT
+ID_CLOSE = wx.ID_CLOSE
+ID_SAVE = wx.ID_SAVE
+ID_SAVEAS = wx.ID_SAVEAS
+ID_PRINT = wx.ID_PRINT
+ID_EXIT = wx.ID_EXIT
+ID_UNDO = wx.ID_UNDO
+ID_REDO = wx.ID_REDO
+ID_CUT = wx.ID_CUT
+ID_COPY = wx.ID_COPY
+ID_PASTE = wx.ID_PASTE
+ID_CLEAR = wx.ID_CLEAR
+ID_SELECTALL = wx.ID_SELECTALL
+ID_EMPTYBUFFER = wx.NewIdRef()
+ID_ABOUT = wx.ID_ABOUT
+ID_HELP = wx.NewIdRef()
+ID_AUTOCOMP_SHOW = wx.NewIdRef()
+ID_AUTOCOMP_MAGIC = wx.NewIdRef()
+ID_AUTOCOMP_SINGLE = wx.NewIdRef()
+ID_AUTOCOMP_DOUBLE = wx.NewIdRef()
+ID_CALLTIPS_SHOW = wx.NewIdRef()
+ID_CALLTIPS_INSERT = wx.NewIdRef()
+ID_COPY_PLUS = wx.NewIdRef()
+ID_NAMESPACE = wx.NewIdRef()
+ID_PASTE_PLUS = wx.NewIdRef()
+ID_WRAP = wx.NewIdRef()
+ID_TOGGLE_MAXIMIZE = wx.NewIdRef()
+ID_SHOW_LINENUMBERS = wx.NewIdRef()
+ID_ENABLESHELLMODE = wx.NewIdRef()
+ID_ENABLEAUTOSYMPY = wx.NewIdRef()
+ID_AUTO_SAVESETTINGS = wx.NewIdRef()
+ID_SAVEACOPY = wx.NewIdRef()
+ID_SAVEHISTORY = wx.NewIdRef()
+ID_SAVEHISTORYNOW = wx.NewIdRef()
+ID_CLEARHISTORY = wx.NewIdRef()
+ID_SAVESETTINGS = wx.NewIdRef()
+ID_DELSETTINGSFILE = wx.NewIdRef()
+ID_EDITSTARTUPSCRIPT = wx.NewIdRef()
+ID_EXECSTARTUPSCRIPT = wx.NewIdRef()
+ID_SHOWPYSLICESTUTORIAL = wx.NewIdRef()
+ID_FIND = wx.ID_FIND
+ID_FINDNEXT = wx.NewIdRef()
+ID_FINDPREVIOUS = wx.NewIdRef()
+ID_SHOWTOOLS = wx.NewIdRef()
+ID_HIDEFOLDINGMARGIN = wx.NewIdRef()
+
+import pcbnew
+
+INTRO = "KiCad - Python Shell"
+
+class KiCadPyFrame():
+    
+    def __init__(self, parent):
+        """Create a Frame instance."""
+        
+        self.parent = parent
+        self.parent.CreateStatusBar()
+        self.parent.SetStatusText('Frame')
+        self.shellName='KiCad Python Editor'
+        self.__createMenus()
+
+        self.iconized = False
+        self.findDlg = None
+        self.findData = wx.FindReplaceData()
+        self.findData.SetFlags(wx.FR_DOWN)
+        
+        self.parent.Bind(wx.EVT_CLOSE, self.OnClose)
+        self.parent.Bind(wx.EVT_ICONIZE, self.OnIconize)
+
+
+    def OnIconize(self, event):
+        """Event handler for Iconize."""
+        self.iconized = event.Iconized()
+
+
+    def OnClose(self, event):
+        """Event handler for closing."""
+        self.parent.Destroy()
+
+
+    def __createMenus(self):
+        # File Menu
+        m = self.fileMenu = wx.Menu()
+        m.Append(ID_NEW, '&New \tCtrl+N',
+                 'New file')
+        m.Append(ID_OPEN, '&Open... \tCtrl+O',
+                 'Open file')
+        m.AppendSeparator()
+        m.Append(ID_REVERT, '&Revert \tCtrl+R',
+                 'Revert to last saved version')
+        m.Append(ID_CLOSE, '&Close \tCtrl+W',
+                 'Close file')
+        m.AppendSeparator()
+        m.Append(ID_SAVE, '&Save... \tCtrl+S',
+                 'Save file')
+        m.Append(ID_SAVEAS, 'Save &As \tCtrl+Shift+S',
+                 'Save file with new name')
+        m.AppendSeparator()
+        m.Append(ID_PRINT, '&Print... \tCtrl+P',
+                 'Print file')
+        m.AppendSeparator()
+        m.Append(ID_NAMESPACE, '&Update Namespace \tCtrl+Shift+N',
+                 'Update namespace for autocompletion and calltips')
+        m.AppendSeparator()
+        m.Append(ID_EXIT, 'E&xit\tCtrl+Q', 'Exit Program')
+
+        # Edit
+        m = self.editMenu = wx.Menu()
+        m.Append(ID_UNDO, '&Undo \tCtrl+Z',
+                 'Undo the last action')
+        m.Append(ID_REDO, '&Redo \tCtrl+Y',
+                 'Redo the last undone action')
+        m.AppendSeparator()
+        m.Append(ID_CUT, 'Cu&t \tCtrl+X',
+                 'Cut the selection')
+        m.Append(ID_COPY, '&Copy \tCtrl+C',
+                 'Copy the selection')
+        m.Append(ID_COPY_PLUS, 'Cop&y Plus \tCtrl+Shift+C',
+                 'Copy the selection - retaining prompts')
+        m.Append(ID_PASTE, '&Paste \tCtrl+V', 'Paste from clipboard')
+        m.Append(ID_PASTE_PLUS, 'Past&e Plus \tCtrl+Shift+V',
+                 'Paste and run commands')
+        m.AppendSeparator()
+        m.Append(ID_CLEAR, 'Cle&ar',
+                 'Delete the selection')
+        m.Append(ID_SELECTALL, 'Select A&ll \tCtrl+A',
+                 'Select all text')
+        m.AppendSeparator()
+        m.Append(ID_EMPTYBUFFER, 'E&mpty Buffer...',
+                 'Delete all the contents of the edit buffer')
+        m.Append(ID_FIND, '&Find Text... \tCtrl+F',
+                 'Search for text in the edit buffer')
+        m.Append(ID_FINDNEXT, 'Find &Next \tCtrl+G',
+                 'Find next instance of the search text')
+        m.Append(ID_FINDPREVIOUS, 'Find Pre&vious \tCtrl+Shift+G',
+                 'Find previous instance of the search text')
+
+        # View
+        m = self.viewMenu = wx.Menu()
+        m.Append(ID_WRAP, '&Wrap Lines\tCtrl+Shift+W',
+                 'Wrap lines at right edge', wx.ITEM_CHECK)
+        m.Append(ID_SHOW_LINENUMBERS, '&Show Line Numbers\tCtrl+Shift+L',
+                 'Show Line Numbers', wx.ITEM_CHECK)
+        m.Append(ID_TOGGLE_MAXIMIZE, '&Toggle Maximize\tF11',
+                 'Maximize/Restore Application')
+
+        # Options
+        m = self.optionsMenu = wx.Menu()
+        
+        self.historyMenu = wx.Menu()
+        self.historyMenu.Append(ID_SAVEHISTORY, '&Autosave History',
+                 'Automatically save history on close', wx.ITEM_CHECK)
+        self.historyMenu.Append(ID_SAVEHISTORYNOW, '&Save History Now',
+                 'Save history')
+        self.historyMenu.Append(ID_CLEARHISTORY, '&Clear History ',
+                 'Clear history')
+        m.AppendSubMenu(self.historyMenu, "&History", "History Options")
+
+        self.startupMenu = wx.Menu()
+        self.startupMenu.Append(ID_EXECSTARTUPSCRIPT,
+                                'E&xecute Startup Script',
+                                'Execute Startup Script', wx.ITEM_CHECK)
+        self.startupMenu.Append(ID_EDITSTARTUPSCRIPT,
+                                '&Edit Startup Script...',
+                                'Edit Startup Script')
+        m.AppendSubMenu(self.startupMenu, '&Startup', 'Startup Options')
+
+        self.settingsMenu = wx.Menu()
+        self.settingsMenu.Append(ID_AUTO_SAVESETTINGS,
+                                 '&Auto Save Settings',
+                                 'Automatically save settings on close', wx.ITEM_CHECK)
+        self.settingsMenu.Append(ID_SAVESETTINGS,
+                                 '&Save Settings',
+                                 'Save settings now')
+        self.settingsMenu.Append(ID_DELSETTINGSFILE,
+                                 '&Revert to default',
+                                 'Revert to the default settings')
+        m.AppendSubMenu(self.settingsMenu, '&Settings', 'Settings Options')
+
+        m = self.helpMenu = wx.Menu()
+        m.Append(ID_HELP, '&Help\tF1', 'Help!')
+        m.AppendSeparator()
+        m.Append(ID_ABOUT, '&About...', 'About this program')
+
+        b = self.menuBar = wx.MenuBar()
+        b.Append(self.fileMenu, '&File')
+        b.Append(self.editMenu, '&Edit')
+        b.Append(self.viewMenu, '&View')
+        b.Append(self.optionsMenu, '&Options')
+        b.Append(self.helpMenu, '&Help')
+        self.parent.SetMenuBar(b)
+
+        self.parent.Bind(wx.EVT_MENU, self.OnFileNew, id=ID_NEW)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileOpen, id=ID_OPEN)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileRevert, id=ID_REVERT)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileClose, id=ID_CLOSE)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileSave, id=ID_SAVE)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileSaveAs, id=ID_SAVEAS)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileSaveACopy, id=ID_SAVEACOPY)
+        self.parent.Bind(wx.EVT_MENU, self.OnFileUpdateNamespace, id=ID_NAMESPACE)
+        self.parent.Bind(wx.EVT_MENU, self.OnFilePrint, id=ID_PRINT)
+        self.parent.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)
+        self.parent.Bind(wx.EVT_MENU, self.OnUndo, id=ID_UNDO)
+        self.parent.Bind(wx.EVT_MENU, self.OnRedo, id=ID_REDO)
+        self.parent.Bind(wx.EVT_MENU, self.OnCut, id=ID_CUT)
+        self.parent.Bind(wx.EVT_MENU, self.OnCopy, id=ID_COPY)
+        self.parent.Bind(wx.EVT_MENU, self.OnCopyPlus, id=ID_COPY_PLUS)
+        self.parent.Bind(wx.EVT_MENU, self.OnPaste, id=ID_PASTE)
+        self.parent.Bind(wx.EVT_MENU, self.OnPastePlus, id=ID_PASTE_PLUS)
+        self.parent.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR)
+        self.parent.Bind(wx.EVT_MENU, self.OnSelectAll, id=ID_SELECTALL)
+        self.parent.Bind(wx.EVT_MENU, self.OnEmptyBuffer, id=ID_EMPTYBUFFER)
+        self.parent.Bind(wx.EVT_MENU, self.OnAbout, id=ID_ABOUT)
+        self.parent.Bind(wx.EVT_MENU, self.OnHelp, id=ID_HELP)
+        self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteShow, id=ID_AUTOCOMP_SHOW)
+        self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteMagic, id=ID_AUTOCOMP_MAGIC)
+        self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteSingle, id=ID_AUTOCOMP_SINGLE)
+        self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteDouble, id=ID_AUTOCOMP_DOUBLE)
+        self.parent.Bind(wx.EVT_MENU, self.OnCallTipsShow, id=ID_CALLTIPS_SHOW)
+        self.parent.Bind(wx.EVT_MENU, self.OnCallTipsInsert, id=ID_CALLTIPS_INSERT)
+        self.parent.Bind(wx.EVT_MENU, self.OnWrap, id=ID_WRAP)
+        self.parent.Bind(wx.EVT_MENU, self.OnToggleMaximize, id=ID_TOGGLE_MAXIMIZE)
+        self.parent.Bind(wx.EVT_MENU, self.OnShowLineNumbers, id=ID_SHOW_LINENUMBERS)
+        self.parent.Bind(wx.EVT_MENU, self.OnEnableShellMode, id=ID_ENABLESHELLMODE)
+        self.parent.Bind(wx.EVT_MENU, self.OnEnableAutoSympy, id=ID_ENABLEAUTOSYMPY)
+        self.parent.Bind(wx.EVT_MENU, self.OnAutoSaveSettings, id=ID_AUTO_SAVESETTINGS)
+        self.parent.Bind(wx.EVT_MENU, self.OnSaveHistory, id=ID_SAVEHISTORY)
+        self.parent.Bind(wx.EVT_MENU, self.OnSaveHistoryNow, id=ID_SAVEHISTORYNOW)
+        self.parent.Bind(wx.EVT_MENU, self.OnClearHistory, id=ID_CLEARHISTORY)
+        self.parent.Bind(wx.EVT_MENU, self.OnSaveSettings, id=ID_SAVESETTINGS)
+        self.parent.Bind(wx.EVT_MENU, self.OnDelSettingsFile, id=ID_DELSETTINGSFILE)
+        self.parent.Bind(wx.EVT_MENU, self.OnEditStartupScript, id=ID_EDITSTARTUPSCRIPT)
+        self.parent.Bind(wx.EVT_MENU, self.OnExecStartupScript, id=ID_EXECSTARTUPSCRIPT)
+        self.parent.Bind(wx.EVT_MENU, self.OnShowPySlicesTutorial, id=ID_SHOWPYSLICESTUTORIAL)
+        self.parent.Bind(wx.EVT_MENU, self.OnFindText, id=ID_FIND)
+        self.parent.Bind(wx.EVT_MENU, self.OnFindNext, id=ID_FINDNEXT)
+        self.parent.Bind(wx.EVT_MENU, self.OnFindPrevious, id=ID_FINDPREVIOUS)
+        self.parent.Bind(wx.EVT_MENU, self.OnToggleTools, id=ID_SHOWTOOLS)
+        self.parent.Bind(wx.EVT_MENU, self.OnHideFoldingMargin, id=ID_HIDEFOLDINGMARGIN)
+        
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_NEW)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_OPEN)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_REVERT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLOSE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEAS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_NAMESPACE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PRINT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_UNDO)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_REDO)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CUT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_COPY)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_COPY_PLUS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PASTE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PASTE_PLUS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLEAR)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SELECTALL)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EMPTYBUFFER)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_SHOW)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_MAGIC)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_SINGLE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_DOUBLE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CALLTIPS_SHOW)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CALLTIPS_INSERT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_WRAP)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOW_LINENUMBERS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_ENABLESHELLMODE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_ENABLEAUTOSYMPY)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTO_SAVESETTINGS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVESETTINGS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_DELSETTINGSFILE)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EXECSTARTUPSCRIPT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOWPYSLICESTUTORIAL)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEHISTORY)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEHISTORYNOW)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLEARHISTORY)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EDITSTARTUPSCRIPT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FIND)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FINDNEXT)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FINDPREVIOUS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOWTOOLS)
+        self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_HIDEFOLDINGMARGIN)
+        
+        self.parent.Bind(wx.EVT_ACTIVATE, self.OnActivate)
+        self.parent.Bind(wx.EVT_FIND, self.OnFindNext)
+        self.parent.Bind(wx.EVT_FIND_NEXT, self.OnFindNext)
+        self.parent.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
+        
+    def OnShowLineNumbers(self, event):
+        win = wx.Window.FindFocus()
+        if hasattr(win, 'lineNumbers'):
+            win.lineNumbers = event.IsChecked()
+            win.setDisplayLineNumbers(win.lineNumbers)
+
+    def OnFileNew(self, event):
+        self.bufferNew()
+
+    def OnFileOpen(self, event):
+        self.bufferOpen()
+
+    def OnFileRevert(self, event):
+        self.bufferRevert()
+
+    def OnFileClose(self, event):
+        self.bufferClose()
+
+    def OnFileSave(self, event):
+        self.bufferSave()
+
+    def OnFileSaveAs(self, event):
+        self.bufferSaveAs()
+
+    def OnFileSaveACopy(self, event):
+        self.bufferSaveACopy()
+
+    def OnFileUpdateNamespace(self, event):
+        self.updateNamespace()
+
+    def OnFilePrint(self, event):
+        self.bufferPrint()
+
+    def OnExit(self, event):
+        self.parent.Close(False)
+
+    def OnUndo(self, event):
+        win = wx.Window.FindFocus()
+        win.Undo()
+
+    def OnRedo(self, event):
+        win = wx.Window.FindFocus()
+        win.Redo()
+
+    def OnCut(self, event):
+        win = wx.Window.FindFocus()
+        win.Cut()
+
+    def OnCopy(self, event):
+        win = wx.Window.FindFocus()
+        win.Copy()
+
+    def OnCopyPlus(self, event):
+        win = wx.Window.FindFocus()
+        win.CopyWithPrompts()
+
+    def OnPaste(self, event):
+        win = wx.Window.FindFocus()
+        win.Paste()
+
+    def OnPastePlus(self, event):
+        win = wx.Window.FindFocus()
+        win.PasteAndRun()
+
+    def OnClear(self, event):
+        win = wx.Window.FindFocus()
+        win.Clear()
+
+    def OnEmptyBuffer(self, event):
+        win = wx.Window.FindFocus()
+        d = wx.MessageDialog(self,
+                             "Are you sure you want to clear the edit buffer,\n"
+                             "deleting all the text?",
+                             "Empty Buffer", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
+        answer = d.ShowModal()
+        d.Destroy()
+        if (answer == wx.ID_OK):
+            win.ClearAll()
+            if hasattr(win,'prompt'):
+                win.prompt()
+
+    def OnSelectAll(self, event):
+        win = wx.Window.FindFocus()
+        win.SelectAll()
+
+    def OnAbout(self, event):
+        """Display an About window."""
+        title = 'About'
+        text = 'Your message here.'
+        dialog = wx.MessageDialog(self.parent, text, title,
+                                  wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+    def OnHelp(self, event):
+        """Display a Help window."""
+        title = 'Help'
+        text = "Type 'shell.help()' in the shell window."
+        dialog = wx.MessageDialog(self.parent, text, title,
+                                  wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+    def OnAutoCompleteShow(self, event):
+        win = wx.Window.FindFocus()
+        win.autoComplete = event.IsChecked()
+
+    def OnAutoCompleteMagic(self, event):
+        win = wx.Window.FindFocus()
+        win.autoCompleteIncludeMagic = event.IsChecked()
+
+    def OnAutoCompleteSingle(self, event):
+        win = wx.Window.FindFocus()
+        win.autoCompleteIncludeSingle = event.IsChecked()
+
+    def OnAutoCompleteDouble(self, event):
+        win = wx.Window.FindFocus()
+        win.autoCompleteIncludeDouble = event.IsChecked()
+
+    def OnCallTipsShow(self, event):
+        win = wx.Window.FindFocus()
+        win.autoCallTip = event.IsChecked()
+
+    def OnCallTipsInsert(self, event):
+        win = wx.Window.FindFocus()
+        win.callTipInsert = event.IsChecked()
+
+    def OnWrap(self, event):
+        win = wx.Window.FindFocus()
+        win.SetWrapMode(event.IsChecked())
+        wx.CallLater(1, self.shell.EnsureCaretVisible)
+
+    def OnToggleMaximize(self, event):
+        self.parent.Maximize(not self.parent.IsMaximized())
+
+    def OnSaveHistory(self, event):
+        self.autoSaveHistory = event.IsChecked()
+
+    def OnSaveHistoryNow(self, event):
+        self.SaveHistory()
+
+    def OnClearHistory(self, event):
+        self.shell.clearHistory()
+
+    def OnEnableShellMode(self, event):
+        self.enableShellMode = event.IsChecked()
+
+    def OnEnableAutoSympy(self, event):
+        self.enableAutoSympy = event.IsChecked()
+
+    def OnHideFoldingMargin(self, event):
+        self.hideFoldingMargin = event.IsChecked()
+
+    def OnAutoSaveSettings(self, event):
+        self.autoSaveSettings = event.IsChecked()
+
+    def OnSaveSettings(self, event):
+        self.DoSaveSettings()
+
+    def OnDelSettingsFile(self, event):
+        if self.config is not None:
+            d = wx.MessageDialog(
+                self.parent, "Do you want to revert to the default settings?\n" +
+                "A restart is needed for the change to take effect",
+                "Warning", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
+            answer = d.ShowModal()
+            d.Destroy()
+            if (answer == wx.ID_OK):
+                self.config.DeleteAll()
+                self.LoadSettings()
+
+
+    def OnEditStartupScript(self, event):
+        if hasattr(self, 'EditStartupScript'):
+            self.EditStartupScript()
+
+    def OnExecStartupScript(self, event):
+        self.execStartupScript = event.IsChecked()
+        self.SaveSettings(force=True)
+
+    def OnShowPySlicesTutorial(self,event):
+        self.showPySlicesTutorial = event.IsChecked()
+        self.SaveSettings(force=True)
+
+    def OnFindText(self, event):
+        if self.findDlg is not None:
+            return
+        win = wx.Window.FindFocus()
+        if self.shellName == 'PyCrust':
+            self.findDlg = wx.FindReplaceDialog(win, self.findData,
+                                               "Find",wx.FR_NOWHOLEWORD)
+        else:
+            self.findDlg = wx.FindReplaceDialog(win, self.findData,
+                "Find & Replace", wx.FR_NOWHOLEWORD|wx.FR_REPLACEDIALOG)
+        self.findDlg.Show()
+
+    def OnFindNext(self, event,backward=False):
+        if backward and (self.findData.GetFlags() & wx.FR_DOWN):
+            self.findData.SetFlags( self.findData.GetFlags() ^ wx.FR_DOWN )
+        elif not backward and not (self.findData.GetFlags() & wx.FR_DOWN):
+            self.findData.SetFlags( self.findData.GetFlags() ^ wx.FR_DOWN )
+
+        if not self.findData.GetFindString():
+            self.OnFindText(event)
+            return
+        if isinstance(event, wx.FindDialogEvent):
+            win = self.findDlg.GetParent()
+        else:
+            win = wx.Window.FindFocus()
+        win.DoFindNext(self.findData, self.findDlg)
+        if self.findDlg is not None:
+            self.OnFindClose(None)
+
+    def OnFindPrevious(self, event):
+        self.OnFindNext(event,backward=True)
+
+    def OnFindClose(self, event):
+        self.findDlg.Destroy()
+        self.findDlg = None
+
+    def OnToggleTools(self, event):
+        self.ToggleTools()
+
+
+    def OnUpdateMenu(self, event):
+        """Update menu items based on current status and context."""
+        win = wx.Window.FindFocus()
+        id = event.GetId()
+        event.Enable(True)
+        try:
+            if id == ID_NEW:
+                event.Enable(hasattr(self, 'bufferNew'))
+            elif id == ID_OPEN:
+                event.Enable(hasattr(self, 'bufferOpen'))
+            elif id == ID_REVERT:
+                event.Enable(hasattr(self, 'bufferRevert')
+                             and self.hasBuffer())
+            elif id == ID_CLOSE:
+                event.Enable(hasattr(self, 'bufferClose')
+                             and self.hasBuffer())
+            elif id == ID_SAVE:
+                event.Enable(hasattr(self, 'bufferSave')
+                             and self.bufferHasChanged())
+            elif id == ID_SAVEAS:
+                event.Enable(hasattr(self, 'bufferSaveAs')
+                             and self.hasBuffer())
+            elif id == ID_SAVEACOPY:
+                event.Enable(hasattr(self, 'bufferSaveACopy')
+                             and self.hasBuffer())
+            elif id == ID_NAMESPACE:
+                event.Enable(hasattr(self, 'updateNamespace')
+                             and self.hasBuffer())
+            elif id == ID_PRINT:
+                event.Enable(hasattr(self, 'bufferPrint')
+                             and self.hasBuffer())
+            elif id == ID_UNDO:
+                event.Enable(win.CanUndo())
+            elif id == ID_REDO:
+                event.Enable(win.CanRedo())
+            elif id == ID_CUT:
+                event.Enable(win.CanCut())
+            elif id == ID_COPY:
+                event.Enable(win.CanCopy())
+            elif id == ID_COPY_PLUS:
+                event.Enable(win.CanCopy() and hasattr(win, 'CopyWithPrompts'))
+            elif id == ID_PASTE:
+                event.Enable(win.CanPaste())
+            elif id == ID_PASTE_PLUS:
+                event.Enable(win.CanPaste() and hasattr(win, 'PasteAndRun'))
+            elif id == ID_CLEAR:
+                event.Enable(win.CanCut())
+            elif id == ID_SELECTALL:
+                event.Enable(hasattr(win, 'SelectAll'))
+            elif id == ID_EMPTYBUFFER:
+                event.Enable(hasattr(win, 'ClearAll') and not win.GetReadOnly())
+            elif id == ID_AUTOCOMP_SHOW:
+                event.Check(win.autoComplete)
+            elif id == ID_AUTOCOMP_MAGIC:
+                event.Check(win.autoCompleteIncludeMagic)
+            elif id == ID_AUTOCOMP_SINGLE:
+                event.Check(win.autoCompleteIncludeSingle)
+            elif id == ID_AUTOCOMP_DOUBLE:
+                event.Check(win.autoCompleteIncludeDouble)
+            elif id == ID_CALLTIPS_SHOW:
+                event.Check(win.autoCallTip)
+            elif id == ID_CALLTIPS_INSERT:
+                event.Check(win.callTipInsert)
+            elif id == ID_WRAP:
+                event.Check(win.GetWrapMode())
+
+            elif id == ID_SHOW_LINENUMBERS:
+                event.Check(win.lineNumbers)
+            elif id == ID_ENABLESHELLMODE:
+                event.Check(self.enableShellMode)
+                event.Enable(self.config is not None)
+            elif id == ID_ENABLEAUTOSYMPY:
+                event.Check(self.enableAutoSympy)
+                event.Enable(self.config is not None)
+            elif id == ID_AUTO_SAVESETTINGS:
+                event.Check(self.autoSaveSettings)
+                event.Enable(self.config is not None)
+            elif id == ID_SAVESETTINGS:
+                event.Enable(self.config is not None and
+                             hasattr(self, 'DoSaveSettings'))
+            elif id == ID_DELSETTINGSFILE:
+                event.Enable(self.config is not None)
+
+            elif id == ID_EXECSTARTUPSCRIPT:
+                event.Check(self.execStartupScript)
+                event.Enable(self.config is not None)
+
+            elif id == ID_SAVEHISTORY:
+                event.Check(self.autoSaveHistory)
+                event.Enable(self.dataDir is not None)
+            elif id == ID_SAVEHISTORYNOW:
+                event.Enable(self.dataDir is not None and
+                             hasattr(self, 'SaveHistory'))
+            elif id == ID_CLEARHISTORY:
+                event.Enable(self.dataDir is not None)
+
+            elif id == ID_EDITSTARTUPSCRIPT:
+                event.Enable(hasattr(self, 'EditStartupScript'))
+                event.Enable(self.dataDir is not None)
+
+            elif id == ID_FIND:
+                event.Enable(hasattr(win, 'DoFindNext'))
+            elif id == ID_FINDNEXT:
+                event.Enable(hasattr(win, 'DoFindNext'))
+            elif id == ID_FINDPREVIOUS:
+                event.Enable(hasattr(win, 'DoFindNext'))
+
+            elif id == ID_SHOWTOOLS:
+                event.Check(self.ToolsShown())
+
+            elif id == ID_HIDEFOLDINGMARGIN:
+                event.Check(self.hideFoldingMargin)
+                event.Enable(self.config is not None)
+
+            else:
+                event.Enable(False)
+        except AttributeError:
+            # This menu option is not supported in the current context.
+            event.Enable(False)
+
+
+    def OnActivate(self, event):
+        """
+        Event Handler for losing the focus of the Frame. Should close
+        Autocomplete listbox, if shown.
+        """
+        if not event.GetActive():
+            # If autocomplete active, cancel it.  Otherwise, the
+            # autocomplete list will stay visible on top of the
+            # z-order after switching to another application
+            win = wx.Window.FindFocus()
+            if hasattr(win, 'AutoCompActive') and win.AutoCompActive():
+                win.AutoCompCancel()
+        event.Skip()
+
+
+
+    def LoadSettings(self, config):
+        """Called by derived classes to load settings specific to the Frame"""
+        pos  = wx.Point(config.ReadInt('Window/PosX', -1),
+                        config.ReadInt('Window/PosY', -1))
+
+        size = wx.Size(config.ReadInt('Window/Width', -1),
+                       config.ReadInt('Window/Height', -1))
+
+        self.SetSize(size)
+        self.Move(pos)
+
+
+    def SaveSettings(self, config):
+        """Called by derived classes to save Frame settings to a wx.Config object"""
+
+        # TODO: track position/size so we can save it even if the
+        # frame is maximized or iconized.
+        if not self.iconized and not self.IsMaximized():
+            w, h = self.GetSize()
+            config.WriteInt('Window/Width', w)
+            config.WriteInt('Window/Height', h)
+
+            px, py = self.GetPosition()
+            config.WriteInt('Window/PosX', px)
+            config.WriteInt('Window/PosY', py)
+
+class KiCadEditorFrame(KiCadPyFrame):
+    def __init__(self, parent=None, id=-1, title='KiCad Python'):
+        
+        """Create EditorFrame instance."""
+        KiCadPyFrame.__init__(self, parent)
+        self.buffers = {}
+        self.buffer = None  # Current buffer.
+        self.editor = None
+        self._defaultText = title
+        self._statusText = self._defaultText
+        self.parent.SetStatusText(self._statusText)
+        self.parent.Bind(wx.EVT_IDLE, self.OnIdle)
+        self._setup()
+
+    def _setup(self):
+        """Setup prior to first buffer creation.
+
+        Useful for subclasses."""
+        pass
+
+    def setEditor(self, editor):
+        self.editor = editor
+        self.buffer = self.editor.buffer
+        self.buffers[self.buffer.id] = self.buffer
+
+    def OnClose(self, event):
+        """Event handler for closing."""
+        for buffer in self.buffers.values():
+            self.buffer = buffer
+            if buffer.hasChanged():
+                cancel = self.bufferSuggestSave()
+                if cancel and event.CanVeto():
+                    event.Veto()
+                    return
+        self.parent.Destroy()
+
+    def OnIdle(self, event):
+        """Event handler for idle time."""
+        self._updateStatus()
+        if hasattr(self, 'notebook'):
+            self._updateTabText()
+        self._updateTitle()
+        event.Skip()
+
+    def _updateStatus(self):
+        """Show current status information."""
+        if self.editor and hasattr(self.editor, 'getStatus'):
+            status = self.editor.getStatus()
+            text = 'File: %s  |  Line: %d  |  Column: %d' % status
+        else:
+            text = self._defaultText
+        if text != self._statusText:
+            self.parent.SetStatusText(text)
+            self._statusText = text
+
+    def _updateTabText(self):
+        """Show current buffer information on notebook tab."""
+##         suffix = ' **'
+##         notebook = self.notebook
+##         selection = notebook.GetSelection()
+##         if selection == -1:
+##             return
+##         text = notebook.GetPageText(selection)
+##         window = notebook.GetPage(selection)
+##         if window.editor and window.editor.buffer.hasChanged():
+##             if text.endswith(suffix):
+##                 pass
+##             else:
+##                 notebook.SetPageText(selection, text + suffix)
+##         else:
+##             if text.endswith(suffix):
+##                 notebook.SetPageText(selection, text[:len(suffix)])
+
+    def _updateTitle(self):
+        """Show current title information."""
+        title = self.GetTitle()
+        if self.bufferHasChanged():
+            if title.startswith('* '):
+                pass
+            else:
+                self.SetTitle('* ' + title)
+        else:
+            if title.startswith('* '):
+                self.SetTitle(title[2:])
+
+    def hasBuffer(self):
+        """Return True if there is a current buffer."""
+        if self.buffer:
+            return True
+        else:
+            return False
+
+    def bufferClose(self):
+        """Close buffer."""
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        self.bufferDestroy()
+        cancel = False
+        return cancel
+
+    def bufferCreate(self, filename=None):
+        """Create new buffer."""
+        self.bufferDestroy()
+        buffer = Buffer()
+        self.panel = panel = wx.Panel(parent=self, id=-1)
+        panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x)
+        editor = Editor(parent=panel)
+        panel.editor = editor
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(editor.window, 1, wx.EXPAND)
+        panel.SetSizer(sizer)
+        panel.SetAutoLayout(True)
+        sizer.Layout()
+        buffer.addEditor(editor)
+        buffer.open(filename)
+        self.setEditor(editor)
+        self.editor.setFocus()
+        self.SendSizeEvent()
+
+
+    def bufferDestroy(self):
+        """Destroy the current buffer."""
+        if self.buffer:
+            for editor in self.buffer.editors.values():
+                editor.destroy()
+            self.editor = None
+            del self.buffers[self.buffer.id]
+            self.buffer = None
+            self.panel.Destroy()
+
+
+    def bufferHasChanged(self):
+        """Return True if buffer has changed since last save."""
+        if self.buffer:
+            return self.buffer.hasChanged()
+        else:
+            return False
+
+    def bufferNew(self):
+        """Create new buffer."""
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        self.bufferCreate()
+        cancel = False
+        return cancel
+
+    def bufferOpen(self):
+        """Open file in buffer."""
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        filedir = ''
+        if self.buffer and self.buffer.doc.filedir:
+            filedir = self.buffer.doc.filedir
+        result = openSingle(directory=filedir)
+        if result.path:
+            self.bufferCreate(result.path)
+        cancel = False
+        return cancel
+
+    def bufferSave(self):
+        """Save buffer to its file."""
+        if self.buffer.doc.filepath:
+            self.buffer.save()
+            cancel = False
+        else:
+            cancel = self.bufferSaveAs()
+        return cancel
+
+    def bufferSaveAs(self):
+        """Save buffer to a new filename."""
+        if self.bufferHasChanged() and self.buffer.doc.filepath:
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        filedir = ''
+        if self.buffer and self.buffer.doc.filedir:
+            filedir = self.buffer.doc.filedir
+        result = saveSingle(directory=filedir)
+        if result.path:
+            self.buffer.saveAs(result.path)
+            cancel = False
+        else:
+            cancel = True
+        return cancel
+
+    def bufferSuggestSave(self):
+        """Suggest saving changes.  Return True if user selected Cancel."""
+        result = messageDialog(parent=None,
+                               message='%s has changed.\n'
+                                       'Would you like to save it first'
+                                       '?' % self.buffer.name,
+                               title='Save current file?')
+        if result.positive:
+            cancel = self.bufferSave()
+        else:
+            cancel = result.text == 'Cancel'
+        return cancel
+
+    def updateNamespace(self):
+        """Update the buffer namespace for autocompletion and calltips."""
+        if self.buffer.updateNamespace():
+            self.SetStatusText('Namespace updated')
+        else:
+            self.SetStatusText('Error executing, unable to update namespace')
+
+
+class KiCadEditorNotebookFrame(KiCadEditorFrame):
+    def __init__(self, parent):
+        """Create EditorNotebookFrame instance."""
+        self.notebook = None
+        KiCadEditorFrame.__init__(self, parent)
+        if self.notebook:
+            dispatcher.connect(receiver=self._editorChange,
+                               signal='EditorChange', sender=self.notebook)
+
+    def _setup(self):
+        """Setup prior to first buffer creation.
+
+        Called automatically by base class during init."""
+        self.notebook = EditorNotebook(parent=self)
+        intro = 'Py %s' % version.VERSION
+        import imp
+        module = imp.new_module('__main__')
+        module.__dict__['__builtins__'] = __builtins__
+        namespace = module.__dict__.copy()
+        self.crust = crust.Crust(parent=self.notebook, intro=intro, locals=namespace)
+        self.shell = self.crust.shell
+        # Override the filling so that status messages go to the status bar.
+        self.crust.filling.tree.setStatusText = self.SetStatusText
+        # Override the shell so that status messages go to the status bar.
+        self.shell.setStatusText = self.SetStatusText
+        # Fix a problem with the sash shrinking to nothing.
+        self.crust.filling.SetSashPosition(200)
+        self.notebook.AddPage(page=self.crust, text='*Shell*', select=True)
+        self.setEditor(self.crust.editor)
+        self.crust.editor.SetFocus()
+
+    def _editorChange(self, editor):
+        """Editor change signal receiver."""
+        if not self:
+            dispatcher.disconnect(receiver=self._editorChange,
+                                  signal='EditorChange', sender=self.notebook)
+            return
+        self.setEditor(editor)
+
+    def _updateTitle(self):
+        """Show current title information."""
+        pass
+
+
+    def bufferCreate(self, filename=None):
+        """Create new buffer."""
+        buffer = Buffer()
+        panel = wx.Panel(parent=self.notebook, id=-1)
+        panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: x)
+        editor = Editor(parent=panel)
+        panel.editor = editor
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(editor.window, 1, wx.EXPAND)
+        panel.SetSizer(sizer)
+        panel.SetAutoLayout(True)
+        sizer.Layout()
+        buffer.addEditor(editor)
+        buffer.open(filename)
+        self.setEditor(editor)
+        self.notebook.AddPage(page=panel, text=self.buffer.name, select=True)
+        self.editor.setFocus()
+
+    def bufferDestroy(self):
+        """Destroy the current buffer."""
+        selection = self.notebook.GetSelection()
+##         print("Destroy Selection:", selection)
+        if selection > 0:  # Don't destroy the PyCrust tab.
+            if self.buffer:
+                del self.buffers[self.buffer.id]
+                self.buffer = None  # Do this before DeletePage().
+            self.notebook.DeletePage(selection)
+
+    def bufferNew(self):
+        """Create new buffer."""
+        self.bufferCreate()
+        cancel = False
+        return cancel
+
+    def bufferOpen(self):
+        """Open file in buffer."""
+        filedir = ''
+        if self.buffer and self.buffer.doc.filedir:
+            filedir = self.buffer.doc.filedir
+        result = openMultiple(directory=filedir)
+        for path in result.paths:
+            self.bufferCreate(path)
+        cancel = False
+        return cancel
+    
+
+class KiCadEditorNotebook(wx.Notebook):
+    """A notebook containing a page for each editor."""
+
+    def __init__(self, parent):
+        """Create EditorNotebook instance."""
+        wx.Notebook.__init__(self, parent, id=-1, style=wx.CLIP_CHILDREN)
+        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging, id=self.GetId())
+        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId())
+        self.Bind(wx.EVT_IDLE, self.OnIdle)
+
+    def OnIdle(self, event):
+        """Event handler for idle time."""
+        self._updateTabText()
+        event.Skip()
+
+    def _updateTabText(self):
+        """Show current buffer display name on all but first tab."""
+        size = 3
+        changed = ' **'
+        unchanged = ' --'
+        selection = self.GetSelection()
+        if selection < 1:
+            return
+        text = self.GetPageText(selection)
+        window = self.GetPage(selection)
+        if not window.editor:
+            return
+        if text.endswith(changed) or text.endswith(unchanged):
+            name = text[:-size]
+        else:
+            name = text
+        if name != window.editor.buffer.name:
+            text = window.editor.buffer.name
+        if window.editor.buffer.hasChanged():
+            if text.endswith(changed):
+                text = None
+            elif text.endswith(unchanged):
+                text = text[:-size] + changed
+            else:
+                text += changed
+        else:
+            if text.endswith(changed):
+                text = text[:-size] + unchanged
+            elif text.endswith(unchanged):
+                text = None
+            else:
+                text += unchanged
+        if text is not None:
+            self.SetPageText(selection, text)
+            self.Refresh()  # Needed on Win98.
+
+    def OnPageChanging(self, event):
+        """Page changing event handler."""
+        event.Skip()
+
+    def OnPageChanged(self, event):
+        """Page changed event handler."""
+        new = event.GetSelection()
+        window = self.GetPage(new)
+        dispatcher.send(signal='EditorChange', sender=self,
+                        editor=window.editor)
+        window.SetFocus()
+        event.Skip()
diff --git a/scripting/kicad_scripting_main.cpp b/scripting/kicad_scripting_main.cpp
index 07f0db13de..d37af5a65e 100644
--- a/scripting/kicad_scripting_main.cpp
+++ b/scripting/kicad_scripting_main.cpp
@@ -17,16 +17,16 @@
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <eda_dde.h>
 #include <kiface_i.h>
 #include <kiface_ids.h>
+#include <kipython_frame.h>
+#include <kipython_settings.h>
 #include <kiway.h>
 #include <pgm_base.h>
+#include <python_scripting.h>
 #include <settings/settings_manager.h>
 
-#include <kipython_settings.h>
-#include <python_scripting.h>
-
-#include <sstream>
 
 
 //-----<KIFACE>-----------------------------------------------------------------
@@ -41,84 +41,15 @@ static struct IFACE : public KIFACE_I
 
     wxWindow* CreateWindow( wxWindow* aParent, int aClassId, KIWAY* aKiway, int aCtlBits = 0 ) override
     {
-        InitSettings( new KIPYTHON_SETTINGS );
-        Pgm().GetSettingsManager().RegisterSettings( KifaceSettings() );
+        KIPYTHON_FRAME* frame = new KIPYTHON_FRAME( aKiway, aParent );
 
-
-        // passing window ids instead of pointers is because wxPython is not
-        // exposing the needed c++ apis to make that possible.
-        std::stringstream pcbnew_pyshell_one_step;
-        pcbnew_pyshell_one_step << "import kicad_pyshell\n";
-        pcbnew_pyshell_one_step << "import wx\n";
-        pcbnew_pyshell_one_step << "\n";
-
-        // parent is actually *PCB_EDIT_FRAME
-        if( aParent )
+        if( Kiface().IsSingle() )
         {
-            pcbnew_pyshell_one_step << "parent = wx.FindWindowById( " << aParent->GetId() << " )\n";
-            pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow( parent )\n";
-        }
-        else
-        {
-            pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow()\n";
+            // only run this under single_top, not under a project manager.
+            frame->CreateServer( KICAD_PY_PORT_SERVICE_NUMBER );
         }
 
-        pcbnew_pyshell_one_step << "newshell.SetName( \"KiCad Shell\" )\n";
-        // return value goes into a "global". It's not actually global, but rather
-        // the dict that is passed to PyRun_String
-        pcbnew_pyshell_one_step << "retval = newshell.GetId()\n";
-
-        // As always, first grab the GIL
-        PyLOCK      lock;
-
-        // Now make a dictionary to serve as the global namespace when the code is
-        // executed.  Put a reference to the builtins module in it.
-
-        PyObject*   globals     = PyDict_New();
-        PyObject*   builtins    = PyImport_ImportModule( "builtins" );
-
-        wxASSERT( builtins );
-
-        PyDict_SetItemString( globals, "__builtins__", builtins );
-        Py_DECREF( builtins );
-
-        // Execute the code to make the makeWindow function we defined above
-        PyObject*   result = PyRun_String( pcbnew_pyshell_one_step.str().c_str(), Py_file_input,
-                                           globals, globals );
-
-        // Was there an exception?
-        if( !result )
-        {
-            PyErr_Print();
-            return NULL;
-        }
-
-        Py_DECREF( result );
-
-        result = PyDict_GetItemString( globals, "retval" );
-
-        if( !PyLong_Check( result ) )
-        {
-            wxLogError( "creation of scripting window didn't return a number" );
-            return NULL;
-        }
-
-        const long windowId = PyLong_AsLong( result );
-
-        // It's important not to decref globals before extracting the window id.
-        // If you do it early, globals, and the retval int it contains, may/will be garbage collected.
-        // We do not need to decref result, because GetItemString returns a borrowed reference.
-        Py_DECREF( globals );
-
-        wxWindow* window = wxWindow::FindWindowById( windowId );
-
-        if( !window )
-        {
-            wxLogError( "unable to find pyshell window with id %d", windowId );
-            return NULL;
-        }
-
-        return window;
+        return frame;
     }
 
     /**
@@ -180,6 +111,8 @@ PGM_BASE* PgmOrNull()
 
 bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits )
 {
+    InitSettings( new KIPYTHON_SETTINGS );
+    Pgm().GetSettingsManager().RegisterSettings( KifaceSettings() );
 
     ScriptingSetup();
     return start_common( aCtlBits );
diff --git a/scripting/kipython_frame.cpp b/scripting/kipython_frame.cpp
new file mode 100644
index 0000000000..d344d07882
--- /dev/null
+++ b/scripting/kipython_frame.cpp
@@ -0,0 +1,91 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2021 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <frame_type.h>
+#include <kipython_frame.h>
+#include <kiway_player.h>
+#include <python_scripting.h>
+
+#include <pybind11/embed.h>
+#include <sstream>
+#include <wx/wx.h>
+#include <pybind11/embed.h>
+
+void KIPYTHON_FRAME::SetupPythonEditor()
+{
+
+    // passing window ids instead of pointers is because wxPython is not
+    // exposing the needed c++ apis to make that possible.
+    std::stringstream pcbnew_pyshell_one_step;
+    pcbnew_pyshell_one_step << "import kicad_pyshell\n";
+    pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow( " << GetId() << " )\n";
+
+    // As always, first grab the GIL
+    PyLOCK      lock;
+
+    // Execute the code to make the makeWindow function we defined above
+    PyRun_SimpleString( pcbnew_pyshell_one_step.str().c_str() );
+}
+
+
+void KIPYTHON_FRAME::redirectStdio()
+{
+    // This is a helpful little tidbit to help debugging and such.  It
+    // redirects Python's stdout and stderr to a window that will popup
+    // only on demand when something is printed, like a traceback.
+
+    PyLOCK      lock;
+
+    using namespace pybind11::literals;
+    int id = GetId();
+    auto locals = pybind11::dict( "parent_id"_a= id );
+
+    pybind11::exec( R"(
+import sys
+import wx
+output = wx.PyOnDemandOutputWindow()
+sys.stderr = output
+parent = wx.Window.FindWindowById( parent_id )
+output.SetParent( parent )
+    )", pybind11::globals(), locals );
+}
+
+KIPYTHON_FRAME::KIPYTHON_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
+            KIWAY_PLAYER( aKiway, aParent, FRAME_PYTHON, wxT( "KiPython" ), wxDefaultPosition,
+                    wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, wxT( "KiPython" ) )
+{
+    m_stdio = 0;
+
+    CallAfter( [&](){ SetupPythonEditor(); } );
+
+    redirectStdio();
+}
+
+
+KIPYTHON_FRAME::~KIPYTHON_FRAME()
+{
+    wxWindow* stdio_window = wxWindow::FindWindowById( m_stdio );
+
+    if( stdio_window )
+        stdio_window->Close( true );
+}
diff --git a/scripting/kipython_frame.h b/scripting/kipython_frame.h
new file mode 100644
index 0000000000..4bb8fc8d67
--- /dev/null
+++ b/scripting/kipython_frame.h
@@ -0,0 +1,63 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2021 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ * or you may search the http://www.gnu.org website for the version 3 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef SCRIPTING_KIPYTHON_FRAME_H_
+#define SCRIPTING_KIPYTHON_FRAME_H_
+
+#include <kiway_player.h>
+
+class wxWindow;
+class APP_SETTINGS_BASE;
+class KIWAY_EXPRESS;
+
+class KIPYTHON_FRAME : public KIWAY_PLAYER
+{
+
+
+public:
+    KIPYTHON_FRAME( KIWAY* aKiway, wxWindow* aParent );
+    ~KIPYTHON_FRAME() override;
+
+    void LoadSettings( APP_SETTINGS_BASE* aCfg ) override {}
+    void SaveSettings( APP_SETTINGS_BASE* aCfg ) override {}
+
+    wxWindow* GetToolCanvas() const override { return nullptr;}
+
+    void ExecuteRemoteCommand( const char* cmdline ) override {}
+
+    void KiwayMailIn( KIWAY_EXPRESS& aEvent ) override {}
+    void ProjectChanged() override {}
+
+    void SetupPythonEditor();
+private:
+
+    void redirectStdio();
+    bool canCloseWindow( wxCloseEvent& aCloseEvent ) override { return true; }
+    void doCloseWindow() override {}
+
+    long m_stdio;
+};
+
+
+
+#endif /* SCRIPTING_KIPYTHON_FRAME_H_ */
diff --git a/scripting/python_scripting.cpp b/scripting/python_scripting.cpp
index e99a0b5342..1cda0a60ea 100644
--- a/scripting/python_scripting.cpp
+++ b/scripting/python_scripting.cpp
@@ -54,17 +54,6 @@
 
 extern "C" PyObject* PyInit__pcbnew( void );
 
-#define EXTRA_PYTHON_MODULES 10     // this is the number of python
-                                    // modules that we want to add into the list
-
-
-/* python inittab that links module names to module init functions
- * we will rebuild it to include the original python modules plus
- * our own ones
- */
-
-struct _inittab*    SwigImportInittab;
-static int          SwigNumModules = 0;
 
 /// True if the wxPython scripting layer was successfully loaded.
 static bool wxPythonLoaded = false;
@@ -75,70 +64,6 @@ bool IsWxPythonLoaded()
     return wxPythonLoaded;
 }
 
-
-/**
- * Add a name + initfuction to our SwigImportInittab
- */
-
-static void swigAddModule( const char* name, PyObject* (* initfunc)() )
-{
-    SwigImportInittab[SwigNumModules].name      = (char*) name;
-    SwigImportInittab[SwigNumModules].initfunc  = initfunc;
-    SwigNumModules++;
-    SwigImportInittab[SwigNumModules].name      = (char*) 0;
-    SwigImportInittab[SwigNumModules].initfunc  = 0;
-}
-
-
-/**
- * Add the builtin python modules
- */
-static void swigAddBuiltin()
-{
-    int i = 0;
-
-    /* discover the length of the pyimport inittab */
-    while( PyImport_Inittab[i].name )
-        i++;
-
-    /* allocate memory for the python module table */
-    SwigImportInittab = (struct _inittab*) malloc(
-        sizeof( struct _inittab ) * ( i + EXTRA_PYTHON_MODULES ) );
-
-    /* copy all pre-existing python modules into our newly created table */
-    i = 0;
-
-    while( PyImport_Inittab[i].name )
-    {
-        swigAddModule( PyImport_Inittab[i].name, PyImport_Inittab[i].initfunc );
-        i++;
-    }
-}
-
-
-/**
- * Add the internal modules to the python scripting so they will be available to the scripts.
- */
-static void swigAddModules()
-{
-    swigAddModule( "_pcbnew", PyInit__pcbnew );
-
-    // finally it seems better to include all in just one module
-    // but in case we needed to include any other modules,
-    // it must be done like this:
-    // swigAddModule( "_kicad", init_kicad );
-}
-
-
-/**
- * Switch the python module table to the Pcbnew built one.
- */
-static void swigSwitchPythonBuiltin()
-{
-    PyImport_Inittab = SwigImportInittab;
-}
-
-
 PyThreadState* g_PythonMainTState;
 
 
@@ -245,9 +170,7 @@ bool InitPythonScripting( const char* aStockScriptingPath, const char* aUserScri
     int  retv;
     char cmd[1024];
 
-    swigAddBuiltin();           // add builtin functions
-    swigAddModules();           // add our own modules
-    swigSwitchPythonBuiltin();  // switch the python builtin modules to our new list
+    PyImport_AppendInittab( "_pcbnew", PyInit__pcbnew );
 
 #ifdef _MSC_VER
     // Under vcpkg/msvc, we need to explicitly set the python home
@@ -266,18 +189,16 @@ bool InitPythonScripting( const char* aStockScriptingPath, const char* aUserScri
         Py_SetPythonHome( pyHome.GetFullPath().c_str() );
     }
 #endif
+//
+//    Py_Initialize();
+    pybind11::initialize_interpreter();
 
-    Py_Initialize();
-    PySys_SetArgv( Pgm().App().argc, Pgm().App().argv );
+//    PySys_SetArgv( Pgm().App().argc, Pgm().App().argv );
 
 #if PY_VERSION_HEX < 0x03070000  // PyEval_InitThreads() is called by Py_Initialize() starting with version 3.7
     PyEval_InitThreads();
 #endif      // if PY_VERSION_HEX < 0x03070000
 
-#if defined( DEBUG )
-    RedirectStdio();
-#endif
-
     wxPythonLoaded = true;
 
     // Save the current Python thread state and release the
@@ -374,7 +295,7 @@ static void RunPythonMethodWithReturnedString( const char* aMethodName, wxString
 void FinishPythonScripting()
 {
     PyEval_RestoreThread( g_PythonMainTState );
-    Py_Finalize();
+    pybind11::finalize_interpreter();
 }
 
 
@@ -426,25 +347,6 @@ void UpdatePythonEnvVar( const wxString& aVar, const wxString& aValue )
         wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, cmd );
 }
 
-void RedirectStdio()
-{
-    // This is a helpful little tidbit to help debugging and such.  It
-    // redirects Python's stdout and stderr to a window that will popup
-    // only on demand when something is printed, like a traceback.
-    const char* python_redirect =
-        "import sys\n"
-        "import wx\n"
-        "output = wx.PyOnDemandOutputWindow()\n"
-        "sys.stderr = output\n";
-
-    PyLOCK lock;
-
-    int retv = PyRun_SimpleString( python_redirect );
-
-    if( retv != 0 )
-        wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, python_redirect );
-}
-
 
 wxString PyStringToWx( PyObject* aString )
 {