Textualize/textual

Mouse movement control sequences leak to terminal at app shutdown

Opened this issue · 11 comments

If the user is moving the mouse while the textual application exits, terminal control sequences from the mouse movement may leak from the application and get into the terminal.

This happens on all terminals I have tested: gnome-terminal and Terminator on Ubuntu Linux, and the Terminal on macOS. The issue can be easily reproduced using the examples from the textual repository. Here's a reproduction with five_by_five.py, where I press q on the keyboard while moving the mouse around the terminal:

control-seq-leak-2024-04-15_10.56.24.mp4

Please find the diagnostics output below:

textual diagnose output
<!-- This is valid Markdown, please paste the following directly in to a GitHub issue -->
# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 0.56.4 |
| Rich    | 13.7.1 |

## Python

| Name           | Value                                        |
|----------------|----------------------------------------------|
| Version        | 3.11.7                                       |
| Implementation | CPython                                      |
| Compiler       | GCC 9.4.0                                    |
| Executable     | /home/saf/code/misc/textual/.venv/bin/python |

## Operating System

| Name    | Value                                        |
|---------|----------------------------------------------|
| System  | Linux                                        |
| Release | 5.4.0-176-generic                            |
| Version | #196-Ubuntu SMP Fri Mar 22 16:46:39 UTC 2024 |

## Terminal

| Name                 | Value          |
|----------------------|----------------|
| Terminal Application | Terminator     |
| TERM                 | xterm-256color |
| COLORTERM            | truecolor      |
| FORCE_COLOR          | *Not set*      |
| NO_COLOR             | *Not set*      |

## Rich Console options

| Name           | Value                |
|----------------|----------------------|
| size           | width=167, height=45 |
| legacy_windows | False                |
| min_width      | 1                    |
| max_width      | 167                  |
| is_terminal    | False                |
| encoding       | utf-8                |
| max_height     | 45                   |
| justify        | None                 |
| overflow       | None                 |
| no_wrap        | False                |
| highlight      | None                 |
| markup         | None                 |
| height         | None                 |

We found the following entries in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

I took a shot at diagnosing this issue, and the issue disappears if we modify the shutdown sequence of the input thread to loop a few times more to drain the mouse movement sequences out from the input descriptor:

class LinuxDriver(Driver):
    ...
    def run_input_thread(self) -> None:
        ...
        countdown = 3

        try:
            with open("/tmp/textual_out", "a") as f:
                while not self.exit_event.is_set() or countdown > 0:
                    selector_events = selector.select(0.1)
                    for _selector_key, mask in selector_events:
                        if mask & EVENT_READ:
                            rdd = read(fileno, 1024)
                            unicode_data = decode(rdd, final=self.exit_event.is_set())
                            for event in feed(unicode_data):
                                self.process_event(event)
                    if self.exit_event.is_set():
                        print(
                            f"Exit event set, countdown: {countdown}, data: {rdd if mask & EVENT_READ else None}",
                            file=f,
                        )
                        termios.tcflush(fileno, termios.TCIFLUSH)
                        rdd = ""
                        countdown -= 1
        finally:
            selector.close()

I'm getting the following output in /tmp/textual_out if I move the mouse while quitting the application:

Exit event set, countdown: 3, data: b'\x1b[<35;101;46M'
Exit event set, countdown: 2, data: b'\x1b[<35;111;45M'
Exit event set, countdown: 1, data: 

so it looks like new mouse movement control sequences may appear on the linux driver's terminal file descriptor, even though the control sequence for disabling mouse support is already sent by disable_input, and the descriptor's input queue is flushed. I'm not sure how to proceed from here: this "draining" mechanism works to resolve this issue, but it looks very odd that we would have to do it.

This issue does seem specific to certain terminal emulators, at least from running quick tests with:

  • xterm
  • urxvt
  • alacritty
  • terminator
  • wezterm
  • Xfce Terminal

FWIW I couldn't reproduce it with kitty on macOS.

@davep How about with macOS Terminal (just for confirmation as not one I'm able to check)?

Didn't test with anything else; I'll try and remember to have a look the next time I'm at a machine.

I've also now tested with Xfce Terminal which shows the same issue, which makes me wonder if perhaps specific to VTE-based terminal emulators? (Though this wouldn't explain issues with macOS terminals.)

for more macOS terminal emulators:
issue can be reproduced on Hyper, but not on iTerm2 and Terminal.app

I can't seem to reproduce it at all with the latest Textual (on macOS).

Actually, I did manage to reproduce it after a few attempt on Hyper. This should help me track it down!

Happens the same to me on Windows PowerShell