i3 / i3

A tiling window manager for X11
https://i3wm.org/
BSD 3-Clause "New" or "Revised" License
9.55k stars 778 forks source link

Bug i3 with tkinter application. I3 seems to stop replying. #2722

Closed iogf closed 6 years ago

iogf commented 7 years ago

Output of i3 --moreversion 2>&- || i3 --version: Binary i3 version: 4.13 (2016-11-08) © 2009 Michael Stapelberg and contributors Running i3 version: 4.13 (2016-11-08) (pid 451)to abort…) Loaded i3 config: /home/tau/.i3/config (Last modified: Mon 20 Mar 2017 10:54:15 PM -03, 545611 seconds ago)

The i3 binary you just called: /usr/bin/i3 The i3 binary you are running: i3

REPLACE: i3 version output 03/27/2017 06:28:04 AM - Additional arguments passed. Sending them as a command to i3. [{"success":false,"parse_error":true,"error":"Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'sticky', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'title_format', 'mode', 'bar'","input":"version","errorposition":"^^^^^^^"}]

The application below after running and one pressing 'Return' it should quit the application and give back focus to the previous active windows. it seems i3 stops replying to commands from the keyboard and i have to click on some container to get things working again. Replacing for all other kind of events it fails too. Notice, it fails if you replace Entry for Button too.

I have other application that depends on the correct behavior. I hope you guys have how to fix it.

Obs: if i comment self.resizable... it fails too.

from Tkinter import *

class BugI3(Tk):

    def __init__(self, title, default_data='', *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)
        # when one quits the application
        # from an event it just makes i3 misbehaves
        # it cant get focus back to the previous application.
        self.bind('<Return>', lambda e: self.quit())

        self.title(title)
        self.resizable(0, 0)

        self.entry = Entry(self)
        self.entry.pack(side='left', expand=True, fill=BOTH)

if __name__ == '__main__':
    bugi3 = BugI3('Word Pass')
    bugi3.mainloop()
i3bot commented 7 years ago

I don’t see a link to logs.i3wm.org. Did you follow http://i3wm.org/docs/debugging.html? (In case you actually provided a link to a logfile, please ignore me.)

Airblader commented 7 years ago

How is this different from your issue #2600?

iogf commented 7 years ago

well, my issue #2600 was closed because one of you assumed it wasnt something that happens in real world applications. Frankly, if i3 stops replying to events after you just have a GUI widget selected and you press some key from your keyboard then it quits, how couldnt it happen in real world applications? This snippet shows that it is an issue that more people may just have futurely. I believe this misbehavior may even be related to other bugs that people may have noticed. If this gets fixed straight it may just prevent other bugs from happening too.

iogf commented 7 years ago

Now, just notice that it gets broken when you click over the widget too.

from Tkinter import *

class BugI3(Tk):

    def __init__(self, title, default_data='', *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)
        # when one quits the application
        # from an event it just makes i3 misbehaves
        # it cant get focus back to the previous application.
        self.bind('<Button-1>', lambda e: self.quit())

        self.title(title)

        self.entry = Entry(self)
        self.entry.pack(side='left', expand=True, fill=BOTH)

if __name__ == '__main__':
    bugi3 = BugI3('Word Pass')
    bugi3.mainloop()

By the way then Tk users wouldnt be allowed to click on a button that has 'quit' written then i3 stops replying to keys... would it be okay?

iogf commented 7 years ago

Notice that it is not related directly to the code defined in Tk.quit

from Tkinter import *
import sys

class BugI3(Tk):

    def __init__(self, title, default_data='', *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)
        # when one quits the application
        # from an event it just makes i3 misbehaves
        # it cant get focus back to the previous application.
        self.bind('<Button-1>', lambda e: sys.exit(0))

        self.title(title)

        self.entry = Entry(self)
        self.entry.pack(side='left', expand=True, fill=BOTH)

if __name__ == '__main__':
    bugi3 = BugI3('Word Pass')
    bugi3.mainloop()

Because if i call sys.exit it fails too.

Airblader commented 7 years ago

well, my issue #2600 was closed because one of you assumed it wasnt something that happens in real world applications.

No, that is not why we closed the issue. We closed it because the application ignored the specifications and violently stole focus from the window manager's control instead of doing it the proper way that allows the window manager to do its job.

And your new application isn't behaving any other way. It rips focus away without allowing the window manager to participate and then doesn't care to properly restore focus afterwards. Instead, X – and i3 – behave exactly like the client specified they should behave: »Give me focus and when I'm done, focus the root window«.

iogf commented 7 years ago

well, it is a tk application, i believe there is a lot of other softwares out there that are misbehaving due to it. what do do then? it used to work before in previous i3 versions.

Airblader commented 7 years ago

what do do then?

tk should either properly follow the specs or, if it chooses to use XSetInputFocus anyway, ensure that they return focus to the appropriate window again.

