extern crate gtk_sys;
extern crate gtk;
extern crate libappindicator;
extern crate notify_rust;
extern crate clipboard;
extern crate pcsc;
use clipboard::ClipboardProvider;
use clipboard::ClipboardContext;
use notify_rust::Notification;
use gtk::prelude::*;
use gtk::{WidgetExt, MenuShellExt, MenuItemExt};
use libappindicator::{AppIndicator, AppIndicatorStatus};
use ykoath;
fn notify_code_copied() {
let message = String::from("TOTP code copied to clipboard.");
Notification::new()
.summary("YubiOATH")
.body(&message)
.icon("pgp-keys")
.show().unwrap();
}
fn update_menu(indicator: &mut AppIndicator) -> gtk::Continue {
let mut menu = gtk::Menu::new();
let context = pcsc::Context::establish(pcsc::Scope::User).unwrap();
let mut readers_buf = [0; 2048];
let readers = context.list_readers(&mut readers_buf).unwrap();
let mut yubikeys: Vec<ykoath::YubiKey> = Vec::new();
for reader in readers {
yubikeys.push(ykoath::YubiKey{
name: reader.to_str().unwrap(),
});
}
if yubikeys.len() == 0 {
let none_connected = gtk::MenuItem::new_with_label(
"(No YubiKeys Detected)"
);
menu.append(&none_connected);
}
for yubikey in yubikeys {
let device_label: String = yubikey.name.to_owned();
let device_entry = gtk::MenuItem::new_with_label(&device_label);
let child_menu = gtk::Menu::new();
let builder = gtk::Builder::new();
let codes = match yubikey.get_oath_codes() {
Ok(codes) => codes,
Err(e) => {
println!("ERROR {}", e);
continue;
},
};
if codes.len() == 0 {
let no_codes = gtk::MenuItem::new_with_label("(No Credentials)");
child_menu.append(&no_codes);
}
for oath in codes {
let code = ykoath::format_code(oath.code.value, oath.code.digits);
let name_clone = oath.name.clone();
let mut label_vec: Vec<&str> = name_clone.split(":").collect();
let mut code_entry_label: String = String::from(
label_vec.remove(0)
);
if label_vec.len() > 0 {
code_entry_label.push_str(" (");
code_entry_label.push_str(&label_vec.join(""));
code_entry_label.push_str(") ");
}
code_entry_label.push_str(&code.clone().to_owned());
println!("DEBUG {}", code_entry_label);
let code_entry = gtk::MenuItem::new_with_label(&code_entry_label);
child_menu.append(&code_entry);
code_entry.connect_activate(move |_| {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
ctx.set_contents(code.clone()).unwrap();
notify_code_copied();
});
}
device_entry.add_child(&builder, &child_menu, Some("submenu"));
menu.append(&device_entry);
}
indicator.set_menu(&mut menu);
menu.show_all();
gtk::Continue(true)
}
fn main() {
gtk::init().unwrap();
let mut indicator = AppIndicator::new("YubiOATH", "");
indicator.set_status(AppIndicatorStatus::APP_INDICATOR_STATUS_ACTIVE);
indicator.set_icon_full("pgp-keys", "icon");
update_menu(&mut indicator);
gtk::timeout_add_seconds(10, move || {
update_menu(&mut indicator)
});
gtk::main();
}