Beispiel #1
0
class XTermWidget(QX11EmbedContainer):
    xterm_cmd = '/usr/bin/xterm'
    close_signal = Signal()

    def __init__(self, parent=None, script_path=None):
        # possible use os to do better justice to script path??
        if script_path:
            xterm_cmd += " -e $SHELL -c 'source %s; $SHELL'" % os.path.abspath(
                script_path)
        super(XTermWidget, self).__init__(parent)
        self.setObjectName('XTermWidget')
        self._process = QProcess(self)
        self._process.finished.connect(self.close_signal)
        # let the widget finish init before embedding xterm
        QTimer.singleShot(100, self._embed_xterm)

    def _embed_xterm(self):
        args = ['-into', str(self.winId())]
        self._process.start(self.xterm_cmd, args)
        if self._process.error() == QProcess.FailedToStart:
            print("failed to execute '%s'" % self.xterm_cmd)

    def shutdown(self):
        self._process.kill()
        self._process.waitForFinished()
Beispiel #2
0
class XTermWidget(QX11EmbedContainer):
    xterm_cmd = '/usr/bin/xterm'
    close_signal = Signal()

    def __init__(self, parent=None):
        super(XTermWidget, self).__init__(parent)
        self.setObjectName('XTermWidget')
        self._process = QProcess(self)
        self._process.finished.connect(self.close_signal)
        # let the widget finish init before embedding xterm
        QTimer.singleShot(100, self._embed_xterm)

    def _embed_xterm(self):
        args = ['-into', str(self.winId())]
        self._process.start(self.xterm_cmd, args)
        if self._process.error() == QProcess.FailedToStart:
            print "failed to execute '%s'" % self.xterm_cmd

    def shutdown(self):
        self._process.kill()
        self._process.waitForFinished()
class PluginHandlerXEmbedContainer(PluginHandler):
    """
    Server part of the `PluginHandlerXEmbed`.
    It starts the plugin in a subprocess and provides the `PluginHandlerDBusService` through a peer-to-peer DBus connection.
    """

    _serial_number = 0

    def __init__(self, parent, main_window, instance_id, application_context,
                 container_manager, argv, dbus_object_path):
        super(PluginHandlerXEmbedContainer,
              self).__init__(parent, main_window, instance_id,
                             application_context, container_manager, argv)
        self.setObjectName('PluginHandlerXEmbedContainer')

        self._dbus_object_path = dbus_object_path
        self._dbus_server = None
        self._dbus_container_service = None
        self._dbus_plugin_settings_service = None
        self._dbus_instance_settings_service = None

        self._process = None
        self._pid = None
        # mapping of widget object name to their embed container
        self._embed_containers = {}
        # mapping of toolbar object name to the toolbar
        self._embed_toolbars = {}
        self._signal_mapper_toolbars = QSignalMapper(self)
        self._signal_mapper_toolbars.mapped[str].connect(
            self._on_toolbar_orientation_changed)

    def _load(self):
        self._dbus_server = Server('tcp:bind=*')
        self._dbus_server.on_connection_added.append(self._add_dbus_connection)
        self._dbus_container_service = PluginHandlerDBusService(
            self, self._dbus_object_path)
        self._dbus_plugin_settings_service = SettingsProxyDBusService(
            self._dbus_object_path + '/plugin')
        self._dbus_instance_settings_service = SettingsProxyDBusService(
            self._dbus_object_path + '/instance')

        self._process = QProcess(self)
        self._process.setProcessChannelMode(QProcess.SeparateChannels)
        self._process.readyReadStandardOutput.connect(
            self._print_process_output)
        self._process.readyReadStandardError.connect(self._print_process_error)
        self._process.finished.connect(self._emit_close_plugin)
        # start python with unbuffered stdout/stderr so that the order of the output is retained
        cmd = sys.executable + ' -u'
        cmd += ' %s' % Main.main_filename
        cmd += ' --qt-binding=%s' % QT_BINDING
        cmd += ' --embed-plugin=%s --embed-plugin-serial=%s --embed-plugin-address=%s' % (
            self.instance_id().plugin_id, self.instance_id().serial_number,
            self._dbus_server.address)
        if self.argv():
            cmd += ' --args %s' % ' '.join(self.argv())
        #qDebug('PluginHandlerXEmbedContainer._load() starting command: %s' % cmd)
        self._process.start(cmd)
        started = self._process.waitForStarted(3000)
        if not started:
            self._dbus_container_service.remove_from_connection()
            self._dbus_plugin_settings_service.remove_from_connection()
            self._dbus_instance_settings_service.remove_from_connection()
            raise RuntimeError(
                'PluginHandlerXEmbedContainer._load() could not start subprocess in reasonable time'
            )
        # QProcess.pid() has been added to PySide in 1.0.5
        if hasattr(self._process, 'pid'):
            self._pid = self._process.pid()
        else:
            # use serial number as a replacement for pid if not available
            self.__class__._serial_number = self._serial_number + 1
            self._pid = self._serial_number

        qDebug(
            'PluginHandlerXEmbedContainer._load() started subprocess (#%s) for plugin "%s"'
            % (self._pid, str(self._instance_id)))
        # self._emit_load_completed is called asynchronous when client signals finished loading via dbus

    def _add_dbus_connection(self, conn):
        self._dbus_container_service.add_to_connection(conn,
                                                       self._dbus_object_path)
        self._dbus_plugin_settings_service.add_to_connection(
            conn, self._dbus_object_path + '/plugin')
        self._dbus_instance_settings_service.add_to_connection(
            conn, self._dbus_object_path + '/instance')

    def _print_process_output(self):
        self._print_process(self._process.readAllStandardOutput(), qDebug)

    def _print_process_error(self):
        self._print_process(self._process.readAllStandardError(), qWarning)

    def _print_process(self, data, method):
        # indent process output and prefix it with the pid
        lines = str(data).split('\n')
        if lines[-1] == '':
            lines.pop()
        for line in lines:
            method('    %d %s' % (self._pid, line))

    def load_completed(self, loaded, has_configuration):
        # TODO timer to detect no response
        exception = None if loaded else True
        self._plugin_has_configuration = has_configuration
        self._update_title_bars()
        self._emit_load_completed(exception)

    def _shutdown_plugin(self):
        qDebug('PluginHandlerXEmbedContainer._shutdown_plugin()')
        self._process.finished.disconnect(self._emit_close_plugin)
        self._dbus_container_service.shutdown_plugin()

    def emit_shutdown_plugin_completed(self):
        self._dbus_container_service.remove_from_connection()
        self._dbus_plugin_settings_service.remove_from_connection()
        self._dbus_instance_settings_service.remove_from_connection()

        self._process.close()
        self._process.waitForFinished(5000)
        if self._process.state() != QProcess.NotRunning:
            self._process.kill()
        self._process = None

        super(PluginHandlerXEmbedContainer,
              self).emit_shutdown_plugin_completed()

    def _unload(self):
        qDebug('PluginHandlerXEmbedContainer._unload()')
        self._emit_unload_completed()

    def _save_settings(self, plugin_settings, instance_settings):
        qDebug('PluginHandlerXEmbedContainer._save_settings()')
        self._dbus_plugin_settings_service.set_settings(plugin_settings)
        self._dbus_instance_settings_service.set_settings(instance_settings)
        self._dbus_container_service.save_settings()

    def emit_save_settings_completed(self):
        self._dbus_plugin_settings_service.set_settings(None)
        self._dbus_instance_settings_service.set_settings(None)
        super(PluginHandlerXEmbedContainer,
              self).emit_save_settings_completed()

    def _restore_settings(self, plugin_settings, instance_settings):
        qDebug('PluginHandlerXEmbedContainer._restore_settings()')
        self._dbus_plugin_settings_service.set_settings(plugin_settings)
        self._dbus_instance_settings_service.set_settings(instance_settings)
        self._dbus_container_service.restore_settings()

    def emit_restore_settings_completed(self):
        self._dbus_plugin_settings_service.set_settings(None)
        self._dbus_instance_settings_service.set_settings(None)
        super(PluginHandlerXEmbedContainer,
              self).emit_restore_settings_completed()

    def _trigger_configuration(self):
        self._dbus_container_service.trigger_configuration()

    def embed_widget(self, pid, widget_object_name):
        dock_widget = self._create_dock_widget()
        embed_container = QX11EmbedContainer(dock_widget)
        #embed_container.clientClosed.connect(self._emit_close_signal)
        self._add_dock_widget(dock_widget, embed_container)
        # update widget title is triggered by client after embedding
        self._embed_containers[widget_object_name] = embed_container
        return embed_container.winId()

    def update_embedded_widget_title(self, widget_object_name, title):
        embed_container = self._embed_containers[widget_object_name]
        embed_container.setWindowTitle(title)

    def unembed_widget(self, widget_object_name):
        embed_container = self._embed_containers[widget_object_name]
        self.remove_widget(embed_container)
        del self._embed_containers[widget_object_name]

    def embed_toolbar(self, pid, toolbar_object_name):
        toolbar = QToolBar()
        toolbar.setObjectName(toolbar_object_name)
        embed_container = QX11EmbedContainer(toolbar)
        toolbar.addWidget(embed_container)
        #embed_container.clientClosed.connect(self._emit_close_signal)
        self._add_toolbar(toolbar)
        self._embed_containers[toolbar_object_name] = embed_container
        # setup mapping to signal change of orientation to client
        self._embed_toolbars[toolbar_object_name] = toolbar
        self._signal_mapper_toolbars.setMapping(toolbar, toolbar_object_name)
        toolbar.orientationChanged.connect(self._signal_mapper_toolbars.map)
        return embed_container.winId()

    def _on_toolbar_orientation_changed(self, toolbar_object_name):
        embed_container = self._embed_containers[toolbar_object_name]
        toolbar = self._embed_toolbars[toolbar_object_name]
        self._dbus_container_service.toolbar_orientation_changed(
            embed_container.winId(),
            toolbar.orientation() == Qt.Horizontal)

    def unembed_toolbar(self, toolbar_object_name):
        embed_container = self._embed_containers[toolbar_object_name]
        del self._embed_containers[toolbar_object_name]
        del self._embed_toolbars[toolbar_object_name]
        self.remove_toolbar(embed_container)
        embed_container.close()