That said, giving it some more thought I still think that perhaps i3 can improve its behavior here. Here's a quick patch with which your tk client works again for me. I haven't yet thought about ramifications of the change (and it's a little hacky at the moment):

diff --git a/include/xcb.h b/include/xcb.h
index 92be7b89..53c932bf 100644
--- a/include/xcb.h
+++ b/include/xcb.h
@@ -53,6 +53,7 @@
                                                                   ConfigureNotify */                  \
                          XCB_EVENT_MASK_POINTER_MOTION |                                              \
                          XCB_EVENT_MASK_PROPERTY_CHANGE |                                             \
+                         XCB_EVENT_MASK_FOCUS_CHANGE |                                                \
                          XCB_EVENT_MASK_ENTER_WINDOW)

 #define xmacro(atom) xcb_atom_t A_##atom;
diff --git a/src/handlers.c b/src/handlers.c
index 9fb9040e..14a97db8 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -1164,6 +1164,13 @@ static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8
  */
 static void handle_focus_in(xcb_focus_in_event_t *event) {
     DLOG("focus change in, for window 0x%08x\n", event->event);
+    if (event->event == root) {
+        DLOG("focus in for root window\n");
+        con_focus(focused);
+        focused_id = XCB_NONE;
+        x_push_changes(croot);
+    }
+
     Con *con;
     if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL)
         return;
kawas44 commented 7 years ago

I experience the same issue with gitk the default git repository browser. Whenever I close this application, I loose focus and I need to use the mouse to click on a workspace number to return to normal.

Airblader commented 7 years ago

Since it's a tk application and the bug is in tk itself that isn't very surprising. Has anyone reported this to tk yet? If not, this would be the time to do so.

tcpeas commented 6 years ago

I'm seeing the same behaviour (on 1.14.1), but with a different test script (the python examples here don't reproduce it for me). Worse, the more I try to isolate it and grab logs the less predictable it becomes.

Can you point me to a reference on what Tk seems to be doing incorrectly here? Or what log events to concentrate on? Presently I can't reproduce consistently and I lack a good mental model of what's likely to be the problem, which is frustrating.

Regardless if Tk is behaving as a poor neighbour here, I think i3wm should handle it better. Broken clients should not be able to put the wm into a bad state where I have to reach for the mouse to recover. Maybe especially so if those clients haven't been shown to be buggy on various other wms for 20+ years :smile:.

Airblader commented 6 years ago

@tcpeas There's a lot of discussion here and in #2600 regarding what the issue is. Is there something specific you'd need help understanding with?

Otherwise, we already agreed that i3 should improve here nonetheless.

tcpeas commented 6 years ago

Specifically, I'm not clear what you're suggesting here:

tk should either properly follow the specs or, if it chooses to use XSetInputFocus anyway, ensure that they return focus to the appropriate window again.

What mechanism should be used instead of XSetInputFocus? And how would a client using it know where to return focus when it exits?

Sorry if this seems naive; I'm by no means an expert in X programming. If you can point to a good reference I'd be glad.

Airblader commented 6 years ago

What mechanism should be used instead of XSetInputFocus?

I'm talking about sending _NET_ACTIVE_WINDOW. However, I may also have underestimated the situation; clients sending XSetInpotFocus is expected in the WM_TAKE_FOCUS protocol. I haven't checked whether this is at play here or not.

Either way, we did already decide that i3 should definitely improve here.

tcpeas commented 6 years ago

I'm talking about sending _NET_ACTIVE_WINDOW.

Ah, no wonder I didn't see this mentioned at tronche.com. Yes, Tk's support for EWMH is limited.

If i3 simply isn't tracking SetInputFocus events in its focus model, that might explain why my tests seem to be inconsistent. Tk always sends SetInputFocus, but only sometimes does it lead to focus=None when the application exits.

I have found a simple way to reproduce the error condition using xdotool:

$ xdotool windowfocus root

This short-cuts the path Tk takes (SetInputFocus to a window whose parent is root, with RevertToParent) and consistently gets me into the error state. Asking afterward where the focus is gives me this:

$ x11trace xdotool getwindowfocus
  ... Reply to GetInputFocus: revert-to=Parent(0x02) focus=None(0x00000000)
xdo_get_focused_window_sane failed (code=1)
xdo_focus_window reported an error

.. while _NET_ACTIVE_WINDOW is still on the terminal where I launched xdotool windowfocus root.

Airblader commented 6 years ago

This short-cuts the path Tk takes (SetInputFocus to a window whose parent is root, with RevertToParent) and consistently gets me into the error state.

Yes, because directly calling XSetInputFocus bypasses the window manager; not only in terms of taking control from it, but the window manager is also not even notified about this. We only get the FocusIn event on the root window. That's why the patch I put together quickly was to handle FocusIn events on the root window.

Using _NET_ACTIVE_WINDOW changes things because now the window manager is the one to call XSetInputFocus, which means it is involved and can update its internal state correctly.

tcpeas commented 6 years ago

the window manager is also not even notified about this.

I'm going to show my ignorance again. If that's the case, how do these windows get the active decoration?

Using the following script, which simply cycles focus between two windows of the same application:

#!/usr/bin/env tclsh
package require Tk
wm withdraw .

