feat(ipc): listen to external ipc messages including a basic api

This commit is contained in:
Aram Drevekenin 2020-10-20 19:17:57 +02:00
parent e50e9770fd
commit bd5824ce3f
6 changed files with 115 additions and 21 deletions

18
Cargo.lock generated
View File

@ -65,6 +65,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bincode"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
dependencies = [
"byteorder",
"serde",
]
[[package]]
name = "bitflags"
version = "1.2.1"
@ -91,6 +101,12 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cache-padded"
version = "1.1.1"
@ -391,10 +407,12 @@ name = "mosaic"
version = "0.1.0"
dependencies = [
"async-std",
"bincode",
"futures",
"insta",
"libc",
"nix",
"serde",
"signal-hook",
"termios",
"unicode-truncate",

View File

@ -15,6 +15,8 @@ unicode-width = "0.1.8"
unicode-truncate = "0.1.1"
vte = "0.8.0"
futures = "0.3.5"
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3.1"
[dependencies.async-std]
version = "1.3.0"

View File

@ -10,10 +10,16 @@ mod boundaries;
use std::io::{Read, Write};
use ::std::thread;
use std::os::unix::net::{UnixStream, UnixListener};
use std::io::prelude::*;
use serde::{Serialize, Deserialize};
use crate::os_input_output::{get_os_input, OsApi};
use crate::terminal_pane::TerminalOutput;
use crate::pty_bus::{VteEvent, PtyBus, PtyInstruction};
use crate::screen::{Screen, ScreenInstruction};
use std::path::{Path, PathBuf};
use std::fs::remove_file;
// sigwinch stuff
use ::signal_hook::iterator::Signals;
@ -21,6 +27,14 @@ use ::signal_hook::iterator::Signals;
pub type OnSigWinch = dyn Fn(Box<dyn Fn()>) + Send;
pub type SigCleanup = dyn Fn() + Send;
#[derive(Serialize, Deserialize, Debug)]
enum ApiCommand {
OpenFile(String),
SplitHorizontally,
SplitVertically,
MoveFocus,
}
fn debug_log_to_file (message: String) {
use std::fs::OpenOptions;
use std::io::prelude::*;
@ -68,17 +82,17 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
.name("pty".to_string())
.spawn({
move || {
pty_bus.spawn_terminal_vertically();
pty_bus.spawn_terminal_vertically(None);
loop {
let event = pty_bus.receive_pty_instructions
.recv()
.expect("failed to receive event on channel");
match event {
PtyInstruction::SpawnTerminalVertically => {
pty_bus.spawn_terminal_vertically();
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
pty_bus.spawn_terminal_vertically(file_to_open);
}
PtyInstruction::SpawnTerminalHorizontally => {
pty_bus.spawn_terminal_horizontally();
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
pty_bus.spawn_terminal_horizontally(file_to_open);
}
PtyInstruction::Quit => {
break;
@ -147,6 +161,50 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
}).unwrap()
);
// TODO: currently we don't push this into active_threads
// because otherwise the app will hang. Need to fix this so it both
// listens to the ipc-bus and is able to quit cleanly
#[cfg(not(test))]
let ipc_thread = thread::Builder::new()
.name("ipc_server".to_string())
.spawn({
let send_pty_instructions = send_pty_instructions.clone();
let send_screen_instructions = send_screen_instructions.clone();
move || {
remove_file("/tmp/mosaic").ok();
let listener = UnixListener::bind("/tmp/mosaic").expect("could not listen on ipc socket");
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let mut buffer = [0; 65535]; // TODO: more accurate
stream.read(&mut buffer).expect("failed to parse ipc message");
let decoded: ApiCommand = bincode::deserialize(&buffer).expect("failed to deserialize ipc message");
match &decoded {
ApiCommand::OpenFile(file_name) => {
let path = PathBuf::from(file_name);
send_pty_instructions.send(PtyInstruction::SpawnTerminalVertically(Some(path))).unwrap();
}
ApiCommand::SplitHorizontally => {
send_pty_instructions.send(PtyInstruction::SpawnTerminalHorizontally(None)).unwrap();
}
ApiCommand::SplitVertically => {
send_pty_instructions.send(PtyInstruction::SpawnTerminalVertically(None)).unwrap();
}
ApiCommand::MoveFocus => {
send_screen_instructions.send(ScreenInstruction::MoveFocus).unwrap();
}
}
}
Err(err) => {
panic!("err {:?}", err);
break;
}
}
}
}
}).unwrap();
let mut stdin = os_input.get_stdin_reader();
loop {
let mut buffer = [0; 1];
@ -162,9 +220,9 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
} else if buffer[0] == 12 { // ctrl-l
send_screen_instructions.send(ScreenInstruction::ResizeRight).unwrap();
} else if buffer[0] == 14 { // ctrl-n
send_pty_instructions.send(PtyInstruction::SpawnTerminalVertically).unwrap();
send_pty_instructions.send(PtyInstruction::SpawnTerminalVertically(None)).unwrap();
} else if buffer[0] == 2 { // ctrl-b
send_pty_instructions.send(PtyInstruction::SpawnTerminalHorizontally).unwrap();
send_pty_instructions.send(PtyInstruction::SpawnTerminalHorizontally(None)).unwrap();
} else if buffer[0] == 17 { // ctrl-q
send_screen_instructions.send(ScreenInstruction::Quit).unwrap();
send_pty_instructions.send(PtyInstruction::Quit).unwrap();

View File

@ -12,6 +12,7 @@ use nix::pty::{forkpty, Winsize};
use std::os::unix::io::RawFd;
use std::process::Command;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::env;
@ -55,7 +56,7 @@ pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
unsafe { ioctl(fd, TIOCSWINSZ.into(), &winsize) };
}
fn spawn_terminal () -> (RawFd, RawFd) {
fn spawn_terminal (file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
let (pid_primary, pid_secondary): (RawFd, RawFd) = {
match forkpty(None, None) {
Ok(fork_pty_res) => {
@ -67,9 +68,22 @@ fn spawn_terminal () -> (RawFd, RawFd) {
child
},
ForkResult::Child => {
Command::new(env::var("SHELL").unwrap()).spawn().expect("failed to spawn");
::std::thread::park(); // TODO: if we remove this, we seem to lose bytes from stdin - find out why
Pid::from_raw(0) // TODO: better
match file_to_open {
Some(file_to_open) => {
if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() {
panic!("Can't edit files if an editor is not define. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)");
}
let editor = env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap());
Command::new(editor).args(&[file_to_open]).spawn().expect("failed to spawn");
::std::thread::park(); // TODO: if we remove this, we seem to lose bytes from stdin - find out why
Pid::from_raw(0) // TODO: better
},
None => {
Command::new(env::var("SHELL").unwrap()).spawn().expect("failed to spawn");
::std::thread::park(); // TODO: if we remove this, we seem to lose bytes from stdin - find out why
Pid::from_raw(0) // TODO: better
}
}
},
};
(pid_primary, pid_secondary.as_raw())
@ -89,7 +103,7 @@ pub trait OsApi: Send + Sync {
fn get_terminal_size_using_fd(&self, pid: RawFd) -> Winsize;
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16);
fn into_raw_mode(&mut self, pid: RawFd);
fn spawn_terminal(&mut self) -> (RawFd, RawFd);
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd);
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error>;
@ -109,8 +123,8 @@ impl OsApi for OsInputOutput {
fn into_raw_mode(&mut self, pid: RawFd) {
into_raw_mode(pid);
}
fn spawn_terminal(&mut self) -> (RawFd, RawFd) {
spawn_terminal()
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
spawn_terminal(file_to_open)
}
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
read(pid, buf)

View File

@ -5,6 +5,7 @@ use ::async_std::task::*;
use ::std::pin::*;
use ::std::sync::mpsc::{channel, Sender, Receiver};
use ::std::time::{Instant, Duration};
use std::path::{Path, PathBuf};
use ::vte;
use crate::os_input_output::OsApi;
@ -133,8 +134,8 @@ impl vte::Perform for VteEventSender {
}
pub enum PtyInstruction {
SpawnTerminalVertically,
SpawnTerminalHorizontally,
SpawnTerminalVertically(Option<PathBuf>),
SpawnTerminalHorizontally(Option<PathBuf>),
Quit
}
@ -209,13 +210,13 @@ impl PtyBus {
os_input,
}
}
pub fn spawn_terminal_vertically(&mut self) {
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
pub fn spawn_terminal_vertically(&mut self, file_to_open: Option<PathBuf>) {
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(file_to_open);
stream_terminal_bytes(pid_primary, self.send_screen_instructions.clone(), self.os_input.clone());
self.send_screen_instructions.send(ScreenInstruction::VerticalSplit(pid_primary)).unwrap();
}
pub fn spawn_terminal_horizontally(&mut self) {
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
pub fn spawn_terminal_horizontally(&mut self, file_to_open: Option<PathBuf>) {
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(file_to_open);
stream_terminal_bytes(pid_primary, self.send_screen_instructions.clone(), self.os_input.clone());
self.send_screen_instructions.send(ScreenInstruction::HorizontalSplit(pid_primary)).unwrap();
}

View File

@ -4,6 +4,7 @@ use ::std::os::unix::io::RawFd;
use ::std::io::{Read, Write};
use ::std::collections::HashMap;
use ::std::sync::{Arc, Mutex};
use ::std::path::PathBuf;
use crate::os_input_output::OsApi;
use crate::tests::possible_tty_inputs::{Bytes, get_possible_tty_inputs};
@ -118,7 +119,7 @@ impl OsApi for FakeInputOutput {
fn into_raw_mode(&mut self, pid: RawFd) {
self.io_events.lock().unwrap().push(IoEvent::IntoRawMode(pid));
}
fn spawn_terminal(&mut self) -> (RawFd, RawFd) {
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
let next_terminal_id = { self.read_buffers.lock().unwrap().keys().len() as RawFd + 1 };
self.add_terminal(next_terminal_id);
(next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here