class SpyderShellWidget(ExternalShellBase):
    """Spyder Shell Widget: execute a shell in a separate process using spyderlib's ExternalShellBase"""
    SHELL_CLASS = TerminalWidget
    close_signal = Signal()

    def __init__(self, parent=None):
        ExternalShellBase.__init__(self, parent=parent, fname=None, wdir='.',
                                   history_filename='.history',
                                   light_background=True,
                                   menu_actions=None,
                                   show_buttons_inside=False,
                                   show_elapsed_time=False)

        self.setObjectName('SpyderShellWidget')

        # capture tab key
        #self.shell._key_tab = self._key_tab

        self.shell.set_pythonshell_font(QFont('Mono'))

        # Additional python path list
        self.path = []

        # For compatibility with the other shells that can live in the external console
        self.is_ipython_kernel = False
        self.connection_file = None

        self.create_process()

    def get_icon(self):
        return QIcon()

    def create_process(self):
        self.shell.clear()

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)

        env = []
        for key_val_pair in self.process.systemEnvironment():
            try:
                value = unicode(key_val_pair)
            except NameError:
                value = str(key_val_pair)
            env.append(value)
        env.append('TERM=xterm')
        env.append('COLORTERM=gnome-terminal')
        self.process.setEnvironment(env)

        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)

        self.process.readyReadStandardOutput.connect(self.write_output)
        self.process.finished.connect(self.finished)
        self.process.finished.connect(self.close_signal)

        self.process.start('/bin/bash', ['-i'])

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            self.shell.addPlainText("Process failed to start")
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))

        return self.process

    def shutdown(self):
        self.process.kill()
        self.process.waitForFinished()

    def _key_tab(self):
        self.process.write('\t')
        self.process.waitForBytesWritten(-1)
        self.write_output()

    def send_to_process(self, text):
        if not is_string(text):
            try:
                text = unicode(text)
            except NameError:
                text = str(text)
        if not text.endswith('\n'):
            text += '\n'
        self.process.write(QTextCodec.codecForLocale().fromUnicode(text))
        self.process.waitForBytesWritten(-1)

    def keyboard_interrupt(self):
        self.send_ctrl_to_process('c')
