def _init_kernel_manager(self): connection_file = find_connection_file(self.app.connection_file) manager = QtKernelManager(connection_file=connection_file) manager.load_connection_file() manager.start_channels() atexit.register(manager.cleanup_connection_file) self.kernel_manager = manager
def launch_qtconsole(connection_file): wait_for_connection_file(connection_file) app = QtWidgets.QApplication([]) app._kernel_has_shutdown = False kernel_manager = QtKernelManager() kernel_manager.load_connection_file(connection_file) kernel_client = kernel_manager.client() kernel_client.start_channels() jupyter_widget = JupyterWidget() jupyter_widget.kernel_manager = kernel_manager jupyter_widget.kernel_client = kernel_client window = MainWindow(jupyter_widget) window.show() def shutdown_kernel(): if app._kernel_has_shutdown: return jupyter_widget.kernel_client.stop_channels() jupyter_widget.kernel_manager.shutdown_kernel() app._kernel_has_shutdown = True app.exit() jupyter_widget.exit_requested.connect(shutdown_kernel) app.aboutToQuit.connect(shutdown_kernel) return app.exec_()
class IPythonConsole(idaapi.PluginForm): def __init__(self, connection_file, *args): super(IPythonConsole, self).__init__(*args) self.connection_file = connection_file def OnCreate(self, form): try: if is_using_pyqt5(): self.parent = self.FormToPyQtWidget(form, ctx=sys.modules[__name__]) else: self.parent = self.FormToPySideWidget( form, ctx=sys.modules[__name__]) layout = self._createConsoleWidget() self.parent.setLayout(layout) except: import traceback print(traceback.format_exc()) def _createConsoleWidget(self): if is_using_pyqt5(): layout = QtWidgets.QVBoxLayout() else: layout = QtGui.QVBoxLayout() connection_file = find_connection_file(self.connection_file) self.kernel_manager = QtKernelManager(connection_file=connection_file) self.kernel_manager.load_connection_file() self.kernel_manager.client_factory = QtKernelClient self.kernel_client = self.kernel_manager.client() self.kernel_client.start_channels() widget_options = {} if sys.platform.startswith('linux'): # Some upstream bug crashes IDA when the ncurses completion is # used. I'm not sure where the bug is exactly (IDA's Qt5 bindings?) # but using the "droplist" instead works around the crash. The # problem is present only on Linux. # See: https://github.com/eset/ipyida/issues/8 widget_options["gui_completion"] = 'droplist' self.ipython_widget = IdaRichJupyterWidget(self.parent, **widget_options) self.ipython_widget.kernel_manager = self.kernel_manager self.ipython_widget.kernel_client = self.kernel_client layout.addWidget(self.ipython_widget) return layout def Show(self, name="IPython Console"): return idaapi.PluginForm.Show(self, name) def OnClose(self, form): try: pass except: import traceback print(traceback.format_exc())
def default_manager(kernel): """ Return a configured QtKernelManager :param kernel: An IPKernelApp instance """ connection_file = find_connection_file(kernel.connection_file) manager = QtKernelManager(connection_file=connection_file) manager.load_connection_file() manager.start_channels() atexit.register(manager.cleanup_connection_file) return manager
class IPythonConsole(idaapi.PluginForm): def __init__(self, connection_file, *args): super(IPythonConsole, self).__init__(*args) self.connection_file = connection_file def OnCreate(self, form): try: if is_using_pyqt5(): self.parent = self.FormToPyQtWidget(form, ctx=sys.modules[__name__]) else: self.parent = self.FormToPySideWidget(form, ctx=sys.modules[__name__]) layout = self._createConsoleWidget() self.parent.setLayout(layout) except: import traceback print(traceback.format_exc()) def _createConsoleWidget(self): if is_using_pyqt5(): layout = QtWidgets.QVBoxLayout() else: layout = QtGui.QVBoxLayout() connection_file = find_connection_file(self.connection_file) self.kernel_manager = QtKernelManager(connection_file=connection_file) self.kernel_manager.load_connection_file() self.kernel_manager.client_factory = QtKernelClient self.kernel_client = self.kernel_manager.client() self.kernel_client.start_channels() widget_options = {} if sys.platform.startswith('linux'): # Some upstream bug crashes IDA when the ncurses completion is # used. I'm not sure where the bug is exactly (IDA's Qt5 bindings?) # but using the "droplist" instead works around the crash. The # problem is present only on Linux. # See: https://github.com/eset/ipyida/issues/8 widget_options["gui_completion"] = 'droplist' self.ipython_widget = IdaRichJupyterWidget(self.parent, **widget_options) self.ipython_widget.kernel_manager = self.kernel_manager self.ipython_widget.kernel_client = self.kernel_client layout.addWidget(self.ipython_widget) return layout def Show(self, name="IPython Console"): return idaapi.PluginForm.Show(self, name) def OnClose(self, form): try: pass except: import traceback print(traceback.format_exc())
class IPythonConsole(cutter.CutterDockWidget): def __init__(self, connection_file, *args): print("[10] inside IPythonConsole init") super(IPythonConsole, self).__init__(*args) self.connection_file = connection_file def create(self): print("[11] inside IPythonConsole create") try: layout = self._createConsoleWidget() self.parent().setLayout(layout) except: import traceback print(traceback.format_exc()) def _createConsoleWidget(self): print("[12] inside IPythonConsole _createConsoleWidget") layout = QtWidgets.QVBoxLayout() connection_file = find_connection_file(self.connection_file) self.kernel_manager = QtKernelManager(connection_file=connection_file) self.kernel_manager.load_connection_file() self.kernel_manager.client_factory = QtKernelClient self.kernel_client = self.kernel_manager.client() self.kernel_client.start_channels() widget_options = {} if sys.platform.startswith('linux'): # Some upstream bug crashes IDA when the ncurses completion is # used. I'm not sure where the bug is exactly (IDA's Qt5 bindings?) # but using the "droplist" instead works around the crash. The # problem is present only on Linux. # See: https://github.com/eset/ipyida/issues/8 widget_options["gui_completion"] = 'droplist' widget_options.update(_user_widget_options) print( "[13] inside IPythonConsole before creating CutterRichJupyterWidget" ) class fake_font(): def pointSize(self): return 16 QtWidgets.QApplication.instance().font = fake_font self.ipython_widget = CutterRichJupyterWidget(self.parent, **widget_options) print( "[15] inside IPythonConsole after creating CutterRichJupyterWidget" ) self.ipython_widget.kernel_manager = self.kernel_manager self.ipython_widget.kernel_client = self.kernel_client self.ipython_widget.setWindowTitle("iPython Widget") layout.addWidget(self.ipython_widget) return layout def setFocusToPrompt(self): # This relies on the internal _control widget but it's the most reliable # way I found so far. if hasattr(self.ipython_widget, "_control"): self.ipython_widget._control.setFocus() else: print( "[IPyIDA] setFocusToPrompt: Widget has no _control attribute.") def close(self, form): try: pass except: import traceback print(traceback.format_exc())
class SpineConsoleWidget(RichJupyterWidget): """Base class for all embedded console widgets that can run tool instances.""" def __init__(self, toolbox, name, owner=None): """ Args: toolbox (ToolboxUI): QMainWindow instance name (str): Console name, e.g. 'Python Console' owner (ProjectItem, NoneType): Item that owns the console. """ super().__init__(parent=toolbox) self._toolbox = toolbox self._name = name self.owners = {owner} self._kernel_starting = False # Warning: Do not use self._starting (protected class variable in JupyterWidget) self.kernel_name = None self.kernel_manager = None self.kernel_client = None self._engine_connection_file = None # To restart kernels controlled by Spine Engine self.normal_cursor = self._control.viewport().cursor() self._copy_input_action = QAction('Copy (Only Input)', self) self._copy_input_action.triggered.connect( lambda checked: self.copy_input()) self._copy_input_action.setEnabled(False) self.copy_available.connect(self._copy_input_action.setEnabled) self.start_console_action = QAction("Start", self) self.start_console_action.triggered.connect(self.start_console) self.restart_console_action = QAction("Restart", self) self.restart_console_action.triggered.connect(self.restart_console) def name(self): """Returns console name.""" return self._name @property def owner_names(self): return "&".join(x.name for x in self.owners) @Slot(bool) def start_console(self, checked=False): """Starts chosen Python/Julia kernel if available and not already running. Context menu start action handler.""" if self._name == "Python Console": k_name = self._toolbox.qsettings().value( "appSettings/pythonKernel", defaultValue="") elif self._name == "Julia Console": k_name = self._toolbox.qsettings().value("appSettings/juliaKernel", defaultValue="") if k_name == "": self._toolbox.msg_error.emit( f"No kernel selected. Go to Settings->Tools to select a kernel for {self._name}" ) return if self.kernel_manager and self.kernel_name == k_name: self._toolbox.msg_warning.emit( f"Kernel {k_name} already running in {self._name}") return self.call_start_kernel(k_name) @Slot(bool) def restart_console(self, checked=False): """Restarts current Python/Julia kernel. Starts a new kernel if it is not running or if chosen kernel has been changed in Settings. Context menu restart action handler.""" if self._engine_connection_file: self._kernel_starting = True # This flag is unset when a correct msg is received from iopub_channel engine_server_address = self._toolbox.qsettings().value( "appSettings/engineServerAddress", defaultValue="") engine_mngr = make_engine_manager(engine_server_address) engine_mngr.restart_kernel(self._engine_connection_file) self._replace_client() return if self._name == "Python Console": k_name = self._toolbox.qsettings().value( "appSettings/pythonKernel", defaultValue="") else: k_name = self._toolbox.qsettings().value("appSettings/juliaKernel", defaultValue="") if k_name == "": self._toolbox.msg_error.emit( f"No kernel selected. Go to Settings->Tools to select a kernel for {self._name}" ) return if self.kernel_manager and self.kernel_name == k_name: # Restart current kernel self._kernel_starting = True # This flag is unset when a correct msg is received from iopub_channel self._toolbox.msg.emit(f"*** Restarting {self._name} ***") # Restart kernel manager blackhole = open(os.devnull, 'w') self.kernel_manager.restart_kernel(now=True, stdout=blackhole, stderr=blackhole) # Start kernel client and attach it to kernel manager self._replace_client() else: # No kernel running in Python Console or Python kernel has been changed in Settings->Tools. Start kernel self.call_start_kernel(k_name) def call_start_kernel(self, k_name=None): """Finds a valid kernel and calls ``start_kernel()`` with it.""" d = { "Python Console": ("Python", "pythonKernel", find_python_kernels), "Julia Console": ("Julia", "juliaKernel", find_julia_kernels), } if self._name not in d: self._toolbox.msg_error.emit("Unknown Console") return language, settings_entry, find_kernels = d[self._name] if not k_name: k_name = self._toolbox.qsettings().value( f"appSettings/{settings_entry}", defaultValue="") if not k_name: self._toolbox.msg_error.emit( f"No kernel selected. Go to Settings->Tools to select a {language} kernel." ) return kernels = find_kernels() try: kernel_path = kernels[k_name] except KeyError: self._toolbox.msg_error.emit( f"Kernel {k_name} not found. Go to Settings->Tools and select another {language} kernel." ) return # Check if this kernel is already running if self.kernel_manager and self.kernel_name == k_name: return self.start_kernel(k_name, kernel_path) def start_kernel(self, k_name, k_path): """Starts a kernel manager and kernel client and attaches the client to Julia or Python Console. Args: k_name (str): Kernel name k_path (str): Directory where the the kernel specs are located """ if self.kernel_manager and self.kernel_name != k_name: old_k_name_anchor = "<a style='color:#99CCFF;' title='{0}' href='#'>{1}</a>".format( k_path, self.kernel_name) self._toolbox.msg.emit( f"Kernel changed in Settings. Shutting down current kernel {old_k_name_anchor}." ) self.shutdown_kernel() self.kernel_name = k_name new_k_name_anchor = "<a style='color:#99CCFF;' title='{0}' href='#'>{1}</a>".format( k_path, self.kernel_name) self._toolbox.msg.emit( f"*** Starting {self._name} (kernel {new_k_name_anchor}) ***") self._kernel_starting = True # This flag is unset when a correct msg is received from iopub_channel km = QtKernelManager(kernel_name=self.kernel_name) try: blackhole = open(os.devnull, 'w') km.start_kernel(stdout=blackhole, stderr=blackhole) kc = km.client() kc.hb_channel.time_to_dead = JUPYTER_KERNEL_TIME_TO_DEAD kc.start_channels() self.kernel_manager = km self.kernel_client = kc return except FileNotFoundError: self._toolbox.msg_error.emit( f"Couldn't find the executable specified by kernel {self.kernel_name}" ) self._kernel_starting = False return except NoSuchKernel: # kernelspecs missing (probably happens when kernel.json file does not exist self._toolbox.msg_error.emit( f"Couldn't find kernel specs for kernel {self.kernel_name}") self._kernel_starting = False return def shutdown_kernel(self): """Shut down Julia/Python kernel.""" if not self.kernel_manager or not self.kernel_manager.is_alive(): return self._toolbox.msg.emit(f"Shutting down {self._name}...") if self.kernel_client is not None: self.kernel_client.stop_channels() self.kernel_manager.shutdown_kernel(now=True) self.kernel_manager.deleteLater() self.kernel_manager = None self.kernel_client.deleteLater() self.kernel_client = None def dragEnterEvent(self, e): """Don't accept project item drops.""" source = e.source() if isinstance(source, ProjectItemDragMixin): e.ignore() else: super().dragEnterEvent(e) @Slot(dict) def _handle_status(self, msg): """Handles status message.""" super()._handle_status(msg) kernel_execution_state = msg["content"].get("execution_state", "") if kernel_execution_state == "starting": # This msg does not show up when starting the Python Console but on Restart it does (strange) self._kernel_starting = True return if kernel_execution_state == "idle" and self._kernel_starting: self._kernel_starting = False self._toolbox.msg_success.emit(f"{self._name} ready for action") self._control.viewport().setCursor(self.normal_cursor) def enterEvent(self, event): """Sets busy cursor during console (re)starts.""" if self._kernel_starting: self._control.viewport().setCursor(Qt.BusyCursor) def _is_complete(self, source, interactive): """See base class.""" raise NotImplementedError() def _context_menu_make(self, pos): """Reimplemented to add actions to console context-menus.""" menu = super()._context_menu_make(pos) for before_action in menu.actions(): if before_action.text() == 'Copy (Raw Text)': menu.insertAction(before_action, self._copy_input_action) break first_action = menu.actions()[0] if self.kernel_manager or self._engine_connection_file: self.restart_console_action.setEnabled(not self._kernel_starting) menu.insertAction(first_action, self.restart_console_action) else: self.start_console_action.setEnabled(not self._kernel_starting) menu.insertAction(first_action, self.start_console_action) menu.insertSeparator(first_action) return menu def copy_input(self): """Copies only input.""" if not self._control.hasFocus(): return text = self._control.textCursor().selection().toPlainText() if not text: return # Remove prompts. lines = text.splitlines() useful_lines = [] for line in lines: m = self._highlighter._classic_prompt_re.match(line) if m: useful_lines.append(line[len(m.group(0)):]) continue m = self._highlighter._ipy_prompt_re.match(line) if m: useful_lines.append(line[len(m.group(0)):]) continue text = '\n'.join(useful_lines) try: was_newline = text[-1] == '\n' except IndexError: was_newline = False if was_newline: # user doesn't need newline text = text[:-1] QApplication.clipboard().setText(text) def _replace_client(self): if self.kernel_manager is None: return kc = self.kernel_manager.client() kc.hb_channel.time_to_dead = JUPYTER_KERNEL_TIME_TO_DEAD # Not crucial, but nicer to keep the same as mngr kc.start_channels() if self.kernel_client is not None: self.kernel_client.stop_channels() self.kernel_client = kc def connect_to_kernel(self, kernel_name, connection_file): """Connects to an existing kernel. Used when Spine Engine is managing the kernel for project execution. Args: kernel_name (str) connection_file (str): Path to the connection file of the kernel """ self.kernel_name = kernel_name self._engine_connection_file = connection_file self._kernel_starting = True self.kernel_manager = QtKernelManager(connection_file=connection_file) self.kernel_manager.load_connection_file() self._replace_client() self.include_other_output = True self.other_output_prefix = "" def interrupt(self): """[TODO: Remove?] Sends interrupt signal to kernel.""" if not self.kernel_manager: return self.kernel_manager.interrupt_kernel()