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

Enable AppFocus and AppBlur in terminal emulators #4265

Merged
merged 15 commits into from
Mar 11, 2024

Conversation

davep
Copy link
Contributor

@davep davep commented Mar 6, 2024

This enables support for receiving and handling FocusIn and FocusOut sequences, and turns then into AppFocus and AppBlur events. Support for enabling this is added to both the "Linux" and Windows drivers. From now on, when a terminal emulator window that supports it sends the FocusIn and FocusOut sequences the app will receive AppFocus and AppBlur.

An automatic effect of this now is that, when the window loses focus, focus will be removed from the widget that has focus. This was already an established feature of Textual applications running via textual-web; it just now extends it to the drivers used on GNU/Linux, macOS and Windows.

The AppFocus and AppBlur events could be useful too.

Screen.Recording.2024-03-07.at.11.56.32.mov

It should be noted that this is technically a breaking change.

Implements #4263 (which in turn stems from #4259).

It also fixes a problem with the current implementation of AppFocus and AppBlur that is mentioned in a comment in #4263.


A natural question is going to be, how widely supported is this? So far the following have been tested and shown to support this:

  • macOS
    • Kitty
    • iTerm2
    • Contour
    • terminal.app
    • WezTerm
    • rio
    • Tabby
    • VSCode Terminal
  • GNU/Linux
  • Windows 11

It has been noted by @TomJGooding that iTerm2 has a setting for turning this off1; when turned off the enable/disable sequence appears to be swallowed and ignored.

It would appear that vterm under Emacs doesn't support this, but running my test app had the same effect as running it under iTerm with support turned off (that is, the enable/disable sequences seem to be swallowed and ignored).

NOTE: This is marked as and should be treated as a breaking change. This can potentially affect application styling in a way that would not have been an issue before. For example, before this change, this application would always look the same no matter if the terminal emulator it was within had focus or not:

from textual.app import App, ComposeResult
from textual.widgets import Input

class OneWidgetApp(App[None]):

    CSS = """
    Screen {
        align: center middle;
    }

    Input:focus {
        border: round red;
        width: 50%;
        background: pink;
    }
    """

    def compose(self) -> ComposeResult:
        yield Input(placeholder="Please give your answer")

if __name__ == "__main__":
    OneWidgetApp().run()

from now on the layout of the application will look very different when focus moves away form the window.

Footnotes

  1. Because of course it does! What doesn't iTerm have a setting for?!?

davep added 2 commits March 6, 2024 16:32
This enables support for receiving and handling FocusIn and FocusOut
sequences, and turns then into AppFocus and AppBlur events.
@davep davep added enhancement New feature or request Task labels Mar 6, 2024
@davep davep self-assigned this Mar 6, 2024
@@ -20,6 +20,8 @@
)
_re_bracketed_paste_start = re.compile(r"^\x1b\[200~$")
_re_bracketed_paste_end = re.compile(r"^\x1b\[201~$")
_re_focusin = re.compile(r"^\x1b\[I$")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like this needs to be a regex. Could it not be a simple string?

Ditto for the bracketed paste stuff. Unless I'm missing something, it looks like they could also just be strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to ask you that very question in the morning, oddly enough. Couldn't for the life of me see why the bracketed paste checking used a regex but followed the pattern with a mental note to ask why it was done that way (although looking at fe151a7 it seems I'd have been asking the wrong person anyway).

If there was no reason I'll make them simple strings; makes way more sense to me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably just following a pattern. But yeah, let's go with strings. And if you wouldn't mind changing the bracketed paste ones while you are there...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

davep and others added 8 commits March 7, 2024 10:21
While the terminal window didn't have focus, anything can could happen. The
widget could be removed, the screen could change, etc. So by the time
AppFocus happens the widget might not be one to focus any more.

Initially I was just making it the focused widget anyway and letting the
focus-handling code do what it needed to do. Sending focus to a widget that
isn't part of the DOM any more isn't exactly a breaking problem; but...

One issue is that you can end up with App.focused saying that a widget is
focused that isn't in the DOM any more. We don't want that. So here I'm a
bit more defensive. This changes things so that we check that the widget's
screen is still the screen that's in play. If the widget has been removed it
won't have a parent and so can't find its screen. All of this means that if
the screen has changed *or* if the widget has been removed, we're covered.
These tests don't test the actual act of blurring or focusing the
application (that's kind of hard to do in tests, really). What it does do is
test that widget focus does the right thing after each of those app-level
events.
Remove the text so say they're only for textual-web, but make it clear
textual-web is supported, as are any other terminals that support the
required sequences.
Co-authored-by: TomJGooding <[email protected]>
Co-authored-by: Will McGugan <[email protected]>
While the application is in an AppBlur state, it's possible that some code
could have been running that updated what's focused. It doesn't make sense
to have Textual itself override the dev's choice to have focus be somewhere
else (perhaps the result of some long-running background process, that
they've tabbed away from, and when they tab back they expect to be in a
specific control).

So here I tweak the code that restores the focused widget so that it only
restores if it's still the case that nothing has focus.
@davep davep changed the title WiP: Enable AppFocus and AppBlur in terminal emulators Enable AppFocus and AppBlur in terminal emulators Mar 7, 2024
@davep davep marked this pull request as ready for review March 7, 2024 15:49
@willmcgugan willmcgugan merged commit a5bcbc6 into Textualize:main Mar 11, 2024
20 checks passed
@davep davep deleted the more-app-focus-blur branch March 11, 2024 13:39
@davep
Copy link
Contributor Author

davep commented Mar 11, 2024

Adding for future reference if someone comes looking: this is also supported by tmux if focus-events is set to on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Task
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Input and TextArea have cursor blinking even though terminal window is not focused
2 participants