Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]

### Added

* Stop the program in raw mode if we receive "q" in stdin #478 - @sigmaSd

### Fixed

* Update CONTRIBUTING information #438 - @YJDoc2 @cyqsimon
Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ fn main() -> eyre::Result<()> {
)?;
}

let os_input = os::get_input(opts.interface.as_deref(), !opts.no_resolve, opts.dns_server)?;
let os_input = os::get_input(
opts.interface.as_deref(),
!opts.no_resolve,
opts.dns_server,
opts.raw,
)?;
if opts.raw {
let terminal_backend = RawTerminalBackend {};
start(terminal_backend, os_input, opts);
Expand Down
80 changes: 76 additions & 4 deletions src/os/shared.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::{
io::{self, ErrorKind, Write},
io::{self, BufRead, BufReader, ErrorKind, Write},
net::Ipv4Addr,
time,
sync::mpsc::{channel, Receiver},
thread,
time::{self, Duration},
};

use crossterm::event::{read, Event};
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use eyre::{bail, eyre};
use itertools::Itertools;
use log::{debug, warn};
Expand Down Expand Up @@ -44,6 +46,69 @@ impl Iterator for TerminalEvents {
}
}

pub struct StdinEvents {
receiver: Receiver<Option<Event>>,
}

impl StdinEvents {
pub fn new() -> Self {
let (sender, receiver) = channel();

// Spawn a thread to read from stdin
thread::spawn(move || {
let stdin = io::stdin();
let reader = BufReader::new(stdin);

for line in reader.lines() {
match line {
Ok(text) => {
let trimmed = text.trim();
if trimmed == "q" {
// Send a 'q' key press event to trigger shutdown
let event = Some(Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: crossterm::event::KeyEventState::empty(),
}));
if sender.send(event).is_err() {
break;
}
}
// For other input, just ignore and continue reading
}
Err(_) => {
// Error reading, send None to signal EOF
let _ = sender.send(None);
break;
}
}
}
// EOF reached, send None
let _ = sender.send(None);
});

Self { receiver }
}
}

impl Iterator for StdinEvents {
type Item = Event;
fn next(&mut self) -> Option<Event> {
// Try to receive with a timeout to avoid blocking forever
match self.receiver.recv_timeout(Duration::from_millis(100)) {
Ok(Some(event)) => Some(event),
Ok(None) => None, // EOF
Err(_) => {
// Timeout or channel error, just try again next time
// Return a dummy resize event to keep the loop going
// This won't trigger any action in raw mode
self.next()
}
}
}
}

pub(crate) fn get_datalink_channel(
interface: &NetworkInterface,
) -> Result<Box<dyn DataLinkReceiver>, GetInterfaceError> {
Expand Down Expand Up @@ -96,6 +161,7 @@ pub fn get_input(
interface_name: Option<&str>,
resolve: bool,
dns_server: Option<Ipv4Addr>,
raw_mode: bool,
) -> eyre::Result<OsInputOutput> {
// get the user's requested interface, if any
// IDEA: allow requesting multiple interfaces
Expand Down Expand Up @@ -207,10 +273,16 @@ pub fn get_input(

let write_to_stdout = create_write_to_stdout();

let terminal_events: Box<dyn Iterator<Item = Event> + Send> = if raw_mode {
Box::new(StdinEvents::new())
} else {
Box::new(TerminalEvents)
};

Ok(OsInputOutput {
interfaces_with_frames,
get_open_sockets,
terminal_events: Box::new(TerminalEvents),
terminal_events,
dns_client,
write_to_stdout,
})
Expand Down
Loading