-1

So I've been looking at this for quite some time and can't figure this problem out.

The goal here is to have it display realtime results line by line from the cmd/ terminal into the tkinter ScrolledText/Text widget, which it does except when ran through the cmd on Windows.

I am running the code on both Linux (Kali) and Windows 10. If I run it using Pycharm on Windows 10 then it will run smoothly and the GUI won't freeze waiting for the code run using Pexpect. If I run the code through the cmd (python tmp_stack.py) then it will freeze the GUI until Pexpect finishes running the file I asked it to.
On Linux terminal the code runs fine and doesn't freeze (after adjusting it for Linux).
I combined multiple files into tmp_stack.py to prevent the need to create more files for no real reason.
I have tested that the configuration runs the same on both Windows and Linux.

Side note: I don't mind changing to subprocess.Popen and I don't mind using threading if it won't complain about main loop and will work.

requirements.txt - pexpect==4.6.0

op.py:

import time

print('This is op.py')
for i in range(10):
    time.sleep(1)
    print(i)

oscheck.py:

import platform

MYPLATFORM = platform.system()

tmp_stack.py:

from tkinter import *
from tkinter.ttk import *
from tkinter.scrolledtext import ScrolledText

import oscheck

if oscheck.MYPLATFORM == 'Windows':
    from pexpect import popen_spawn
elif oscheck.MYPLATFORM == 'Linux':
    from pexpect import spawn


class TextFrame(Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        self.textarea = ScrolledText(master=self, wrap=WORD, bg='black', fg='white')
        self.textarea.pack(side=TOP, fill=BOTH, expand=True)

    def insert(self, text):
        self.textarea['state'] = 'normal'
        # Insert at the end of the TextArea.
        self.textarea.insert(END, text)
        self.textarea['state'] = 'disabled'
        self.update()


class TheFrame(TextFrame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        self.btn = Button(master=self, text="Run op", command=self.run_op)
        self.btn.pack(fill=X)
        # Ignore first child of pexpect on Linux because it is the bin bash prefix.
        self.linux_flag = True

    def run_op(self):
        filename = 'op.py'
        cmd = ['python', filename]

        self.cmdstdout = RunCommand(cmd)

        # Get root.
        root_name = self._nametowidget(self.winfo_parent()).winfo_parent()
        root = self._nametowidget(root_name)

        root.after(0, self.updateLines())

    def updateLines(self):
        for line in self.cmdstdout.get_child():
            if oscheck.MYPLATFORM == 'Linux' and self.linux_flag:
                self.linux_flag = False
                continue
            try:
                self.insert(line.decode('utf-8'))
            except TclError as e:
                print("Window has been closed.\n", e)
                self.close()
                break

    def close(self):
        self.cmdstdout.close()


class RunCommand:
    def __init__(self, command):
        command = ' '.join(command)
        if oscheck.MYPLATFORM == 'Windows':
            print('You are on Windows.')
            self.child = popen_spawn.PopenSpawn(command, timeout=None)
        elif oscheck.MYPLATFORM == 'Linux':
            print('You are on Linux.')
            self.child = spawn("/bin/bash", timeout=None)
            self.child.sendline(command)
        else:
            print('Not Linux or Windows, probably Mac')
            self.child = spawn(command, timeout=None)

    def get_child(self):
        return self.child

    def close(self):
        if oscheck.MYPLATFORM == 'Linux':
            self.child.terminate(True)


def on_close(root):
    root.quit()
    root.destroy()


root = Tk()

if oscheck.MYPLATFORM == 'Windows':
    root.state('zoomed')
elif oscheck.MYPLATFORM == 'Linux':
    root.attributes('-zoomed', True)

the_frame = TheFrame(root, padding="1")
the_frame.grid(column=0, row=0, sticky=N+W+S+E)

root.protocol("WM_DELETE_WINDOW", lambda: on_close(root))
mainloop()
BoobyTrap
  • 967
  • 7
  • 18

2 Answers2

3

Try running the child Python process with output buffering disabled.

In other words, try replacing the line

        cmd = ['python', filename]

with

        cmd = ['python', '-u', filename]

See also the Python documentation for the -u option.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
0

I had the same thing - a process spawned from my main script read the stdout (pexpect) in PyCharm, but not when launched from a shell.

The -u option fixed this, thanks.

GarethD
  • 112
  • 1
  • 8