class SpyderShellWidget(ExternalShellBase):
    """Spyder Shell Widget: execute a shell in a separate process using spyderlib's ExternalShellBase"""
    SHELL_CLASS = TerminalWidget
    close_signal = Signal()

    def __init__(self, parent=None):
        ExternalShellBase.__init__(self, parent=parent, fname=None, wdir='.',
                                   history_filename='.history',
                                   light_background=True,
                                   menu_actions=None,
                                   show_buttons_inside=False,
                                   show_elapsed_time=False)

        self.setObjectName('SpyderShellWidget')

        # capture tab key
        #self.shell._key_tab = self._key_tab

        self.shell.set_pythonshell_font(QFont('Mono'))

        # Additional python path list
        self.path = []

        # For compatibility with the other shells that can live in the external console
        self.is_ipython_kernel = False
        self.connection_file = None

        self.create_process()

    def get_icon(self):
        return QIcon()

    def create_process(self):
        self.shell.clear()

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)

        env = [unicode(key_val_pair) for key_val_pair in self.process.systemEnvironment()]
        env.append('TERM=xterm')
        env.append('COLORTERM=gnome-terminal')
        self.process.setEnvironment(env)

        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)

        self.process.readyReadStandardOutput.connect(self.write_output)
        self.process.finished.connect(self.finished)
        self.process.finished.connect(self.close_signal)

        self.process.start('/bin/bash', ['-i'])

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            self.shell.addPlainText("Process failed to start")
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))

        return self.process

    def shutdown(self):
        self.process.kill()
        self.process.waitForFinished()

    def _key_tab(self):
        self.process.write('\t')
        self.process.waitForBytesWritten(-1)
        self.write_output()

    def send_to_process(self, text):
        if not isinstance(text, basestring):
            text = unicode(text)
        if not text.endswith('\n'):
            text += '\n'
        self.process.write(QTextCodec.codecForLocale().fromUnicode(text))
        self.process.waitForBytesWritten(-1)

    def keyboard_interrupt(self):
        self.send_ctrl_to_process('c')
