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 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}