use std::collections::HashMap;
use std::ffi::OsString;
use std::path::PathBuf;
use std::time::Duration;
#[cfg(unix)]
use libc;
#[derive(Debug, Clone)]
pub struct PtyConfig {
pub working_directory: Option<PathBuf>,
pub env: Option<HashMap<OsString, OsString>>,
pub env_add: HashMap<OsString, OsString>,
pub env_remove: Vec<OsString>,
pub window_size: (u16, u16),
pub new_session: bool,
pub spawn_timeout: Option<Duration>,
#[cfg(unix)]
pub controlling_terminal: bool,
#[cfg(windows)]
pub allocate_console: bool,
}
impl Default for PtyConfig {
fn default() -> Self {
Self {
working_directory: None,
env: None,
env_add: HashMap::new(),
env_remove: Vec::new(),
window_size: (80, 24),
new_session: true,
spawn_timeout: None,
#[cfg(unix)]
controlling_terminal: true,
#[cfg(windows)]
allocate_console: true,
}
}
}
impl PtyConfig {
#[must_use]
pub fn builder() -> PtyConfigBuilder {
PtyConfigBuilder::new()
}
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn effective_env(&self) -> HashMap<OsString, OsString> {
let mut env = self
.env
.clone()
.unwrap_or_else(|| std::env::vars_os().collect());
env.extend(self.env_add.clone());
for key in &self.env_remove {
env.remove(key);
}
env
}
}
#[derive(Debug, Clone, Default)]
pub struct PtyConfigBuilder {
config: PtyConfig,
}
impl PtyConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn working_directory(mut self, path: impl Into<PathBuf>) -> Self {
self.config.working_directory = Some(path.into());
self
}
#[must_use]
pub fn env_clear(mut self) -> Self {
self.config.env = Some(HashMap::new());
self
}
#[must_use]
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.config.env_add.insert(key.into(), value.into());
self
}
#[must_use]
pub fn env_remove(mut self, key: impl Into<OsString>) -> Self {
self.config.env_remove.push(key.into());
self
}
#[must_use]
pub const fn window_size(mut self, cols: u16, rows: u16) -> Self {
self.config.window_size = (cols, rows);
self
}
#[must_use]
pub const fn new_session(mut self, value: bool) -> Self {
self.config.new_session = value;
self
}
#[must_use]
pub const fn spawn_timeout(mut self, timeout: Duration) -> Self {
self.config.spawn_timeout = Some(timeout);
self
}
#[cfg(unix)]
#[must_use]
pub const fn controlling_terminal(mut self, value: bool) -> Self {
self.config.controlling_terminal = value;
self
}
#[cfg(windows)]
#[must_use]
pub fn allocate_console(mut self, value: bool) -> Self {
self.config.allocate_console = value;
self
}
#[must_use]
pub fn build(self) -> PtyConfig {
self.config
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PtySignal {
Interrupt,
Quit,
Terminate,
Kill,
Hangup,
WindowChange,
#[cfg(unix)]
Stop,
#[cfg(unix)]
Continue,
#[cfg(unix)]
User1,
#[cfg(unix)]
User2,
}
impl PtySignal {
#[cfg(unix)]
#[must_use]
pub const fn as_unix_signal(self) -> Option<i32> {
match self {
Self::Interrupt => Some(libc::SIGINT),
Self::Quit => Some(libc::SIGQUIT),
Self::Terminate => Some(libc::SIGTERM),
Self::Kill => Some(libc::SIGKILL),
Self::Hangup => Some(libc::SIGHUP),
Self::WindowChange => Some(libc::SIGWINCH),
Self::Stop => Some(libc::SIGTSTP),
Self::Continue => Some(libc::SIGCONT),
Self::User1 => Some(libc::SIGUSR1),
Self::User2 => Some(libc::SIGUSR2),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WindowSize {
pub cols: u16,
pub rows: u16,
pub xpixel: u16,
pub ypixel: u16,
}
impl WindowSize {
#[must_use]
pub const fn new(cols: u16, rows: u16) -> Self {
Self {
cols,
rows,
xpixel: 0,
ypixel: 0,
}
}
#[must_use]
pub const fn with_pixels(cols: u16, rows: u16, xpixel: u16, ypixel: u16) -> Self {
Self {
cols,
rows,
xpixel,
ypixel,
}
}
}
impl Default for WindowSize {
fn default() -> Self {
Self::new(80, 24)
}
}
impl From<(u16, u16)> for WindowSize {
fn from((cols, rows): (u16, u16)) -> Self {
Self::new(cols, rows)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_builder() {
let config = PtyConfig::builder()
.working_directory("/tmp")
.env("FOO", "bar")
.window_size(120, 40)
.build();
assert_eq!(config.working_directory, Some(PathBuf::from("/tmp")));
assert_eq!(config.window_size, (120, 40));
assert!(config.env_add.contains_key(&OsString::from("FOO")));
}
#[test]
fn window_size_default() {
let size = WindowSize::default();
assert_eq!(size.cols, 80);
assert_eq!(size.rows, 24);
}
}