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

Temporarily disable async key event polling #837

Open
fritzrehde opened this issue Nov 2, 2023 · 1 comment
Open

Temporarily disable async key event polling #837

fritzrehde opened this issue Nov 2, 2023 · 1 comment

Comments

@fritzrehde
Copy link

Is your feature request related to a problem? Please describe.
In the TUI I am building, I would like to temporarily disable the entire TUI I am painting, execute a command in a subshell that paints its own TUI (e.g. text editor like vim), and then resume my TUI. I use async Rust with Tokio for the key event polling. I have key event polling setup on a separate thread. If I just "leave this polling on" (meaning disable the painting of my TUI, but keep listening to crossterm key events), then my experience in the other TUI (the text editor) is affected by crossterm, as key presses become slugish and take longer to recognize (I'm guessing this is maybe because crossterm is somehow consuming and re-emitting the key presses?). This behaviour is fine though, I shouldn't be listening for key events in my app if I expect the child TUI I am executing to listen to the same key presses. Therefore, I wanted to temporarily (as long as the child TUI is executing) disable key event polling. This is how I currently have that implemented:

/// A command sent to a polling thread.
enum PollingCommand {
    /// Continue listening/polling for terminal events.
    Listen,
    /// Pause listening/polling for terminal events. Notifies event's sender
    /// once polling has actually been paused.
    Pause(Sender<()>),
}

/// Continuously listens for terminal-related events, and sends relevant events
/// back to the main thread.
async fn poll_terminal_events(event_tx: Sender<Event>, mut polling_rx: Receiver<PollingCommand>) {
    let mut terminal_event_reader = EventStream::new();

    'polling_loop: loop {
        tokio::select! {
            // Wait for receival of a polling command from main event loop thread.
            polling = polling_rx.recv() => match polling {
                Some(PollingCommand::Pause(polling_paused_tx)) => {
                    log::info!("Terminal event listener has been paused.");

                    // Notify sender thread that polling has been paused.
                    let _ = polling_paused_tx.send(()).await;

                    // Wait until another Listen command is received.
                    'wait_for_listen: while let Some(polling) = polling_rx.recv().await {
                        if let PollingCommand::Listen = polling {
                            break 'wait_for_listen;
                        }
                    }

                    log::info!("Terminal event listener is listening again.");
                },
                // Currently already listening for terminal events.
                Some(PollingCommand::Listen) => {},
                // Channel has been closed.
                None => break 'polling_loop,
            },
            // Wait for a terminal event.
            event = terminal_event_reader.next().fuse() => match event {
                Some(Ok(CrosstermEvent::Key(key_event))) => {
                    if event_tx.send(Event::KeyPressed(key_event)).await.is_err() {
                        break 'polling_loop;
                    };
                }
                _ => {}
            }
        }
    }

    log::info!("Shutting down terminal event listener task");
}

This does mostly solve the issue I had before. Key events are handled smoothly in the child TUI (text editor). However, some key presses are still exclusively handled by crossterm, i.e. I press a key inside the child TUI and it doesn't register there, but seems to be somehow "saved" by crossterm and then emitted once I leave the child TUI. I am assuming this has something to do with terminal_event_reader.next().fuse(). I don't know anything about how EventStream works. Is it buffering some events? I was thinking maybe I should clear/delete the entire buffer somehow once I re-enter the Listen state, but I don't know how to do that.

Describe the solution you'd like
Any help? Maybe I am approaching this problem in a wrong way (with all the channels etc.) and there is a simpler solution.
I would appreciate any help/comments you have!

Additional context
You can check out the exact code in my project: watchbind.

@TyberiusPrime
Copy link

TyberiusPrime commented Mar 14, 2024

Currently stuck on the same issue.

If I drop my EventStream, the child appears to get all the key strokes. But recreating the event stream doesn't get my app the events again.

(edit: That turned to out to be an issue between chair and keyboard. I had another level of 'suspend input' in my layers that needed cleaning up. So I can confirm, dropping the EventStream and re recreating it works as one would expect.).

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

No branches or pull requests

2 participants