我想使用 wxPython 创建一个弹出窗口,其行为类似于 bash shell。我不需要终端模拟器,不需要作业控制,我只想要一个基于 bash 进程的 REPL(读取、评估、打印循环)。
有没有一种简单的方法可以使用 wxPython 来做到这一点?作为一名 tcl/tk 程序员,我知道基本概念,但我的 wxPython 能力很弱,如果不需要的话,我不想重新发明轮子。我读过一些有关 py.shell 的内容。 Shell 但看起来它创建了一个 python shell,我想要一个来运行 bash 命令。
好的,这是另一个尝试,它也在一个单独的线程中读取所有输出和错误,并通过队列进行通信。 我知道它并不完美(例如,带有延迟输出的命令将不起作用,并且输出将进入下一个命令,例如 tryr sleep 1; date)并且复制整个 bash 并不简单,但对于我测试的几个命令来说,它似乎工作得很好
关于wx.py.shell的API,我只是实现了Shell类为Interpreter调用的那些方法,如果你仔细阅读Shell的源代码你就会明白。 基本上
getAutoCompleteList 返回列表 匹配给定文本的命令
getCallTip "显示参数规范和 弹出窗口中的文档字符串。因此对于 bash 我们可能会显示手册页:)
这是源代码
import threading
import Queue
import time
import wx
import wx.py
from subprocess import Popen, PIPE
class BashProcessThread(threading.Thread):
def __init__(self, readlineFunc):
threading.Thread.__init__(self)
self.readlineFunc = readlineFunc
self.outputQueue = Queue.Queue()
self.setDaemon(True)
def run(self):
while True:
line = self.readlineFunc()
self.outputQueue.put(line)
def getOutput(self):
""" called from other thread """
lines = []
while True:
try:
line = self.outputQueue.get_nowait()
lines.append(line)
except Queue.Empty:
break
return ''.join(lines)
class MyInterpretor(object):
def __init__(self, locals, rawin, stdin, stdout, stderr):
self.introText = "Welcome to stackoverflow bash shell"
self.locals = locals
self.revision = 1.0
self.rawin = rawin
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.more = False
# bash process
self.bp = Popen('bash', shell=False, stdout=PIPE, stdin=PIPE, stderr=PIPE)
# start output grab thread
self.outputThread = BashProcessThread(self.bp.stdout.readline)
self.outputThread.start()
# start err grab thread
self.errorThread = BashProcessThread(self.bp.stderr.readline)
self.errorThread.start()
def getAutoCompleteKeys(self):
return [ord('\t')]
def getAutoCompleteList(self, *args, **kwargs):
return []
def getCallTip(self, command):
return ""
def push(self, command):
command = command.strip()
if not command: return
self.bp.stdin.write(command+"\n")
# wait a bit
time.sleep(.1)
# print output
self.stdout.write(self.outputThread.getOutput())
# print error
self.stderr.write(self.errorThread.getOutput())
app = wx.PySimpleApp()
frame = wx.py.shell.ShellFrame(InterpClass=MyInterpretor)
frame.Show()
app.SetTopWindow(frame)
app.MainLoop()
我找到了解决问题的方法。有趣的是,它以前从未出现在谷歌搜索中。它不是生产就绪的代码,但最终,我正在寻找一种在 wxPython 窗口中运行 bash shell 的方法。
http://sivachandran.blogspot.com/2008/04/termemulator-10-released.html 在网络存档上 https://sourceforge.net/projects/termemulator/files/TermEmulator/1.0/
我搜索过,但似乎没有任何现有的 wxPython bash shell 虽然 wx.py 模块有用于 python 解释器的 Shell 模块 好处是你可以将你自己的解释器传递给它,所以我提供了非常简单的 bash 解释器。 示例当前仅从 bash stdout 读取一行,否则会卡住, 在实际代码中,您必须读取线程中的输出或使用 select
import wx
import wx.py
from subprocess import Popen, PIPE
class MyInterpretor(object):
def __init__(self, locals, rawin, stdin, stdout, stderr):
self.introText = "Welcome to stackoverflow bash shell"
self.locals = locals
self.revision = 1.0
self.rawin = rawin
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
#
self.more = False
# bash process
self.bp = Popen('bash', shell=False, stdout=PIPE, stdin=PIPE, stderr=PIPE)
def getAutoCompleteKeys(self):
return [ord('\t')]
def getAutoCompleteList(self, *args, **kwargs):
return []
def getCallTip(self, command):
return ""
def push(self, command):
command = command.strip()
if not command: return
self.bp.stdin.write(command+"\n")
self.stdout.write(self.bp.stdout.readline())
app = wx.PySimpleApp()
frame = wx.py.shell.ShellFrame(InterpClass=MyInterpretor)
frame.Show()
app.SetTopWindow(frame)
app.MainLoop()
看看我能想出什么。
但是如果你改变主意并决定使用 pygtk 来代替,那就是:
编辑
我开始使用文本控制小部件制作一个穷人版本的终端。 我停止是因为存在无法修复的缺陷,例如当您使用 sudo 命令时。
import wx
import subprocess
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MyFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.prompt = "user@stackOvervlow:~ "
self.textctrl = wx.TextCtrl(self, -1, '', style=wx.TE_PROCESS_ENTER|wx.TE_MULTILINE)
self.default_txt = self.textctrl.GetDefaultStyle()
self.textctrl.AppendText(self.prompt)
self.__set_properties()
self.__do_layout()
self.__bind_events()
def __bind_events(self):
self.Bind(wx.EVT_TEXT_ENTER, self.__enter)
def __enter(self, e):
self.value = (self.textctrl.GetValue())
self.eval_last_line()
e.Skip()
def __set_properties(self):
self.SetTitle("Poor Man's Terminal")
self.SetSize((800, 600))
self.textctrl.SetFocus()
def __do_layout(self):
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add(self.textctrl, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
def eval_last_line(self):
nl = self.textctrl.GetNumberOfLines()
ln = self.textctrl.GetLineText(nl-1)
ln = ln[len(self.prompt):]
args = ln.split(" ")
proc = subprocess.Popen(args, stdout=subprocess.PIPE)
retvalue = proc.communicate()[0]
c = wx.Colour(239, 177, 177)
tc = wx.TextAttr(c)
self.textctrl.SetDefaultStyle(tc)
self.textctrl.AppendText(retvalue)
self.textctrl.SetDefaultStyle(self.default_txt)
self.textctrl.AppendText(self.prompt)
self.textctrl.SetInsertionPoint(GetLastPosition() - 1)
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
如果真的想要,可以解决这个问题。
清理后,这适用于 Windows 12。错误将转发到 STDOUT。 提示之后没有换行符:
import wx
import subprocess
from pprint import pprint as pp
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MyFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.prompt = "user@stackOvervlow:~ "
self.textctrl = wx.TextCtrl(self, -1, '', style=wx.TE_PROCESS_ENTER|wx.TE_MULTILINE)
self.default_txt = self.textctrl.GetDefaultStyle()
self.textctrl.AppendText(self.prompt)
self.__set_properties()
self.__do_layout()
self.__bind_events()
def __bind_events(self):
self.Bind(wx.EVT_TEXT_ENTER, self.__enter)
def __enter(self, e):
print('__enter')
self.value = (self.textctrl.GetValue())
self.eval_last_line()
#e.Skip()
def __set_properties(self):
self.SetTitle("Poor Man's Terminal")
self.SetSize((800, 600))
self.textctrl.SetFocus()
def __do_layout(self):
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add(self.textctrl, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
def eval_last_line(self):
nl = self.textctrl.GetNumberOfLines()
ln = self.textctrl.GetLineText(nl-1)
self.prompt = self.prompt.strip()
ln = ln[len(self.prompt):]
args = ln.split(" ")
pp(args)
proc = subprocess.Popen(['cmd', '/C']+args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
retvalue = proc.communicate()[0]
c = wx.Colour(239, 177, 177)
tc = wx.TextAttr(c)
self.textctrl.SetDefaultStyle(tc)
self.textctrl.AppendText(b'\n' + retvalue)
self.textctrl.SetDefaultStyle(self.default_txt)
self.textctrl.AppendText('\n' + self.prompt)
self.textctrl.SetInsertionPointEnd()
pp(self.textctrl.GetValue())
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()