6

There is an existing environment and framework usable via Bash terminal around which I want to make a GUI. What I have in mind is the following flow:

  • In a Bash session, the framework environment is set up. This results in everything from environment variables to authentications being set up in the session.
  • A Python GUI script is run in order to wrap around the existing session and make it easier to run subsequent steps.
  • The GUI appears, displaying on one side the Bash session in an embedded terminal and on the other side a set of buttons corresponding to various commands that can be run in the existing framework environment.
  • Buttons can be pressed in the GUI resulting in certain Bash commands being run. The results of the runs are displayed in the embedded terminal.

What is a good way to approach the creation of such a GUI? I realise that the idea of interacting with the existing environment could be tricky. If it is particularly tricky, I am open to recreating the environment in a session of the GUI. In any case, how can the GUI interact with the embedded terminal. How can commands be run and displayed in the embedded terminal when buttons of the GUI are pressed?

A basic start of the GUI (featuring an embedded terminal) is as follows:

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class embeddedTerminal(QWidget):

    def __init__(self):

        QWidget.__init__(self)
        self.resize(800, 600)
        self.process  = QProcess(self)
        self.terminal = QWidget(self)
        layout = QVBoxLayout(self)
        layout.addWidget(self.terminal)
        self.process.start(
            'xterm',
            ['-into', str(self.terminal.winId())]
        )

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = embeddedTerminal()
    main.show()
    sys.exit(app.exec_())

How could I run, say, top on this embedded terminal following the press of a button in the GUI?

Sean
  • 5,290
  • 2
  • 24
  • 21
d3pd
  • 7,935
  • 24
  • 76
  • 127
  • Could you explain a little more why you would like to approach your problem in this way rather than a more conventional GUI? Is it to save you some work writing the GUI, or save you work changing the existing commands? Or both, or something else? Are the commands interactive, or do they run to completion without further user input (after the command line is typed in and they hit return)? You say "bash command" -- I'm guessing the situation in fact that certain processes need to get run, and bash is not really intimately involved except as a way to run them? – Croad Langshan Mar 18 '15 at 20:07
  • Hey, thanks for considering this. Basically, there's a huge infrastructure that users are expected to interact with via a terminal. Typical usage could involve invoking multiple commands corresponding to scripts in multiple languages, most with complex command line arguments. I know of ways to automate the creation of various commands that are to be run in the terminal and I want to see if I can simplify things for users a bit effectively by adding a GUI to the terminal with buttons available for automatic generation and running of some commands. – d3pd Mar 18 '15 at 22:28
  • Effectively, I want to have an interactive terminal and bolt onto it (or have it bolted onto) a simple PyQt GUI that can interact with the terminal too (pipe in commands and run them and get the output of the commands). – d3pd Mar 18 '15 at 22:30
  • So does it actually need to be a terminal + bash, and not say a simple line editor that you implement yourself -- in which case you can just run the commands yourself using QProcess or subprocess. Are pipelines and job control used much? If only a few cases, consider wrapping those cases with a few new commands so that there are zero cases left over? – Croad Langshan Mar 19 '15 at 00:23
  • On a different line than my questioning in comments above, and just to show the range of possibilities / inspiration (I'm not suggesting you use this or do anything similar): https://github.com/mseaborn/coconut-shell (this is a combined shell and terminal, written in python -- so could be extended to interact with as you like) – Croad Langshan Mar 19 '15 at 00:26

2 Answers2

7

If it has to be a real terminal and a real shell (and not just accepting a line of input, running some command, then displaying output) -- how about tmux?

You could use something like tee to get the output back into your program.

Note that tmux sessions may persist across your program runs, so you'd need to read up on how that works and how to control it.

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class embeddedTerminal(QWidget):

    def __init__(self):
        QWidget.__init__(self)
        self._processes = []
        self.resize(800, 600)
        self.terminal = QWidget(self)
        layout = QVBoxLayout(self)
        layout.addWidget(self.terminal)
        self._start_process(
            'xterm',
            ['-into', str(self.terminal.winId()),
             '-e', 'tmux', 'new', '-s', 'my_session']
        )
        button = QPushButton('List files')
        layout.addWidget(button)
        button.clicked.connect(self._list_files)

    def _start_process(self, prog, args):
        child = QProcess()
        self._processes.append(child)
        child.start(prog, args)

    def _list_files(self):
        self._start_process(
            'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter'])

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = embeddedTerminal()
    main.show()
    sys.exit(app.exec_())

A bit more here: https://superuser.com/questions/492266/run-or-send-a-command-to-a-tmux-pane-in-a-running-tmux-session

Community
  • 1
  • 1
Croad Langshan
  • 2,646
  • 3
  • 24
  • 37
  • I've finally completed considering this. Thank you very much for all of your very helpful guidance on this problem. It may well be that a freshly-constructed terminal within Python is a cleaner solution, but the tmux approach is doing basically exactly what I want. Thanks! :) – d3pd Apr 02 '15 at 16:46
  • In case it was not clear, the comment at the top of the answer wasn't comparing this answer to Coconut Shell -- it was comparing it to the even simpler idea of the user typing commands into a line edit, having your program run that command when they hit enter (with QProcess, say), and just printing the output in a text area. That idea is similar to what browsers do with their JS shell (but needn't be as fancy). – Croad Langshan Apr 02 '15 at 20:11
  • This is awesome. Thank you – answerSeeker Dec 12 '16 at 06:35
0

If anyone else comes upon this made some slight mods to it to close the tmux session if it exists since the previous one did not close it on exit. Also set it up for PySide2

About the only thing it needs now is resize support.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import psutil
import os
import platform
import sys
from pathlib import Path
from subprocess import call

from PySide2 import QtCore
from PySide2.QtCore import *
from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication

platform = platform.system()
print(str(platform))

term_dir = Path(os.path.abspath(os.path.dirname(__file__))) / 'terminus'

if platform == 'Windows':
    term_bin = str(term_dir) + '/' + str(platform.lower()) + '/' + 'terminus.exe'

elif platform == 'Linux':
    term_bin = str(term_dir) + '/' + str(platform.lower()) + '/' + 'terminus'

print(term_bin)


class embeddedTerminal(QWidget):

    def __init__(self):
        QWidget.__init__(self)
        self._processes = []
        self.resize(800, 600)
        self.terminal = QWidget(self)
        layout = QVBoxLayout(self)
        layout.addWidget(self.terminal)
        self._stop_process()
        self._start_process(
            'xterm',
            ['-into', str(self.terminal.winId()),
             '-e', 'tmux', 'new', '-s', 'my_session']
        )
        button = QPushButton('List files')
        layout.addWidget(button)
        button.clicked.connect(self._list_files)

    def _start_process(self, prog, args):
        child = QProcess()
        self._processes.append(child)
        child.start(prog, args)

    def _list_files(self):
        self._start_process(
            'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter'])

    @classmethod
    def _stop_process(self):
        call(["tmux", "kill-session", "-t", "my_session"])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = embeddedTerminal()
    main.show()
    sys.exit(app.exec_())

Mike R
  • 679
  • 7
  • 13