Tkinter's overrideredirect prevents certain events in Mac and Linux
Asked Answered
T

1

13

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:

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.

Transfiguration answered 3/1, 2016 at 23:2 Comment(2)
Disable mainloop and how to access it again ? Binding (key listen) work on with sub elements. You gained a ghost application if listener is frozen.Ceasefire
A tricks : Make your [X] button as mainloop (so deiconify())!Ceasefire
E
4

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 the wm title . myname command to give your window a specific name and use that name in the devilspie configuration fragment below. Remove the overrideredirect 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, the devilspie configuration will be applied.

(if (is (application_name) "t.tcl")
   (begin (undecorate)))
Excruciate answered 25/8, 2016 at 18:13 Comment(4)
Err...that's for Linux. I don't know if there's an equivalent program for the Mac.Excruciate
Thanks, works perfectly for me (Archlinux with LXDE).Prickett
A window with overrideredircct is deliberately prevented from taking focus, and thus cannot get key events: core.tcl.tk/tk/artifact/… That line and comment is ancient, and might be possible to remove. Not sure though.Moorhead
I set overrideredirect=1 for a terminal emulator (using xdotool in Archlinux) and I noticed that in this case too the window could not take focus. So it seems that it is not a specificity of Tcl/Tk.Prickett

© 2022 - 2024 — McMap. All rights reserved.