//! IPC stuff for starting to split things into a client and server model. use crate::{ cli::CliArgs, data::{ClientId, InputMode, Style}, errors::{get_current_ctx, prelude::*, ErrorContext}, input::keybinds::Keybinds, input::{actions::Action, layout::Layout, options::Options, plugins::PluginsConfig}, pane_size::{Size, SizeInPixels}, }; use interprocess::local_socket::LocalSocketStream; use log::warn; use nix::unistd::dup; use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Error, Formatter}, io::{self, Write}, marker::PhantomData, os::unix::io::{AsRawFd, FromRawFd}, }; 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)] pub struct ClientAttributes { pub size: Size, pub style: Style, pub keybinds: Keybinds, } #[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct PixelDimensions { pub text_area_size: Option, pub character_cell_size: Option, } impl PixelDimensions { pub fn merge(&mut self, other: PixelDimensions) { if let Some(text_area_size) = other.text_area_size { self.text_area_size = Some(text_area_size); } if let Some(character_cell_size) = other.character_cell_size { self.character_cell_size = Some(character_cell_size); } } } // 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,*/ DetachSession(Vec), TerminalPixelDimensions(PixelDimensions), BackgroundColor(String), ForegroundColor(String), ColorRegisters(Vec<(usize, String)>), TerminalResize(Size), NewClient( ClientAttributes, Box, Box, Box, Option, ), AttachClient(ClientAttributes, Options), Action(Action, Option), ClientExited, KillSession, ConnStatus, ListClients, } // 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), Connected, ActiveClients(Vec), } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ExitReason { Normal, NormalDetached, 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::NormalDetached => write!(f, "Session detached"), Self::ForceDetached => write!( f, "Session was detached 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 occurred 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) -> Result<()> { let err_ctx = get_current_ctx(); if rmp_serde::encode::write(&mut self.sender, &(msg, err_ctx)).is_err() { Err(anyhow!("failed to send message to client")) } else { // 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(); Ok(()) } } /// 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) -> Option<(T, ErrorContext)> { match rmp_serde::decode::from_read(&mut self.receiver) { Ok(msg) => Some(msg), Err(e) => { warn!("Error in IpcReceiver.recv(): {:?}", e); None }, } } /// 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) } }