Tkinter: Updating GUI from subprocess output in realtime - python

I've searched the whole internet to answer my problem, but nobody seems to have the same one: I'm trying to update my tkinter GUI dynamically from a subprocess output, which works fine, if I'm starting my GUI inside eclipse. BUT if I'm running it in the file explorer or in visual studio, the 'stdout.readline' command waits, until the subprocess is finished. Only then the complete output is printed to my textarea... I am working with a thread and I've tried 2 ways: one is shown in 'App.py', the other one is threading the 'read_update' method instead (and not using 'reader_thread' and 'update' methods).
Interesting sidenote: the sys.argv command in Test.py does not return my string "var_test". Can anyone tell me why?
My Classes are:
GUI.py
import Tkinter as tk
from App import App
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
root.mainloop()
App.py
#App.py
import Tkinter as tk
import tkFont as tkfont
import subprocess
from subprocess import Popen
from subprocess import PIPE
from itertools import islice
from threading import Thread
from ttk import Scrollbar
from Tkinter import *
from Queue import Queue, Empty
class App():
def __init__(self, root):
self.root = root
self.root.title_font = tkfont.Font(family = "Helvetica", size = 18, weight = "bold", slant = "italic")
Grid.columnconfigure(self.root, 5, weight = 1)
button_ok = tk.Button(self.root, text = "OK", width = 10, command = lambda: self.on_okay())
button_ok.grid(row = 7, column = 0, padx = (20,0), pady = 10, sticky = W)
xscrollbar = Scrollbar(self.root, orient=HORIZONTAL)
xscrollbar.grid(row=8, column=1, columnspan=4, sticky=E + W)
yscrollbar = Scrollbar(self.root, orient=VERTICAL)
yscrollbar.grid(row=8, column=5, sticky=N + S)
self.textarea = Text(self.root, wrap=NONE, bd=0,
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set)
self.textarea.grid(row=8, column=1, columnspan=4, rowspan=1,
padx=0, sticky=E + W + S + N)
def on_okay(self):
self.textarea.delete("1.0", END)
exec_path = r"\Test.py" #insert location of Test.py
self.process = subprocess.Popen([exec_path, 'var_test'], shell = True, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
self.q = Queue(maxsize = 1024)
t = Thread(target=self.reader_thread, args=[self.q])
t.daemon = True
t.start()
self.update(self.q)
def reader_thread(self, q):
try:
with self.process.stdout as pipe:
for line in iter(pipe.readline, b''):
q.put(line)
finally:
q.put(None)
def update(self, q):
for line in self.iter_except(q.get_nowait, Empty):
if line is None:
#self.quit()
return
else:
self.textarea.insert(INSERT, line)
self.textarea.yview(END)
break
self.root.after(40, self.update, q)
def iter_except(self, function, exception):
try:
while True:
yield function()
except exception:
return
def read_update(self):
while True:
line = self.process.stdout.readline()
if line == "" and self.process.poll() != None:
break
elif line == "":
pass
else:
self.textarea.insert(INSERT, line)
self.textarea.yview(END)
self.textarea.update_idletasks()
def quit(self):
try:
self.process.kill()
except AttributeError:
pass
finally:
self.root.destroy()
Test.py
import sys
from time import sleep
var = sys.argv
print var
for i in range(1, 10):
print i
print "finished printing numbers"
sleep(10)
print "finished"
Thank you for your help! I'm pretty desperate 'cause I've been trying to solve this problems for many hours now...

use sys.stdout.flush() after print

Related

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
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.
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.

Textbox not showing inserts in tkinter

import tkinter as tk
from tkinter import *
import time
def centertitle(e):
w = int(root.winfo_width() / 3.5)
s = 'Installation Wizard v1.0'.rjust(w//2)
root.title(s)
def textbox():
textbox=Text(root.canvas, width = 62, height = 25, state=DISABLED)
textbox.pack()
textbox.insert(constants.INSERT,'You text goes here')
#Configure tkinter window and title
root = Tk()
root.canvas = Canvas(root, width = 500, height = 404, bg="#3D3D3D", highlightthickness=0)
root.canvas.pack(expand=True, fill=BOTH)
root.iconbitmap(default='transparent.ico')
root.bind("<Configure>", centertitle)
root.resizable(False, False)
#Buttons
btn = tk.Button(root, text='Start Install', height = 2, width = 15)
btn['command'] = lambda b=btn:[b.pack_forget(), b.place_forget(), textbox()]
btn.pack(fill=BOTH)
btn.place(x=190, y=181)
root.mainloop()
What im trying to create, is a simple "install" gui, which i was gonna pack into an exe. all i really want is a button, that starts another python file (the one that installs everything) and when it calls print, i want it sent to the text box in the gui... its hard to explain but i hope you get what i mean
could someone help me im really confused with guis
code from other .py:
import os
import sys
import shutil
import subprocess;
from pathlib import Path
import psutil
import time
def startProgram(program):
SW_HIDE = 0
info = subprocess.STARTUPINFO()
info.dwFlags = subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = SW_HIDE
subprocess.Popen(program, startupinfo=info)
def terminateProgram(processName):
for proc in psutil.process_iter():
if processName.lower() in proc.name().lower():
proc.terminate()
def checkIfProcessRunning(processName):
for proc in psutil.process_iter():
try:
if processName.lower() in proc.name().lower():
return True
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return False;
st_inf = subprocess.STARTUPINFO()
st_inf.dwFlags = st_inf.dwFlags | subprocess.STARTF_USESHOWWINDOW
user_profile = os.environ['USERPROFILE']
appdata_local = os.environ['LOCALAPPDATA']
FolderDictionary = [(user_profile + '\spicetify-cli'), (user_profile + '\.spicetify'), (appdata_local + '\spotify')]
for x in FolderDictionary:
try:
shutil.rmtree(x)
print('"%s", has been deleted.\n' % x)
except OSError as e :
print('"%s", was not found.\n' % x)
print("Installing Spotify.\n")
terminateProgram('Spotify.exe')
startProgram('.\\data\spotify-1-1-62-583.exe')
while True:
if checkIfProcessRunning('spotify-1-1-62-583.exe') == False:
print("Finished Installing Spotify.\n")
terminateProgram('Spotify.exe')
break
print("Installing Spicetify.\n")
terminateProgram('powershell')
subprocess.Popen(["powershell","$v='2.5.0'; Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/khanhas/spicetify-cli/master/install.ps1' | Invoke-Expression\nspicetify\nspicetify backup apply enable-devtool"], startupinfo=st_inf)
while True:
if checkIfProcessRunning('powershell') == False:
print("Finished Installing Spicetify.\n")
terminateProgram('Spotify.exe')
break
print("Downloading Themes.\n")
terminateProgram('powershell')
subprocess.Popen(["powershell",'$sp_dir = "${HOME}\spicetify-cli"\n$zip_file = "${sp_dir}\Themes.zip"\n$download_uri = "https://github.com/morpheusthewhite/spicetify-themes/archive/refs/heads/v2.zip"\nInvoke-WebRequest -Uri $download_uri -UseBasicParsing -OutFile $zip_file\nExpand-Archive -Path $zip_file -DestinationPath $sp_dir -Force\nRemove-Item -Path $zip_file\nRemove-Item -LiteralPath "${HOME}\spicetify-cli\Themes" -Force -Recurse\nRename-Item "${HOME}\spicetify-cli\spicetify-themes-2" "${HOME}\spicetify-cli\Themes"\nRemove-Item "${HOME}\spicetify-cli\Themes\*.*" -Force -Recurse | Where { ! $_.PSIsContainer }\nRename-Item "${HOME}\spicetify-cli\Themes\default" "${HOME}\spicetify-cli\Themes\SpicetifyDefault"'], startupinfo=st_inf)
while True:
if checkIfProcessRunning('powershell') == False:
print("Finished Downloading Themes.\n")
break
You've set the state of the text widget to disabled, so you can't insert anything into it. If you want it to be disabled, disable it after inserting the text rather than before.
def textbox():
textbox=Text(root.canvas, width = 62, height = 25)
textbox.pack()
textbox.insert(constants.INSERT,'You text goes here')
textbox.configure(state=DISABLED)

subprocess not working after onefile pyinstaller even with stdout/stdin defined

I have a tkinter GUI with a text box and run button. Pressing the run button turns it to yellow and starts a subroutine that prints a few numbers. Text output from the subroutine is redirected to the GUI text box. However, after creating a standalone executable file with pyinstaller, it no longer works. Pressing the run button doesn't seem to start the subprocess. It does turn yellow, but no text appears in the text box and it seems to start another instance of the main program - a second GUI appears after about 10 seconds which is how long it takes for the initial GUI to appear. The run button stays yellow on the initial GUI.
I've seen a bit online about other people having issues with subprocesses not running after pyinstaller, but most of the solutions seem to be to make sure stdout, stdin are set to subprocess.PIPE which I have, so I'm at a bit of a loss what to try next.
I'm creating my standalone with this:
pyinstaller --onefile simpleGUI.py
My subprocess file, testsubprocess.py is:
import time
for i in range(3):
print("%d.%d" % divmod(i, 10))
time.sleep(0.5)
My main GUI file, simpleGUI.py, is:
import sys
import subprocess
from threading import Thread
import tkinter as tk
from queue import Queue, Empty
def iter_except(function, exception):
try:
while True:
yield function()
except exception:
return
class DisplaySubprocessOutputDemo:
def __init__(self, root):
self.root = root
width=600
height=350
xloc=0
yloc=10
self.root.geometry('%dx%d+%d+%d' % (width, height, xloc, yloc))
self.statustext = tk.Text(self.root, height=4, width=30)
self.statustext.grid(row=3, column=1)
self.startbutton = tk.Button(self.root, text = 'Start', command=self.startprocess, bg='green', activebackground = 'orange')
self.startbutton.config(height = 2, width = 15)
self.startbutton.grid(row = 5, column=0,sticky='E')
self.startbuttonpresses = 0
exitbutton = tk.Button(self.root, text = 'Exit', command=self.quit, bg='red')
exitbutton.config(height = 2, width = 15)
exitbutton.grid(row = 5, column=4, sticky='E')
def startprocess(self):
self.startbuttonpresses = self.startbuttonpresses+1
if self.startbuttonpresses == 1:
self.startbutton.configure(bg='yellow')
self.startbutton.configure(text='Stop')
self.process = subprocess.Popen([sys.executable, "-u", "testsubprocess.py"], shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
q = Queue(maxsize=1024)
t = Thread(target=self.reader_thread, args=[q])
t.daemon = True
t.start()
self.updatetext(q)
else:
self.startbuttonpresses = 0
self.process.kill()
self.startbutton.configure(bg='green')
self.startbutton.configure(text='Start')
def reader_thread(self, q):
try:
with self.process.stdout as pipe:
for line in iter(pipe.readline, b''):
q.put(line)
finally:
q.put(None)
def updatetext(self, q):
for line in iter_except(q.get_nowait, Empty): # display all content
if line is None:
self.startbuttonpresses = 0
self.startbutton.configure(bg='green')
self.startbutton.configure(text='Start')
return
else:
self.statustext.insert(tk.END, line)
self.root.after(400, self.updatetext, q)
def quit(self):
try:
self.process.kill()
except Exception:
pass
self.root.destroy()
root = tk.Tk()
app = DisplaySubprocessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
root.mainloop()
When the process is executing with PyInstaller sys.executable is pointing to the exe file created and not to the python.exe like in the interpreter so the Popen will not work.
You can try to create another executable for the testsubprocess.py and point Popen to it or use multiprocessing (don’t forget to use freeze_support if you do https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support)

after() hangs the output window

I am trying to build a real time project where the status gets updated every second, so some part of code repeats continuously. when i want to change the information which has to be get updated i will just click on new button which gives me the first window where i can update the new information. but by doing so gives me the following error. if i use after() instead of threading, the error wont be there but the output window gets hanged.Please help me with the idea to resolve this. Thank you.
Exception in thread Thread-2:
Traceback (most recent call last):
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 1177, in run
self.function(*self.args, **self.kwargs)
File "C:/Users/Desktop/Tool/t.py", line 47, in ae
self.treeview.insert('', 'end',image=self._img, value=(a))
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\tkinter\ttk.py", line 1370, in insert
res = self.tk.call(self._w, "insert", parent, index, *opts)
_tkinter.TclError: invalid command name ".!treeview"
Code where i have problem with:
def aaa(self):
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map(self.ping_func, [x for x in Demo2.t1])
self.process_incoming()
#threading.Timer(1.0, self.aaa).start()-this gives the error while pressing new button and updating information
self.master.after(100, self.aaa) #it hangs the output window
Sample code:
import multiprocessing.dummy
import multiprocessing
import os
import socket
import sys
import subprocess
import re
import time
import threading
import tkinter.messagebox
from tkinter import ttk
import queue
from tkinter import *
class Demo1: #window 1
data=[]
def __init__(self, master):
self.master = master
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.grid(row=1, column=1)
self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
self.button.grid(row=2,column=1)
def new_window(self):
self.inputValue=self.t.get("1.0",'end-1c')
Demo1.data=self.inputValue.split("\n")
self.master.destroy() # close the current window
self.master = tkinter.Tk() # create another Tk instance
self.app = Demo2(self.master) # create Demo2 window
self.master.mainloop()
class Demo2: #window 2
value = []
display = []
num=0
def __init__(self, master):
self.master = master
self.queue = queue.Queue()
Demo2.value = Demo1.data
self.button = tkinter.Button(self.master,height=2,width=11, text="new",command=self.new).place(x=0,y=0)
self.label = tkinter.Label(self.master, text="monitor", font=("Arial",20)).grid(row=0, columnspan=3)
cols = ('aa','bb')
self.treeview = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.treeview.heading(col, text=col)
self.treeview.column(col,minwidth=0,width=170)
self.treeview.grid(row=1, column=0)
self._img=tkinter.PhotoImage(file="green1.gif")
self.aaa()
def aaa(self):
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map(self.ping_func, [x for x in Demo2.value])
self.process_incoming()
#threading.Timer(1.0, self.aaa).start()
self.master.after(100, self.aaa)
def ping_func(self,ip): #Ping every ip and append the result
ping_result = []
pingCmd = "ping -n 1 -w 1000 " + ip
childStdout = os.popen(pingCmd)
result = (childStdout.readlines())
childStdout.close()
ping_result.append(ip)
if(any('Reply from' in i for i in result)):
ping_result.append("success")
else:
ping_result.append("failed")
self.queue.put(ping_result) #Thread value to queue
def process_incoming(self): #add the ping result to treeview
while self.queue.qsize():
try:
if Demo2.num<len(Demo1.data):
self._img=tkinter.PhotoImage(file="green1.gif")
self._img1=tkinter.PhotoImage(file="red.gif")
msg = self.queue.get_nowait()
Demo2.display.append(msg) #adding queue value to variable(display)
if(len(Demo2.display)==len(Demo1.data)):
self.treeview.insert("","end",values=(0,0,0,0,0))
self.treeview.delete(*self.treeview.get_children())
for i,(a,b) in enumerate(Demo2.display):
if(Demo2.display[i][1]=='success' ):
self.treeview.insert('', 'end',image=self._img, value=(a,b))
else:
self.treeview.insert('', 'end',image=self._img1, value=(a,b))
Demo2.num=Demo2.num+1
Demo2.display.clear()
else:
Demo2.display.clear()
Demo2.num=0
except queue.Empty: # Shouldn't happen.
pass
def periodic_call(self):
self.master.after(200, self.periodic_call) # checking its contents periodically
self.process_incoming()
if not self.running:
import sys
sys.exit(1)
def new(self):
self.master.destroy() # close the current window
self.master = tkinter.Tk() # create another Tk instance
self.app = Demo1(self.master) # create Demo2 window
self.master.mainloop()
def main():
root = tkinter.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
Main problem is that ping -w 1000 need a lot of time to run but Pool.map() waits for all results. You could even run results = p.map(...) without queue (but with return result) but it could also block tkinter
You may use map_async() to run it without waiting for results.
I also use starmap (or rather starmap_async()) to send two arguments ip, queue to every process.
I also made other changes - ie. rename variables, move some code to __init__ to create only once (images, Pool, Queue). I also send list of IP to other window as argument Window2(master, data) and back Window1(master, data) - so I can edit this list.
BTW: because I run on Linux so I changed arguments for ping and check different text to test if it get answer.
import os
import multiprocessing.dummy
import queue
import tkinter as tk
import tkinter.ttk as ttk
# --- classes ---
class Window1:
def __init__(self, master, data=None):
self.master = master
self.data = data
if self.data is None:
self.data = []
self.text = tk.Text(self.master, height=20, width=50)
self.text.grid(row=1, column=1)
self.button = tk.Button(self.master, height=3, width=10, text="OK", command=self.new_window)
self.button.grid(row=2, column=1)
# put self.data in Text
#for item in self.data:
# self.text.insert('end', item + '\n')
self.text.insert('end', '\n'.join(self.data))
def new_window(self):
text = self.text.get('1.0', 'end')
# remove empty lines
self.data = [item.strip() for item in text.split("\n") if item.strip()]
self.master.destroy()
root = tk.Tk()
Window2(root, self.data)
root.mainloop()
class Window2:
def __init__(self, master, data):
self.master = master
# keep list
self.data = data
# create dictionary for results
self.results = {ip: [ip, '???'] for ip in self.data}
self.button = tk.Button(self.master, height=2, width=11, text='New', command=self.new)
self.button.grid(row=0, column=0, sticky='w')
self.label = tk.Label(self.master, text='monitor', font=("Arial", 20))
self.label.grid(row=0, column=1)
cols = ('IP','Result')
self.treeview = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.treeview.heading(col, text=col)
self.treeview.column(col,minwidth=0,width=170)
self.treeview.grid(row=1, column=0, columnspan=3)
# create only once
self._image_green = None # tk.PhotoImage(file="green1.gif")
self._image_red = None # tk.PhotoImage(file="red.gif")
# to reduce number of processes for small `data`
n = min(5, len(self.data))
# create only once
self.queue = queue.Queue()
self.num_threads = n * multiprocessing.cpu_count()
self.p = multiprocessing.dummy.Pool(self.num_threads)
# to stop `after()`
self.running = True
# run first time
self.update_treeview() # to display it before running processes
self.run_processes()
self.processes_incoming() # first create window to display it faster
def run_processes(self):
if self.running:
self.p.starmap_async(self.ping, [(ip, self.queue) for ip in self.data])
self.after_ID2 = self.master.after(500, self.run_processes)
def ping(self, ip, queue):
#print('start ping:', ip)
#cmd = 'ping -n 1 -w 3 ' + ip
cmd = 'ping -w 1 ' + ip # Linux
child_stdout = os.popen(cmd)
result = child_stdout.readlines()
child_stdout.close()
#print('end ping:', ip)
#if any('Reply from' in line for line in result):
if any('bytes from' in line for line in result): # Linux
value = [ip, 'success']
else:
value = [ip, 'failed']
queue.put(value)
def update_treeview(self):
self.treeview.delete(*self.treeview.get_children())
for ip in self.data:
ip, value = self.results[ip]
if value == 'success':
image = self._image_green
elif value == 'failed':
image = self._image_red
else:
image = None
#self.treeview.insert('', 'end', image=image, value=(ip, valueb))
self.treeview.insert('', 'end', value=(ip, value))
def processes_incoming(self):
if self.running:
# get all from queue
new_values = False
while self.queue.qsize():
#while not self.queue.empty:
data = self.queue.get_nowait()
ip, value = data
self.results[ip] = data
new_values = True
# update only if new values
if new_values:
self.update_treeview()
# repeate after 100ms
self.after_ID1 = self.master.after(100, self.processes_incoming)
def new(self):
# to stop all `after()`
self.running = False
self.master.after_cancel(self.after_ID1)
self.master.after_cancel(self.after_ID2)
self.master.destroy()
root = tk.Tk()
Window1(root, self.data)
root.mainloop()
# --- functions ---
def main():
examples = [
'127.0.0.1', # localhost
'10.0.0.1', # IP in local network
'192.168.0.1', # IP in local network
'8.8.8.8', # Google DNS
'8.8.4.4', # Google DNS
]
root = tk.Tk()
Window1(root, examples)
root.mainloop()
# --- main ---
if __name__ == '__main__':
main()

Using keyword or wake word to activate Tkinter window in python

I found this amazing wake-word program called Porcupine. I got it to work and want to implement it into a Tkinter window. The idea is to have a state of on/off with the wake word turning it on. I have working code but the problem is that no matter where I put the listener in the code it will not make the window until I use the wake word.
I want the following behavior: I would like the window to be created and to appear and then to run the wake-word module so I can change the self.state variable. This wake word will then put me in a loop that will create other frames and features. I am assuming there is a threading solution but I could not figure it out.
from Tkinter import *
import threading
import ttk
import os
import sys
sys.path.append('Porcupine/demo/python')
import porcupine__demo
class FullscreenWindow:
def __init__(self):
self.state = False
self.tk = Tk()
self.tk.configure(background='black')
self.listen()
print(self.state)
def listen(self):
self.state = porcupine_demo.listen_for_keyword()
if __name__ == '__main__':
w = FullscreenWindow()
w.tk.mainloop()
The method *.listen_for_keyword() is a method I wrote that returns True when the wake word is captured.
I found an amazing blog post that answers this question. I have changed their code to reflect my needs. The blog is here:
from Tkinter import *
import threading
import sys
sys.path.append('Porcupine/demo/python')
import porcupine_demo
class App(threading.Thread):
def __init__(self, tk_root):
self.root = tk_root
threading.Thread.__init__(self)
self.start()
def run(self):
listening = True
while listening:
self.state = porcupine_demo.listen_for_keyword()
if self.state:
print("heard you:",self.state)
LABEL = Label(self.root, text="Hello, world!")
LABEL.pack()
ROOT = Tk()
ROOT.configure(background='black')
APP = App(ROOT)
ROOT.mainloop()
The porcupine package comes with a demo of how to use porcupine in a non-blocking mode, which is necessary to allow mainloop to process tkinter events. The key is to attach a callback to the audio stream.
I've never used porcupine before so I don't know how well this works in the real world, but it works for me on my mac, and doesn't require explicit threading.
import sys
sys.path.append("/tmp/Porcupine/binding/python")
import Tkinter as tk
import struct
from datetime import datetime
from porcupine import Porcupine
import pyaudio
library_path = "/tmp/Porcupine/lib/mac/x86_64/libpv_porcupine.dylib"
model_file_path = "/tmp/Porcupine/lib/common/porcupine_params.pv"
keyword_file_paths = ["/tmp/Porcupine/words_mac.ppn",]
num_keywords = len(keyword_file_paths)
sensitivities = [0.5] * num_keywords
class TkPorcupine(object):
def __init__(self):
self.initialize_ui()
self.initialize_porcupine()
def initialize_ui(self):
self.state = False
self.root = tk.Tk()
self.text = tk.Text(self.root, width=60, height=8)
self.vsb = tk.Scrollbar(self.root, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.text.pack(fill="both", expand=True)
def initialize_porcupine(self):
self.porcupine = Porcupine(library_path, model_file_path,
keyword_file_paths=keyword_file_paths,
sensitivities=sensitivities)
self.audio_stream = pyaudio.PyAudio().open(
rate=self.porcupine.sample_rate,
channels=1,
format=pyaudio.paInt16,
input=True,
frames_per_buffer=self.porcupine.frame_length,
input_device_index=None,
stream_callback=self.audio_callback)
self.audio_stream.start_stream()
def audio_callback(self, in_data, frame_count, time_info, status):
if frame_count >= self.porcupine.frame_length:
pcm = struct.unpack_from("h" * self.porcupine.frame_length, in_data)
result = self.porcupine.process(pcm)
if num_keywords == 1 and result:
message = '[%s] detected keyword' % str(datetime.now())
self.text.insert("end", message + "\n")
self.text.see("end")
elif num_keywords > 1 and result >= 0:
message = '[%s] detected keyword #%d' % (str(datetime.now()), result)
self.text.insert("end", message + "\n")
self.text.see("end")
return None, pyaudio.paContinue
if __name__ == '__main__':
demo = TkPorcupine()
tk.mainloop()

Categories

Resources