Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ polling = "3"
rusqlite = "0.33"
rustix = "0.38"
serde = "1"
signal-hook = "0.3"
smol = "2"
thiserror = "2"
tracing = "0.1"
Expand Down
13 changes: 8 additions & 5 deletions crates/egui-term/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ thiserror.workspace = true
tracing.workspace = true
wezterm-ssh = { workspace = true, features = ["vendored-openssl"] }

[target.'cfg(unix)'.dependencies]
signal-hook.workspace = true

[dev-dependencies]
eframe = { workspace = true, features = [
"accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
"default_fonts", # Embed the default egui fonts.
"wgpu", # Use the glow rendering backend. Alternative: "wgpu".
"persistence", # Enable restoring app state when restarting the app.
] }
"accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
"default_fonts", # Embed the default egui fonts.
"wgpu", # Use the glow rendering backend. Alternative: "wgpu".
"persistence", # Enable restoring app state when restarting the app.
] }
95 changes: 68 additions & 27 deletions crates/egui-term/src/ssh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use alacritty_terminal::tty::{ChildEvent, EventedPty, EventedReadWrite};
use anyhow::Context;
use polling::{Event, PollMode, Poller};
use std::collections::HashMap;
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use tracing::{error, trace};
use wezterm_ssh::{
Expand All @@ -14,10 +13,23 @@ use wezterm_ssh::{
};

#[cfg(unix)]
use std::os::fd::{AsFd, AsRawFd};
use signal_hook::{
consts,
low_level::{pipe, unregister},
SigId,
};

#[cfg(unix)]
use std::os::{
fd::{AsFd, AsRawFd},
unix::net::UnixStream,
};

#[cfg(windows)]
use std::os::windows::io::{AsRawSocket, AsSocket};
use std::{
net::{TcpListener, TcpStream},
os::windows::io::{AsRawSocket, AsSocket},
};

// Interest in PTY read/writes.
#[cfg(unix)]
Expand All @@ -30,12 +42,22 @@ const PTY_CHILD_EVENT_TOKEN: usize = 1;
pub struct Pty {
pub pty: SshPty,
pub child: SshChildProcess,
pub signal: TcpStream,
#[cfg(unix)]
pub signals: UnixStream,
#[cfg(unix)]
pub sig_id: SigId,
#[cfg(windows)]
pub signals: TcpStream,
}

impl Drop for Pty {
fn drop(&mut self) {
let _ = self.child.kill();

// Clear signal-hook handler.
#[cfg(unix)]
unregister(self.sig_id);

let _ = self.child.wait();
}
}
Expand Down Expand Up @@ -66,16 +88,16 @@ impl EventedReadWrite for Pty {
interest.key = PTY_READ_WRITE_TOKEN;
let _ = self.pty.reader.set_non_blocking(true);
let _ = self.pty.writer.set_non_blocking(true);
let _ = self.signal.set_nonblocking(true);
let _ = self.signals.set_nonblocking(true);

#[cfg(unix)]
{
poller.add_with_mode(self.pty.reader.as_raw_fd(), interest, mode)?;
poller.add_with_mode(self.pty.writer.as_raw_fd(), interest, mode)?;

poller.add_with_mode(
self.signal.as_raw_fd(),
Event::readable(PTY_CHILD_EVENT_TOKEN),
&self.signals,
Event::writable(PTY_CHILD_EVENT_TOKEN),
PollMode::Level,
)?;
}
Expand All @@ -86,8 +108,8 @@ impl EventedReadWrite for Pty {
poller.add_with_mode(self.pty.writer.as_raw_socket(), interest, mode)?;

poller.add_with_mode(
self.signal.as_raw_socket(),
Event::readable(crate::ssh::PTY_CHILD_EVENT_TOKEN),
self.signals.as_raw_socket(),
Event::readable(PTY_CHILD_EVENT_TOKEN),
PollMode::Level,
)?;
}
Expand All @@ -109,8 +131,8 @@ impl EventedReadWrite for Pty {
poller.modify_with_mode(self.pty.writer.as_fd(), interest, mode)?;

poller.modify_with_mode(
self.signal.as_fd(),
Event::readable(PTY_CHILD_EVENT_TOKEN),
&self.signals,
Event::writable(PTY_CHILD_EVENT_TOKEN),
PollMode::Level,
)?;
}
Expand All @@ -121,8 +143,8 @@ impl EventedReadWrite for Pty {
poller.modify_with_mode(self.pty.writer.as_socket(), interest, mode)?;

poller.modify_with_mode(
self.signal.as_socket(),
Event::readable(crate::ssh::PTY_CHILD_EVENT_TOKEN),
self.signals.as_socket(),
Event::readable(PTY_CHILD_EVENT_TOKEN),
PollMode::Level,
)?;
}
Expand All @@ -136,15 +158,15 @@ impl EventedReadWrite for Pty {
poller.delete(self.pty.reader.as_fd())?;
poller.delete(self.pty.writer.as_fd())?;

poller.delete(self.signal.as_fd())?;
poller.delete(&self.signals)?;
}

#[cfg(windows)]
{
poller.delete(self.pty.reader.as_socket())?;
poller.delete(self.pty.writer.as_socket())?;

poller.delete(self.signal.as_socket())?;
poller.delete(self.signals.as_socket())?;
}

Ok(())
Expand Down Expand Up @@ -206,10 +228,6 @@ impl Pty {
verify.answer(true).await.context("send verify response")?;
}
SessionEvent::Authenticate(auth) => {
for a in auth.prompts.iter() {
println!("prompt: {}", a.prompt);
}

let mut answers = vec![];
for prompt in auth.prompts.iter() {
if prompt.prompt.contains("Password") {
Expand All @@ -234,15 +252,43 @@ impl Pty {

// FIXME: set in settings
let mut env = HashMap::new();
env.insert("LANG".to_string(), "zh_CN.utf8".to_string());
env.insert("LANG".to_string(), "en_US.UTF-8".to_string());
env.insert("LC_COLLATE".to_string(), "C".to_string());

let (pty, child) = session
.request_pty("xterm-256color", PtySize::default(), None, Some(env))
.await?;

let signal = tcp_signal()?;
Ok(Pty { pty, child, signal })
#[cfg(unix)]
{
// Prepare signal handling before spawning child.
let (signals, sig_id) = {
let (sender, recv) = UnixStream::pair()?;

// Register the recv end of the pipe for SIGCHLD.
let sig_id = pipe::register(consts::SIGCHLD, sender)?;
recv.set_nonblocking(true)?;
(recv, sig_id)
};

Ok(Pty {
pty,
child,
signals,
sig_id,
})
}

#[cfg(windows)]
{
let listener = TcpListener::bind("127.0.0.1:0")?;
let signals = TcpStream::connect(listener.local_addr()?)?;
Ok(Pty {
pty,
child,
signals,
})
}
})
}
}
Expand All @@ -261,8 +307,3 @@ pub enum Authentication {
Password(String, String),
Config,
}

fn tcp_signal() -> std::io::Result<TcpStream> {
let listener = TcpListener::bind("127.0.0.1:0")?;
TcpStream::connect(listener.local_addr()?)
}
Binary file modified nxshell/assets/imgs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 13 additions & 2 deletions nxshell/src/ui/tab_view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,27 @@ impl egui_dock::TabViewer for TabViewer<'_> {
type Tab = Tab;

fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
let tab_id = tab.id();
match &mut tab.inner {
TabInner::Term(term) => match term.term_type {
TermType::Ssh { ref options } => {
let icon = match options.auth {
Authentication::Config => DRONE,
Authentication::Password(..) => NUMPAD,
};
format!("{icon} {}", options.name).into()
if tab_id > 0 {
format!("{icon} {} ({tab_id})", options.name).into()
} else {
format!("{icon} {}", options.name).into()
}
}
TermType::Regular { .. } => {
if tab_id > 0 {
format!("local ({tab_id})").into()
} else {
"local".into()
}
}
TermType::Regular { .. } => "local".into(),
},
TabInner::SessionList(_) => "sessions".into(),
}
Expand Down
Loading