Skip to main content

impala/
device.rs

1use anyhow::Context;
2use anyhow::Result;
3use std::sync::Arc;
4
5use iwdrs::{device::Device as iwdDevice, modes::Mode, session::Session};
6
7use ratatui::{
8    Frame,
9    layout::{Constraint, Direction, Flex, Layout},
10    style::{Color, Style, Stylize},
11    text::{Line, Span},
12    widgets::{Block, BorderType, Borders, Padding, Row, Table, TableState},
13};
14
15use crate::{
16    app::FocusedBlock,
17    config::Config,
18    mode::{ap::AccessPoint, station::Station},
19};
20
21#[derive(Clone)]
22pub struct Device {
23    device: iwdDevice,
24    session: Arc<Session>,
25    pub name: String,
26    pub address: String,
27    pub mode: Mode,
28    pub is_powered: bool,
29    pub station: Option<Station>,
30    pub ap: Option<AccessPoint>,
31}
32
33impl Device {
34    pub async fn new(session: Arc<Session>) -> Result<Self> {
35        let device = session.devices().await?.pop().context("No device found")?;
36        let name = device.name().await?;
37        let address = device.address().await?;
38        let mode = device.get_mode().await?;
39        let is_powered = device.is_powered().await?;
40
41        let (station, ap) = match mode {
42            Mode::Station => {
43                if let Ok(station) = Station::new(session.clone()).await {
44                    (Some(station), None)
45                } else {
46                    (None, None)
47                }
48            }
49            Mode::Ap => {
50                if let Ok(ap) = AccessPoint::new(session.clone()).await {
51                    (None, Some(ap))
52                } else {
53                    (None, None)
54                }
55            }
56        };
57
58        Ok(Self {
59            device,
60            session,
61            name,
62            address,
63            mode,
64            is_powered,
65            station,
66            ap,
67        })
68    }
69
70    pub async fn set_mode(&self, mode: Mode) -> Result<()> {
71        self.device.set_mode(mode).await?;
72        Ok(())
73    }
74
75    pub async fn power_off(&self) -> Result<()> {
76        self.device.set_power(false).await?;
77        Ok(())
78    }
79
80    pub async fn power_on(&self) -> Result<()> {
81        self.device.set_power(true).await?;
82        Ok(())
83    }
84
85    pub async fn refresh(&mut self) -> Result<()> {
86        self.is_powered = self.device.is_powered().await?;
87        self.mode = self.device.get_mode().await?;
88        if self.is_powered {
89            match self.mode {
90                Mode::Station => {
91                    if let Some(station) = &mut self.station {
92                        station.refresh().await?;
93                    } else {
94                        self.station = Station::new(self.session.clone()).await.ok();
95                    }
96                }
97                Mode::Ap => {
98                    if let Some(ap) = &mut self.ap {
99                        ap.refresh().await?;
100                    } else {
101                        self.ap = AccessPoint::new(self.session.clone()).await.ok();
102                    }
103                }
104            }
105        }
106        Ok(())
107    }
108
109    pub fn render(&mut self, frame: &mut Frame, focused_block: FocusedBlock, config: Arc<Config>) {
110        let (device_block, help_block) = {
111            let chunks = Layout::default()
112                .direction(Direction::Vertical)
113                .constraints([
114                    Constraint::Fill(1),
115                    Constraint::Length(5),
116                    Constraint::Length(1),
117                ])
118                .margin(1)
119                .split(frame.area());
120            (chunks[1], chunks[2])
121        };
122
123        //
124        // Device
125        //
126        let row = Row::new(vec![Line::from(self.name.clone()).centered(), {
127            if self.is_powered {
128                Line::from("On").centered()
129            } else {
130                Line::from("Off").centered()
131            }
132        }]);
133
134        let widths = [Constraint::Length(10), Constraint::Length(8)];
135
136        let device_table = Table::new(vec![row], widths)
137            .header({
138                Row::new(vec![
139                    Line::from("Name").yellow().centered(),
140                    Line::from("Powered").yellow().centered(),
141                ])
142                .style(Style::new().bold())
143                .bottom_margin(1)
144            })
145            .block(
146                Block::default()
147                    .title(" Device ")
148                    .title_style(Style::default().bold())
149                    .borders(Borders::ALL)
150                    .border_style(Style::default().fg(Color::Green))
151                    .border_type(BorderType::Thick)
152                    .padding(Padding::horizontal(1)),
153            )
154            .column_spacing(1)
155            .flex(Flex::SpaceAround)
156            .row_highlight_style(Style::default().bg(Color::DarkGray).fg(Color::White));
157
158        let mut device_state = TableState::default().with_selected(0);
159        frame.render_stateful_widget(device_table, device_block, &mut device_state);
160
161        let help_message = match focused_block {
162            FocusedBlock::Device => Line::from(vec![
163                Span::from(config.device.infos.to_string()).bold(),
164                Span::from(" Infos"),
165                Span::from(" | "),
166                Span::from(config.device.toggle_power.to_string()).bold(),
167                Span::from(" Toggle Power"),
168            ]),
169            FocusedBlock::AdapterInfos => {
170                Line::from(vec![Span::from("󱊷 ").bold(), Span::from(" Discard")])
171            }
172            _ => Line::from(""),
173        };
174
175        let help_message = help_message.centered().blue();
176
177        frame.render_widget(help_message, help_block);
178    }
179}