diff options
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 115 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/main.rs | 478 | 
4 files changed, 502 insertions, 94 deletions
| @@ -1,2 +1,3 @@  /target +.vscode  macaw.db @@ -34,6 +34,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"  [[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]]  name = "ahash"  version = "0.7.8"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -372,6 +383,15 @@ dependencies = [  ]  [[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]]  name = "block2"  version = "0.5.1"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -464,6 +484,15 @@ dependencies = [  ]  [[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]]  name = "cc"  version = "1.2.13"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -499,6 +528,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"  [[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]]  name = "circular"  version = "0.3.0"  dependencies = [ @@ -1981,6 +2020,16 @@ dependencies = [  ]  [[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]]  name = "instant"  version = "0.1.13"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2254,6 +2303,7 @@ dependencies = [   "iced",   "jid",   "luz", + "secret-service",   "tokio",   "tokio-stream",   "tracing", @@ -2476,6 +2526,30 @@ dependencies = [  ]  [[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]]  name = "num-bigint-dig"  version = "0.8.4"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2493,6 +2567,15 @@ dependencies = [  ]  [[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]]  name = "num-integer"  version = "0.1.46"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2513,6 +2596,17 @@ dependencies = [  ]  [[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]]  name = "num-traits"  version = "0.2.19"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3492,6 +3586,25 @@ dependencies = [  ]  [[package]] +name = "secret-service" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand", + "serde", + "sha2", + "zbus", +] + +[[package]]  name = "security-framework"  version = "2.11.1"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4246,6 +4359,7 @@ dependencies = [   "signal-hook-registry",   "socket2",   "tokio-macros", + "tracing",   "windows-sys 0.52.0",  ] @@ -5454,6 +5568,7 @@ dependencies = [   "serde_repr",   "sha1",   "static_assertions", + "tokio",   "tracing",   "uds_windows",   "windows-sys 0.52.0", @@ -11,4 +11,4 @@ tokio = "1.43.0"  tokio-stream = "0.1.17"  tracing-subscriber = "0.3.19"  tracing = "0.1.41" - +secret-service = { version = "4.0.0", features = ["rt-tokio-crypto-rust"] } diff --git a/src/main.rs b/src/main.rs index b2308fb..1a940fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,127 @@ +use std::borrow::Cow;  use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut};  use iced::futures::{SinkExt, Stream, StreamExt}; -use iced::widget::{button, column, row, text, text_input}; -use iced::{stream, Element, Subscription, Task, Theme}; +use iced::widget::text::{Fragment, IntoFragment}; +use iced::widget::{ +    button, center, column, container, mouse_area, opaque, row, stack, text, text_input, Text, +}; +use iced::{stream, Color, Element, Subscription, Task, Theme};  use jid::JID;  use luz::chat::{Chat, Message as ChatMessage};  use luz::presence::{Offline, Presence};  use luz::CommandMessage;  use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage}; -use tokio::sync::oneshot; +use tokio::sync::{mpsc, oneshot};  use tokio_stream::wrappers::ReceiverStream; +use tracing::info; -#[derive(Default)]  pub struct Macaw { -    client: Option<LuzHandle>, +    client: Account,      roster: HashMap<JID, Contact>,      users: HashMap<JID, User>,      presences: HashMap<JID, Presence>,      chats: HashMap<JID, (Chat, Vec<ChatMessage>)>,      subscription_requests: HashSet<JID>, -    connection_status: Option<Presence>, +} + +pub struct Creds { +    jid: String, +    password: String, +} + +impl Macaw { +    pub fn new(client: Option<Client>) -> Self { +        let account; +        if let Some(client) = client { +            account = Account::LoggedIn(client); +        } else { +            account = Account::LoggedOut { +                jid: "".to_string(), +                password: "".to_string(), +                error: None, +            }; +        } + +        Self { +            client: account, +            roster: HashMap::new(), +            users: HashMap::new(), +            presences: HashMap::new(), +            chats: HashMap::new(), +            subscription_requests: HashSet::new(), +        } +    } +} + +pub enum Account { +    LoggedIn(Client), +    LoggedOut { +        jid: String, +        password: String, +        error: Option<Error>, +    }, +} + +#[derive(Debug, Clone)] +pub enum Error { +    InvalidJID(String), +    DatabaseConnection, +} + +#[derive(Clone, Debug)] +pub struct Client { +    client: LuzHandle, +    jid: JID, +    connection_status: Presence, +} + +impl DerefMut for Client { +    fn deref_mut(&mut self) -> &mut Self::Target { +        &mut self.client +    } +} + +impl Deref for Client { +    type Target = LuzHandle; + +    fn deref(&self) -> &Self::Target { +        &self.client +    }  }  fn main() -> iced::Result {      tracing_subscriber::fmt::init(); -    iced::application("Macaw", Macaw::update, Macaw::view) -        .subscription(Macaw::subscription) -        .run() +    let client: Option<(JID, LuzHandle, mpsc::Receiver<UpdateMessage>)> = None; + +    if let Some((jid, luz_handle, update_recv)) = client { +        let stream = ReceiverStream::new(update_recv); +        let stream = stream.map(|message| Message::Luz(message)); +        iced::application("Macaw", Macaw::update, Macaw::view).run_with(|| { +            ( +                Macaw::new(Some(Client { +                    client: luz_handle, +                    // TODO: +                    jid, +                    connection_status: Presence::Offline(Offline::default()), +                })), +                // TODO: autoconnect config +                Task::stream(stream), +            ) +        }) +    } else { +        iced::application("Macaw", Macaw::update, Macaw::view) +            .run_with(|| (Macaw::new(None), Task::none())) +    }  }  #[derive(Debug, Clone)]  enum Message { -    ClientCreated(LuzHandle), +    LoginModal(LoginModalMessage), +    ClientCreated(Client),      Luz(UpdateMessage),      Roster(HashMap<JID, Contact>),      Connect, @@ -40,27 +129,15 @@ enum Message {      OpenChat(JID),  } -impl Macaw { -    fn stream() -> impl Stream<Item = Message> { -        stream::channel(100, |mut output| async { -            let (luz, recv) = LuzHandle::new( -                "test@blos.sm".try_into().unwrap(), -                "slayed".to_string(), -                "./macaw.db", -            ) -            .await -            .unwrap(); -            output.send(Message::ClientCreated(luz)).await; -            let stream = ReceiverStream::new(recv); -            let stream = stream.map(|message| Message::Luz(message)).map(Ok); -            stream.forward(output).await; -        }) -    } - -    fn subscription(&self) -> Subscription<Message> { -        Subscription::run(Macaw::stream) -    } +#[derive(Debug, Clone)] +enum LoginModalMessage { +    JID(String), +    Password(String), +    Submit, +    Error(Error), +} +impl Macaw {      fn update(&mut self, message: Message) -> Task<Message> {          match message {              Message::Luz(update_message) => match update_message { @@ -68,18 +145,35 @@ impl Macaw {                      tracing::error!("Luz error: {:?}", error);                      Task::none()                  } -                UpdateMessage::Online(online, vec) => { -                    self.connection_status = Some(Presence::Online(online)); -                    let mut roster = HashMap::new(); -                    for contact in vec { -                        roster.insert(contact.user_jid.clone(), contact); +                UpdateMessage::Online(online, vec) => match &mut self.client { +                    Account::LoggedIn(client) => { +                        client.connection_status = Presence::Online(online); +                        let mut roster = HashMap::new(); +                        for contact in vec { +                            roster.insert(contact.user_jid.clone(), contact); +                        } +                        self.roster = roster; +                        Task::none()                      } -                    self.roster = roster; -                    Task::none() -                } +                    Account::LoggedOut { +                        jid, +                        password, +                        error, +                    } => Task::none(), +                },                  UpdateMessage::Offline(offline) => { -                    self.connection_status = Some(Presence::Offline(offline)); -                    Task::none() +                    // TODO: update all contacts' presences to unknown (offline) +                    match &mut self.client { +                        Account::LoggedIn(client) => { +                            client.connection_status = Presence::Offline(offline); +                            Task::none() +                        } +                        Account::LoggedOut { +                            jid, +                            password, +                            error, +                        } => Task::none(), +                    }                  }                  UpdateMessage::FullRoster(vec) => {                      let mut macaw_roster = HashMap::new(); @@ -118,13 +212,13 @@ impl Macaw {                      Task::none()                  }              }, -            Message::ClientCreated(luz_handle) => { -                let cloned: LuzHandle = luz_handle.clone(); -                self.client = Some(cloned); +            // TODO: NEXT +            Message::ClientCreated(client) => { +                self.client = Account::LoggedIn(client.clone());                  let (send, recv) = oneshot::channel();                  Task::perform(                      async move { -                        luz_handle.send(CommandMessage::GetRoster(send)).await; +                        client.client.send(CommandMessage::GetRoster(send)).await;                          recv.await                      },                      |result| { @@ -141,64 +235,262 @@ impl Macaw {                  self.roster = hash_map;                  Task::none()              } -            Message::Connect => { -                let client = self.client.clone(); -                Task::future(async move { -                    client.clone().unwrap().send(CommandMessage::Connect).await; -                }) -                .discard() -            } -            Message::Disconnect => { -                let client = self.client.clone(); -                Task::future(async move { -                    client -                        .clone() -                        .unwrap() -                        .send(CommandMessage::Disconnect(Offline::default())) -                        .await; -                }) -                .discard() -            } +            Message::Connect => match &self.client { +                Account::LoggedIn(client) => { +                    let client = client.client.clone(); +                    Task::future(async move { +                        client.send(CommandMessage::Connect).await; +                    }) +                    .discard() +                } +                Account::LoggedOut { +                    jid, +                    password, +                    error, +                } => Task::none(), +            }, +            Message::Disconnect => match &self.client { +                Account::LoggedIn(client) => { +                    let client = client.client.clone(); +                    Task::future(async move { +                        client +                            .send(CommandMessage::Disconnect(Offline::default())) +                            .await; +                    }) +                    .discard() +                } +                Account::LoggedOut { +                    jid, +                    password, +                    error, +                } => Task::none(), +            },              Message::OpenChat(jid) => todo!(), +            Message::LoginModal(login_modal_message) => match login_modal_message { +                LoginModalMessage::JID(j) => match &mut self.client { +                    Account::LoggedIn(_client) => Task::none(), +                    Account::LoggedOut { +                        jid, +                        password, +                        error, +                    } => { +                        *jid = j; +                        Task::none() +                    } +                }, +                LoginModalMessage::Password(p) => match &mut self.client { +                    Account::LoggedIn(_client) => Task::none(), +                    Account::LoggedOut { +                        jid, +                        password, +                        error, +                    } => { +                        *password = p; +                        Task::none() +                    } +                }, +                LoginModalMessage::Submit => match &self.client { +                    Account::LoggedIn(_client) => Task::none(), +                    Account::LoggedOut { +                        jid: jid_str, +                        password, +                        error, +                    } => { +                        info!("submitting login"); +                        let jid_str = jid_str.clone(); +                        let password = password.clone(); +                        Task::future(async move { +                            let jid: Result<JID, _> = jid_str.parse(); +                            match jid { +                                Ok(j) => { +                                    let result = +                                        LuzHandle::new(j.clone(), password.to_string(), "macaw.db") +                                            .await; +                                    match result { +                                        Ok((luz_handle, receiver)) => { +                                            let stream = ReceiverStream::new(receiver); +                                            let stream = +                                                stream.map(|message| Message::Luz(message)); +                                            vec![ +                                                Task::done(Message::ClientCreated(Client { +                                                    client: luz_handle, +                                                    jid: j, +                                                    connection_status: Presence::Offline( +                                                        Offline::default(), +                                                    ), +                                                })), +                                                Task::stream(stream), +                                            ] +                                        } +                                        Err(e) => { +                                            tracing::error!("error (database probably)"); +                                            return vec![Task::done(Message::LoginModal( +                                                LoginModalMessage::Error(Error::DatabaseConnection), +                                            ))]; +                                        } +                                    } +                                } +                                Err(_) => { +                                    tracing::error!("parsing jid"); +                                    return vec![Task::done(Message::LoginModal( +                                        LoginModalMessage::Error(Error::InvalidJID( +                                            jid_str.to_string(), +                                        )), +                                    ))]; +                                } +                            } +                        }) +                        .then(|tasks| Task::batch(tasks)) +                    } +                }, +                LoginModalMessage::Error(e) => match &mut self.client { +                    Account::LoggedIn(_client) => Task::none(), +                    Account::LoggedOut { +                        jid, +                        password, +                        error, +                    } => { +                        tracing::error!("luz::new: {:?}", e); +                        *error = Some(e); +                        Task::none() +                    } +                }, +            },          }      }      fn view(&self) -> Element<Message> { -        let mut contacts: Vec<Element<Message>> = Vec::new(); -        for (_, contact) in &self.roster { -            contacts.push( -                button(match &contact.user_jid.localpart { -                    Some(u) => u, -                    None => "no username", -                }) -                .on_press(Message::OpenChat(contact.user_jid.clone())) -                .into(), -            ); -        } -        let column = column(contacts); -        let connection_status = match &self.connection_status { -            Some(s) => match s { -                Presence::Online(online) => "connected", -                Presence::Offline(offline) => "disconnected", -            }, -            None => "no account", +        let ui = { +            let mut contacts: Vec<Element<Message>> = Vec::new(); +            for (_, contact) in &self.roster { +                let jid: Cow<'_, str> = (&contact.user_jid).into(); +                contacts.push( +                    button(text(jid)) +                        .on_press(Message::OpenChat(contact.user_jid.clone())) +                        .into(), +                ); +            } +            let column = column(contacts); +            let connection_status = match &self.client { +                Account::LoggedIn(client) => match &client.connection_status { +                    Presence::Online(_online) => "online", +                    Presence::Offline(_offline) => "disconnected", +                }, +                Account::LoggedOut { +                    jid: _, +                    password: _, +                    error, +                } => "disconnected", +            }; +            // match &self.client.as_ref().map(|client| &client.connection_status) { +            //     Some(s) => match s { +            //         Presence::Online(online) => "connected", +            //         Presence::Offline(offline) => "disconnected", +            //     }, +            //     None => "no account", +            // }; +            let client_jid: Cow<'_, str> = match &self.client { +                Account::LoggedIn(client) => (&client.jid).into(), +                Account::LoggedOut { +                    jid: _, +                    password: _, +                    error, +                } => Cow::from("no account"), +                // map(|client| (&client.jid).into()); +            }; +            column![ +                row![ +                    text(client_jid), +                    text(connection_status), +                    button("connect").on_press(Message::Connect), +                    button("disconnect").on_press(Message::Disconnect) +                ], +                text("Buddy List:"), +                // +                // +                column, +            ]          }; -        column![ -            row![ -                text("test@blos.sm"), -                text(connection_status), -                button("connect").on_press(Message::Connect), -                button("disconnect").on_press(Message::Disconnect) -            ], -            text("Buddy List:"), -            // -            // -            column, -        ] -        .into() + +        // temporarily center to fill space +        let ui = center(ui).into(); + +        match &self.client { +            Account::LoggedIn(_client) => ui, +            Account::LoggedOut { +                jid, +                password, +                error, +            } => { +                let signup = container( +                    column![ +                        text("Log In").size(24), +                        column![ +                            column![ +                                text("JID").size(12), +                                text_input("berry@macaw.chat", &jid) +                                    .on_input(|j| Message::LoginModal(LoginModalMessage::JID(j))) +                                    .on_submit(Message::LoginModal(LoginModalMessage::Submit)) +                                    .padding(5), +                            ] +                            .spacing(5), +                            column![ +                                text("Password").size(12), +                                text_input("", &password) +                                    .on_input(|p| Message::LoginModal(LoginModalMessage::Password( +                                        p +                                    ))) +                                    .on_submit(Message::LoginModal(LoginModalMessage::Submit)) +                                    .secure(true) +                                    .padding(5), +                            ] +                            .spacing(5), +                            button(text("Submit")) +                                .on_press(Message::LoginModal(LoginModalMessage::Submit)), +                        ] +                        .spacing(10) +                    ] +                    .spacing(20), +                ) +                .width(300) +                .padding(10) +                .style(container::rounded_box); + +                // signup.into() +                modal(ui, signup) +            } +        }      }      fn theme(&self) -> Theme {          Theme::Dark      }  } + +fn modal<'a, Message>( +    base: impl Into<Element<'a, Message>>, +    content: impl Into<Element<'a, Message>>, +    // on_blur: Message, +) -> Element<'a, Message> +where +    Message: Clone + 'a, +{ +    stack![ +        base.into(), +        opaque( +            mouse_area(center(opaque(content)).style(|_theme| { +                container::Style { +                    background: Some( +                        Color { +                            a: 0.8, +                            ..Color::BLACK +                        } +                        .into(), +                    ), +                    ..container::Style::default() +                } +            })) // .on_press(on_blur) +        ) +    ] +    .into() +} | 
