🐝 I really like where this mua is(was?) headed, but it seems as though there has not been much activity recently.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

391 lines
14 KiB

/*
* meli
*
* Copyright 2017-2020 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::split_command;
use crate::terminal::position::*;
use melib::log;
use melib::ERROR;
use nix::fcntl::{open, OFlag};
use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
use nix::sys::stat;
use nix::unistd::{dup2, fork, ForkResult};
use nix::{ioctl_none_bad, ioctl_write_ptr_bad};
use std::ffi::CString;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
mod grid;
pub use grid::EmbedGrid;
// ioctl request code to "Make the given terminal the controlling terminal of the calling process"
use libc::TIOCSCTTY;
// ioctl request code to set window size of pty:
use libc::TIOCSWINSZ;
use std::path::Path;
use std::convert::TryFrom;
use std::io::Read;
use std::io::Write;
use std::sync::{Arc, Mutex};
// Macro generated function that calls ioctl to set window size of slave pty end
ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize);
ioctl_none_bad!(set_controlling_terminal, TIOCSCTTY);
pub fn create_pty(
width: usize,
height: usize,
command: String,
) -> nix::Result<Arc<Mutex<EmbedGrid>>> {
// Open a new PTY master
let master_fd = posix_openpt(OFlag::O_RDWR)?;
// Allow a slave to be generated for it
grantpt(&master_fd)?;
unlockpt(&master_fd)?;
// Get the name of the slave
let slave_name = unsafe { ptsname(&master_fd) }?;
// Try to open the slave
//let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
{
let winsize = Winsize {
ws_row: <u16>::try_from(height).unwrap(),
ws_col: <u16>::try_from(width).unwrap(),
ws_xpixel: 0,
ws_ypixel: 0,
};
let master_fd = master_fd.clone().into_raw_fd();
unsafe { set_window_size(master_fd, &winsize).unwrap() };
}
let child_pid = match fork() {
Ok(ForkResult::Child) => {
/* Open slave end for pseudoterminal */
let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())?;
// assign stdin, stdout, stderr to the tty
dup2(slave_fd, STDIN_FILENO).unwrap();
dup2(slave_fd, STDOUT_FILENO).unwrap();
dup2(slave_fd, STDERR_FILENO).unwrap();
/* Become session leader */
nix::unistd::setsid().unwrap();
match unsafe { set_controlling_terminal(slave_fd) } {
Ok(c) if c < 0 => {
log(
format!(
"Could not execute `{}`: ioctl(fd, TIOCSCTTY, NULL) returned {}",
command, c,
),
ERROR,
);
std::process::exit(c);
}
Ok(_) => {}
Err(err) => {
log(
format!(
"Could not execute `{}`: ioctl(fd, TIOCSCTTY, NULL) returned {}",
command, err,
),
ERROR,
);
std::process::exit(-1);
}
}
let parts = split_command!(command);
let (cmd, _) = (parts[0], &parts[1..]);
let _parts = parts
.iter()
.map(|&a| CString::new(a).unwrap())
.collect::<Vec<CString>>();
if let Err(e) = nix::unistd::execv(
&CString::new(cmd).unwrap(),
_parts
.iter()
.map(|a| a.as_c_str())
.collect::<Vec<&_>>()
.as_slice(),
) {
log(format!("Could not execute `{}`: {}", command, e,), ERROR);
std::process::exit(-1);
}
/* This path shouldn't be executed. */
std::process::exit(-1);
}
Ok(ForkResult::Parent { child }) => child,
Err(e) => panic!(e),
};
let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
let mut embed_grid = EmbedGrid::new(stdin, child_pid);
embed_grid.set_terminal_size((width, height));
let grid = Arc::new(Mutex::new(embed_grid));
let grid_ = grid.clone();
std::thread::Builder::new()
.spawn(move || {
let master_fd = master_fd.into_raw_fd();
let master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
forward_pty_translate_escape_codes(master_file, grid_);
})
.unwrap();
Ok(grid)
}
fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, grid: Arc<Mutex<EmbedGrid>>) {
let mut bytes_iter = pty_fd.bytes();
debug!("waiting for bytes");
while let Some(Ok(byte)) = bytes_iter.next() {
debug!("got a byte? {:?}", byte as char);
/* Drink deep, and descend. */
grid.lock().unwrap().process_byte(byte);
}
}
#[derive(Debug)]
pub enum State {
ExpectingControlChar,
G0, // Designate G0 Character Set
Osc1(Vec<u8>), //ESC ] Operating System Command (OSC is 0x9d).
Osc2(Vec<u8>, Vec<u8>),
Csi, // ESC [ Control Sequence Introducer (CSI is 0x9b).
Csi1(Vec<u8>),
Csi2(Vec<u8>, Vec<u8>),
Csi3(Vec<u8>, Vec<u8>, Vec<u8>),
CsiQ(Vec<u8>),
Normal,
}
/* Used for debugging */
struct EscCode<'a>(&'a State, u8);
impl<'a> From<(&'a mut State, u8)> for EscCode<'a> {
fn from(val: (&mut State, u8)) -> EscCode {
let (s, b) = val;
EscCode(s, b)
}
}
impl<'a> From<(&'a State, u8)> for EscCode<'a> {
fn from(val: (&State, u8)) -> EscCode {
let (s, b) = val;
EscCode(s, b)
}
}
impl std::fmt::Display for EscCode<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use State::*;
macro_rules! unsafestr {
($buf:ident) => {
unsafe { std::str::from_utf8_unchecked($buf) }
};
}
match self {
EscCode(G0, b'B') => write!(f, "ESC(B\t\tG0 USASCII charset set"),
EscCode(G0, c) => write!(f, "ESC({}\t\tG0 charset set", *c as char),
EscCode(Osc1(ref buf), ref c) => {
write!(f, "ESC]{}{}\t\tOSC", unsafestr!(buf), *c as char)
}
EscCode(Osc2(ref buf1, ref buf2), c) => write!(
f,
"ESC]{};{}{}\t\tOSC [UNKNOWN]",
unsafestr!(buf1),
unsafestr!(buf2),
*c as char
),
EscCode(ExpectingControlChar, b'D') => write!(
f, "ESC D Linefeed"
),
EscCode(Csi, b'm') => write!(
f,
"ESC[m\t\tCSI Character Attributes | Set Attr and Color to Normal (default)"
),
EscCode(Csi, b'K') => write!(
f,
"ESC[K\t\tCSI Erase from the cursor to the end of the line"
),
EscCode(Csi, b'L') => write!(
f,
"ESC[L\t\tCSI Insert one blank line"
),
EscCode(Csi, b'M') => write!(
f,
"ESC[M\t\tCSI delete line"
),
EscCode(Csi, b'J') => write!(
f,
"ESC[J\t\tCSI Erase from the cursor to the end of the screen"
),
EscCode(Csi, b'H') => write!(f, "ESC[H\t\tCSI Move the cursor to home position."),
EscCode(Csi, c) => write!(f, "ESC[{}\t\tCSI [UNKNOWN]", *c as char),
EscCode(Csi1(ref buf), b'L') => write!(
f,
"ESC[{}L\t\tCSI Insert {} blank lines",
unsafestr!(buf),
unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'm') => write!(
f,
"ESC[{}m\t\tCSI Character Attributes | Set fg, bg color",
unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'n') => write!(
f,
"ESC[{}n\t\tCSI Device Status Report (DSR)| Report Cursor Position",
unsafestr!(buf)
),
EscCode(Csi1(ref buf), b't') if buf == b"18" => write!(
f,
"ESC[18t\t\tReport the size of the text area in characters",
),
EscCode(Csi1(ref buf), b't') => write!(
f,
"ESC[{buf}t\t\tWindow manipulation, skipped",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'B') => write!(
f,
"ESC[{buf}B\t\tCSI Cursor Down {buf} Times",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'C') => write!(
f,
"ESC[{buf}C\t\tCSI Cursor Forward {buf} Times",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'D') => write!(
f,
"ESC[{buf}D\t\tCSI Cursor Backward {buf} Times",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'E') => write!(
f,
"ESC[{buf}E\t\tCSI Cursor Next Line {buf} Times",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'F') => write!(
f,
"ESC[{buf}F\t\tCSI Cursor Preceding Line {buf} Times",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'G') => write!(
f,
"ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'M') => write!(
f,
"ESC[{buf}M\t\tDelete P s Lines(s) (default = 1) (DCH). ",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'P') => write!(
f,
"ESC[{buf}P\t\tDelete P s Character(s) (default = 1) (DCH). ",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'S') => write!(
f,
"ESC[{buf}S\t\tCSI P s S Scroll up P s lines (default = 1) (SU), VT420, EC",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), b'J') => write!(
f,
"Erase in display {buf}",
buf = unsafestr!(buf)
),
EscCode(Csi1(ref buf), c) => {
write!(f, "ESC[{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char)
}
EscCode(Csi2(ref buf1, ref buf2), b'r') => write!(
f,
"ESC[{};{}r\t\tCSI Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM), VT100.",
unsafestr!(buf1),
unsafestr!(buf2),
),
EscCode(Csi2(ref buf1, ref buf2), c) => write!(
f,
"ESC[{};{}{}\t\tCSI",
unsafestr!(buf1),
unsafestr!(buf2),
*c as char
),
EscCode(Csi3(ref buf1, ref buf2, ref buf3), b'm') => write!(
f,
"ESC[{};{};{}m\t\tCSI Character Attributes | Set fg, bg color",
unsafestr!(buf1),
unsafestr!(buf2),
unsafestr!(buf3),
),
EscCode(Csi3(ref buf1, ref buf2, ref buf3), c) => write!(
f,
"ESC[{};{};{}{}\t\tCSI [UNKNOWN]",
unsafestr!(buf1),
unsafestr!(buf2),
unsafestr!(buf3),
*c as char
),
EscCode(CsiQ(ref buf), b's') => write!(
f,
"ESC[?{}r\t\tCSI Save DEC Private Mode Values",
unsafestr!(buf)
),
EscCode(CsiQ(ref buf), b'r') => write!(
f,
"ESC[?{}r\t\tCSI Restore DEC Private Mode Values",
unsafestr!(buf)
),
EscCode(CsiQ(ref buf), b'h') if buf == b"25" => write!(
f,
"ESC[?25h\t\tCSI DEC Private Mode Set (DECSET) show cursor",
),
EscCode(CsiQ(ref buf), b'h') if buf == b"12" => write!(
f,
"ESC[?12h\t\tCSI DEC Private Mode Set (DECSET) Start Blinking Cursor.",
),
EscCode(CsiQ(ref buf), b'h') => write!(
f,
"ESC[?{}h\t\tCSI DEC Private Mode Set (DECSET). [UNKNOWN]",
unsafestr!(buf)
),
EscCode(CsiQ(ref buf), b'l') if buf == b"12" => write!(
f,
"ESC[?12l\t\tCSI DEC Private Mode Set (DECSET) Stop Blinking Cursor",
),
EscCode(CsiQ(ref buf), b'l') if buf == b"25" => write!(
f,
"ESC[?25l\t\tCSI DEC Private Mode Set (DECSET) hide cursor",
),
EscCode(CsiQ(ref buf), c) => {
write!(f, "ESC[?{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char)
}
EscCode(unknown, c) => {
write!(f, "{:?}{} [UNKNOWN]", unknown, c)
}
}
}
}