1use std::{
5 fs::File,
6 iter::Peekable,
7 str::Chars,
8
9 io::{
10 BufRead,
11 BufReader
12 }
13};
14
15use crate::{
16 constants::{
17 DE_PLASMA,
18 DE_LXQT
19 },
20
21 properties::{
22 parse_prop_line,
23 get_config_dirs,
24 parse_prop_file,
25 parse_prop_file_config,
26 parse_prop_file_config_values
27 }
28};
29
30#[derive(Default)]
31pub struct QtResult {
32 pub widget_style: String,
33 pub color_scheme: String,
34 pub icons: String,
35 pub font: String,
36 pub wallpaper: String
37}
38
39impl QtResult {
40 fn all_set(&self) -> bool {
41 !self.widget_style.is_empty() && !self.color_scheme.is_empty() && !self.icons.is_empty() && !self.font.is_empty() && !self.wallpaper.is_empty()
42 }
43}
44
45#[derive(Debug, PartialEq)]
46enum PlasmaCategory {
47 General,
48 Kde,
49 Icons,
50 Other
51}
52
53fn detect_plasma_from_file(filename: &str, result: &mut QtResult) -> bool {
54 let file: File = match File::open(filename) {
55 Ok(f) => f,
56 Err(_) => return false
57 };
58
59 let reader: BufReader<File> = BufReader::new(file);
60 let mut category: PlasmaCategory = PlasmaCategory::Other;
61
62 for line in reader.lines().flatten() {
63 let line: &str = line.trim();
64
65 if line.starts_with('[') {
66 if let Some(end) = line.find(']') {
67 let cat_name: &str = &line[1..end];
68
69 category = match cat_name.to_lowercase().as_str() {
70 "general" => PlasmaCategory::General,
71 "kde" => PlasmaCategory::Kde,
72 "icons" => PlasmaCategory::Icons,
73
74 _ => PlasmaCategory::Other
75 };
76 }
77
78 continue;
79 }
80
81 match category {
82 PlasmaCategory::Kde if result.widget_style.is_empty() => {
83 if let Some(value) = parse_prop_line(line, "widgetStyle =") {
84 result.widget_style = value;
85 }
86 },
87
88 PlasmaCategory::Icons if result.icons.is_empty() => {
89 if let Some(value) = parse_prop_line(line, "Theme =") {
90 result.icons = value;
91 }
92 },
93
94 PlasmaCategory::General => {
95 if result.color_scheme.is_empty() && let Some(value) = parse_prop_line(line, "ColorScheme =") {
96 result.color_scheme = value;
97 }
98
99 if result.font.is_empty() {
100 if let Some(value) = parse_prop_line(line, "font =") {
101 result.font = value;
102 } else if let Some(value) = parse_prop_line(line, "Font =") {
103 result.font = value;
104 }
105 }
106 },
107
108 _ => {}
109 }
110 }
111
112 true
113}
114
115fn detect_plasma(result: &mut QtResult) -> () {
116 let config_dirs: Vec<String> = get_config_dirs();
117 let mut found_file: bool = false;
118
119 for config_dir in &config_dirs {
120 let kdeglobals: String = format!("{}kdeglobals", config_dir);
121
122 if detect_plasma_from_file(&kdeglobals, result) {
123 found_file = true;
124 }
125
126 let plasma_rc: String = format!("{}plasma-org.kde.plasma.desktop-appletsrc", config_dir);
127
128 if let Some(wp) = parse_prop_file(&plasma_rc, "Image=") && result.wallpaper.is_empty() {
129 result.wallpaper = wp;
130 }
131
132 if result.all_set() {
133 return;
134 }
135 }
136
137 if !found_file {
138 return;
139 }
140
141 if result.widget_style.is_empty() {
142 result.widget_style = "Breeze".to_string();
143 }
144
145 if result.color_scheme.is_empty() {
146 result.color_scheme = "BreezeLight".to_string();
147 }
148
149 if result.icons.is_empty() {
150 result.icons = "Breeze".to_string();
151 }
152
153 if result.font.is_empty() {
154 result.font = "Noto Sans, 10".to_string();
155 }
156}
157
158fn detect_lxqt(result: &mut QtResult) -> () {
159 let config_dirs: Vec<String> = get_config_dirs();
160
161 let mut queries: Vec<(&str, String)> = vec![
162 ("style = ", String::new()),
163 ("icon_theme = ", String::new()),
164 ("font = ", String::new())
165 ];
166
167 parse_prop_file_config_values(&config_dirs, "lxqt/lxqt.conf", &mut queries);
168
169 if result.widget_style.is_empty() && !queries[0].1.is_empty() {
170 result.widget_style = queries[0].1.clone();
171 }
172
173 if result.icons.is_empty() && !queries[1].1.is_empty() {
174 result.icons = queries[1].1.clone();
175 }
176
177 if result.font.is_empty() && !queries[2].1.is_empty() {
178 result.font = queries[2].1.clone();
179 }
180
181 if let Some(wp) = parse_prop_file_config(&config_dirs, "pcmanfm-qt/lxqt/settings.conf", "Wallpaper=") && result.wallpaper.is_empty() {
182 result.wallpaper = wp;
183 }
184}
185
186fn detect_qtct(qver: char, result: &mut QtResult) -> () {
187 let config_dirs: Vec<String> = get_config_dirs();
188 let config_file: String = format!("qt{}ct/qt{}ct.conf", qver, qver);
189
190 let mut queries: Vec<(&str, String)> = vec![
191 ("style=", String::new()),
192 ("icon_theme=", String::new()),
193 ("font=", String::new())
194 ];
195
196 parse_prop_file_config_values(&config_dirs, &config_file, &mut queries);
197
198 if result.widget_style.is_empty() && !queries[0].1.is_empty() {
199 result.widget_style = queries[0].1.clone();
200 }
201
202 if result.icons.is_empty() && !queries[1].1.is_empty() {
203 result.icons = queries[1].1.clone();
204 }
205
206 let font_raw: &String = &queries[2].1;
207
208 if !font_raw.is_empty() && result.font.is_empty() {
209 if font_raw.starts_with('@') {
210 result.font = format!("qt{}ct", qver);
211 } else if qver == '5' {
212 result.font = decode_qt5_string(font_raw);
213 } else {
214 result.font = font_raw.clone();
215 }
216 }
217}
218
219fn decode_qt5_string(input: &str) -> String {
220 let mut result: String = String::new();
221 let mut chars: Peekable<Chars> = input.chars().peekable();
222
223 while let Some(ch) = (&mut chars).next() {
224 if (ch == '\\') && (&mut chars).peek() == Some(&'x') {
225 (&mut chars).next();
226 let hex: String = (&mut chars).by_ref().take(4).collect();
227
228 if (hex.len() == 4) && let Ok(codepoint) = u32::from_str_radix(&hex, 16) && let Some(unicode_char) = char::from_u32(codepoint) {
229 (&mut result).push(unicode_char);
230 continue;
231 }
232
233 (&mut result).push('\\');
234 (&mut result).push('x');
235 (&mut result).push_str(&hex);
236 } else {
237 (&mut result).push(ch);
238 }
239 }
240
241 result
242}
243
244fn detect_kvantum(result: &mut QtResult) -> () {
245 let config_dirs: Vec<String> = get_config_dirs();
246
247 if let Some(theme) = parse_prop_file_config(&config_dirs, "Kvantum/kvantum.kvconfig", "theme=") && result.widget_style.is_empty() {
248 result.widget_style = theme;
249 }
250}
251
252pub fn detect_qt(de_name: Option<&str>) -> QtResult {
253 let mut result: QtResult = QtResult::default();
254
255 if let Some(de) = de_name {
256 if de.eq_ignore_ascii_case(DE_PLASMA) {
257 detect_plasma(&mut result);
258 } else if de.eq_ignore_ascii_case(DE_LXQT) {
259 detect_lxqt(&mut result);
260 }
261 }
262
263 if result.widget_style.is_empty() && let Ok(qpa) = std::env::var("QT_QPA_PLATFORMTHEME") && ((qpa == "qt5ct") || (qpa == "qt6ct")) {
264 let qver: char = qpa.chars().nth(2).unwrap_or('5');
265 detect_qtct(qver, &mut result);
266 }
267
268 if (result.widget_style == "kvantum") || (result.widget_style == "kvantum-dark") {
269 (&mut result).widget_style.clear();
270 detect_kvantum(&mut result);
271 }
272
273 result
274}