Reputation: 179
I need to be able to send control-c to the new thread that I start. How would I go about this. I can start the thread, but I need to stop like I do in the command line using control-c.
from Tkinter import *
import threading # should use the threading module instead!
import Queue
import os
def show1():
os.system('ola_recorder -p MyRecord -i 0')
def show2 ():
os.system('ola_recorder -p MyRecord ')
def stop ():
os.system('^c')
t = threading.Thread(name='Show2', target=show2)
root = Tk()
b = Button(root, text="Show 1", command=lambda: thread.start_new(show1, ()))
b.pack()
b2 = Button(root, text="Show 2", command=lambda: t.start())
b2.pack()
root.mainloop()
Upvotes: 1
Views: 1724
Reputation: 365597
First, the reason what you're doing doesn't work:
Each call to os.system
just calls your platform's system
(or _system
, on some Windows platforms) function, which creates a brand-new shell process each time. So, there's no way you can do anything with os.system
to affect another call.
If you want to send a ^C to an arbitrary process, you can do that with os.kill
. To make it portable, you have to do something like this:
def send_ctrl_c(pid):
try:
sig = signal.CTRL_C_EVENT
except AttributeError:
sig = signal.SIGINT
else:
os.signal(pid, sig)
However, you need the other process's pid to do that, and os.system
doesn't give you that.
So, the right thing to do is to use the subprocess
module:
proc2 = None
def show2():
global proc2
proc2 = subprocess.Popen('ola_recorder -p MyRecord', shell=True)
proc2.wait()
Since there's no good reason to use the shell here, you'd probably be better off passing Popen
a list of args, instead of a string, and leaving off the shell=True
. And of course it would be a lot cleaner to not stick proc2
in a global, but I'll ignore that for this toy example.
Anyway, now you can get the pid and use it:
def stop():
send_ctrl_c(proc2.pid)
However, if you're going to do that, you might as well just use the Popen
object directly. See the docs for full details of what you can do with it, but here's a quick version:
def stop():
global proc2
try:
sig = signal.CTRL_C_EVENT
except AttributeError:
sig = signal.SIGINT
proc.send_signal(sig)
When you call stop
, the process will get killed exactly as if it had received a ^C (POSIX), or as close as possible to as if it had received a ^C (Windows), the wait
call will return (with a -2, at least on POSIX), and your thread will finish as well.
One last note: You almost never want to use the thread
module directly, and you do not want to reuse threading.Thread
objects.
So, instead of this:
b = Button(root, text="Show 1", command=lambda: thread.start_new(show1, ()))
… or this:
b2 = Button(root, text="Show 2", command=lambda: t.start())
… do this:
def start2():
global t2
t2 = threading.Thread(name='Show2', target=show2)
t2.start()
Of course, a non-toy program should avoid using a global, and will want to keep track of all existing threads instead of just the last one (if you're allowed to click the button while a background thread is already running), and will probably want to join
all of its threads at quit.
Upvotes: 6