i3status_rs/blocks/vpn/
mullvad.rs1use regex::Regex;
2use std::process::Stdio;
3use tokio::process::Command;
4
5use crate::blocks::prelude::*;
6use crate::util::country_flag_from_iso_code;
7
8use super::{Driver, Status};
9
10pub struct MullvadDriver {
11 regex_country_code: Regex,
12}
13
14impl MullvadDriver {
15 pub async fn new() -> MullvadDriver {
16 MullvadDriver {
17 regex_country_code: Regex::new("Connected to ([a-z]{2}).*, ([A-Z][a-z]*).*\n").unwrap(),
18 }
19 }
20
21 async fn run_network_command(arg: &str) -> Result<()> {
22 let code = Command::new("mullvad")
23 .args([arg])
24 .stdin(Stdio::null())
25 .stdout(Stdio::null())
26 .spawn()
27 .error(format!("Problem running mullvad command: {arg}"))?
28 .wait()
29 .await
30 .error(format!("Problem running mullvad command: {arg}"))?;
31
32 if code.success() {
33 Ok(())
34 } else {
35 Err(Error::new(format!(
36 "mullvad command failed with nonzero status: {code:?}"
37 )))
38 }
39 }
40}
41
42#[async_trait]
43impl Driver for MullvadDriver {
44 async fn get_status(&self) -> Result<Status> {
45 let stdout = Command::new("mullvad")
46 .args(["status"])
47 .output()
48 .await
49 .error("Problem running mullvad command")?
50 .stdout;
51
52 let status = String::from_utf8(stdout).error("mullvad produced non-UTF8 output")?;
53
54 if status.contains("Disconnected") {
55 return Ok(Status::Disconnected);
56 } else if status.contains("Connected") {
57 let (country_flag, country) = self
58 .regex_country_code
59 .captures_iter(&status)
60 .next()
61 .map(|capture| {
62 let country_code = capture[1].to_uppercase();
63 let country = capture[2].to_owned();
64 let country_flag = country_flag_from_iso_code(&country_code);
65 (country_flag, country)
66 })
67 .unwrap_or_default();
68
69 return Ok(Status::Connected {
70 country,
71 country_flag,
72 });
73 }
74 Ok(Status::Error)
75 }
76
77 async fn toggle_connection(&self, status: &Status) -> Result<()> {
78 match status {
79 Status::Connected { .. } => Self::run_network_command("disconnect").await?,
80 Status::Disconnected => Self::run_network_command("connect").await?,
81 Status::Error => (),
82 }
83 Ok(())
84 }
85}