I am writing a program in Python with a Tkinter UI. I want to have a small window with no title bar. This window must receive keyboard input. I am not picky whether this is in the form of an Entry widget or just binding to KeyPress. overrideredirect(True)
is typically how the title bar is disabled. Unfortunately, (except in Windows), this seems to prevent many events from being received. I wrote this code to illustrate the problem:
#!/usr/bin/env python
from __future__ import print_function
import Tkinter
class AppWindow(Tkinter.Tk):
def __init__(self, *args, **kwargs):
Tkinter.Tk.__init__(self, *args, **kwargs)
self.overrideredirect(True)
self.geometry("400x25+100+300")
titleBar = Tkinter.Frame(self)
titleBar.pack(expand = 1, fill = Tkinter.BOTH)
closeButton = Tkinter.Label(titleBar, text = "x")
closeButton.pack(side = Tkinter.RIGHT)
closeButton.bind("<Button-1>", lambda event: self.destroy())
self.bind("<KeyPress>", lambda event: print("<KeyPress %s>" % event.char))
self.bind("<Button-1>", lambda event: print("<Button-1>"))
self.bind("<Enter>", lambda event: print("<Enter>"))
self.bind("<Leave>", lambda event: print("<Leave>"))
self.bind("<FocusIn>", lambda event: print("<FocusIn>"))
self.bind("<FocusOut>", lambda event: print("<FocusOut>"))
if __name__ == "__main__":
app = AppWindow()
app.mainloop()
This creates a little window (with no title bar) that prints the name of common events when it receives them. I have run this script on Windows 7, Mac OSX (El Capitan), and Ubuntu 14.04.1. I ran only Ubuntu in a virtual machine (VMWare).
In Windows, this seems to work as expected. All events that my code tests for can be received.
In Ubuntu, the Tkinter window receives
<Enter>
,<Leave>
, and<Button-1>
events as expected, but<KeyPress>
,<FocusIn>
, and<FocusOut>
are never received. In fact, even after the window has been clicked on, the last window with focus continues to receive the key presses.In OSX, the Tkinter window receives
<Button-1>
events as expected, but<KeyPress>
,<FocusIn>
, and<FocusOut>
are never received. The last window with focus does not continue to receive key presses like in Ubuntu. The<Enter>
and<Leave>
events behave a little oddly. The<Enter>
event is not received until the window is clicked. Then, once the<Leave>
event occurs, the window needs to be clicked again to receive another<Enter>
event.
I have also tried self.focus_force()
just before the end of the __init__
function. This causes the window to receive a <FocusIn>
event when the program starts, but no further <KeyPress>
, <FocusIn>
, or <FocusOut>
events are never received.
Ultimately, my question is this: is there any way to hide the title bar but continue to receive keyboard input in OSX and Linux?
I am aware of some other questions dealing with this same problem. In these three questions:
- python tkinter overrideredirect; cannot receive keystrokes (Linux)
- root.overrideredirect and <Any-KeyPress> binding
- How to bind Tkinter destroy() to a key in Debian?
The accepted answer is to use self.attributes('-fullscreen', True)
, which will not work for me as I want a tiny little window, not a fullscreen application.
There is one other question: Tkinter overrideredirect no longer receiving event bindings. This seems very close to my question, but provided less detail and has no answer.
Update: I have been attempting to investigate the underlying mechanism of my problem. I know that Tkinter is a wrapper around Tcl/Tk, so I thought I would try rewriting my code in Tcl. I don't really know Tcl, but I think I managed to (more or less) translate my Python:
#!/usr/bin/env wish
wm overrideredirect . True
wm geometry . "400x25+100+300"
bind . <KeyPress> {puts "<KeyPress %K>"}
bind . <Button-1> {puts "<Button-1>"}
bind . <Enter> {puts "<Enter>"}
bind . <Leave> {puts "<Leave>"}
bind . <FocusIn> {puts "<FocusIn>"}
bind . <FocusOut> {puts "<FocusOut>"}
I tried the resulting program in Windows and Mac OSX. In Windows I received <KeyPress>
events, but in OSX I did not. Without the wm overrideredirect . True
line, OSX does receive the <KeyPress>
events. Therefore it looks like this problem is not with Python, but with Tcl/Tk.
I have submitted a bug report to Tk for this situation.
You can use the
devilspie
program to remove the decorations from your window. Use thewm title . myname
command to give your window a specific name and use that name in thedevilspie
configuration fragment below. Remove theoverrideredirect
command from your program.I have tested this (as a Tk program), and the undecorated window will still receive the keypress &etc. bindings.
Note that
devilspie
is written as a daemon process and stays active. The daemon can be killed after it is started and the window changes it made will still be in effect. Or it can be left running, and any time your window is activated, thedevilspie
configuration will be applied.