toplevel .a -takefocus 1
toplevel .b -takefocus 1
puts "a is [winfo id .a]"
puts "b is [winfo id .b]"

proc ping {} {
    focus .a
    puts [focus]
    after 500 pong
}

proc pong {} {
    focus .b
    puts [focus]
    after 500 ping
}

ping

.. I see these log messages (trimmed to window creation and focus changes):

- handlers.c:handle_configure_request:284 - window 0x0180000e wants to be at 0x0 with 63x28 
- handlers.c:handle_configure_request:289 - Configure request for unmanaged window, can do that.
- handlers.c:handle_event:1484 - event type 20, xkb_base 85
- handlers.c:handle_map_request:265 - window = 0x0180000e, serial is 5084.
- manage.c:manage_window:166 - Managing window 0x0180000e 
- WM_CLASS changed to b (instance), Toplevel (class)
- WM_NAME changed to "b"
- handlers.c:handle_configure_request:284 - window 0x01800018 wants to be at 0x0 with 63x28 
- handlers.c:handle_configure_request:289 - Configure request for unmanaged window, can do that.
- handlers.c:handle_event:1484 - event type 20, xkb_base 85
- handlers.c:handle_map_request:265 - window = 0x01800018, serial is 5296.
- manage.c:manage_window:166 - Managing window 0x01800018 
- WM_CLASS changed to a (instance), Toplevel (class)
- WM_NAME changed to "a"
 ...
- handlers.c:handle_event:1484 - event type 9, xkb_base 85
- handlers.c:handle_focus_in:1207 - focus change in, for window 0x0180000e 
- handlers.c:handle_focus_in:1211 - That is con 0x561765b82a30 / (null)
- handlers.c:handle_focus_in:1237 - focus is different / refocusing floating window: updating decorations
- con.c:con_focus:225 - con_focus = 0x561765b82a30
- handlers.c:handle_event:1484 - event type 9, xkb_base 85
- handlers.c:handle_focus_in:1207 - focus change in, for window 0x01800018 
- handlers.c:handle_focus_in:1211 - That is con 0x561765b40f30 / (null)
- handlers.c:handle_focus_in:1237 - focus is different / refocusing floating window: updating decorations
- con.c:con_focus:225 - con_focus = 0x561765b40f30

FocusIn is 9 in X.h, so this looks to me like i3 is seeing those events?

Airblader commented 6 years ago

If that's the case, how do these windows get the active decoration?

I should've been more clear, sorry. We are notified via FocusIn events, but these happen after the fact and also do not really tell you what is going on (who changed focus, why focus was changed, …). Of course a window manager should still deal with such notifications (hence my proposed patch), but the actual responsibility of the window manager is to orchestrate and moderate these things. If all we get is a »Hey, I did this already« then we can't do our job properly, sort to speak.

tcpeas commented 6 years ago

Gotcha, that makes sense. Thanks for taking the time to explain these things and help me sharpen my questions.

I think I know enough now to try and figure out why my tests don't always reproduce, and try to understand if focused is always the correct container to recover focus. One wrinkle might be that these FocusIn(root) events are generated by X itself while the application is shutting down, probably among a cascade of other events. I'll run some experiments, gather some logs and report back when I know more! :smile:

gargantuanprism commented 6 years ago

Having this same issue, but I've found it only happens when the keyboard event callback calls self.quit(), not when self.quit_button is clicked. Apologies if I've missed something in the thread.

Python 2.7.12 python-tk 2.7.11-2 Ubuntu 16.04.3 LTS

import Tkinter as tk

class App(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)

        self.grid()
        self.create_widgets()

    def create_widgets(self):
        self.entry = tk.Entry(self)
        self.quit_button = tk.Button(self, text='quit', command=self.quit)

        self.entry.grid()
        self.quit_button.grid()

        self.entry.focus()

        self.bind('<Escape>', lambda _: self.quit())
        self.entry.bind('<Escape>', lambda _: self.quit())

if __name__ == '__main__':
    root = tk.Tk()
    root.attributes('-type', 'dialog')

    a = App(master=root)
    root.title(__file__)
    a.mainloop()
kevmitch commented 6 years ago

Have been running the patch on my work and home machines (on top of 2eca0f0 (master) and e9abc064de8dbde6602c363975e8ef8a1624cab (next) respectively) approaching two weeks > 8 hrs /day now. It solves the issue for me and doesn't appear to have any negative side effects.

For me, the issue manifested when my password-gorilla session timed out and reverted to the master password window. If I used the mouse to click "exit" to close password-gorilla altogether, my keyboard would stop working until I switched workspaces back and forth using the mouse on the i3bar. This no longer happens with @Airblader's patch.

MartinAlacam commented 6 years ago

This same issue happens with easygui too. May.be even worse, with tkinter the freeze didn't occur as long as I avoided using the keyboard, with easygui it is there all the time. It also managed to freeze my whole desktop when run as a plugin from a java program. I understand it is not i3's responsibility to fix non-standard behavior of other software, but this makes any kind of python gui development (and usage) under i3 very painful.

stapelberg commented 6 years ago

This issue has been marked fixed. Please open a new issue.