//! IPC stuff for starting to split things into a client and server model. use crate::{ cli::CliArgs, errors::{get_current_ctx, ErrorContext}, input::{actions::Action, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig}, pane_size::Size, }; use interprocess::local_socket::LocalSocketStream; use nix::unistd::dup; use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Error, Formatter}, io::{self, Write}, marker::PhantomData, os::unix::io::{AsRawFd, FromRawFd}, }; use zellij_tile::data::{InputMode, Palette}; type SessionId = u64; #[derive(PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct Session { // Unique ID for this session id: SessionId, // Identifier for the underlying IPC primitive (socket, pipe) conn_name: String, // User configured alias for the session alias: String, } // How do we want to connect to a session? #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ClientType { Reader, Writer, } #[derive(Default, Serialize, Deserialize, Debug, Clone, Copy)] pub struct ClientAttributes { pub size: Size, pub palette: Palette, } // Types of messages sent from the client to the server #[allow(clippy::large_enum_variant)] #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ClientToServerMsg { /*// List which sessions are available ListSessions, // Create a new session CreateSession, // Attach to a running session AttachToSession(SessionId, ClientType), // Force detach DetachSession(SessionId), // Disconnect from the session we're connected to DisconnectFromSession,*/ TerminalResize(Size), NewClient( ClientAttributes, Box, Box, LayoutFromYaml, Option, ), AttachClient(ClientAttributes, Options), Action(Action), ClientExited, } // Types of messages sent from the server to the client #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ServerToClientMsg { /*// Info about a particular session SessionInfo(Session), // A list of sessions SessionList(HashSet),*/ Render(String), UnblockInputThread, Exit(ExitReason), SwitchToMode(InputMode), } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ExitReason { Normal, ForceDetached, CannotAttach, Error(String), } impl Display for ExitReason { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { Self::Normal => write!(f, "Bye from Zellij!"), Self::ForceDetached => write!( f, "Session was detach from this client (possibly because another client connected)" ), Self::CannotAttach => write!( f, "Session attached to another client. Use --force flag to force connect." ), Self::Error(e) => write!(f, "Error occured in server:\n{}", e), } } } /// Sends messages on a stream socket, along with an [`ErrorContext`]. pub struct IpcSenderWithContext { sender: io::BufWriter, _phantom: PhantomData, } impl IpcSenderWithContext { /// Returns a sender to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream). pub fn new(sender: LocalSocketStream) -> Self { Self { sender: io::BufWriter::new(sender), _phantom: PhantomData, } } /// Sends an event, along with the current [`ErrorContext`], on this [`IpcSenderWithContext`]'s socket. pub fn send(&mut self, msg: T) { let err_ctx = get_current_ctx(); bincode::serialize_into(&mut self.sender, &(msg, err_ctx)).unwrap(); // TODO: unwrapping here can cause issues when the server disconnects which we don't mind // do we need to handle errors here in other cases? let _ = self.sender.flush(); } /// Returns an [`IpcReceiverWithContext`] with the same socket as this sender. pub fn get_receiver(&self) -> IpcReceiverWithContext where F: for<'de> Deserialize<'de> + Serialize, { let sock_fd = self.sender.get_ref().as_raw_fd(); let dup_sock = dup(sock_fd).unwrap(); let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) }; IpcReceiverWithContext::new(socket) } } /// Receives messages on a stream socket, along with an [`ErrorContext`]. pub struct IpcReceiverWithContext { receiver: io::BufReader, _phantom: PhantomData, } impl IpcReceiverWithContext where T: for<'de> Deserialize<'de> + Serialize, { /// Returns a receiver to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream). pub fn new(receiver: LocalSocketStream) -> Self { Self { receiver: io::BufReader::new(receiver), _phantom: PhantomData, } } /// Receives an event, along with the current [`ErrorContext`], on this [`IpcReceiverWithContext`]'s socket. pub fn recv(&mut self) -> (T, ErrorContext) { bincode::deserialize_from(&mut self.receiver).unwrap() } /// Returns an [`IpcSenderWithContext`] with the same socket as this receiver. pub fn get_sender(&self) -> IpcSenderWithContext { let sock_fd = self.receiver.get_ref().as_raw_fd(); let dup_sock = dup(sock_fd).unwrap(); let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) }; IpcSenderWithContext::new(socket) } }