diff --git a/Cargo.toml b/Cargo.toml index 09fbc491..98e53b02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" chrono = "0.4.0" gdk-pixbuf = "0.2.0" gio = "0.2.0" +glib = "0.3.1" regex = "0.2.2" reqwest = "0.7.3" secret-service = "0.4.0" @@ -16,10 +17,13 @@ time = "0.1.38" url = "1.5.1" xdg = "2.1.0" +[dependencies.cairo-rs] +features = ["png"] +version = "0.2.0" + [dependencies.gtk] features = ["v3_22"] version = "0.2.0" -[dependencies.cairo-rs] -version = "0.2.0" -features = ["png"] +[dependencies.libnotify] +git = "https://github.com/danigm/rust-libnotify" diff --git a/TODO b/TODO index 8aa616b8..7c744d2c 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ Fixs: + * Fix room list after join * Ignore launched threads when changing room... * Sort rooms by last message or fav? @@ -16,6 +17,11 @@ Functionality: * Send media messages (images / videos) * Store last read message to show differently +Events to manage: + * New room events + * Invite event + * Join events + Other stuff: * Set the app name correctly to show in the shell topbar diff --git a/src/app.rs b/src/app.rs index 220326b5..5f48d811 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,10 @@ +extern crate glib; extern crate gtk; extern crate gio; extern crate gdk_pixbuf; - extern crate secret_service; +extern crate libnotify; + use self::secret_service::SecretService; use self::secret_service::EncryptionType; @@ -45,6 +47,7 @@ pub struct AppOp { pub backend: Sender, pub active_room: String, pub members: HashMap, + pub rooms: HashMap, pub load_more_btn: gtk::Button, } @@ -124,7 +127,6 @@ impl AppOp { let ser = server_url.clone(); self.backend.send(BKCommand::Register(uname, pass, ser)).unwrap(); self.hide_popup(); - self.clear_room_list(); } pub fn connect(&self, username: String, password: String, server: Option) { @@ -145,7 +147,6 @@ impl AppOp { let ser = server_url.clone(); self.backend.send(BKCommand::Login(uname, pass, ser)).unwrap(); self.hide_popup(); - self.clear_room_list(); } pub fn connect_guest(&self, server: Option) { @@ -157,7 +158,6 @@ impl AppOp { self.show_user_loading(); self.backend.send(BKCommand::Guest(server_url)).unwrap(); self.hide_popup(); - self.clear_room_list(); } pub fn get_username(&self) { @@ -295,7 +295,11 @@ impl AppOp { } pub fn sync(&self) { - self.backend.send(BKCommand::Sync).unwrap(); + let tx = self.backend.clone(); + gtk::timeout_add(1000, move || { + tx.send(BKCommand::Sync).unwrap(); + gtk::Continue(false) + }); } pub fn set_rooms(&mut self, rooms: Vec, def: Option) { @@ -304,7 +308,11 @@ impl AppOp { let mut array: Vec = vec![]; + self.rooms.clear(); + store.clear(); + for r in rooms { + self.rooms.insert(r.id.clone(), r.clone()); array.push(r); } @@ -340,17 +348,10 @@ impl AppOp { .expect("Can't find main_content_stack in ui file.") .set_visible_child_name("Chat"); - self.clear_room_list(); self.room_panel(RoomPanel::Loading); self.backend.send(BKCommand::SyncForced).unwrap(); } - pub fn clear_room_list(&self) { - let store: gtk::TreeStore = self.gtk_builder.get_object("rooms_tree_store") - .expect("Couldn't find rooms_tree_store in ui file."); - store.clear(); - } - pub fn set_active_room(&mut self, room: String, name: String) { self.active_room = room; @@ -604,6 +605,50 @@ impl AppOp { btn.set_label("Search"); btn.set_sensitive(true); } + + pub fn notify(&self, msg: &Message) { + let roomname = match self.rooms.get(&msg.room) { + Some(r) => r.name.clone(), + None => msg.room.clone(), + }; + + let mut body = msg.body.clone(); + body.truncate(80); + + let (tx, rx): (Sender<(String, String)>, Receiver<(String, String)>) = channel(); + self.backend.send(BKCommand::GetUserInfoAsync(msg.sender.clone(), tx)).unwrap(); + gtk::timeout_add(50, move || { + match rx.try_recv() { + Err(_) => gtk::Continue(true), + Ok((name, avatar)) => { + let summary = format!("@{} / {}", name, roomname); + let n = libnotify::Notification::new(&summary, + Some(&body[..]), + Some(&avatar[..])); + n.show().unwrap(); + gtk::Continue(false) + } + } + }); + } + + pub fn show_room_messages(&self, msgs: Vec, init: bool) { + for msg in msgs.iter() { + self.add_room_message(msg, MsgPos::Bottom); + if !init { + self.notify(msg); + } + } + + if !msgs.is_empty() { + self.scroll_down(); + self.mark_as_read(msgs); + } + + if init { + self.room_panel(RoomPanel::Room); + } + } } /// State for the main thread. @@ -639,11 +684,12 @@ impl App { backend: apptx, active_room: String::from(""), members: HashMap::new(), + rooms: HashMap::new(), } )); let theop = op.clone(); - gtk::timeout_add(50, move || { + gtk::timeout_add(500, move || { let recv = rx.try_recv(); match recv { Ok(BKResponse::Token(uid, _)) => { @@ -673,16 +719,10 @@ impl App { theop.lock().unwrap().set_room_avatar(avatar); }, Ok(BKResponse::RoomMessages(msgs)) => { - for msg in msgs.iter() { - theop.lock().unwrap().add_room_message(msg, MsgPos::Bottom); - } - - if !msgs.is_empty() { - theop.lock().unwrap().scroll_down(); - theop.lock().unwrap().mark_as_read(msgs); - } - - theop.lock().unwrap().room_panel(RoomPanel::Room); + theop.lock().unwrap().show_room_messages(msgs, false); + }, + Ok(BKResponse::RoomMessagesInit(msgs)) => { + theop.lock().unwrap().show_room_messages(msgs, true); }, Ok(BKResponse::RoomMessagesTo(msgs)) => { for msg in msgs.iter().rev() { @@ -941,6 +981,14 @@ impl App { pub fn run(self) { self.op.lock().unwrap().init(); + if let Err(err) = libnotify::init("guillotine") { + println!("Error: can't init notifications: {}", err); + }; + + glib::set_application_name("guillotine"); + glib::set_prgname(Some("guillotine")); gtk::main(); + + libnotify::uninit(); } } diff --git a/src/backend.rs b/src/backend.rs index 55fa62a5..c35627d6 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -55,6 +55,7 @@ pub enum BKCommand { GetRoomMessagesTo(String), GetAvatarAsync(String, Sender), GetThumbAsync(String, Sender), + GetUserInfoAsync(String, Sender<(String, String)>), SendMsg(String, String), SetRoom(String), ShutDown, @@ -74,6 +75,7 @@ pub enum BKResponse { RoomDetail(String, String), RoomAvatar(String), RoomMessages(Vec), + RoomMessagesInit(Vec), RoomMessagesTo(Vec), RoomMembers(Vec), SendMsg, @@ -162,6 +164,10 @@ impl Backend { let r = self.get_avatar_async(&sender, ctx); bkerror!(r, tx, BKResponse::CommandError); }, + Ok(BKCommand::GetUserInfoAsync(sender, ctx)) => { + let r = self.get_user_info_async(&sender, ctx); + bkerror!(r, tx, BKResponse::CommandError); + }, Ok(BKCommand::GetThumbAsync(media, ctx)) => { let r = self.get_thumb_async(media, ctx); bkerror!(r, tx, BKResponse::CommandError); @@ -347,7 +353,7 @@ impl Backend { let tx = self.tx.clone(); thread::spawn(move || { match get_user_avatar(&baseu, &userid) { - Ok(fname) => { + Ok((_, fname)) => { tx.send(BKResponse::Avatar(fname)).unwrap(); }, Err(err) => { @@ -511,7 +517,7 @@ impl Backend { ms.push(m); } match to { - false => tx.send(BKResponse::RoomMessages(ms)).unwrap(), + false => tx.send(BKResponse::RoomMessagesInit(ms)).unwrap(), true => tx.send(BKResponse::RoomMessagesTo(ms)).unwrap(), }; }, @@ -569,7 +575,7 @@ impl Backend { let u = String::from(uid); thread::spawn(move || { match get_user_avatar(&baseu, &u) { - Ok(fname) => { tx.send(fname).unwrap(); }, + Ok((_, fname)) => { tx.send(fname).unwrap(); }, Err(_) => { tx.send(String::from("")).unwrap(); } }; }); @@ -577,6 +583,20 @@ impl Backend { Ok(()) } + pub fn get_user_info_async(&self, uid: &str, tx: Sender<(String, String)>) -> Result<(), Error> { + let baseu = self.get_base_url()?; + + let u = String::from(uid); + thread::spawn(move || { + match get_user_avatar(&baseu, &u) { + Ok(info) => { tx.send(info).unwrap(); }, + Err(_) => { tx.send((String::new(), String::new())).unwrap(); } + }; + }); + + Ok(()) + } + pub fn get_thumb_async(&self, media: String, tx: Sender) -> Result<(), Error> { let baseu = self.get_base_url()?; diff --git a/src/util.rs b/src/util.rs index afe9a9bc..d309fa59 100644 --- a/src/util.rs +++ b/src/util.rs @@ -276,22 +276,22 @@ pub fn json_q(method: &str, url: &Url, attrs: &JsonValue) -> Result Result { +pub fn get_user_avatar(baseu: &Url, userid: &str) -> Result<(String, String), Error> { let url = baseu.join("/_matrix/client/r0/profile/")?.join(userid)?; let attrs = json!(null); match json_q("get", &url, &attrs) { Ok(js) => { + let name = String::from(js["displayname"].as_str().unwrap_or("@")); match js["avatar_url"].as_str() { - Some(url) => Ok(thumb!(baseu, &url)?), + Some(url) => Ok((name.clone(), thumb!(baseu, &url)?)), None => { - let name = js["displayname"].as_str().unwrap_or("@"); - Ok(draw_identicon(userid, String::from(name))?) + Ok((name.clone(), draw_identicon(userid, name)?)) }, } }, Err(_) => { - Ok(draw_identicon(userid, String::from(&userid[1..2]))?) + Ok((String::from(userid), draw_identicon(userid, String::from(&userid[1..2]))?)) } } }