Skip to main content

nmrs_gui/ui/
wired_devices.rs

1use gtk::Align;
2use gtk::GestureClick;
3use gtk::prelude::*;
4use gtk::{Box, Image, Label, ListBox, ListBoxRow, Orientation};
5use nmrs::models;
6use std::rc::Rc;
7
8use crate::ui::networks::NetworksContext;
9use crate::ui::wired_page::WiredPage;
10
11pub struct WiredDeviceRowController {
12    pub row: gtk::ListBoxRow,
13    pub arrow: gtk::Image,
14    pub ctx: Rc<NetworksContext>,
15    pub device: models::Device,
16    pub details_page: Rc<WiredPage>,
17}
18
19impl WiredDeviceRowController {
20    pub fn new(
21        row: gtk::ListBoxRow,
22        arrow: gtk::Image,
23        ctx: Rc<NetworksContext>,
24        device: models::Device,
25        details_page: Rc<WiredPage>,
26    ) -> Self {
27        Self {
28            row,
29            arrow,
30            ctx,
31            device,
32            details_page,
33        }
34    }
35
36    pub fn attach(&self) {
37        self.attach_arrow();
38        self.attach_row_double();
39    }
40
41    fn attach_arrow(&self) {
42        let click = GestureClick::new();
43
44        let device = self.device.clone();
45        let stack = self.ctx.stack.clone();
46        let page = self.details_page.clone();
47
48        click.connect_pressed(move |_, _, _, _| {
49            let device_c = device.clone();
50            let stack_c = stack.clone();
51            let page_c = page.clone();
52
53            glib::MainContext::default().spawn_local(async move {
54                page_c.update(&device_c);
55                stack_c.set_visible_child_name("wired-details");
56            });
57        });
58
59        self.arrow.add_controller(click);
60    }
61
62    fn attach_row_double(&self) {
63        let click = GestureClick::new();
64
65        let ctx = self.ctx.clone();
66        let device = self.device.clone();
67        let interface = device.interface.clone();
68
69        let status = ctx.status.clone();
70        let window = ctx.parent_window.clone();
71        let on_success = ctx.on_success.clone();
72
73        click.connect_pressed(move |_, n, _, _| {
74            if n != 2 {
75                return;
76            }
77
78            status.set_text(&format!("Connecting to {interface}..."));
79
80            let nm_c = ctx.nm.clone();
81            let status_c = status.clone();
82            let window_c = window.clone();
83            let on_success_c = on_success.clone();
84
85            glib::MainContext::default().spawn_local(async move {
86                window_c.set_sensitive(false);
87                match nm_c.connect_wired().await {
88                    Ok(_) => {
89                        status_c.set_text("");
90                        on_success_c();
91                    }
92                    Err(e) => status_c.set_text(&format!("Failed to connect: {e}")),
93                }
94                window_c.set_sensitive(true);
95                status_c.set_text("");
96            });
97        });
98
99        self.row.add_controller(click);
100    }
101}
102
103pub fn wired_devices_view(
104    ctx: Rc<NetworksContext>,
105    devices: &[models::Device],
106    details_page: Rc<WiredPage>,
107) -> ListBox {
108    let list = ListBox::new();
109
110    for device in devices {
111        let row = ListBoxRow::new();
112        let hbox = Box::new(Orientation::Horizontal, 6);
113
114        row.add_css_class("network-selection");
115
116        if device.state == models::DeviceState::Activated {
117            row.add_css_class("connected");
118        }
119
120        let display_name = format!("{} ({})", device.interface, device.device_type);
121        hbox.append(&Label::new(Some(&display_name)));
122
123        if device.state == models::DeviceState::Activated {
124            let connected_label = Label::new(Some("Connected"));
125            connected_label.add_css_class("connected-label");
126            hbox.append(&connected_label);
127        }
128
129        let spacer = Box::new(Orientation::Horizontal, 0);
130        spacer.set_hexpand(true);
131        hbox.append(&spacer);
132
133        // Only show state for meaningful states (not transitional ones)
134        let state_text = match device.state {
135            models::DeviceState::Activated => Some("Connected"),
136            models::DeviceState::Disconnected => Some("Disconnected"),
137            models::DeviceState::Unavailable => Some("Unavailable"),
138            models::DeviceState::Failed => Some("Failed"),
139            // Hide transitional states (Unmanaged, Prepare, Config, etc)
140            _ => None,
141        };
142
143        if let Some(text) = state_text {
144            let state_label = Label::new(Some(text));
145            state_label.add_css_class(match device.state {
146                models::DeviceState::Activated => "network-good",
147                models::DeviceState::Unavailable
148                | models::DeviceState::Disconnected
149                | models::DeviceState::Failed => "network-poor",
150                _ => "network-okay",
151            });
152            hbox.append(&state_label);
153        }
154
155        let icon = Image::from_icon_name("network-wired-symbolic");
156        icon.add_css_class("wired-icon");
157        hbox.append(&icon);
158
159        let arrow = Image::from_icon_name("go-next-symbolic");
160        arrow.set_halign(Align::End);
161        arrow.add_css_class("network-arrow");
162        arrow.set_cursor_from_name(Some("pointer"));
163        hbox.append(&arrow);
164
165        row.set_child(Some(&hbox));
166
167        let controller = WiredDeviceRowController::new(
168            row.clone(),
169            arrow.clone(),
170            ctx.clone(),
171            device.clone(),
172            details_page.clone(),
173        );
174
175        controller.attach();
176
177        list.append(&row);
178    }
179    list
180}