line wrap kinda working
This commit is contained in:
commit
22f109e8cf
4 changed files with 780 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
164
Cargo.lock
generated
Normal file
164
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fde55d2a2bfaa4c9668bbc63f531fbdeee3ffe188f4662511ce2c22b3eedebe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mosaic"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"nix",
|
||||||
|
"signal-hook",
|
||||||
|
"termios",
|
||||||
|
"unicode-truncate",
|
||||||
|
"unicode-width",
|
||||||
|
"vte",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"void",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termios"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-truncate"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ac822301b0f9f2c2e60509da419b2401a5dc480c784e951ac6bebeee16a7beb"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "void"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"utf8parse",
|
||||||
|
"vte_generate_state_changes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte_generate_state_changes"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "mosaic"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Aram Drevekenin <aram@poor.dev>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
termios = "0.3"
|
||||||
|
libc = "0.2"
|
||||||
|
nix = "0.17.0"
|
||||||
|
signal-hook = "0.1.10"
|
||||||
|
unicode-width = "0.1.8"
|
||||||
|
unicode-truncate = "0.1.1"
|
||||||
|
vte = "0.8.0"
|
||||||
599
src/main.rs
Normal file
599
src/main.rs
Normal file
|
|
@ -0,0 +1,599 @@
|
||||||
|
use std::{mem, io};
|
||||||
|
use std::io::{stdin, stdout, Read, Write};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use nix::unistd::{read, write, ForkResult};
|
||||||
|
use nix::fcntl::{fcntl, FcntlArg, OFlag};
|
||||||
|
use nix::sys::termios::SpecialCharacterIndices::{VMIN, VTIME};
|
||||||
|
use nix::sys::termios::{
|
||||||
|
tcgetattr,
|
||||||
|
cfmakeraw,
|
||||||
|
tcsetattr,
|
||||||
|
SetArg,
|
||||||
|
tcdrain,
|
||||||
|
tcflush,
|
||||||
|
FlushArg,
|
||||||
|
cfsetispeed,
|
||||||
|
cfsetospeed,
|
||||||
|
BaudRate,
|
||||||
|
InputFlags,
|
||||||
|
};
|
||||||
|
use nix::pty::{forkpty, Winsize};
|
||||||
|
use std::os::unix::io::{RawFd, FromRawFd};
|
||||||
|
use std::process::Command;
|
||||||
|
use ::std::{thread, time};
|
||||||
|
use ::std::fs::File;
|
||||||
|
use ::std::io::prelude::*;
|
||||||
|
use ::std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
use unicode_truncate::UnicodeTruncateStr;
|
||||||
|
use vte;
|
||||||
|
|
||||||
|
// fn read_from_pid (pid: RawFd) -> (usize, [u8; 115200]) {
|
||||||
|
fn read_from_pid (pid: RawFd) -> Option<Vec<u8>> {
|
||||||
|
let mut read_buffer = [0; 115200];
|
||||||
|
let read_result = read(pid, &mut read_buffer);
|
||||||
|
match read_result {
|
||||||
|
Ok(res) => {
|
||||||
|
Some(read_buffer[..=res].to_vec())
|
||||||
|
// (res, read_buffer)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
nix::Error::Sys(errno) => {
|
||||||
|
if errno == nix::errno::Errno::EAGAIN {
|
||||||
|
None
|
||||||
|
// (0, read_buffer)
|
||||||
|
} else {
|
||||||
|
panic!("error {:?}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => panic!("error {:?}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_raw_mode(pid: RawFd) {
|
||||||
|
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
|
||||||
|
cfmakeraw(&mut tio);
|
||||||
|
match tcsetattr(pid, SetArg::TCSANOW, &mut tio) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => panic!("error {:?}", e)
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_vmin_and_vtime(pid: RawFd) {
|
||||||
|
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
|
||||||
|
// tio.control["VMIN"] = 1;
|
||||||
|
tio.control_chars[VMIN as usize] = 0;
|
||||||
|
tio.control_chars[VTIME as usize] = 0;
|
||||||
|
match tcsetattr(pid, SetArg::TCSANOW, &mut tio) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => panic!("error {:?}", e)
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_baud_rate(pid: RawFd) {
|
||||||
|
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
|
||||||
|
cfsetospeed(&mut tio, BaudRate::B115200).expect("could not set baud rate");
|
||||||
|
cfsetispeed(&mut tio, BaudRate::B115200).expect("could not set baud rate");
|
||||||
|
tcsetattr(pid, SetArg::TCSANOW, &mut tio).expect("could not set attributes");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal_size_using_fd(fd: RawFd) -> Winsize {
|
||||||
|
// TODO: do this with the nix ioctl
|
||||||
|
use libc::ioctl;
|
||||||
|
use libc::TIOCGWINSZ;
|
||||||
|
|
||||||
|
let mut winsize = Winsize {
|
||||||
|
ws_row: 0,
|
||||||
|
ws_col: 0,
|
||||||
|
ws_xpixel: 0,
|
||||||
|
ws_ypixel: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut winsize) };
|
||||||
|
winsize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_terminal_size_using_fd(fd: RawFd, ws: &Winsize) {
|
||||||
|
// TODO: do this with the nix ioctl
|
||||||
|
use libc::ioctl;
|
||||||
|
use libc::TIOCSWINSZ;
|
||||||
|
|
||||||
|
let mut winsize = Winsize {
|
||||||
|
ws_row: ws.ws_row,
|
||||||
|
ws_col: ws.ws_col,
|
||||||
|
ws_xpixel: 0,
|
||||||
|
ws_ypixel: 0,
|
||||||
|
};
|
||||||
|
unsafe { ioctl(fd, TIOCSWINSZ.into(), &winsize) };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn spawn_terminal () -> (RawFd, RawFd, Winsize) {
|
||||||
|
// let ws = Winsize { ws_row: 11, ws_col: 116, ws_xpixel: 0, ws_ypixel: 0 };
|
||||||
|
let ws = get_terminal_size_using_fd(0);
|
||||||
|
let (pid_primary, pid_secondary): (RawFd, RawFd) = {
|
||||||
|
match forkpty(Some(&ws), None) {
|
||||||
|
Ok(fork_pty_res) => {
|
||||||
|
let pid_primary = fork_pty_res.master;
|
||||||
|
let pid_secondary = match fork_pty_res.fork_result {
|
||||||
|
ForkResult::Parent { child } => {
|
||||||
|
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
|
||||||
|
child
|
||||||
|
},
|
||||||
|
ForkResult::Child => {
|
||||||
|
// TODO: why does $SHELL not work?
|
||||||
|
// Command::new("$SHELL").spawn().expect("failed to spawn");
|
||||||
|
set_baud_rate(0);
|
||||||
|
Command::new("/usr/bin/fish").spawn().expect("failed to spawn");
|
||||||
|
::std::thread::sleep(std::time::Duration::from_millis(300000));
|
||||||
|
panic!("I am secondary, why?!");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(pid_primary, pid_secondary.as_raw())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("failed to fork {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(pid_primary, pid_secondary, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_utf8_lines(buf: &[u8]) -> Vec<String> {
|
||||||
|
let buf_utf8 = String::from_utf8(buf.to_vec()).unwrap();
|
||||||
|
let mut lines: Vec<String> = buf_utf8.lines().map(|l| l.to_string()).collect();
|
||||||
|
for i in 0..lines.len() - 1 {
|
||||||
|
// lines[i].push('\r');
|
||||||
|
// lines[i].push('\n'); // TODO: remove these?
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A type implementing Perform that just logs actions
|
||||||
|
struct CharacterCounter {
|
||||||
|
pub characters: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterCounter {
|
||||||
|
pub fn new() -> CharacterCounter {
|
||||||
|
CharacterCounter {
|
||||||
|
characters: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl vte::Perform for CharacterCounter {
|
||||||
|
fn print(&mut self, c: char) {
|
||||||
|
self.characters += 1;
|
||||||
|
// println!("[print] {:?}", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&mut self, byte: u8) {
|
||||||
|
// println!("[execute] {:02x}", byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
||||||
|
// println!(
|
||||||
|
// "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}",
|
||||||
|
// params, intermediates, ignore, c
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put(&mut self, byte: u8) {
|
||||||
|
// println!("[put] {:02x}", byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unhook(&mut self) {
|
||||||
|
// println!("[unhook]");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||||
|
// println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
||||||
|
// println!(
|
||||||
|
// "[csi_dispatch] params={:?}, intermediates={:?}, ignore={:?}, char={:?}",
|
||||||
|
// params, intermediates, ignore, c
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
|
||||||
|
// println!(
|
||||||
|
// "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}",
|
||||||
|
// intermediates, ignore, byte
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn wrap_row (row: &str, columns: u16) -> Vec<String> {
|
||||||
|
// TODO:
|
||||||
|
// * create a new character_counter
|
||||||
|
// * loop through characters in row
|
||||||
|
// * add each character to the character_counter
|
||||||
|
// * once the counter reaches columns, push line into lines
|
||||||
|
let row = row.as_bytes();
|
||||||
|
let mut wrapped_lines = vec![];
|
||||||
|
let mut vte_parser = vte::Parser::new();
|
||||||
|
let mut character_counter = CharacterCounter::new();
|
||||||
|
let mut index_in_row = 0;
|
||||||
|
let mut line = vec![];
|
||||||
|
loop {
|
||||||
|
let character = row[index_in_row];
|
||||||
|
line.push(character);
|
||||||
|
vte_parser.advance(&mut character_counter, character);
|
||||||
|
if character_counter.characters == columns * (wrapped_lines.len() as u16 + 1) {
|
||||||
|
let mut string_line = String::from_utf8(line.clone()).expect("could not create utf8 string");
|
||||||
|
// string_line.push('\r');
|
||||||
|
// string_line.push('\n');
|
||||||
|
wrapped_lines.push(string_line);
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
if index_in_row == row.len() - 1 {
|
||||||
|
if line.len() > 0 {
|
||||||
|
let mut string_line = String::from_utf8(line.clone()).expect("could not create utf8 string");
|
||||||
|
// string_line.push('\n');
|
||||||
|
// string_line.push('\r');
|
||||||
|
wrapped_lines.push(string_line);
|
||||||
|
line.clear(); // TODO: we don't need this?
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index_in_row += 1;
|
||||||
|
}
|
||||||
|
// println!("\rwrapped_lines {:?}", wrapped_lines);
|
||||||
|
wrapped_lines
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// let mut wrapped_lines = vec![];
|
||||||
|
// let mut line = String::new();
|
||||||
|
// let mut index_in_row = 0;
|
||||||
|
// loop {
|
||||||
|
// let (line, w) = &row.get(index_in_row..).unwrap().unicode_truncate(columns as usize);
|
||||||
|
// // let rest_of_line = &row.get(line.chars().count()..).unwrap();
|
||||||
|
// index_in_row += line.chars().count();
|
||||||
|
// let mut wrapped_line = String::from(*line);
|
||||||
|
// if index_in_row < row.chars().count() {
|
||||||
|
// // this is not the last row, so add newline
|
||||||
|
// wrapped_line.push('\r');
|
||||||
|
// wrapped_line.push('\n');
|
||||||
|
// }
|
||||||
|
// // wrapped_lines.push(String::from(*line));
|
||||||
|
// wrapped_lines.push(wrapped_line);
|
||||||
|
// if index_in_row >= row.chars().count() {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// wrapped_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines_in_buffer(buffer: &Vec<String>, ws: &Winsize) -> Vec<u8> {
|
||||||
|
let column_count = ws.ws_col;
|
||||||
|
let row_count = ws.ws_row;
|
||||||
|
let mut rows = VecDeque::new();
|
||||||
|
|
||||||
|
if buffer.is_empty() {
|
||||||
|
return vec![]
|
||||||
|
};
|
||||||
|
let carriage_return = String::from("\r");
|
||||||
|
let mut index_in_buffer = buffer.len() - 1;
|
||||||
|
loop {
|
||||||
|
if rows.len() >= row_count as usize {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let current_row = &buffer[index_in_buffer];
|
||||||
|
let mut current_row_wrapped = wrap_row(current_row, column_count);
|
||||||
|
current_row_wrapped.reverse();
|
||||||
|
for mut line in current_row_wrapped {
|
||||||
|
if rows.len() < row_count as usize {
|
||||||
|
line.push('\r');
|
||||||
|
line.push('\n');
|
||||||
|
rows.push_front(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index_in_buffer -= 1;
|
||||||
|
if index_in_buffer == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// rows.push_front(String::from("\r\n"));
|
||||||
|
}
|
||||||
|
let rows_length = rows.len();
|
||||||
|
rows[rows_length - 1].pop(); // remove last \n (ugly hack, TODO better)
|
||||||
|
// println!("\rrow_count, rows.len {:?}, {:?}", row_count, rows.len());
|
||||||
|
// for row in rows {
|
||||||
|
// println!("\rrow: {:?}", row);
|
||||||
|
// }
|
||||||
|
// ::std::process::exit(2);
|
||||||
|
|
||||||
|
rows.push_front(carriage_return); // TODO: ??
|
||||||
|
let bytes: Vec<u8> = rows.iter().fold(vec![], |mut acc, l| {
|
||||||
|
for byte in l.as_bytes() {
|
||||||
|
acc.push(*byte)
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_empty_lines(ws: &Winsize) -> Vec<u8> {
|
||||||
|
let columns = ws.ws_col;
|
||||||
|
let rows = ws.ws_row;
|
||||||
|
let mut lines = vec![];
|
||||||
|
let carriage_return = String::from("\r");
|
||||||
|
lines.append(carriage_return.as_bytes().to_vec().as_mut());
|
||||||
|
let mut empty_line = String::new();
|
||||||
|
let empty_char = ' ';
|
||||||
|
// for _i in 0..columns - 1 {
|
||||||
|
for _i in 0..columns {
|
||||||
|
empty_line.push(empty_char);
|
||||||
|
}
|
||||||
|
empty_line.push('\n');
|
||||||
|
for _i in 0..rows {
|
||||||
|
let mut line = vec![];
|
||||||
|
let carriage_return = String::from("\r");
|
||||||
|
line.append(carriage_return.as_bytes().to_vec().as_mut());
|
||||||
|
line.append(empty_line.as_bytes().to_vec().as_mut());
|
||||||
|
lines.append(&mut line);
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// sigwinch stuff
|
||||||
|
use ::signal_hook::iterator::Signals;
|
||||||
|
|
||||||
|
pub type OnSigWinch = dyn Fn(Box<dyn Fn()>) + Send;
|
||||||
|
pub type SigCleanup = dyn Fn() + Send;
|
||||||
|
|
||||||
|
pub fn sigwinch() -> (Box<OnSigWinch>, Box<SigCleanup>) {
|
||||||
|
let signals = Signals::new(&[signal_hook::SIGWINCH]).unwrap();
|
||||||
|
let on_winch = {
|
||||||
|
let signals = signals.clone();
|
||||||
|
move |cb: Box<dyn Fn()>| {
|
||||||
|
for signal in signals.forever() {
|
||||||
|
match signal {
|
||||||
|
signal_hook::SIGWINCH => cb(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let cleanup = move || {
|
||||||
|
signals.close();
|
||||||
|
};
|
||||||
|
(Box::new(on_winch), Box::new(cleanup))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut active_threads = vec![];
|
||||||
|
|
||||||
|
let (first_terminal_pid, pid_secondary, first_terminal_ws): (RawFd, RawFd, Winsize) = spawn_terminal();
|
||||||
|
let (second_terminal_pid, pid_secondary, second_terminal_ws): (RawFd, RawFd, Winsize) = spawn_terminal();
|
||||||
|
let stdin = io::stdin();
|
||||||
|
into_raw_mode(0);
|
||||||
|
set_baud_rate(0);
|
||||||
|
::std::thread::sleep(std::time::Duration::from_millis(2000));
|
||||||
|
let active_terminal = Arc::new(Mutex::new(first_terminal_pid));
|
||||||
|
let terminal1_buffer: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
let terminal2_buffer: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
let first_terminal_ws = Arc::new(Mutex::new(first_terminal_ws));
|
||||||
|
let second_terminal_ws = Arc::new(Mutex::new(second_terminal_ws));
|
||||||
|
active_threads.push(
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("terminal_stdout_handler".to_string())
|
||||||
|
.spawn({
|
||||||
|
let active_terminal = active_terminal.clone();
|
||||||
|
let terminal1_buffer = terminal1_buffer.clone();
|
||||||
|
move || {
|
||||||
|
let mut read_buffer = vec![];
|
||||||
|
loop {
|
||||||
|
match read_from_pid(first_terminal_pid) {
|
||||||
|
Some(mut read_bytes) => {
|
||||||
|
read_buffer.append(&mut read_bytes);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if read_buffer.len() > 0 {
|
||||||
|
{
|
||||||
|
let mut terminal1_buffer = terminal1_buffer.lock().unwrap();
|
||||||
|
let mut lines = to_utf8_lines(&read_buffer);
|
||||||
|
terminal1_buffer.append(&mut lines);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let active_terminal = active_terminal.lock().unwrap();
|
||||||
|
if *active_terminal == first_terminal_pid {
|
||||||
|
::std::io::stdout().write_all(&read_buffer).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read_buffer.clear();
|
||||||
|
}
|
||||||
|
::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
active_threads.push(
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("terminal_stdout_handler2".to_string())
|
||||||
|
.spawn({
|
||||||
|
let active_terminal = active_terminal.clone();
|
||||||
|
let terminal2_buffer = terminal2_buffer.clone();
|
||||||
|
move || {
|
||||||
|
let mut read_buffer = vec![];
|
||||||
|
loop {
|
||||||
|
match read_from_pid(second_terminal_pid) {
|
||||||
|
Some(mut read_bytes) => {
|
||||||
|
read_buffer.append(&mut read_bytes);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if read_buffer.len() > 0 {
|
||||||
|
{
|
||||||
|
let mut terminal2_buffer = terminal2_buffer.lock().unwrap();
|
||||||
|
let mut lines = to_utf8_lines(&read_buffer);
|
||||||
|
terminal2_buffer.append(&mut lines);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let active_terminal = active_terminal.lock().unwrap();
|
||||||
|
if *active_terminal == second_terminal_pid {
|
||||||
|
::std::io::stdout().write_all(&read_buffer).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read_buffer.clear();
|
||||||
|
}
|
||||||
|
::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let (on_sigwinch, cleanup) = sigwinch();
|
||||||
|
active_threads.push(
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("resize_handler".to_string())
|
||||||
|
.spawn({
|
||||||
|
let active_terminal = active_terminal.clone();
|
||||||
|
let first_terminal_ws = first_terminal_ws.clone();
|
||||||
|
let second_terminal_ws = second_terminal_ws.clone();
|
||||||
|
let terminal1_buffer = terminal1_buffer.clone();
|
||||||
|
let terminal2_buffer = terminal2_buffer.clone();
|
||||||
|
move || {
|
||||||
|
on_sigwinch(Box::new(move || {
|
||||||
|
let active_terminal = active_terminal.lock().unwrap();
|
||||||
|
let ws = get_terminal_size_using_fd(0);
|
||||||
|
|
||||||
|
let empty_lines = create_empty_lines(&ws);
|
||||||
|
|
||||||
|
|
||||||
|
set_terminal_size_using_fd(*active_terminal, &ws);
|
||||||
|
if *active_terminal == first_terminal_pid {
|
||||||
|
let mut first_terminal_ws = first_terminal_ws.lock().unwrap();
|
||||||
|
*first_terminal_ws = ws;
|
||||||
|
|
||||||
|
let terminal1_buffer = terminal1_buffer.lock().unwrap();
|
||||||
|
let new_lines = lines_in_buffer(&*terminal1_buffer, &ws);
|
||||||
|
|
||||||
|
::std::io::stdout().write_all(&empty_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().write_all(&new_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
} else {
|
||||||
|
let mut second_terminal_ws = second_terminal_ws.lock().unwrap();
|
||||||
|
*second_terminal_ws = ws;
|
||||||
|
|
||||||
|
let terminal2_buffer = terminal2_buffer.lock().unwrap();
|
||||||
|
let new_lines = lines_in_buffer(&*terminal2_buffer, &ws);
|
||||||
|
|
||||||
|
::std::io::stdout().write_all(&empty_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().write_all(&new_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut temp_ws = get_terminal_size_using_fd(0);
|
||||||
|
loop {
|
||||||
|
let mut buffer = [0; 1];
|
||||||
|
{
|
||||||
|
let mut handle = stdin.lock();
|
||||||
|
handle.read(&mut buffer).expect("failed to read stdin");
|
||||||
|
if buffer[0] == 10 { // ctrl-j
|
||||||
|
let mut active_terminal = active_terminal.lock().unwrap();
|
||||||
|
temp_ws.ws_col -= 10;
|
||||||
|
let empty_lines = create_empty_lines(&temp_ws);
|
||||||
|
if *active_terminal == first_terminal_pid {
|
||||||
|
let mut first_terminal_ws = first_terminal_ws.lock().unwrap();
|
||||||
|
*first_terminal_ws = temp_ws;
|
||||||
|
|
||||||
|
let terminal1_buffer = terminal1_buffer.lock().unwrap();
|
||||||
|
let new_lines = lines_in_buffer(&*terminal1_buffer, &temp_ws);
|
||||||
|
|
||||||
|
::std::io::stdout().write_all(&empty_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().write_all(&new_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
|
||||||
|
set_terminal_size_using_fd(*active_terminal, &temp_ws);
|
||||||
|
} else {
|
||||||
|
panic!("not terminal 1");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if buffer[0] == 11 { // ctrl-k
|
||||||
|
let mut active_terminal = active_terminal.lock().unwrap();
|
||||||
|
temp_ws.ws_col += 10;
|
||||||
|
let empty_lines = create_empty_lines(&temp_ws);
|
||||||
|
if *active_terminal == first_terminal_pid {
|
||||||
|
let mut first_terminal_ws = first_terminal_ws.lock().unwrap();
|
||||||
|
*first_terminal_ws = temp_ws;
|
||||||
|
|
||||||
|
let terminal1_buffer = terminal1_buffer.lock().unwrap();
|
||||||
|
let new_lines = lines_in_buffer(&*terminal1_buffer, &temp_ws);
|
||||||
|
|
||||||
|
::std::io::stdout().write_all(&empty_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().write_all(&new_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
} else {
|
||||||
|
panic!("not terminal 1");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if buffer[0] == 16 { // ctrl-p
|
||||||
|
let mut active_terminal = active_terminal.lock().unwrap();
|
||||||
|
if *active_terminal == first_terminal_pid {
|
||||||
|
*active_terminal = second_terminal_pid;
|
||||||
|
// TODO: this is actually not correct: we need to use the first terminal width to
|
||||||
|
// clear and the second terminal width to write
|
||||||
|
let first_terminal_ws = first_terminal_ws.lock().unwrap();
|
||||||
|
|
||||||
|
let empty_lines = create_empty_lines(&*first_terminal_ws);
|
||||||
|
|
||||||
|
let second_terminal_ws = second_terminal_ws.lock().unwrap();
|
||||||
|
let terminal2_buffer = terminal2_buffer.lock().unwrap();
|
||||||
|
let new_lines = lines_in_buffer(&*terminal2_buffer, &*second_terminal_ws);
|
||||||
|
|
||||||
|
::std::io::stdout().write_all(&empty_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().write_all(&new_lines).expect("cannot write to stdout");
|
||||||
|
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
} else {
|
||||||
|
*active_terminal = first_terminal_pid;
|
||||||
|
let second_terminal_ws = second_terminal_ws.lock().unwrap();
|
||||||
|
let empty_lines = create_empty_lines(&*second_terminal_ws);
|
||||||
|
|
||||||
|
let first_terminal_ws = first_terminal_ws.lock().unwrap();
|
||||||
|
let terminal1_buffer = terminal1_buffer.lock().unwrap();
|
||||||
|
let lines = lines_in_buffer(&*terminal1_buffer, &*first_terminal_ws);
|
||||||
|
|
||||||
|
::std::io::stdout().write_all(&empty_lines).expect("cannot write to stdout");
|
||||||
|
::std::io::stdout().write_all(&lines).expect("cannot write to stdout");
|
||||||
|
|
||||||
|
::std::io::stdout().flush().expect("could not flush");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let active_terminal = active_terminal.lock().unwrap();
|
||||||
|
write(*active_terminal, &mut buffer).expect("failed to write to terminal");
|
||||||
|
tcdrain(*active_terminal).expect("failed to drain terminal");
|
||||||
|
};
|
||||||
|
// cleanup();
|
||||||
|
|
||||||
|
// for thread_handler in active_threads {
|
||||||
|
// thread_handler.join().unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue