Artem
Artem

Reputation: 35

How to implement cmd output into tkinter gui?

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

Upvotes: 0

Views: 1531

Answers (2)

TheLizzard
TheLizzard

Reputation: 7710

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.

Upvotes: 1

acw1668
acw1668

Reputation: 47194

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.

Upvotes: 1

Related Questions