Use terminal window focus to determine when a message has actually been seen (#94)

This commit is contained in:
Ulyssa 2023-06-14 22:42:23 -07:00
parent 430c601ff2
commit 885b56038f
No known key found for this signature in database
GPG key ID: 1B3965A3D18B9B64
5 changed files with 51 additions and 13 deletions

2
Cargo.lock generated
View file

@ -2137,7 +2137,7 @@ dependencies = [
[[package]] [[package]]
name = "modalkit" name = "modalkit"
version = "0.0.15" version = "0.0.15"
source = "git+https://github.com/ulyssa/modalkit?rev=7979680#797968002c53287d907ca3ae823e3482265c94bb" source = "git+https://github.com/ulyssa/modalkit?rev=f641162#f6411625caa1ddb764c91a3fc47e052cfdc405b5"
dependencies = [ dependencies = [
"anymap2", "anymap2",
"arboard", "arboard",

View file

@ -52,7 +52,7 @@ url = {version = "^2.2.2", features = ["serde"]}
[dependencies.modalkit] [dependencies.modalkit]
git = "https://github.com/ulyssa/modalkit" git = "https://github.com/ulyssa/modalkit"
rev = "7979680" rev = "f641162"
[dependencies.matrix-sdk] [dependencies.matrix-sdk]
version = "0.6" version = "0.6"

View file

@ -22,7 +22,15 @@ use matrix_sdk::ruma::OwnedUserId;
use modalkit::crossterm::{ use modalkit::crossterm::{
self, self,
cursor::Show as CursorShow, cursor::Show as CursorShow,
event::{poll, read, DisableBracketedPaste, EnableBracketedPaste, Event}, event::{
poll,
read,
DisableBracketedPaste,
DisableFocusChange,
EnableBracketedPaste,
EnableFocusChange,
Event,
},
execute, execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle}, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle},
}; };
@ -109,6 +117,7 @@ struct Application {
bindings: KeyManager<TerminalKey, ProgramAction, RepeatType, ProgramContext>, bindings: KeyManager<TerminalKey, ProgramAction, RepeatType, ProgramContext>,
actstack: VecDeque<(ProgramAction, ProgramContext)>, actstack: VecDeque<(ProgramAction, ProgramContext)>,
screen: ScreenState<IambWindow, IambInfo>, screen: ScreenState<IambWindow, IambInfo>,
focused: bool,
} }
impl Application { impl Application {
@ -120,6 +129,7 @@ impl Application {
crossterm::terminal::enable_raw_mode()?; crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(stdout, EnterAlternateScreen)?; crossterm::execute!(stdout, EnterAlternateScreen)?;
crossterm::execute!(stdout, EnableBracketedPaste)?; crossterm::execute!(stdout, EnableBracketedPaste)?;
crossterm::execute!(stdout, EnableFocusChange)?;
let title = format!("iamb ({})", settings.profile.user_id); let title = format!("iamb ({})", settings.profile.user_id);
crossterm::execute!(stdout, SetTitle(title))?; crossterm::execute!(stdout, SetTitle(title))?;
@ -154,11 +164,13 @@ impl Application {
bindings, bindings,
actstack, actstack,
screen, screen,
focused: true,
}) })
} }
fn redraw(&mut self, full: bool, store: &mut ProgramStore) -> Result<(), std::io::Error> { fn redraw(&mut self, full: bool, store: &mut ProgramStore) -> Result<(), std::io::Error> {
let bindings = &mut self.bindings; let bindings = &mut self.bindings;
let focused = self.focused;
let sstate = &mut self.screen; let sstate = &mut self.screen;
let term = &mut self.terminal; let term = &mut self.terminal;
@ -176,7 +188,11 @@ impl Application {
// Don't show terminal cursor when we show a dialog. // Don't show terminal cursor when we show a dialog.
let hide_cursor = !dialogstr.is_empty(); let hide_cursor = !dialogstr.is_empty();
let screen = Screen::new(store).show_dialog(dialogstr).show_mode(modestr).borders(true); let screen = Screen::new(store)
.show_dialog(dialogstr)
.show_mode(modestr)
.borders(true)
.focus(focused);
f.render_stateful_widget(screen, area, sstate); f.render_stateful_widget(screen, area, sstate);
if hide_cursor { if hide_cursor {
@ -212,8 +228,11 @@ impl Application {
Event::Mouse(_) => { Event::Mouse(_) => {
// Do nothing for now. // Do nothing for now.
}, },
Event::FocusGained | Event::FocusLost => { Event::FocusGained => {
// Do nothing for now. self.focused = true;
},
Event::FocusLost => {
self.focused = false;
}, },
Event::Resize(_, _) => { Event::Resize(_, _) => {
// We'll redraw for the new size next time step() is called. // We'll redraw for the new size next time step() is called.
@ -532,13 +551,18 @@ async fn run(settings: ApplicationSettings) -> IambResult<()> {
login(worker, &settings).await.unwrap_or_else(print_exit); login(worker, &settings).await.unwrap_or_else(print_exit);
fn restore_tty() {
let _ = crossterm::terminal::disable_raw_mode();
let _ = crossterm::execute!(stdout(), DisableBracketedPaste);
let _ = crossterm::execute!(stdout(), DisableFocusChange);
let _ = crossterm::execute!(stdout(), LeaveAlternateScreen);
let _ = crossterm::execute!(stdout(), CursorShow);
}
// Make sure panics clean up the terminal properly. // Make sure panics clean up the terminal properly.
let orig_hook = std::panic::take_hook(); let orig_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| { std::panic::set_hook(Box::new(move |panic_info| {
let _ = crossterm::terminal::disable_raw_mode(); restore_tty();
let _ = crossterm::execute!(stdout(), DisableBracketedPaste);
let _ = crossterm::execute!(stdout(), LeaveAlternateScreen);
let _ = crossterm::execute!(stdout(), CursorShow);
orig_hook(panic_info); orig_hook(panic_info);
process::exit(1); process::exit(1);
})); }));
@ -547,6 +571,7 @@ async fn run(settings: ApplicationSettings) -> IambResult<()> {
// We can now run the application. // We can now run the application.
application.run().await?; application.run().await?;
restore_tty();
Ok(()) Ok(())
} }

View file

@ -848,7 +848,9 @@ impl<'a> StatefulWidget for Chat<'a> {
// Render the message scrollback. // Render the message scrollback.
let scrollback_focused = state.focus.is_scrollback() && self.focused; let scrollback_focused = state.focus.is_scrollback() && self.focused;
let scrollback = Scrollback::new(self.store).focus(scrollback_focused); let scrollback = Scrollback::new(self.store)
.focus(scrollback_focused)
.room_focus(self.focused);
scrollback.render(scrollarea, buf, &mut state.scrollback); scrollback.render(scrollarea, buf, &mut state.scrollback);
} }
} }

View file

@ -1206,13 +1206,21 @@ impl TerminalCursor for ScrollbackState {
} }
pub struct Scrollback<'a> { pub struct Scrollback<'a> {
room_focused: bool,
focused: bool, focused: bool,
store: &'a mut ProgramStore, store: &'a mut ProgramStore,
} }
impl<'a> Scrollback<'a> { impl<'a> Scrollback<'a> {
pub fn new(store: &'a mut ProgramStore) -> Self { pub fn new(store: &'a mut ProgramStore) -> Self {
Scrollback { focused: false, store } Scrollback { room_focused: false, focused: false, store }
}
/// Indicate whether the room window is currently focused, regardless of whether the scrollback
/// also is.
pub fn room_focus(mut self, focused: bool) -> Self {
self.room_focused = focused;
self
} }
/// Indicate whether the scrollback is currently focused. /// Indicate whether the scrollback is currently focused.
@ -1307,7 +1315,10 @@ impl<'a> StatefulWidget for Scrollback<'a> {
y += 1; y += 1;
} }
if settings.tunables.read_receipt_send && state.cursor.timestamp.is_none() { if self.room_focused &&
settings.tunables.read_receipt_send &&
state.cursor.timestamp.is_none()
{
// If the cursor is at the last message, then update the read marker. // If the cursor is at the last message, then update the read marker.
info.read_till = info.messages.last_key_value().map(|(k, _)| k.1.clone()); info.read_till = info.messages.last_key_value().map(|(k, _)| k.1.clone());
} }