From c7c407a9957ba72df759dc5231b90eedb722dffa Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 6 Oct 2019 00:19:44 +0200 Subject: [PATCH] WIP NetworkManager widget --- src/config.rs | 10 + src/main.rs | 37 ++- src/widget.rs | 2 +- src/widgets/audio/alsa_sound.rs | 2 +- src/widgets/audio/pulse_sound.rs | 2 +- src/widgets/backlight.rs | 2 +- src/widgets/bar_widget.rs | 12 +- src/widgets/battery.rs | 34 +- src/widgets/calendar.rs | 2 +- src/widgets/clock.rs | 2 +- src/widgets/date.rs | 2 +- src/widgets/launcher.rs | 2 +- src/widgets/mod.rs | 1 + src/widgets/networkmanager.rs | 516 +++++++++++++++++++++++++++++++ 14 files changed, 590 insertions(+), 36 deletions(-) create mode 100644 src/widgets/networkmanager.rs diff --git a/src/config.rs b/src/config.rs index 7778eff..ab44b1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -58,6 +58,10 @@ pub enum Widget { font_size: f32, length: u32, }, + NetworkManager { + font_size: f32, + length: u32, + } } impl Widget { @@ -141,6 +145,12 @@ impl Widget { Err(_) => None, } } + Widget::NetworkManager { font_size, length } => { + match widgets::networkmanager::NetworkManager::new(font_size, length, tx.clone()) { + Ok(w) => Some(w), + Err(_) => None, + } + } } } } diff --git a/src/main.rs b/src/main.rs index 04c1e9d..4af1560 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,10 @@ use std::os::unix::io::AsRawFd; use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::mpsc::channel; -use timerfd::{TimerFd, TimerState, SetTimeFlags}; +use chrono::{Duration, Local}; use nix::poll::{poll, PollFd, PollFlags}; use os_pipe::pipe; -use chrono::{Local, Duration}; +use timerfd::{SetTimeFlags, TimerFd, TimerState}; mod app; mod buffer; @@ -198,7 +198,7 @@ fn main() { app.cmd_queue().lock().unwrap().push_back(Cmd::Draw); let mut visible = !daemon; - let mut wait_ctx = WaitContext{ + let mut wait_ctx = WaitContext { fds: Vec::new(), target_time: None, }; @@ -275,13 +275,20 @@ fn main() { } else { Duration::seconds(0) }; - timer.set_state(TimerState::Oneshot(sleep.to_std().unwrap()), SetTimeFlags::Default); + timer.set_state( + TimerState::Oneshot(sleep.to_std().unwrap()), + SetTimeFlags::Default, + ); wait_ctx.fds.push(tm_fd); } poll(&mut wait_ctx.fds, -1).unwrap(); - if wait_ctx.fds[0].revents().unwrap().contains(PollFlags::POLLIN) { + if wait_ctx.fds[0] + .revents() + .unwrap() + .contains(PollFlags::POLLIN) + { if let Some(guard) = app.event_queue().prepare_read() { if let Err(e) = guard.read_events() { if e.kind() != ::std::io::ErrorKind::WouldBlock { @@ -298,12 +305,20 @@ fn main() { .expect("Failed to dispatch all messages."); } - if wait_ctx.fds[1].revents().unwrap().contains(PollFlags::POLLIN) { + if wait_ctx.fds[1] + .revents() + .unwrap() + .contains(PollFlags::POLLIN) + { let mut v = [0x00]; rx_pipe.read_exact(&mut v).unwrap(); } - if wait_ctx.fds[2].revents().unwrap().contains(PollFlags::POLLIN) { + if wait_ctx.fds[2] + .revents() + .unwrap() + .contains(PollFlags::POLLIN) + { if let Ok((stream, _)) = listener.accept() { let client_queue = q.clone(); let mut client_pipe = ipc_pipe.try_clone().unwrap(); @@ -331,8 +346,12 @@ fn main() { } } - if wait_ctx.target_time.is_some() && - wait_ctx.fds[wait_ctx.fds.len()-1].revents().unwrap().contains(PollFlags::POLLIN) { + if wait_ctx.target_time.is_some() + && wait_ctx.fds[wait_ctx.fds.len() - 1] + .revents() + .unwrap() + .contains(PollFlags::POLLIN) + { timer.read(); q.lock().unwrap().push_back(Cmd::Draw); } diff --git a/src/widget.rs b/src/widget.rs index 8d18feb..c4dd7b5 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,7 +1,7 @@ -use nix::poll::{PollFd}; use crate::buffer::Buffer; use crate::color::Color; use chrono::{DateTime, Local}; +use nix::poll::PollFd; pub use smithay_client_toolkit::keyboard::{KeyState, ModifiersState}; pub struct DrawContext<'a> { diff --git a/src/widgets/audio/alsa_sound.rs b/src/widgets/audio/alsa_sound.rs index 4079143..fb1ec09 100644 --- a/src/widgets/audio/alsa_sound.rs +++ b/src/widgets/audio/alsa_sound.rs @@ -1,6 +1,6 @@ use crate::color::Color; -use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use crate::widget::WaitContext; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use alsa::mixer::{Mixer, Selem, SelemChannelId, SelemId}; diff --git a/src/widgets/audio/pulse_sound.rs b/src/widgets/audio/pulse_sound.rs index b8f8a79..9d95a49 100644 --- a/src/widgets/audio/pulse_sound.rs +++ b/src/widgets/audio/pulse_sound.rs @@ -1,7 +1,7 @@ use crate::cmd::Cmd; use crate::color::Color; -use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use crate::widget::WaitContext; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use std::cell::RefCell; use std::collections::HashMap; diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index c8a350a..7fe10da 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -1,6 +1,6 @@ use crate::color::Color; -use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use crate::widget::WaitContext; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use std::fs::OpenOptions; use std::io::{Error, ErrorKind}; diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index 46cd15b..9f0ed1f 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -1,6 +1,6 @@ use crate::color::Color; use crate::draw::{draw_bar, draw_box, Font, ROBOTO_REGULAR}; -use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}; use std::sync::{Arc, Mutex}; @@ -34,9 +34,9 @@ impl BarWidget { Box::new(BarWidget { bar_impl: w, dirty: Arc::new(Mutex::new(true)), - font: font, + font, font_size: font_size as u32, - length: length, + length, }) } @@ -53,10 +53,10 @@ impl BarWidget { Ok(Box::new(BarWidget { bar_impl: im, - dirty: dirty, - font: font, + dirty, + font, font_size: font_size as u32, - length: length, + length, })) } } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 421263d..5efd792 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,7 +1,7 @@ use crate::cmd::Cmd; use crate::color::Color; -use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use crate::widget::WaitContext; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; @@ -44,7 +44,7 @@ struct DbusConnection(dbus::Connection); impl std::convert::AsRef for DbusConnection { fn as_ref(&self) -> &dbus::Connection { - return &self.0 + return &self.0; } } @@ -70,12 +70,14 @@ enum UpowerBatteryState { } impl UpowerBattery { - pub fn from_device(dirty: Arc>, sender: Sender, device: &str) -> Result { - let con = dbus::Connection::get_private(dbus::BusType::System) - .map_err(|_| ::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to open dbus", - ))?; + pub fn from_device( + dirty: Arc>, + sender: Sender, + device: &str, + ) -> Result { + let con = dbus::Connection::get_private(dbus::BusType::System).map_err(|_| { + ::std::io::Error::new(::std::io::ErrorKind::Other, "unable to open dbus") + })?; let device_path = format!("/org/freedesktop/UPower/devices/battery_{}", device); let rule = format!( @@ -85,11 +87,12 @@ impl UpowerBattery { member='PropertiesChanged'", &device_path ); - con.add_match(&rule) - .map_err(|_| ::std::io::Error::new( + con.add_match(&rule).map_err(|_| { + ::std::io::Error::new( ::std::io::ErrorKind::Other, "unable to add match rule to dbus connection", - ))?; + ) + })?; let upower_type: dbus::arg::Variant = match get_upower_property(&con, &device_path, "Type")?.get1() { Some(v) => v, @@ -173,7 +176,11 @@ impl UpowerBattery { impl BarWidgetImpl for UpowerBattery { fn wait(&mut self, ctx: &mut WaitContext) { - for _ in self.con.as_ref().watch_handle(self.watch.fd(), dbus::WatchEvent::Readable as u32) { + for _ in self + .con + .as_ref() + .watch_handle(self.watch.fd(), dbus::WatchEvent::Readable as u32) + { let capacity = get_upower_property(self.con.as_ref(), &self.device_path, "Percentage") .unwrap() .get1::>() @@ -199,7 +206,8 @@ impl BarWidgetImpl for UpowerBattery { self.sender.send(Cmd::Draw).unwrap(); } - ctx.fds.push(PollFd::new(self.watch.fd(), PollFlags::POLLIN)); + ctx.fds + .push(PollFd::new(self.watch.fd(), PollFlags::POLLIN)); } fn name(&self) -> &str { "battery" diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index 16ee667..dbbf85f 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -1,7 +1,7 @@ use crate::buffer::Buffer; use crate::color::Color; use crate::draw::{Font, DEJAVUSANS_MONO, ROBOTO_REGULAR}; -use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}; use chrono::{Date, Datelike, Local}; diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index b925ea4..f503ae4 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -1,6 +1,6 @@ use crate::color::Color; use crate::draw::{Font, ROBOTO_REGULAR}; -use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}; use chrono::{DateTime, Datelike, Duration, Local, Timelike}; diff --git a/src/widgets/date.rs b/src/widgets/date.rs index 38cb500..1264653 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -1,6 +1,6 @@ use crate::color::Color; use crate::draw::{Font, ROBOTO_REGULAR}; -use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}; use chrono::{DateTime, Datelike, Local}; diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 1b5aa53..e210ec0 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -3,7 +3,7 @@ use crate::cmd::Cmd; use crate::color::Color; use crate::desktop::{load_desktop_files, Desktop}; use crate::draw::{Font, ROBOTO_REGULAR}; -use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}; use std::cell::RefCell; use std::cmp::Ordering; diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index b388f09..9ed0795 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -5,6 +5,7 @@ pub mod calendar; pub mod clock; pub mod date; pub mod launcher; +pub mod networkmanager; #[cfg(any(feature = "alsa-widget", feature = "pulseaudio-widget"))] pub mod audio; diff --git a/src/widgets/networkmanager.rs b/src/widgets/networkmanager.rs new file mode 100644 index 0000000..fd23928 --- /dev/null +++ b/src/widgets/networkmanager.rs @@ -0,0 +1,516 @@ +use crate::color::Color; +use crate::draw::{Font, ROBOTO_REGULAR}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}; +use crate::cmd::Cmd; + +use std::sync::mpsc::Sender; +use std::cell::RefCell; +use std::net::Ipv4Addr; +use std::fmt; + +use nix::poll::{PollFd, PollFlags}; + +use dbus; + + +#[derive(Debug)] +enum NetworkState { + Unknown = 0, + Asleep = 10, + Disconnected = 20, + Disconnecting = 30, + Connecting = 40, + ConnectedLocal = 50, + ConnectedSite = 60, + ConnectedGlobal = 70, +} + +impl From for NetworkState { + fn from(id: u32) -> Self { + match id { + // https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMState + 10 => NetworkState::Asleep, + 20 => NetworkState::Disconnected, + 30 => NetworkState::Disconnecting, + 40 => NetworkState::Connecting, + 50 => NetworkState::ConnectedLocal, + 60 => NetworkState::ConnectedSite, + 70 => NetworkState::ConnectedGlobal, + _ => NetworkState::Unknown, + } + } +} + +enum ActiveConnectionState { + Unknown, + Activating, + Activated, + Deactivating, + Deactivated, +} + +impl From for ActiveConnectionState { + fn from(id: u32) -> Self { + match id { + // https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMActiveConnectionState + 1 => ActiveConnectionState::Activating, + 2 => ActiveConnectionState::Activated, + 3 => ActiveConnectionState::Deactivating, + 4 => ActiveConnectionState::Deactivated, + _ => ActiveConnectionState::Unknown, + } + } +} + +#[derive(Debug)] +enum DeviceType { + Unknown, + Ethernet, + Wifi, + Modem, + Bridge, + TUN, + Wireguard, +} + +impl From for DeviceType { + fn from(id: u32) -> Self { + match id { + // https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType + 1 => DeviceType::Ethernet, + 2 => DeviceType::Wifi, + 8 => DeviceType::Modem, + 13 => DeviceType::Bridge, + 16 => DeviceType::TUN, + 29 => DeviceType::Wireguard, + _ => DeviceType::Unknown, + } + } +} + +#[derive(Debug)] +struct Ipv4Address { + address: Ipv4Addr, + prefix: u32, + gateway: Ipv4Addr, +} + +trait ByteOrderSwap { + fn swap(&self) -> Self; +} + +impl ByteOrderSwap for u32 { + fn swap(&self) -> u32 { + ((self & 0x000000FF) << 24) | ((self & 0x0000FF00) << 8) | ((self & 0x00FF0000) >> 8) | ((self & 0xFF000000) >> 24) + } +} + +impl<'a> From>> for Ipv4Address { + fn from(s: dbus::arg::Array<'a, u32, dbus::arg::Iter<'a>>) -> Ipv4Address { + let mut i = s.into_iter(); + Ipv4Address { + address: Ipv4Addr::from(i.next().unwrap().swap()), + prefix: i.next().unwrap(), + gateway: Ipv4Addr::from(i.next().unwrap().swap()), + } + } +} + +impl fmt::Display for Ipv4Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.address, self.prefix) + } +} + +fn get_nm_property<'a>( + con: &dbus::Connection, + obj: &str, + path: &dbus::Path<'a>, + property: &str, +) -> Result { + let msg = dbus::Message::new_method_call( + obj, + path, + "org.freedesktop.DBus.Properties", + "Get", + ) + .map_err(|_| { + ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not send make dbus method call", + ) + })? + .append2( + dbus::MessageItem::Str("org.freedesktop.NetworkManager".to_string()), + dbus::MessageItem::Str(property.to_string()), + ); + + con.send_with_reply_and_block(msg, 1000).map_err(|_| { + ::std::io::Error::new(::std::io::ErrorKind::Other, "could not send dbus message") + }) +} + +fn state(con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager", &"/org/freedesktop/NetworkManager".into(), "State").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve state", + ))?; + let state: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(NetworkState::from(state.0)) +} + +fn primary_connection(con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager", &"/org/freedesktop/NetworkManager".into(), "PrimaryConnection").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not primary connection", + ))?; + let primary_connection: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + if let Ok(conn) = primary_connection.0.as_cstr().to_str() { + if conn == "/" { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "no primary connection", + )); + } + } + Ok(NmConnection { path: primary_connection.0.clone() }) +} + +fn active_connections(con: &dbus::Connection) -> Result, ::std::io::Error> { + let m = get_nm_property(con, "org.freedesktop.NetworkManager", &"/org/freedesktop/NetworkManager".into(), "ActiveConnections").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve active connections", + ))?; + let active_connections: dbus::arg::Variant> = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(active_connections.0.into_iter().map(|x| NmConnection { path: x }).collect()) +} + +#[derive(Clone)] +struct NmConnection<'a> { + path: dbus::Path<'a>, +} + +impl<'a> NmConnection<'a> { + fn state(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.Connection.Active", &self.path, "State").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve connection state", + ))?; + let state: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(ActiveConnectionState::from(state.0)) + } + + fn id(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.Connection.Active", &self.path, "Id").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve connection ID", + ))?; + let id: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(id.0) + } + + fn devices(&self, con: &dbus::Connection) -> Result, ::std::io::Error> { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.Connection.Active", &self.path, "Devices").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve connection devices", + ))?; + let devices: dbus::arg::Variant> = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(devices.0.into_iter().map(|x| NmDevice { path: x }).collect()) + } +} + +#[derive(Clone)] +struct NmDevice<'a> { + path: dbus::Path<'a>, +} + +impl<'a> NmDevice<'a> { + fn device_type(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.Device", &self.path, "DeviceType").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve device type", + ))?; + + let device_type: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(DeviceType::from(device_type.0)) + } + + fn ip4config(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.Device", &self.path, "Ip4Config").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve ip4config", + ))?; + + let ip4config: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(NmIp4Config { path: ip4config.0 }) + } + + fn active_access_point(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.Device.Wireless", &self.path, "ActiveAccessPoint").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve device type", + ))?; + + let active_ap: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(NmAccessPoint { path: active_ap.0 }) + } +} + +#[derive(Clone)] +struct NmAccessPoint<'a> { + path: dbus::Path<'a>, +} + +impl<'a> NmAccessPoint<'a> { + fn ssid(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.AccessPoint", &self.path, "Ssid").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve ssid", + ))?; + + let ssid: dbus::arg::Variant> = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(std::str::from_utf8(&ssid.0.into_iter().collect::>()) + .map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not parse ssid", + ))? + .to_string()) + } + + fn strength(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.AccessPoint", &self.path, "Strength").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve access point strength", + ))?; + + let strength: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(strength.0) + } + + fn frequency(&self, con: &dbus::Connection) -> Result { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.AccessPoint", &self.path, "Frequency").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve access point frequency", + ))?; + + let frequency: dbus::arg::Variant = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(frequency.0) + } +} + +#[derive(Clone)] +struct NmIp4Config<'a> { + path: dbus::Path<'a>, +} + +impl<'a> NmIp4Config<'a> { + fn addresses(&self, con: &dbus::Connection) -> Result, ::std::io::Error> { + let m = get_nm_property(con, "org.freedesktop.NetworkManager.IP4Config", &self.path, "Addresses").map_err(|_| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not retrieve addresses", + ))?; + + let addresses: dbus::arg::Variant, dbus::arg::Iter>> = m.get1().ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::Other, + "could not read property", + ))?; + Ok(addresses.0.into_iter().map(|addr| Ipv4Address::from(addr)).collect()) + } +} + +// The widget is created on a different thread than where it will be used, so +// we need it to be Send. However, dbus::Connection has a void*, so it's not +// auto-derived. So, we make a small wrapper where we make it Send. +struct DbusConnection(dbus::Connection); + +impl std::convert::AsRef for DbusConnection { + fn as_ref(&self) -> &dbus::Connection { + return &self.0; + } +} + +unsafe impl Send for DbusConnection {} + +#[derive(Debug)] +struct Device { + // name: String, + t: DeviceType, + ips: Vec, +} + +struct Connection { + devices: Vec, +} + +pub struct NetworkManager { + con: DbusConnection, + watch: dbus::Watch, + + font: RefCell, + font_size: u32, + state: NetworkState, + dirty: bool, + tx: Sender, + length: u32, + connections: Vec, +} + +impl NetworkManager { + fn update(&mut self) -> Result<(), ::std::io::Error> { + self.state = state(self.con.as_ref())?; + + self.connections = active_connections(self.con.as_ref())? + .into_iter() + .map(|c| { + let devices = c.devices(self.con.as_ref()).unwrap() + .into_iter() + .map(|d| { + let t = d.device_type(self.con.as_ref()).unwrap(); + let ips = d.ip4config(self.con.as_ref()).unwrap().addresses(self.con.as_ref()).unwrap(); + Device{ + t, + ips, + } + }) + .collect(); + Connection{ + devices + } + }) + .collect(); + + Ok(()) + } + + pub fn new(font_size: f32, length: u32, tx: Sender) -> Result, ::std::io::Error> { + let con = dbus::Connection::get_private(dbus::BusType::System).map_err(|_| { + ::std::io::Error::new(::std::io::ErrorKind::Other, "unable to open dbus") + })?; + + + let font = Font::new(&ROBOTO_REGULAR, font_size); + + let fds = con.watch_fds(); + if fds.len() != 1 { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "expected 1 watch fd from dbus", + )); + } + + let mut nm = NetworkManager { + con: DbusConnection(con), + font: RefCell::new(font), + font_size: font_size as u32, + length, + state: NetworkState::Unknown, + dirty: true, + watch: fds[0], + tx, + connections: Vec::new(), + }; + + nm.update()?; + + Ok(Box::new(nm)) + } +} + +impl Widget for NetworkManager { + fn wait(&mut self, ctx: &mut WaitContext) { + let mut stuff = false; + for _ in self + .con + .as_ref() + .watch_handle(self.watch.fd(), dbus::WatchEvent::Readable as u32) + { + stuff = true; + } + if stuff { + self.update().unwrap(); + self.dirty = true; + self.tx.send(Cmd::Draw).unwrap(); + } + + ctx.fds + .push(PollFd::new(self.watch.fd(), PollFlags::POLLIN)); + } + fn enter(&mut self) {} + fn leave(&mut self) {} + fn size(&self) -> (u32, u32) { + (self.length, self.font_size * self.connections.len() as u32) + } + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + let (width, height) = self.size(); + { + if !self.dirty && !ctx.force { + return Ok(DrawReport::empty(width, height)); + } + self.dirty = false; + } + + let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; + buf.memset(ctx.bg); + + for con in &self.connections { + if con.devices.len() == 0 { + continue; + } + let d = &con.devices[0]; + let c = Color::new(1.0, 1.0, 1.0, 1.0); + self.font.borrow_mut().auto_draw_text(buf, ctx.bg, &c, "network")?; + let bar_off = 5 * self.font_size; + self.font.borrow_mut().auto_draw_text(&mut buf.offset((bar_off, 0))?, ctx.bg, &c, &format!("{:?}", d))?; + } + Ok(DrawReport { + width: width, + height: height, + damage: vec![buf.get_signed_bounds()], + full_damage: false, + }) + } + + fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} + fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} + fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} +}