0

I have gone through many solutions on stackoverflow, but none was helpful to me. I'm stuck on implementing cmd into tkinter to see output inside of gui and be able to enter values there. I appreciate any help, thanks for advance!

from subprocess import Popen
from tkinter import Tk, Button, messagebox, Label
from PIL import ImageTk, Image


gui = Tk(className='IDPass')
gui.geometry('500x500')
gui.iconbitmap('Turnstile/icons/mini_logo.ico')
img = ImageTk.PhotoImage(Image.open('Turnstile/icons/logo.png'))
panel = Label(gui, image=img)


def run_server():
    global process
    process = Popen(['python', 'C:/Test/Turnstile/manage.py', 'runserver'])

def run_rfid_scanner():
    global process
    process = Popen('python C:/Test/Turnstile/rfid_scanner.py')
    
def run_face_scanner():
    global process
    process = Popen('python C:/Test/Turnstile/face_scanner.py')
    
def run_photo_deleter():
    global process
    process = Popen('python C:/Test/Turnstile/photo_deleter.py')
    
def run_face_recognizer():
    global process
    process = Popen('python C:/Test/Turnstile/face_recognizer.py')

def stop_program():
    process.kill()
    messagebox.showinfo('Информационное окно', 'Программа остановлена')


server = Button(gui, text='Запустить сервер', command=run_server, bg='green')
rfid_scanner = Button(gui, text='Запустить RFID сканер', command=run_rfid_scanner, bg='green')
face_scanner = Button(gui, text='Добавить фото для сканирования', command=run_face_scanner, bg='green')
face_recognizer = Button(gui, text='Начать распознавание лица', command=run_face_recognizer, bg='green')

photo_deleter = Button(gui, text='Удалить фото пользователя', command=run_photo_deleter, bg='grey')
stop_programm = Button(gui, text='Остановить выполнение программы', command=stop_program, bg='grey')

panel.pack()
server.pack()
rfid_scanner.pack()
face_scanner.pack()
face_recognizer.pack()
photo_deleter.pack()
stop_programm.pack()

gui.mainloop()

This is how I want to see it

enter image description here

2
  • Does this answer your question? Redirect command line results to a tkinter GUI Commented Sep 3, 2021 at 6:07
  • I've tried that, but it didn't help me unfortunately. Window opens and closes automatically Commented Sep 3, 2021 at 6:08

2 Answers 2

1

One of the way is:

  • create a Text box to show the command output
  • create a threaded task to get the process output and put the output in a queue
  • create a periodic task to get output from the queue and insert it into text box
  • redirect command output using subprocess.PIPE
import sys
import threading
from queue import Queue
from subprocess import Popen, PIPE
from tkinter import Tk, Button, messagebox, Label, Text
...
process = None
queue = Queue()

def run_server():
    global process
    if process:
        process.terminate()
    #process = Popen(['python', 'C:/Test/Turnstile/manage.py', 'runserver'])
    process = Popen([sys.executable, '-u', 'C:/Test/Turnstile/manage.py', 'runserver'], stdout=PIPE, bufsize=1, text=True)

...

output = Text(gui, width=100, height=20)
output.pack(padx=20, pady=20)

def monitor_output(q):
    while True:
        if process and process.stdout:
            msg = process.stdout.readline()
            if msg:
                q.put(msg)

def check_output(q):
    while not q.empty():
        output.insert('end', q.get())
        output.see('end')
    gui.after(10, check_output, q)

threading.Thread(target=monitor_output, args=[queue], daemon=True).start()
check_output(queue)

gui.mainloop()

Note that I have used sys.executable instead of 'python' to make sure same Python interpreter is used.

Sign up to request clarification or add additional context in comments.

14 Comments

This is what I need, thank you! But I have a problem, monitor_output() blocks a thread and my window is not responsible to anything, so I can't even run another functions. How to fix it?
monitor_output() is just a periodic task using after() and it should not block other thread. Also did you modify other Popen(...) to use the same options in my answer?
Yes, I did same with another function, but once I click "Run server" button my gui freezes and I can't press button to run another function. I probably should go with a new thread for that
@acw1668 I think that process.stdout.readline() is a blocking read. So if the new process is running but isn't outputting to stdout, the .readline() will block that thread.
@Temik26 Answer updated to use threading to read command output. Used Queue to send back the output to main task and use after() to read the output periodically.
|
1

Try this:

from subprocess import Popen, PIPE
from threading import Thread, Lock
import tkinter as tk


class TkinterPopen(tk.Text):
    def __init__(self, master, state="disabled", **kwargs):
        super().__init__(master, state=state, **kwargs)
        self.commands = []
        self.proc = None
        self.running = True
        self.stdout_buffer = ""
        self.stdout_buffer_lock = Lock()

    def stdout_loop(self, last_loop:bool=False) -> None:
        with self.stdout_buffer_lock:
            # Get the data and clear the buffer:
            data, self.stdout_buffer = self.stdout_buffer, ""
        state = super().cget("state")
        super().config(state="normal")
        super().insert("end", data)
        super().see("end")
        super().config(state=state)
        if self.proc is None:
            if len(self.commands) == 0:
                # If we are done with all of the commands:
                if last_loop:
                    return None
                super().after(100, self.stdout_loop, True)
            else:
                # If we have more commands to do call `start_next_proc`
                self.start_next_proc()
        else:
            super().after(100, self.stdout_loop)

    def start_next_proc(self) -> None:
        command = self.commands.pop(0) # Take the first one from the list
        self.proc = Popen(command, stdout=PIPE)
        new_thread = Thread(target=self.read_stdout, daemon=True)
        new_thread.start()
        self.stdout_loop()

    def run_commands(self, commands:list) -> None:
        self.commands = commands
        self.start_next_proc()

    def read_stdout(self):
        while self.proc.poll() is None:
            self._read_stdout()
        self._read_stdout()
        self.proc = None

    def _read_stdout(self) -> None:
        line = self.proc.stdout.readline()
        with self.stdout_buffer_lock:
            self.stdout_buffer += line.decode()


if __name__ == "__main__":
    def start_echo():
        command = ["echo", "hi"]
        tkinter_popen.run_commands([command])

    def start_ping():
        # For linux use "-c". For windows use "-n"
        command = ["ping", "1.1.1.1", "-n", "3"]
        tkinter_popen.run_commands([command])

    root = tk.Tk()

    tkinter_popen = TkinterPopen(root)
    tkinter_popen.pack()

    button = tk.Button(root, text="Run echo", command=start_echo)
    button.pack()

    button = tk.Button(root, text="Run ping", command=start_ping)
    button.pack()

    root.mainloop()

I think this is the functionality that you wanted. The code is similar to @acw1668 but I read stdout in another thread and out the data in a queue named self.stdout_buffer.

This is just a copy of the answer I gave here.

8 Comments

I just tried to put one of my script in command like this: commands = [sys.executable, '-u', 'C:/Test/Turnstile/manage.py', 'runserver'] and it opened python interpreter for me. Was it a wrong way?
Try commands = [[sys.executable, '-u', 'C:/Test/Turnstile/manage.py', 'runserver']]. commands is meant to be a list of different commands to run. In the example I put 3 commands just to show how it works.
You made 1 button to run all commands as I see, but I need to run it separately, I mean 1 button = 1 program
@Temik26 i updated my answer and added another button.
Yes, did it. Is it possible to make cmd input as well? Now it's output only
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.