@@ -6,6 +6,10 @@ struct Veltrix {
66 configs : Vec < String > ,
77 selected : Option < usize > ,
88 laptop_model : String ,
9+ temperature : String ,
10+ current_fan_speed : String ,
11+ target_fan_speed : String ,
12+ fan_speed_value : u8 ,
913}
1014
1115impl Veltrix {
@@ -14,63 +18,159 @@ impl Veltrix {
1418 }
1519
1620 fn new ( _cc : & eframe:: CreationContext < ' _ > ) -> Self {
17- let configs = get_nbfc_list ( ) ;
21+ let configs = get_config_list ( ) ;
1822 let laptop_model = read_laptop_model ( ) ;
19-
2023 let selected = configs. iter ( ) . position ( |c| c == & laptop_model) ;
2124
22- Self {
25+ let mut app = Self {
2326 configs,
2427 selected,
2528 laptop_model,
29+ temperature : String :: new ( ) ,
30+ current_fan_speed : String :: new ( ) ,
31+ target_fan_speed : String :: new ( ) ,
32+ fan_speed_value : 0 ,
33+ } ;
34+
35+ app. refresh ( ) ;
36+ app
37+ }
38+
39+ fn refresh ( & mut self ) {
40+ let status = get_status ( ) ;
41+
42+ self . temperature = status
43+ . iter ( )
44+ . find ( |line| line. contains ( "Temperature" ) )
45+ . cloned ( )
46+ . unwrap_or_else ( || "Unknown Temp" . to_string ( ) ) ;
47+
48+ self . current_fan_speed = status
49+ . iter ( )
50+ . find ( |line| line. contains ( "Current Fan Speed" ) )
51+ . cloned ( )
52+ . unwrap_or_else ( || "Unknown Speed" . to_string ( ) ) ;
53+
54+ self . target_fan_speed = status
55+ . iter ( )
56+ . find ( |line| line. contains ( "Target Fan Speed" ) )
57+ . cloned ( )
58+ . unwrap_or_else ( || "Unknown Speed" . to_string ( ) ) ;
59+ }
60+
61+ fn apply_fan_speed ( & self ) {
62+ match Command :: new ( "nbfc" )
63+ . arg ( "set" )
64+ . arg ( "-s" )
65+ . arg ( self . fan_speed_value . to_string ( ) )
66+ . output ( )
67+ {
68+ Ok ( output) if !output. status . success ( ) => {
69+ eprintln ! ( "Failed to apply fan speed: {:?}" , output) ;
70+ }
71+ Err ( e) => eprintln ! ( "Error running nbfc: {}" , e) ,
72+ _ => { }
2673 }
2774 }
75+
76+ fn set_fan_speed_to_auto ( & self ) {
77+ Command :: new ( "nbfc" )
78+ . arg ( "set" )
79+ . arg ( "-a" )
80+ . output ( )
81+ . expect ( "failed to set fan speed to auto" ) ;
82+ }
2883}
2984
3085impl eframe:: App for Veltrix {
3186 fn update ( & mut self , ctx : & egui:: Context , _frame : & mut eframe:: Frame ) {
3287 egui:: CentralPanel :: default ( ) . show ( ctx, |ui| {
33- ui. heading ( "Welcome to Veltrix ! \n " ) ;
34- if self . selected . is_none ( ) {
35- ui. label ( "Please select a config " ) ;
36- } else {
37- ui . label ( format ! ( "Laptop model: {}" , self . laptop_model ) ) ;
38- }
88+ ui. vertical_centered ( |ui| {
89+ ui . heading ( "Veltrix" ) ;
90+ ui. label ( "Notebook Fan Controller GUI " ) ;
91+ } ) ;
92+
93+ ui . add_space ( 10.0 ) ;
3994
40- if !self . configs . is_empty ( ) {
41- let selected_text = self
42- . selected
43- . map ( |i| self . configs [ i] . as_str ( ) )
44- . unwrap_or ( "Select NBFC Config" ) ;
45-
46- egui:: ComboBox :: from_label ( "Config List\n " )
47- . selected_text ( selected_text)
48- . show_ui ( ui, |ui| {
49- for ( i, config) in self . configs . iter ( ) . enumerate ( ) {
50- if ui
51- . selectable_label ( self . selected == Some ( i) , config)
52- . clicked ( )
53- {
54- self . selected = Some ( i) ;
95+ // 🔹 Config Section
96+ ui. group ( |ui| {
97+ ui. heading ( "📂 Configuration" ) ;
98+
99+ if !self . configs . is_empty ( ) {
100+ let selected_text = self
101+ . selected
102+ . map ( |i| self . configs [ i] . as_str ( ) )
103+ . unwrap_or ( "Select NBFC Config" ) ;
104+
105+ egui:: ComboBox :: from_label ( "Available configs" )
106+ . selected_text ( selected_text)
107+ . show_ui ( ui, |ui| {
108+ for ( i, config) in self . configs . iter ( ) . enumerate ( ) {
109+ if ui
110+ . selectable_label ( self . selected == Some ( i) , config)
111+ . clicked ( )
112+ {
113+ self . selected = Some ( i) ;
114+ }
55115 }
56- }
57- } ) ;
116+ } ) ;
58117
59- if let Some ( i) = self . selected {
60- ui. colored_label ( egui:: Color32 :: GREEN , format ! ( "Selected Config: {}" , self . configs[ i] ) ) ;
118+ if let Some ( i) = self . selected {
119+ ui. colored_label (
120+ egui:: Color32 :: GREEN ,
121+ format ! ( "✅ Selected: {}" , self . configs[ i] ) ,
122+ ) ;
123+ } else {
124+ ui. colored_label ( egui:: Color32 :: YELLOW , "⚠ No config selected" ) ;
125+ }
61126 } else {
62- ui. colored_label ( egui:: Color32 :: YELLOW , "No config selected !" ) ;
127+ ui. colored_label ( egui:: Color32 :: RED , "❌ No config found !" ) ;
63128 }
64- } else {
65- ui. colored_label ( egui:: Color32 :: RED , "No config found!" ) ;
66- }
129+ } ) ;
130+
131+ ui. add_space ( 10.0 ) ;
132+
133+ ui. group ( |ui| {
134+ ui. heading ( "📊 System Status" ) ;
135+ ui. label ( format ! ( "💻 Laptop: {}" , self . laptop_model) ) ;
136+ ui. label ( & self . temperature ) ;
137+ ui. label ( & self . current_fan_speed ) ;
138+ ui. label ( & self . target_fan_speed ) ;
139+
140+ if ui. button ( "🔄 Refresh" ) . clicked ( ) {
141+ self . refresh ( ) ;
142+ }
143+ } ) ;
144+
145+ ui. add_space ( 10.0 ) ;
146+
147+ ui. group ( |ui| {
148+ ui. heading ( "⚡ Fan Control" ) ;
149+
150+ ui. add (
151+ egui:: Slider :: new ( & mut self . fan_speed_value , 0 ..=96 )
152+ . text ( "Manual Fan Speed" ) ,
153+ ) ;
154+
155+ ui. horizontal ( |ui| {
156+ if ui. button ( "✅ Apply" ) . clicked ( ) {
157+ self . apply_fan_speed ( ) ;
158+ self . refresh ( ) ;
159+ }
160+
161+ if ui. button ( "♻ Set Auto" ) . clicked ( ) {
162+ self . set_fan_speed_to_auto ( ) ;
163+ self . refresh ( ) ;
164+ }
165+ } ) ;
166+ } ) ;
67167 } ) ;
68168 }
69169}
70170
71171fn main ( ) -> eframe:: Result < ( ) > {
72172 let native_options = eframe:: NativeOptions {
73- viewport : egui:: ViewportBuilder :: default ( ) . with_inner_size ( ( 600 .0, 700 .0) ) ,
173+ viewport : egui:: ViewportBuilder :: default ( ) . with_inner_size ( ( 400 .0, 450 .0) ) ,
74174 ..eframe:: NativeOptions :: default ( )
75175 } ;
76176
@@ -88,12 +188,26 @@ fn read_laptop_model() -> String {
88188 . to_string ( )
89189}
90190
91- fn get_nbfc_list ( ) -> Vec < String > {
191+ fn get_config_list ( ) -> Vec < String > {
92192 let output = Command :: new ( "nbfc" )
93193 . arg ( "config" )
94194 . arg ( "--list" )
95195 . output ( )
96- . expect ( "failed to execute nbfc" ) ;
196+ . expect ( "failed to get config list" ) ;
197+
198+ String :: from_utf8_lossy ( & output. stdout )
199+ . lines ( )
200+ . map ( |line| line. trim ( ) . to_string ( ) )
201+ . filter ( |line| !line. is_empty ( ) )
202+ . collect ( )
203+ }
204+
205+ fn get_status ( ) -> Vec < String > {
206+ let output = Command :: new ( "nbfc" )
207+ . arg ( "status" )
208+ . arg ( "-a" )
209+ . output ( )
210+ . expect ( "failed to get status" ) ;
97211
98212 String :: from_utf8_lossy ( & output. stdout )
99213 . lines ( )
0 commit comments