Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key events should not be sent to an inactive screen #4704

Closed
darrenburns opened this issue Jul 4, 2024 · 6 comments · Fixed by #4789
Closed

Key events should not be sent to an inactive screen #4704

darrenburns opened this issue Jul 4, 2024 · 6 comments · Fixed by #4789

Comments

@darrenburns
Copy link
Member

darrenburns commented Jul 4, 2024

Key events get sent to non-active screens for a short period on calling dismiss, before the screen has been completely dismissed. Most people won't expect this and may be doing things in their code which assumes the screen is active - leading to crashes.

We should ensure key events are only delivered to the screen while it remains active.

Edit:

from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.widgets import Label, Select


class MyScreen(Screen):
    def compose(self):
        yield Label("Screen")
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])
        yield Select([("bar", "bar")])

    async def on_key(self):
        await self.dismiss()


class MyApp(App):
    def compose(self):
        yield Label("App")
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])
        yield Select([("foo", "foo")])

    def on_key(self):
        self.push_screen(MyScreen())


app = MyApp()
app.run()
@merriam
Copy link
Contributor

merriam commented Jul 8, 2024

So, your example is "press a key twice and crash with 'TypeError: object NoneType...`on the self.dismiss()?

To clarify, you can remove all the Select controls.

The issue is using await on a non-async call. Instead of await self.dismiss(), just use self.dismiss().

Ref: https://stackoverflow.com/questions/56872323/typeerror-object-nonetype-cant-be-used-in-await-expression

@darrenburns
Copy link
Member Author

Yeah, the example is actually Will's edit :)

Sorry, this issue is kind of confusing and without context, as it was a quick note I made for Will based on something we discussed last week.

The issue is that key events get sent to non-active screens for a short period, before the screen has been completely dismissed. Most people won't expect this and may be doing things in their code which assumes the screen is active - leading to crashes.

@merriam
Copy link
Contributor

merriam commented Jul 9, 2024

The claimed sequence is:

  • self.dismiss() is called and returns.
  • a key is pressed
  • an on_key() handler is called inside the dismissed screen

So, something more like:

     def do_dismiss():
            self.in_dismissal = True
            self.dismiss()

      def on_key(self):
            if self.in_dismissal:
                 print("Error")

Then on a mouse move or timer, dismiss the window and pop it back, while pounding the keyboard and we should eventually hit "error"?

@willmcgugan
Copy link
Collaborator

The original issue was a deadlock which tripped deadlock detection. That no longer occurs, because we removed the deadlock detection after improving the way widgets are removed. However the deadlock remains in the above code.

Essentially, the call to await dismiss() on the screen was awaiting for itself to be removed. The solution would be to not await it, but that it's not great developer experience. I think either we report a helpful error, or make it work somehow. I'm working on that at the moment.

There is some other behavior that might need explaining, because it confused me initially. When you press a key on MyScreen, both on_key methods fire. This is because the event bubbles to the App. Obvious in retrospect.

@willmcgugan
Copy link
Collaborator

Hopefully this is clear...

Screenshot 2024-07-23 at 11 16 45

Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants