Hi friends, this quick intro is about the Terminal console using PyQt5. The code is self explanatory. We will use two Widgets as small windows in the main gui namely, self.cmdWindow
and self.textWindow
. The former is responsible for entering the commands and the later is for displaying the output as the user will press Enter key. We will set the style of the main window using self.stylesheet() function. A function named self.run() will execute the command. You can add more functionalities accordingly.
Copy the main.py
code and run in Python3 as:
python3 main.py
main.py
from PyQt5.QtCore import QProcess, QStandardPaths, Qt, QEvent, QSettings, QPoint, QSize
from PyQt5.QtWidgets import QWidget, QApplication, QPlainTextEdit, QVBoxLayout, QMainWindow
from PyQt5.QtGui import QTextCursor
import sys
import shlex
import getpass
import socket
import os
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.cmdlist = []
self.track = 0
os.chdir(os.path.expanduser("~"))
self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname())
+ ":" + str(os.getcwd()) + "$ ")
self.setWindowTitle('Terminal')
self.proc = QProcess(self)
self.proc.setProcessChannelMode(QProcess.MergedChannels)
self.proc.readyRead.connect(self.dataReady)
self.proc.finished.connect(self.isFinished)
self.proc.setWorkingDirectory(os.getcwd())
self.cmdWindow = QPlainTextEdit()
self.cmdWindow.setLineWrapMode(QPlainTextEdit.NoWrap)
self.cmdWindow.setFixedHeight(55)
self.cmdWindow.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.cmdWindow.setAcceptDrops(True)
self.cursor = self.cmdWindow.textCursor()
self.textWindow = QPlainTextEdit(self)
self.setStyleSheet(stylesheet(self))
self.textWindow.setReadOnly(True)
layout = QVBoxLayout()
layout.addWidget(self.cmdWindow)
layout.addWidget(self.textWindow)
self.wid = QWidget()
self.wid.setLayout(layout)
self.setCentralWidget(self.wid)
self.setGeometry(0, 0, 640, 480)
self.cmdWindow.setPlainText(self.name)
self.cursorEnd()
self.cmdWindow.setFocus()
self.cmdWindow.installEventFilter(self)
QApplication.setCursorFlashTime(1000)
self.cursorEnd()
print(self.proc.workingDirectory())
self.settings = QSettings("QTerminal", "QTerminal")
self.readSettings()
def closeEvent(self, e):
self.writeSettings()
def cursorEnd(self):
self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname())
+ ":" + str(os.getcwd()) + "$ ")
self.cmdWindow.setPlainText(self.name)
cursor = self.cmdWindow.textCursor()
cursor.movePosition(11, 0)
self.cmdWindow.setTextCursor(cursor)
self.cmdWindow.setFocus()
def eventFilter(self, source, event):
if source == self.cmdWindow:
if (event.type() == QEvent.DragEnter):
event.accept()
return True
elif (event.type() == QEvent.Drop):
print ('Drop')
self.setDropEvent(event)
return True
elif (event.type() == QEvent.KeyPress):
cursor = self.cmdWindow.textCursor()
if event.key() == Qt.Key_Backspace:
if cursor.positionInBlock() <= len(self.name):
return True
else:
return False
elif event.key() == Qt.Key_Return:
self.run_command()
return True
elif event.key() == Qt.Key_Left:
if cursor.positionInBlock() <= len(self.name):
return True
else:
return False
elif event.key() == Qt.Key_Delete:
if cursor.positionInBlock() <= len(self.name) - 1:
return True
else:
return False
elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_C:
return True
elif event.key() == Qt.Key_Up:
try:
if self.track != 0:
cursor.select(QTextCursor.BlockUnderCursor)
cursor.removeSelectedText()
self.cmdWindow.appendPlainText(self.name)
self.cmdWindow.insertPlainText(self.cmdlist[self.track])
self.track -= 1
except IndexError:
self.track = 0
return True
elif event.key() == Qt.Key_Down:
try:
if self.track != 0:
cursor.select(QTextCursor.BlockUnderCursor)
cursor.removeSelectedText()
self.cmdWindow.appendPlainText(self.name)
self.cmdWindow.insertPlainText(self.cmdlist[self.track])
self.track += 1
except IndexError:
self.track = 0
return True
else:
return False
else:
return False
else:
return False
def copyText(self):
self.textWindow.copy()
def pasteText(self):
self.cmdWindow.paste()
def setDropEvent(self, event):
self.cmdWindow.setFocus()
if event.mimeData().hasUrls():
f = str(event.mimeData().urls()[0].toLocalFile())
print("is file:", f)
if " " in f:
self.cmdWindow.insertPlainText("'{}'".format(f))
else:
self.cmdWindow.insertPlainText(f)
event.accept()
elif event.mimeData().hasText():
ft = event.mimeData().text()
print("is text:", ft)
if " " in ft:
self.cmdWindow.insertPlainText("'{}'".format(ft))
else:
self.cmdWindow.insertPlainText(ft)
else:
event.ignore()
def run_command(self):
"""This function will be called once a command is written and the Enter key is pressed.
"""
print("started")
cli = []
cmd = ""
t = ""
self.textWindow.setFocus()
self.textWindow.appendPlainText(self.cmdWindow.toPlainText())
cli = shlex.split(self.cmdWindow.toPlainText().replace(self.name, '').replace("'", '"'), posix=False)
cmd = str(cli[0])
if cmd == "exit":
quit()
elif cmd == "cd":
del cli[0]
path = " ".join(cli)
os.chdir(os.path.abspath(path))
self.proc.setWorkingDirectory(os.getcwd())
print("Directory:", self.proc.workingDirectory())
self.cursorEnd()
else:
self.proc.setWorkingDirectory(os.getcwd())
print("Directory", self.proc.workingDirectory())
del cli[0]
if (QStandardPaths.findExecutable(cmd)):
self.cmdlist.append(self.cmdWindow.toPlainText().replace(self.name, ""))
print("Command", cmd, "found")
t = " ".join(cli)
if self.proc.state() != 2:
self.proc.waitForStarted()
self.proc.waitForFinished()
if "|" in t or ">" in t or "<" in t:
print("special characters")
self.proc.start('sh -c "' + cmd + ' ' + t + '"')
print("running",('sh -c "' + cmd + ' ' + t + '"'))
else:
self.proc.start(cmd + " " + t)
print("running", (cmd + " " + t))
else:
print("Command not found ...")
self.textWindow.appendPlainText("Command not found ...")
self.cursorEnd()
def dataReady(self):
out = ""
try:
out = str(self.proc.readAll(), encoding = 'utf8').rstrip()
except TypeError:
out = str(self.proc.readAll()).rstrip()
self.textWindow.moveCursor(self.cursor.Start)
self.textWindow.appendPlainText(out)
def isFinished(self):
self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname())
+ ":" + str(os.getcwd()) + "$ ")
self.cmdWindow.setPlainText(self.name)
self.cursorEnd()
def readSettings(self):
if self.settings.contains("commands"):
self.cmdlist = self.settings.value("commands")
if self.settings.contains("pos"):
pos = self.settings.value("pos", QPoint(200, 200))
self.move(pos)
if self.settings.contains("size"):
size = self.settings.value("size", QSize(400, 400))
self.resize(size)
def writeSettings(self):
self.settings.setValue("commands", self.cmdlist)
self.settings.setValue("pos", self.pos())
self.settings.setValue("size", self.size())
def stylesheet(self):
"""Here you can set the fond color of the text and the background"""
return """
QMainWindow{
background-color: #FFFFFF; }
QMainWindow:title
{
background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #ca0619);
color: #3465a4;
}
QPlainTextEdit
{font-family: Noto Sans Mono;
font-size: 10pt;
background-color: #000000;
color: #1aee30; padding: 2;
border: none;}
QPlainTextEdit:focus {
border: none; }
QScrollBar {
border: 1px solid #2e3436;
background: #292929;
width:8px;
height: 8px;
margin: 0px 0px 0px 0px;
}
QScrollBar::handle {
background: #2e3436;
min-height: 0px;
min-width: 0px;
}
QScrollBar::add-line {
background: #2e3436;
height: 0px;
width: 0px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line {
background: #2e3436;
height: 0px;
width: 0px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QStatusBar {
font-family: Noto Sans Mono;
font-size: 7pt;
color: #1aee30;}
"""
def main():
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
ex.raise_()
sys.exit(app.exec_())
if __name__ == '__main__':
main()