0

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?

MIku
  • 89
  • 6
  • Sending Ctrl+C on Windows to a subprocess is not trivial, see https://stackoverflow.com/questions/7085604/sending-c-to-python-subprocess-objects-on-windows for further details. *Sending* `b'\x03'` will not be equivalent to Ctrl+C on any platform. (On Linux/macOS/etc. you can use `.send_signal(signal.SIGINT)` and thereafter `.communicate()`.) – chris_se Mar 06 '23 at 22:39
  • After some research and fun with chatgpt I decided to collect ping statistics with my ping3 fork. But, this question is still important for me. So, everybody are welcome to answer :) – MIku Mar 08 '23 at 03:10

1 Answers1

1

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:

  1. Use 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.
  2. To terminate the 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()
steTATO
  • 550
  • 4
  • 12
  • Thank you very much for your response. As i can see, the `line = self.process.communicate()` causes console listening in endless loop, bc ping -t calls ping with no time limit. So, it's not possible to receive any text line from cmd, as long as `communicate()` terminates when it reaches the end of file. But ping -t = no end of file. (I just don't know how to describe code freezing on that line :") – MIku Mar 08 '23 at 19:43
  • @Miku I am really sorry, I posted the wrong code on the first try... Here is an updated version. You are right, that `communicate` waits for new `STDOUT` to come. However, we can only print a fixed number of lines after sending the `CTRL_BREAK_EVENT` signal. – steTATO Mar 09 '23 at 09:37
  • One more question. Is it possible to sort lines from console? I mean, is it possible to understand which line is response and which is error (like Timeout). because it's not usefull to parse strings with some flag words. Simply, i can setup flag word 'error' and try to ping 'error.net' as example. And every response will be classified as an error. So, it it possible to sort which line is good response and which one is bad. (May be with stderr, stdout options?) – MIku Mar 09 '23 at 19:50
  • this, this is amazing. really workable solution. I tried to collect statistics from responses and calculate my own statistics message after closing ping thread. But, i like your solution much more! Thank you so much. – MIku Mar 09 '23 at 19:51