#include "apps/vpn_page.h" #include #include "app_keys.h" #include "ipv4.h" #include "ui/fonts.h" #include "ui/theme.h" #include "ui/widgets.h" namespace roro { void VpnPage::enter() { list_.setCount(kRows); confirm_.reset(); message_.clear(); } bool VpnPage::onKey(const KeyEvent& e) { if (confirm_) { confirm_->onKey(e); int result = confirm_->result(); if (result == DialogModel::kPending) return true; Ask asked = ask_; ask_ = Ask::None; confirm_.reset(); if (result == 1 && asked == Ask::DeleteFile) { storage_.runJob([]() { SD.remove(kConfPath); }); message_ = "Imported, and the file is deleted"; } else if (result == 1 && asked == Ask::Forget) { vpn_.forget(); message_ = "Forgotten"; } return true; } switch (e.key) { case Key::Up: list_.up(); break; case Key::Down: list_.down(); break; case Key::Back: return false; case Key::Left: case Key::Right: case Key::Select: if (e.key != Key::Select && list_.selected() > kAuto) break; message_.clear(); switch (list_.selected()) { case kSwitch: if (!vpn_.configured()) message_ = "Import a .conf first"; else vpn_.want(!vpn_.wanted()); break; case kAuto: if (!vpn_.configured()) message_ = "Import a .conf first"; else settings_.setBool(Setting::VpnAuto, !settings_.getBool(Setting::VpnAuto)); break; case kImport: { std::string why = vpn_.importFile(storage_, kConfPath); if (!why.empty()) { message_ = why; break; } message_ = "Imported"; ask_ = Ask::DeleteFile; // the card can be taken out, and the key is in that file confirm_.reset(new DialogModel({"Keep it", "Delete it"})); break; } case kForget: if (!vpn_.configured()) break; ask_ = Ask::Forget; confirm_.reset(new DialogModel({"Cancel", "Forget"})); break; default: break; } break; default: break; } return true; } void VpnPage::help(std::vector& out) const { if (confirm_) return keys::add(out, keys::kDialog); keys::add(out, keys::kVpn); } void VpnPage::draw(Canvas& c) { const auto& area = theme::kContent; c.setTextDatum(top_left); bool set = vpn_.configured(); widgets::list( c, list_, {area.x, area.y, area.w, kRows * theme::kLineHeight}, [](int i) -> std::string { switch (i) { case kSwitch: return "VPN"; case kAuto: return "Start with Wi-Fi"; case kImport: return "Import /vpn/wg0.conf"; default: return "Forget it"; } }, [&](int i) -> std::string { switch (i) { case kSwitch: return !set ? "Not set" : vpn_.wanted() ? "On" : "Off"; case kAuto: return settings_.getBool(Setting::VpnAuto) ? "On" : "Off"; default: return ""; } }); int y = area.y + kRows * theme::kLineHeight + 4; c.setFont(&fonts::small); auto line = [&](const std::string& text, uint16_t colour) { c.setTextColor(colour); c.drawString(text.c_str(), 4, y); y += 10; }; if (set) { const net::WgConfig& k = vpn_.config(); std::string state = std::string("It is ") + vpn_.stateText(); int64_t now = clock_.utcNow(), last = vpn_.lastHandshake(); if (vpn_.state() == VpnService::State::Up && now >= 0 && last > 0 && now >= last) state += ", heard " + std::to_string(now - last) + " s ago"; line(state, vpn_.state() == VpnService::State::Up ? theme::kAccent : theme::kText); line("Server " + k.endpointHost + ":" + std::to_string(k.endpointPort), theme::kMuted); line("This device " + net::formatIpv4(k.address) + ", through it " + net::describeWgRouting(k), theme::kMuted); } else { line("Copy a WireGuard .conf to the card as", theme::kMuted); line(std::string(kConfPath) + ", then import it.", theme::kMuted); } if (!message_.empty()) line(message_, theme::kWarning); else if (!vpn_.lastError().empty()) line(vpn_.lastError(), theme::kWarning); if (confirm_ && ask_ == Ask::DeleteFile) widgets::dialog(c, "Delete the file?", "It is stored in the device now. The file on the card still holds the private key.", *confirm_); else if (confirm_) widgets::dialog(c, "Forget the VPN?", "The tunnel stops and its keys are erased from the device.", *confirm_); } } // namespace roro