2009-06-12 22 views
7

Chcę utworzyć okno wyskakujące za pomocą wxPython, które działa jak powłoka bash. Nie chcę emulatora terminala, nie potrzebuję kontroli pracy, chcę tylko REPL (Read, Eval, Print Loop) opartego na procesie basha.wxPython: jak utworzyć okno powłoki bash?

Czy istnieje prosty sposób na zrobienie tego za pomocą wxPython? Znam podstawowe pojęcie z moich czasów jako programista tcl/tk, ale mój wxPython fu jest słaby i nie chcę na nowo wymyślać koła, jeśli nie muszę. Czytałem trochę o py.shell. Powłoka, ale wygląda na to, że tworzy powłokę Pythona, a ja chcę zamiast niej uruchamiać polecenia Bash.

+0

Co to jest "REPL"? –

+1

Read-Eval-Print-Loop –

Odpowiedz

0

szukałem ale nie wydaje się być dowolny wychodzenia powłoki bash dla wxPython choć moduł wx.py posiada moduł Shell, która jest dla Pythona tłumacza Dobrą rzeczą jest można przekazać swój własny tłumacza do niego, więc mam pochodzą z bardzo prostym tłumaczem bash. przykład obecnie czyta tylko jedną linię z bash stdout, inaczej będzie utknąć, w rzeczywistym kodu należy odczytać wyjście w wątku lub wykorzystać wybrać

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() 
+0

Dlaczego -1? proszę przynajmniej komentarz po oddaniu głosu, więc mam co najmniej szansę poprawić się, nie widzę powodu do głosowania w dół? –

+0

Wznosiłem głos, ponieważ to nie działa kod. Biorąc pod uwagę, że istnieje bounty, pasek jest nieco wyższy niż zwykle tutaj. OK, kod działa bezbłędnie, ale nie jest to coś, co może być użyte tak jak jest, a nie kiedy czyta po jednym wierszu danych wyjściowych po każdym poleceniu. Doceniam wskaźnik do widżetu wx.py.Shell i jak stworzyć specjalnego tłumacza, ale to wciąż za mało IMO, aby zarobić nagrodę. –

+3

To nie ma być usługa wynajmująca, w której oczekuje się ode mnie pełnego rozwiązania, myślę, że wszystko, co dałem, było dobrym punktem wyjścia i mam uzasadniony powód, dla którego powinienem ją ulepszyć, np. Powiedziałem, że używam readline, ponieważ czytanie wszystkich dat blokuje , więc jeśli nie podoba ci się to, że nie masz sleetc i nie dostanę nagrody, ale przez głosowanie w dół zniechęcasz mnie do podawania prawidłowych wskazówek dla końcowego rozwiązania –

0

Going to zobaczyć, co mogę wymyślić.

Ale jeśli zmienisz zdanie i zdecydujesz się skorzystać pygtk zamiast, to jest tutaj:

enjoy!!

EDIT

zacząłem tworzyć wersję człowiek ubogi w terminalu za pomocą tekstu widżet kontrolny. Zatrzymałem się, ponieważ istnieją usterki, których nie można naprawić, na przykład podczas korzystania z polecenia 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 = "[email protected]:~ " 
     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() 

Jeśli naprawdę tego pragniesz, możesz na to popracować.

+0

pygtk nie jest opcją, dotyczy istniejącej aplikacji z kilkoma tysiącami linii wxPython. Osobiście, gdybym miał wybór, użyłbym Tkintera. –

+0

Tworzysz proces dla każdego polecenia? To nie zadziała, ponieważ zmiana katalogów nie będzie trwała od jednego polecenia do drugiego. Nie obsługujesz również poleceń wieloliniowych. Dzięki i tak. –

+0

Bez problemu. Spróbowałbym pytać w #wxwidgets na freenode. – sqram

6

ok, tutaj jest kolejna próba, która odczytuje wszystkie dane wyjściowe i błędy również w osobnym wątku i komunikuje się za pośrednictwem kolejki. Wiem, że to nie jest doskonałe (np. Polecenie z opóźnionym wyjściem nie zadziała i tam dane wyjściowe przejdą do następnego polecenia, na przykład tryr sleep 1; date) i replikowanie całego basha nie jest trywialne, ale dla kilku poleceń testowałem, wydaje się, że działa dobrze

Jeśli chodzi o interfejs API wx.py.shell, właśnie zaimplementowałem metodę, którą klasa Shell nazywała Interpreter, jeśli przejdziesz przez kod źródłowy powłoki, zrozumiesz. zasadzie

  • Push gdzie użytkownik wszedł komenda jest wysyłana do Interpreter
  • getAutoCompleteKeys zwraca klucze które użytkownik może użytkownik za auto ukończenie polecenia np Zakładka klucz
  • getAutoCompleteList lista powrót dopasowania poleceń danego tekstu

  • getCallTip „specyfikację argumentów Wyświetlacz i docstring w oknie popup.tak dla bash możemy wyświetlić stronę podręcznika :)

tutaj jest kod źródłowy

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() 
+0

Ten przykład jest użyteczny. Wciąż ma pewne problemy, ale myślę, że wystarczy, że dam mi pojęcie o tym, co można i czego nie można zrobić. Jeśli lepsza odpowiedź nie pojawi się w ciągu najbliższych kilku dni, zaakceptuję tę. –

+0

Wiem, że to pytanie jest stare, ale nie mogłem znaleźć nic lepszego, więc mam nadzieję zadać następne pytanie w tej sprawie. Chciałbym mieć dwie powłoki ShellFrame pionowo w jednym oknie! Czy to możliwe? Korzystając z powyższego kodu, mogę otworzyć tylko dwie powłoki shellFrame, ale nie są one zamknięte w jednym oknie. – theAlse