class PluginHandlerXEmbedContainer(PluginHandler):

    """
    Server part of the `PluginHandlerXEmbed`.
    It starts the plugin in a subprocess and provides the `PluginHandlerDBusService` through a peer-to-peer DBus connection.
    """

    _serial_number = 0

    def __init__(self, parent, main_window, instance_id, application_context, container_manager, argv, dbus_object_path):
        super(PluginHandlerXEmbedContainer, self).__init__(parent, main_window, instance_id, application_context, container_manager, argv)
        self.setObjectName('PluginHandlerXEmbedContainer')

        self._dbus_object_path = dbus_object_path
        self._dbus_server = None
        self._dbus_container_service = None
        self._dbus_plugin_settings_service = None
        self._dbus_instance_settings_service = None

        self._process = None
        self._pid = None
        # mapping of widget object name to their embed container
        self._embed_containers = {}
        # mapping of toolbar object name to the toolbar
        self._embed_toolbars = {}
        self._signal_mapper_toolbars = QSignalMapper(self)
        self._signal_mapper_toolbars.mapped[str].connect(self._on_toolbar_orientation_changed)

    def _load(self):
        if not Main.main_filename:
            raise RuntimeError('PluginHandlerXEmbedContainer._load() filename of initially started script is unknown')

        self._dbus_server = Server('tcp:bind=*')
        self._dbus_server.on_connection_added.append(self._add_dbus_connection)
        self._dbus_container_service = PluginHandlerDBusService(self, self._dbus_object_path)
        self._dbus_plugin_settings_service = SettingsProxyDBusService(self._dbus_object_path + '/plugin')
        self._dbus_instance_settings_service = SettingsProxyDBusService(self._dbus_object_path + '/instance')

        self._process = QProcess(self)
        self._process.setProcessChannelMode(QProcess.SeparateChannels)
        self._process.readyReadStandardOutput.connect(self._print_process_output)
        self._process.readyReadStandardError.connect(self._print_process_error)
        self._process.finished.connect(self._emit_close_plugin)
        # start python with unbuffered stdout/stderr so that the order of the output is retained
        cmd = sys.executable + ' -u'
        cmd += ' %s' % Main.main_filename
        cmd += ' --qt-binding=%s' % QT_BINDING
        cmd += ' --embed-plugin=%s --embed-plugin-serial=%s --embed-plugin-address=%s' % (self.instance_id().plugin_id, self.instance_id().serial_number, self._dbus_server.address)
        if self.argv():
            cmd += ' --args %s' % ' '.join(self.argv())
        #qDebug('PluginHandlerXEmbedContainer._load() starting command: %s' % cmd)
        self._process.start(cmd)
        started = self._process.waitForStarted(3000)
        if not started:
            self._dbus_container_service.remove_from_connection()
            self._dbus_plugin_settings_service.remove_from_connection()
            self._dbus_instance_settings_service.remove_from_connection()
            raise RuntimeError('PluginHandlerXEmbedContainer._load() could not start subprocess in reasonable time')
        # QProcess.pid() has been added to PySide in 1.0.5
        if hasattr(self._process, 'pid'):
            self._pid = self._process.pid()
        else:
            # use serial number as a replacement for pid if not available
            self.__class__._serial_number = self._serial_number + 1
            self._pid = self._serial_number

        qDebug('PluginHandlerXEmbedContainer._load() started subprocess (#%s) for plugin "%s"' % (self._pid, str(self._instance_id)))
        # self._emit_load_completed is called asynchronous when client signals finished loading via dbus

    def _add_dbus_connection(self, conn):
        self._dbus_container_service.add_to_connection(conn, self._dbus_object_path)
        self._dbus_plugin_settings_service.add_to_connection(conn, self._dbus_object_path + '/plugin')
        self._dbus_instance_settings_service.add_to_connection(conn, self._dbus_object_path + '/instance')

    def _print_process_output(self):
        self._print_process(self._process.readAllStandardOutput(), qDebug)

    def _print_process_error(self):
        self._print_process(self._process.readAllStandardError(), qWarning)

    def _print_process(self, data, method):
        # indent process output and prefix it with the pid
        lines = str(data).split('\n')
        if lines[-1] == '':
            lines.pop()
        for line in lines:
            method('    %d %s' % (self._pid, line))

    def load_completed(self, loaded, has_configuration):
        # TODO timer to detect no response
        exception = None if loaded else True
        self._plugin_has_configuration = has_configuration
        self._update_title_bars()
        self._emit_load_completed(exception)

    def _shutdown_plugin(self):
        qDebug('PluginHandlerXEmbedContainer._shutdown_plugin()')
        self._process.finished.disconnect(self._emit_close_plugin)
        self._dbus_container_service.shutdown_plugin()

    def emit_shutdown_plugin_completed(self):
        self._dbus_container_service.remove_from_connection()
        self._dbus_plugin_settings_service.remove_from_connection()
        self._dbus_instance_settings_service.remove_from_connection()

        self._process.close()
        self._process.waitForFinished(5000)
        if self._process.state() != QProcess.NotRunning:
            self._process.kill()
        self._process = None

        super(PluginHandlerXEmbedContainer, self).emit_shutdown_plugin_completed()

    def _unload(self):
        qDebug('PluginHandlerXEmbedContainer._unload()')
        self._emit_unload_completed()

    def _save_settings(self, plugin_settings, instance_settings):
        qDebug('PluginHandlerXEmbedContainer._save_settings()')
        self._dbus_plugin_settings_service.set_settings(plugin_settings)
        self._dbus_instance_settings_service.set_settings(instance_settings)
        self._dbus_container_service.save_settings()

    def emit_save_settings_completed(self):
        self._dbus_plugin_settings_service.set_settings(None)
        self._dbus_instance_settings_service.set_settings(None)
        super(PluginHandlerXEmbedContainer, self).emit_save_settings_completed()

    def _restore_settings(self, plugin_settings, instance_settings):
        qDebug('PluginHandlerXEmbedContainer._restore_settings()')
        self._dbus_plugin_settings_service.set_settings(plugin_settings)
        self._dbus_instance_settings_service.set_settings(instance_settings)
        self._dbus_container_service.restore_settings()

    def emit_restore_settings_completed(self):
        self._dbus_plugin_settings_service.set_settings(None)
        self._dbus_instance_settings_service.set_settings(None)
        super(PluginHandlerXEmbedContainer, self).emit_restore_settings_completed()

    def _trigger_configuration(self):
        self._dbus_container_service.trigger_configuration()

    def embed_widget(self, pid, widget_object_name):
        dock_widget = self._create_dock_widget()
        embed_container = QX11EmbedContainer(dock_widget)
        #embed_container.clientClosed.connect(self._emit_close_signal)
        self._add_dock_widget(dock_widget, embed_container)
        # update widget title is triggered by client after embedding
        self._embed_containers[widget_object_name] = embed_container
        return embed_container.winId()

    def update_embedded_widget_icon(self, widget_object_name, icon_str):
        embed_container = self._embed_containers[widget_object_name]
        # deserialize icon base64-encoded string
        ba = QByteArray.fromBase64(icon_str)
        s = QDataStream(ba, QIODevice.ReadOnly)
        icon = QIcon()
        s >> icon
        embed_container.setWindowIcon(icon)

    def update_embedded_widget_title(self, widget_object_name, title):
        embed_container = self._embed_containers[widget_object_name]
        embed_container.setWindowTitle(title)

    def unembed_widget(self, widget_object_name):
        embed_container = self._embed_containers[widget_object_name]
        self.remove_widget(embed_container)
        del self._embed_containers[widget_object_name]

    def embed_toolbar(self, pid, toolbar_object_name):
        toolbar = QToolBar()
        toolbar.setObjectName(toolbar_object_name)
        embed_container = QX11EmbedContainer(toolbar)
        toolbar.addWidget(embed_container)
        #embed_container.clientClosed.connect(self._emit_close_signal)
        self._add_toolbar(toolbar)
        self._embed_containers[toolbar_object_name] = embed_container
        # setup mapping to signal change of orientation to client
        self._embed_toolbars[toolbar_object_name] = toolbar
        self._signal_mapper_toolbars.setMapping(toolbar, toolbar_object_name)
        toolbar.orientationChanged.connect(self._signal_mapper_toolbars.map)
        return embed_container.winId()

    def _on_toolbar_orientation_changed(self, toolbar_object_name):
        embed_container = self._embed_containers[toolbar_object_name]
        toolbar = self._embed_toolbars[toolbar_object_name]
        self._dbus_container_service.toolbar_orientation_changed(embed_container.winId(), toolbar.orientation() == Qt.Horizontal)

    def unembed_toolbar(self, toolbar_object_name):
        embed_container = self._embed_containers[toolbar_object_name]
        del self._embed_containers[toolbar_object_name]
        del self._embed_toolbars[toolbar_object_name]
        self.remove_toolbar(embed_container)
        embed_container.close()