Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,015 changes: 3,517 additions & 498 deletions Cargo.lock

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"crates/cardwire-daemon",
"crates/cardwire-cli",
"crates/cardwire-ebpf",
"crates/cardwire-gui",
]

[workspace.package]
Expand All @@ -21,7 +22,7 @@ clap = { version = "4.6.1", features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
log = "0.4.32"
thiserror = "2.0.18"
clap_complete = "4.6.5"
clap_complete = "4.6.5"
anyhow = "1.0.102"
env_logger = "0.11"
toml = "1.0.3"
Expand All @@ -31,6 +32,19 @@ tokio-stream = "0.1.18"
xdg = "3.0.0"
freedesktop-desktop-entry = "0.8.1"
udev = { version = "^0.9.3", features = ["send", "sync"] }
# don't use default-features to remove X11
# cardwire doesn't support X11 and will never support it unless someone maintains it
iced = { version = "0.14.0", default-features = false, features = [
"crisp",
"linux-theme-detection",
"tokio",
"tiny-skia",
"wayland",
"web-colors",
"wgpu",
] }
iced_aw = "0.14.1"
strum = { version = "0.28", features = ["derive"] }

[profile.dev]
overflow-checks = true
Expand All @@ -41,4 +55,4 @@ overflow-checks = true
lto = "fat"
opt-level = 3
strip = true
# Before: 7.3M After: 5.0M
# Before: 7.3M After: 5.0M
24 changes: 24 additions & 0 deletions crates/cardwire-gui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "cardwire-gui"
version.workspace = true
authors.workspace = true
edition.workspace = true
repository.workspace = true
rust-version.workspace = true
license.workspace = true
description = "GUI for cardwire GPU management"

[dependencies]
tokio.workspace = true
tokio-stream.workspace = true
zbus.workspace = true
log.workspace = true
env_logger.workspace = true
iced.workspace = true
strum.workspace = true
serde.workspace = true
iced_aw.workspace = true

[[bin]]
name = "cardwire-gui"
path = "src/main.rs"
206 changes: 206 additions & 0 deletions crates/cardwire-gui/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use iced::{
Alignment, Element, Length::{Fill, Fixed}, Task, widget::{column, container, row, text}
};
use log::error;
use std::collections::BTreeMap;

use crate::{
helpers::{CardwireDbus, GpuDevice}, message::Message, models::{DaemonSettings, MainState, Mode, Page, SettingState}, ui::{self, daemon_setting_page, error_bar}
};

#[derive(Debug)]
pub struct AppState {
pub current_tab: Page,
pub error: Option<String>,
pub zbus_conn: CardwireDbus,
pub gpu_list: BTreeMap<usize, GpuDevice>,
pub main_state: MainState,
pub setting_state: SettingState,
}

impl AppState {
pub fn new() -> Self {
AppState {
current_tab: Page::default(),
error: None,
zbus_conn: CardwireDbus::new(),
gpu_list: BTreeMap::default(),
main_state: MainState::default(),
setting_state: SettingState::default(),
}
}

pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::SwitchPage(page) => {
self.current_tab = page;
self.error = None;
}
Message::AllDevicesFetched(res) => match res {
Ok(map) => {
self.gpu_list = map;
// Clear error
self.error = None;
}
Err(err) => self.error = Some(format!("Error fetching GPUs: {}", err)),
},
Message::FetchedMode(mode) => match mode {
Ok(mode) => self.main_state.current_mode = Some(mode),
Err(err) => self.error = Some(format!("Error fetching Mode: {}", err)),
},
Message::SetMode(mode) => {
let conn = self.zbus_conn.clone();
return Task::perform(
async move {
conn.set_mode(mode.into())
.await
.map_err(|e| e.to_string())?;
conn.get_mode().await.map_err(|e| e.to_string())
},
|res| match res {
Ok(val) => match Mode::from_repr(val) {
Some(m) => Message::FetchedMode(Ok(m)),
None => Message::FetchedMode(Err(format!("Unknown mode: {}", val))),
},
Err(err) => Message::FetchedMode(Err(err)),
},
);
}
Message::UpdateNvidiaSetting(setting) => {
let conn = self.zbus_conn.clone();
return Task::perform(
async move {
conn.set_setting(DaemonSettings::ExpNvidiaBlock, setting, None)
.await
.map_err(|e| e.to_string())?;
Ok((DaemonSettings::ExpNvidiaBlock, Some(setting), None::<Mode>))
},
|res| match res {
Ok(res) => Message::FetchedSetting(Ok(res)),
Err(err) => Message::FetchedSetting(Err(err)),
},
);
}
Message::UpdateStateSetting(setting) => {
let conn = self.zbus_conn.clone();
return Task::perform(
async move {
conn.set_setting(DaemonSettings::AutoApplyGpuState, setting, None)
.await
.map_err(|e| e.to_string())?;
Ok((
DaemonSettings::AutoApplyGpuState,
Some(setting),
None::<Mode>,
))
},
|res| match res {
Ok(res) => Message::FetchedSetting(Ok(res)),
Err(err) => Message::FetchedSetting(Err(err)),
},
);
}
Message::UpdateBatterySetting(setting) => {
let conn = self.zbus_conn.clone();
return Task::perform(
async move {
conn.set_setting(DaemonSettings::BattAutoSwitch, setting, None)
.await
.map_err(|e| e.to_string())?;
Ok((DaemonSettings::BattAutoSwitch, Some(setting), None::<Mode>))
},
|res| match res {
Ok(res) => Message::FetchedSetting(Ok(res)),
Err(err) => Message::FetchedSetting(Err(err)),
},
);
}
Message::UpdateBatteryMode(setting) => {
let conn = self.zbus_conn.clone();
return Task::perform(
async move {
conn.set_setting(DaemonSettings::BattAutoSwitchMode, false, Some(setting))
.await
.map_err(|e| e.to_string())?;
Ok((DaemonSettings::BattAutoSwitchMode, None, Some(setting)))
},
|res| match res {
Ok(res) => Message::FetchedSetting(Ok(res)),
Err(err) => Message::FetchedSetting(Err(err)),
},
);
}
Message::UpdateGpuPowerState(id, new_state) => {
// Get the gpu by key
if let Some(gpu) = self.gpu_list.get_mut(&id) {
// Update the gpu power_state
gpu.power_state = Some(new_state);
} else {
error!("couldnt get gpu key: {}", id);
}
}
Message::FetchedSetting(res) => match res {
Ok(val) => match val.0 {
DaemonSettings::ExpNvidiaBlock => {
if let Some(new_val) = val.1 {
self.setting_state.nvidia_checked = new_val
}
}
DaemonSettings::AutoApplyGpuState => {
if let Some(new_val) = val.1 {
self.setting_state.state_checked = new_val
}
}
DaemonSettings::BattAutoSwitch => {
if let Some(new_val) = val.1 {
self.setting_state.battery_checked = new_val
}
}
DaemonSettings::BattAutoSwitchMode => {
if let Some(new_mode) = val.2 {
self.setting_state.battery_mode = Some(new_mode)
}
}
},
Err(err) => self.error = Some(format!("error fetching Setting: {}", err)),
},
Message::ToggleMenu(index) => {
self.main_state.open_gpu_menu = index;
}
Message::None => {}
Message::ClearError => self.error = None,
}
Task::none()
}

pub fn view(&self) -> Element<'_, Message> {
let mut app = column![].spacing(10).width(Fill).height(Fill);
if let Some(err) = &self.error {
app = app.push(error_bar(err))
}
app = app.push(container(match &self.current_tab {
Page::Main => ui::main_page(&self.main_state, &self.gpu_list),
Page::SmartMode => text("Smart Mode TODO").into(),
Page::CardwireSettings => daemon_setting_page(&self.setting_state),
Page::AccessLogs => text!("TODO").into(),
Page::About => ui::about_page(),
}));
row![
container(ui::page_bar())
.width(Fixed(200.0))
.height(Fill)
.style(container::rounded_box)
.padding(5),
app.width(Fill)
.height(Fill)
.align_x(Alignment::Center)
.padding(30)
]
.spacing(15)
.into()
}

pub fn title(&self) -> String {
format!("Cardwire - {}", self.current_tab)
}
}
Loading
Loading