I'm puzzled by some strange behavior when my Python/Tkinter
application quits (on linux): the terminal from which I started Python
is messed up.
If start up python, then import the code below, then start the program
with Application(), then click the Quit button, my terminal never
prints anything again (such as commands I type).
<code>
import Tkinter
import sys
class Application(Tkinter.Tk):
def __init__(self,**config):
Tkinter.Tk.__init__(self,**config)
Tkinter.Button(self,text="Quit",command=self.quit_application).pack()
def quit_application(self):
sys.exit()
</code>
Can anyone tell me what I'm doing wrong?
Thanks for your help.
Chris
What happens if you type 'stty sane' (and of course, a carriage
return) afterwards?
The terminal returns to normal, thanks!
But does anyone know why the Tkinter program is doing this to the
terminal in the first place? I don't want to have to tell users of my
program that they must recover their terminal's sanity each time after
running my program.
I don't know about Tkinter, but my guess would be that
it has to change the terminal settings to do what it does, and
you are quitting without restoring the settings.
Is there some Tkinter clean up that you have omitted ?
Have you ensured that the clean up runs on both normal
exit and abnormal exit (eg ^C) ?
For example, the curses library (in C) requires an
endwin() call before exit to restore settings. If this
is omitted, a "stty sane" is needed to set the terminal to
a semi-sensible default. On Unix and similar systems, signal
handlers are normally installed to ensure (as far as possible)
that the cleanup occurs if the process is killed. This also
applies to vi and similar programs that take total control of
the terminal.
Charles
As previously stated, I know nothing about Tkinter,
but it definitely looks like there is some cleanup being skipped
on a GUI exit that is in fact being run on exit via ^D and
sys.exit from the terminal.
If I remember correctly, the low level details of how
this sort of thing is usually handled on unix like systems is
for the application to
- Do an ioctl TCGETA system call to get the current
settings, and save them somewhere.
- Do an ioctl TCSETA system call to change the settings.
- Arrange, via signal handlers, atexit, or other means
for a cleanup routine to be called on exit.
- The cleanup routine does an ioctl TCSETA call with
the saved settings.
(all this would typically involve C calls)
I believe that in your case this last cleanup is being omitted
on exit via the GUI.
> For the moment, including the line 'os.system("stty sane")'
> before sys.exit() is the solution I'll use.
If that is satisfactory, well and good. However, there
is a possibility that you may lose some settings that you would
prefer to keep. The terminal settings have been trashed, and
stty sane has restored a minimally workable set, but some
settings may not be what you expect.
If you are on a unix like system (eg Linux), and can wrap
your Tkinter application in a shell script, you should be able to
save/restore the settings outside of Tkinter and python, ie
#! /bin/sh
saved_settings=`stty -g` i # Note backquotes, save current settings
python Tkinter-application.py # Or whatever
stty "$saved_settings" i # Restore settings
Doing the save within the Tkinter application, with
(for example) os.system("stty -g > saved_settings") and
os.system("stty `cat saved_settings`") may not work as
the setting may have been changed by Tkinter before you get
a chance to save them - I just do not know.
Just in case, I did a google search. I am not familiar
with TKinter, but a couple of articles out there imply
that rather than calling sys.exit you should be calling a
TkInter routine root.destroy. I am not sure if root is a
variable for the main window (ie you call the destroy method
on the main window) or if it has some special Tkinter meaning.
Presumably this routine cleans things up before calling sys.exit
or an equivalent.
Charles
> Have you ensured that the clean up runs on both normal
> exit and abnormal exit (eg ^C) ?
(^C doesn't make the application exit, it just raises a
KeyboardInterrupt error.)
Quitting with ^D from the interpreter (the usual way to quit Python)
causes the application to quit normally without any subsequent
terminal trouble. Quitting by typing 'sys.exit()' in the interpreter
also works fine. Only quitting via the GUI seems to cause the
problem.
For the moment, including the line 'os.system("stty sane")' before
sys.exit() is the solution I'll use.
Thanks for everyone's help.
> If that is satisfactory, well and good. However, there
> is a possibility that you may lose some settings that you would
> prefer to keep. The terminal settings have been trashed, and
> stty sane has restored a minimally workable set, but some
> settings may not be what you expect.
I agree: I'll consider saving the terminal settings as you suggest.
> Just in case, I did a google search. I am not familiar
> withTKinter, but a couple of articles out there imply
> that rather than calling sys.exit you should be calling aTkInterroutine root.destroy. I am not sure if root is a
> variable for the main window (ie you call the destroy method
> on the main window) or if it has some specialTkintermeaning.
> Presumably this routine cleans things up before calling sys.exit
> or an equivalent.
"root" is the name of a variable typically used by people to hold an
instance of Tkinter.Tk, the main application window (from
http://epydoc.sourceforge.net/stdlib/Tkinter.Tk-class.html: "Toplevel
widget of Tk which represents mostly the main window of an appliation.
It has an associated Tcl interpreter."). Instead of subclassing
Tkinter.Tk and instantiating that subclass for my application, I could
create a Tk instance and withdraw() it, then use a Toplevel. In my
example code above, I could call any 'root' methods on an instance of
my Application class, presumably with the same effect.
In any case, that might not be important - I think the problem comes
from not calling mainloop():
<code>
import Tkinter
import sys
root = Tkinter.Tk()
Tkinter.Button(root,text="Quit",command=sys.exit).pack()
root.mainloop()
</code>
Clicking 'Quit' or on the window's 'x' causes the application to quit
without messing up the terminal. With root.mainloop() commented out,
though, no combination of root.quit(), root.destroy(), and sys.exit()
stops the terminal from getting messed up.
So, I should call mainloop() for my application...except that I want
to use the commandline, too, and calling mainloop() freezes the
commandline. I wonder if there is another way to use the commandline
and have a GUI? I couldn't find any clear information about that.
Thanks again,
Chris
Can you run it in the background? IIRC, if you put an ampersand ('&')
at the end of the command line, it will run as a background process and
leave your command line available for other tasks. (The marker may be
something other than &, it's been a long, long time since I've used *nix
in a gui environment.)
---
-Bill Hamilton
Ah, sorry, I wasn't being precise. I meant the python commandline
python interpreter.
So from a terminal I type (for example):
python -i application.py
This launches the interpreter in my terminal. Then I can start the GUI
(by typing "Application()", for example). If I use mainloop(), I can't
interact with the interpreter from the terminal until I quit the GUI.
Without mainloop(), I can continue to enter python commands.
Thanks, but I was just explaining why I don't want to call mainloop().
In my original example, I can type Application() at the interactive
prompt and get a GUI (which seems to work apart from not quitting
properly and leaving a messed-up terminal on some versions of linux)
while still being able to use the interactive interpreter.
I need to find out what cleanup is performed after mainloop() exists,
I guess.
Incidentally, I found the information in the thread
http://thread.gmane.org/gmane.comp.python.scientific.user/4153
quite useful regarding mainloop() and being able to use python
interactively from the prompt while still having a GUI.
Maybe the Python interpreter is setup specially for Tkinter, I don't
know - but one can definitely have a Tkinter GUI working and use the
interpreter too (by not calling mainloop()). The Application example
in my first post works (except for the messed-up terminal) and the
interpreter is not blocked.
Perhaps it becomes necessary to call update or update_idletasks()
after some operations for the GUI to update itself - I'm not entirely
sure (I haven't been able to find any documentation) - but apart from
that there are no problems I know about.
>
> > I need to find out what cleanup is performed after mainloop() exists,
> > I guess.
>
> <G> If you are calling it, it "exists"... Whether it "exits" is
> another matter <G>
>
>
>
> > Incidentally, I found the information in the thread
> >http://thread.gmane.org/gmane.comp.python.scientific.user/4153
> > quite useful regarding mainloop() and being able to use python
> > interactively from the prompt while still having a GUI.
>
> I'll admit to being surprised at seeing a claim that a tkinter
> application, started within an interactive session, without a mainloop,
> even runs... I could see it maybe happening from Idle, since Idle is
> running a tkinter mainloop, so the application bindings may have just
> become "added widgets" to the Idle loop (but of course, a second
> mainloop would conflict heavily).
You can try by building a working Tkinter GUI interactively from the
standard Python interpreter, and see that the GUI works (i.e.
processes events) at the same time.
> That link reads as if the IronPython interpreter can be started with
> a wx/gtk mainloop already running as a thread -- so any application code
> might, as with my tkinter/Idle thoughts, be added to the already running
> loop.
I don't know how ipython works, just that it allows a GUI and
interpreter to work together for wxpython (whereas usually you can
only use a GUI and the python interpreter when the GUI is Tkinter).
Chris
If you build it as a class (such as the code in Chris's original post)
it works; if you do it all directly, nothing happens until you run
mainloop(). It works, but I'm not sure that it was intended to work
that way. I think your problem is related to that difference.
You'll probably be better off creating a new interpreter window as part
of your program, if you really need access to the interpreter alongside
your GUI. You may be able to extract IDLE's interpreter window and use
it directly.
---
-Bill Hamilton
Do you have any idea where I should go to find out the differences
between using mainloop() and not using it? So far I have found this
quitting problem (which does not occur on all platforms), and the need
to call update() or update_idletasks() after some operations, but no
others.
> You'll probably be better off creating a new interpreter window as part
> of your program, if you really need access to the interpreter alongside
> your GUI. You may be able to extract IDLE's interpreter window and use
> it directly.
I really do need access to the interpreter alongside the GUI in my
real program*, since the GUI cannot really offer all the functionality
of the command line (or is a long way from doing so...). I do offer
simple access to the interpreter in my gui (via
code.InteractiveConsole and a text widget, but I agree with the author
of the comp.lang.python posting "Has anybody made a Tkinter text
widget into a terminal emulator?" (http://groups.google.com/group/
comp.lang.python/browse_thread/thread/a3f223f563205156/
fc729e1de51ca2dc): it's a shame to force people to use a particular
editor. I don't know if she found a solution; I haven't found anything
reasonable yet.
Thanks
* Actually it's not my program, it's an open-source software package
for modelling neural maps: topographica.org. The GUI code is being
reorganized.