From bd5824ce3fe72dc0b48e28776e2c801eff268972 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 20 Oct 2020 19:17:57 +0200 Subject: [PATCH] feat(ipc): listen to external ipc messages including a basic api --- Cargo.lock | 18 +++++++++++ Cargo.toml | 2 ++ src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++---- src/os_input_output.rs | 28 ++++++++++++---- src/pty_bus.rs | 13 ++++---- src/tests/fakes.rs | 3 +- 6 files changed, 115 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaaec76b..f0239832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index ac02214c..4c35e286 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/main.rs b/src/main.rs index 6c337250..79609108 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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) + 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) { .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) { }).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) { } 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(); diff --git a/src/os_input_output.rs b/src/os_input_output.rs index 75c131c7..e86b1919 100644 --- a/src/os_input_output.rs +++ b/src/os_input_output.rs @@ -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) -> (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) -> (RawFd, RawFd); fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result; fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result; 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) -> (RawFd, RawFd) { + spawn_terminal(file_to_open) } fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { read(pid, buf) diff --git a/src/pty_bus.rs b/src/pty_bus.rs index 7a7cf790..02d2658f 100644 --- a/src/pty_bus.rs +++ b/src/pty_bus.rs @@ -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), + SpawnTerminalHorizontally(Option), 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) { + 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) { + 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(); } diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index 5f3dd1aa..a0569db4 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -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) -> (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