Skip to content

Instantly share code, notes, and snippets.

@zed
Last active March 30, 2023 18:24
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save zed/42324397516310c86288 to your computer and use it in GitHub Desktop.
Save zed/42324397516310c86288 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
"""
- read output from a subprocess in a background thread
- show the output in the GUI
- stop subprocess using a Tkinter button
https://stackoverflow.com/questions/15362372/display-realtime-output-of-a-subprocess-in-a-tkinter-widget
"""
from __future__ import print_function
import sys
from subprocess import Popen, PIPE, STDOUT
from textwrap import dedent
from threading import Thread
try:
import Tkinter as tk
except ImportError:
import tkinter as tk # Python 3
info = print
class StopProcessDemo:
def __init__(self, root):
self.root = root
# show subprocess' stdout in GUI
self._var = tk.StringVar() # put subprocess output here
tk.Label(root, textvariable=self._var).pack()
# to access event's .data use tcl to bind the event
# https://stackoverflow.com/questions/41912004/how-to-use-tcl-tk-bind-function-on-tkinters-widgets-in-python
on_reading_line = root.register(self._var.set)
root.tk.call("bind", root, '<<line>>', on_reading_line + " %d")
# stop subprocess using a button
tk.Button(root, text="Stop subprocess", command=self.stop).pack()
for t in range(1):
# start dummy subprocess to generate some output
self.process = Popen([sys.executable, "-u", "-c", dedent("""
import itertools, sys, time
for i in itertools.count():
print(i, sys.argv[1])
time.sleep(0.1)
"""), str(t)], stdout=PIPE, stderr=STDOUT, universal_newlines=True)
# launch thread to read the subprocess output:
# process.readline -> event <<line>> -> label
t = Thread(target=self.reader_thread,
# https://mail.python.org/pipermail/tkinter-discuss/2013-November/003526.html
args=[lambda line: root.event_generate('<<line>>', when='tail', data=line)])
t.daemon = True # close pipe if GUI process exits
t.start()
def reader_thread(self, emit):
"""Read subprocess output and emit lines."""
with self.process.stdout as pipe:
for line in iter(pipe.readline, ''):
emit(line)
def stop(self):
"""Stop subprocess and quit GUI."""
info('stoping')
self.process.terminate() # tell the subprocess to exit
# kill subprocess if it hasn't exited after a countdown
def kill_after(countdown):
if self.process.poll() is None: # subprocess hasn't exited yet
countdown -= 1
if countdown < 0: # do kill
info('killing')
self.process.kill() # more likely to kill on *nix
else:
self.root.after(100, kill_after, countdown)
return # continue countdown
# clean up
self.process.stdout.close() # close fd
self.process.wait() # wait for the subprocess' exit
self.root.destroy() # exit GUI
kill_after(countdown=5)
root = tk.Tk()
app = StopProcessDemo(root)
root.protocol("WM_DELETE_WINDOW", app.stop) # exit subprocess if GUI is closed
root.mainloop()
info('exited')
@sandba66er
Copy link

God bless you! I'm working on my first python project with a limited coding background. After weeks of researching the possible methods to stream live data to my graph while keeping it interactive, I think this is the winner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment