Beispiel #1
0
 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
Beispiel #2
0
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_()
Beispiel #3
0
 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
Beispiel #4
0
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())
Beispiel #5
0
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
Beispiel #6
0
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())
Beispiel #7
0
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
Beispiel #8
0
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()