diff options
author | mia <mia@mia.jetzt> | 2024-04-16 19:05:41 -0700 |
---|---|---|
committer | mia <mia@mia.jetzt> | 2024-04-16 19:05:41 -0700 |
commit | 796b2cafc798a7faa80a007002831a4c40635fe8 (patch) | |
tree | d8e68590524f4adab7ff8ff6e2cb3dfbb0c64b37 /src/tui/mod.rs | |
download | dissociate-0.1.0.tar.gz dissociate-0.1.0.zip |
initial commit v0.1.0
Diffstat (limited to 'src/tui/mod.rs')
-rw-r--r-- | src/tui/mod.rs | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/src/tui/mod.rs b/src/tui/mod.rs new file mode 100644 index 0000000..7c748c1 --- /dev/null +++ b/src/tui/mod.rs @@ -0,0 +1,159 @@ +mod accounts; + +use std::{ + io::{Read, Write}, + os::unix::net::UnixStream, +}; + +use cursive::{ + event::Key, + theme::{BorderStyle, Palette, Theme}, + utils::markup::StyledString, + view::Margins, + views::{Dialog, PaddedView, Panel, ScrollView, SelectView, TextView}, + Cursive, CursiveExt, +}; +use eyre::ContextCompat; + +use crate::ipc::{CreateInviteRequest, IPCResponse, RequestDefinition, ResponseDefinition}; + +pub fn run() -> eyre::Result<()> { + let Some(socket_path) = std::env::args().skip(2).next() else { + eyre::bail!("must specify socket path"); + }; + let stream = UnixStream::connect(socket_path)?; + let ipc = Ipc(stream); + + let mut siv = Cursive::new(); + siv.set_user_data(ipc); + + siv.set_theme(Theme { + shadow: false, + borders: BorderStyle::Simple, + palette: Palette::terminal_default(), + }); + + enum SelectItem { + Accounts, + Invite, + } + siv.add_layer( + SelectView::new() + .item("accounts", SelectItem::Accounts) + .item("invite", SelectItem::Invite) + .on_submit(move |siv, item| match item { + SelectItem::Accounts => accounts::show(siv), + SelectItem::Invite => invite(siv), + }) + .float("dissociate"), + ); + + siv.add_global_callback(Key::Esc, |siv| { + if siv.pop_layer().is_none() || siv.screen().is_empty() { + siv.quit(); + } + }); + + siv.run(); + + Ok(()) +} + +fn invite(siv: &mut Cursive) { + if let Some(invite) = siv.ipc(CreateInviteRequest {}) { + siv.add_layer( + Dialog::around(invite.link.text_view().hpad()) + .button("ok", |siv| { + siv.pop_layer(); + }) + .title("invite") + .pad(), + ); + } +} + +struct Ipc(UnixStream); + +impl Ipc { + fn exec<T: RequestDefinition>( + &mut self, + request: T, + ) -> eyre::Result<Result<T::Response, <T::Response as ResponseDefinition>::Error>> { + let request_buffer = + bincode::encode_to_vec(request.into_request(), bincode::config::standard())?; + self.0 + .write_all(&(request_buffer.len() as u16).to_be_bytes())?; + self.0.write_all(&request_buffer)?; + let mut response_len_buffer = [0u8; 2]; + self.0.read_exact(&mut response_len_buffer)?; + let response_len = u16::from_be_bytes(response_len_buffer) as usize; + let mut response_buffer = vec![0u8; response_len]; + self.0.read_exact(&mut response_buffer)?; + let (response, _): (IPCResponse, _) = + bincode::decode_from_slice(&response_buffer, bincode::config::standard())?; + T::Response::from_response(response).wrap_err("got wrong response type") + } +} + +trait CursiveIpc { + fn ipc<T: RequestDefinition>(&mut self, request: T) -> Option<T::Response>; +} + +impl CursiveIpc for Cursive { + fn ipc<T: RequestDefinition>(&mut self, request: T) -> Option<T::Response> { + match self.user_data::<Ipc>().unwrap().exec(request) { + Ok(Ok(resp)) => Some(resp), + Err(err) => { + self.add_layer( + Dialog::new() + .content(format!("ipc error: {err:?}").text_view()) + .button("quit", |siv| siv.quit()), + ); + None + } + Ok(Err(err)) => { + self.add_layer( + Dialog::new() + .content(format!("error: {err}").text_view()) + .button("ok", |siv| { + siv.pop_layer(); + }), + ); + None + } + } + } +} + +trait ViewExt: Sized { + fn float(self, title: &str) -> PaddedView<Panel<ScrollView<PaddedView<Self>>>>; + fn pad(self) -> PaddedView<Self>; + fn hpad(self) -> PaddedView<Self>; +} + +impl<V: Sized> ViewExt for V { + fn float(self, title: &str) -> PaddedView<Panel<ScrollView<PaddedView<Self>>>> { + PaddedView::new( + Margins::lrtb(1, 1, 1, 1), + Panel::new(ScrollView::new(PaddedView::new(Margins::lr(1, 1), self))).title(title), + ) + } + + fn pad(self) -> PaddedView<Self> { + PaddedView::new(Margins::lrtb(1, 1, 1, 1), self) + } + + fn hpad(self) -> PaddedView<Self> { + PaddedView::new(Margins::lr(1, 1), self) + } +} + +trait ToTextView { + fn text_view(self) -> TextView; +} + +impl<S: Into<StyledString>> ToTextView for S { + fn text_view(self) -> TextView { + TextView::new(self) + } +} |