I have this code, product of my imagination and ChatGPT help:
import subprocess
import threading
import tkinter as tk
class PingThread(threading.Thread):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
self.process = None
self.stop_event = threading.Event()
def run(self):
self.process = subprocess.Popen(['cmd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
self.process.stdin.write(b'ping -t google.com\n')
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode('cp866')
if not line:
break
self.text_widget.insert(tk.END, line)
self.text_widget.see(tk.END)
def stop(self):
if self.process:
self.process.communicate(b'\x03')
self.process.wait() #Window freezes on the .communicate line, so this and line below are not executing at all
self.stop_event.set()
def ping():
ping_thread = PingThread(text)
ping_thread.start()
def handle_ctrl_c(event):
ping_thread.stop()
text.insert(tk.END, '\nProcess terminated.\n')
root.bind('<Control-c>', handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='Ping', command=ping)
button.pack()
root.mainloop()
I'm trying to create a console simulation in Tkinter. I'm sending ping request and listening console for it's response. All works well, except Ctrl+C
command, which should finishes ping
execution and response the statistics from console. Window just freezing, when i try to send self.process.communicate(b'\x03')
What causes that? As i understand, this line should send Ctrl+C
to the console and while loop
should receive last lines from the console, with ping's statistics?
So now that I better understand your issue, here is another try. My answer is not working perfectly, because I use a workaround to get the last 4 lines after sending CTRL_BREAK_EVENT
However, for your question the 2 most important things:
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
when you create your process. If I understand it correctly, this creates a different process for the terminal and the ping command, so you can still get the output from the terminal after passing the stop signal.ping
command, send signal.CTRL_BREAK_EVENT
on Windows. In general, these signals seem to be not consistent for operating systems. (see also the comment from @chris_se)The workaround I use is, to only read the next 4 lines of STDOUT
after setting the stop event (exiting the while loop). I know that the statistics summary of the ping
command will print this number of lines. Of course we could increase it to be sure to not miss anything.
Here is the code:
import subprocess
import threading
import tkinter as tk
import signal
class PingThread(threading.Thread):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
self.process = None
self.stop_event = threading.Event()
def run(self):
self.process = subprocess.Popen(['cmd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
self.process.stdin.write(b'ping -t google.com\n')
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode('cp866')
if not line:
break
self.text_widget.insert(tk.END, line)
self.text_widget.see(tk.END)
self.text_widget.insert(tk.END, self.process.stdout.readline().decode('cp866'))
for i in range(4):
self.text_widget.insert(tk.END, self.process.stdout.readline().decode('cp866'))
self.text_widget.see(tk.END)
i += 1
self.process.stdout.close()
self.process.kill()
self.text_widget.insert(tk.END, '\nProcess terminated.\n')
root.unbind('<Control-c>')
def stop(self):
if self.process:
self.stop_event.set()
self.process.send_signal(signal.CTRL_BREAK_EVENT)
def ping():
ping_thread = PingThread(text)
ping_thread.start()
def handle_ctrl_c(event):
ping_thread.stop()
root.bind('<Control-c>', handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='Ping', command=ping)
button.pack()
root.mainloop()