diff --git a/Cargo.lock b/Cargo.lock index b896dd9..a6cceac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "volt" -version = "2.2.0" +version = "2.2.1" dependencies = [ "ash", "libc", diff --git a/Cargo.toml b/Cargo.toml index 5b6b068..c9d9c9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "volt" -version = "2.2.0" +version = "2.2.1" edition = "2021" rust-version = "1.85.1" diff --git a/images/1.png b/images/1.png index 017bb80..33dad32 100644 Binary files a/images/1.png and b/images/1.png differ diff --git a/images/2.png b/images/2.png index 985391b..fcb80c4 100644 Binary files a/images/2.png and b/images/2.png differ diff --git a/images/3.png b/images/3.png index 0798c79..53d3d6c 100644 Binary files a/images/3.png and b/images/3.png differ diff --git a/src/volt-gui/database.py b/src/volt-gui/database.py index 55084a4..c62f82d 100644 --- a/src/volt-gui/database.py +++ b/src/volt-gui/database.py @@ -15,7 +15,7 @@ from probe import shading_options -APP_VERSION: Final[str] = "2.2.0" +APP_VERSION: Final[str] = "2.2.1" APP_AUTHOR: Final[str] = "pythonlover02" APP_LICENSE: Final[str] = "GPL 3.0 License" APP_DESCRIPTION: Final[str] = "My AMD Adrenaline / NVIDIA Settings Linux Alternative" diff --git a/src/volt-gui/volt-gui.py b/src/volt-gui/volt-gui.py index c3aca4f..ab7e725 100644 --- a/src/volt-gui/volt-gui.py +++ b/src/volt-gui/volt-gui.py @@ -68,7 +68,9 @@ SINGLETON_PORT: Final[int] = 47832 OPTIONS_SAVE_DEBOUNCE_MS: Final[int] = 500 NEW_PROFILE_LABEL: Final[str] = "New Profile..." +SUBMENU_TITLE: Final[str] = "Apply Profile " DELETE_PROFILE_LABEL: Final[str] = "Delete Current" +DEFAULT_PROFILE_LABEL: Final[str] = "Default" SCALE_MIN: Final[float] = 0.5 SCALE_MAX: Final[float] = 3.0 DEFAULT_SCALE: Final[str] = "1.0" @@ -89,6 +91,22 @@ def build_preview_args(profile_name: str) -> list: return ["--probe", profile_name, "--", PREVIEW_TARGET] +def build_profile_label(profile_name: str) -> str: + match profile_name == DEFAULT_PROFILE: + case True: + return DEFAULT_PROFILE_LABEL + case False: + return profile_name + + +def resolve_profile_label(label: str) -> str: + match label == DEFAULT_PROFILE_LABEL: + case True: + return DEFAULT_PROFILE + case False: + return label + + def build_launch_command(profile_name: str) -> str: match profile_name == DEFAULT_PROFILE: case True: @@ -251,11 +269,23 @@ def create_options_tab_widget() -> dict: return {"tab": widget, "widgets": options_widgets} +def _add_named_profiles(combo_widget, profiles: tuple) -> None: + match len(profiles) > 1: + case True: + combo_widget.insertSeparator(combo_widget.count()) + for profile_name in profiles[1:]: + combo_widget.addItem(profile_name) + case False: + pass + return None + + def process_profile_list_update(main_window) -> None: + profiles = find_all_profiles() main_window.profile_selector.blockSignals(True) main_window.profile_selector.clear() - for profile_name in find_all_profiles(): - main_window.profile_selector.addItem(profile_name) + main_window.profile_selector.addItem(build_profile_label(profiles[0])) + _add_named_profiles(main_window.profile_selector, profiles) main_window.profile_selector.insertSeparator(main_window.profile_selector.count()) main_window.profile_selector.addItem(NEW_PROFILE_LABEL) main_window.profile_selector.addItem(DELETE_PROFILE_LABEL) @@ -265,7 +295,7 @@ def process_profile_list_update(main_window) -> None: def process_profile_selector_restore(main_window) -> None: main_window.profile_selector.blockSignals(True) - main_window.profile_selector.setCurrentText(main_window.current_profile) + main_window.profile_selector.setCurrentText(build_profile_label(main_window.current_profile)) main_window.profile_selector.blockSignals(False) return None @@ -363,7 +393,7 @@ def process_profile_combo_change(main_window, selected_text: str) -> None: process_profile_selector_restore(main_window) process_current_profile_delete(main_window) case s: - process_profile_change(main_window, s) + process_profile_change(main_window, resolve_profile_label(s)) return None @@ -396,7 +426,7 @@ def create_system_tray_widget(main_window) -> None: main_window.tray_icon.setIcon(QIcon.fromTheme("preferences-system")) menu = QMenu() menu.addAction(QAction("Show", main_window, triggered=lambda: process_window_show(main_window))) - main_window.profile_submenu = QMenu("Apply Profile", menu) + main_window.profile_submenu = QMenu(SUBMENU_TITLE, menu) process_tray_menu_update(main_window) menu.addMenu(main_window.profile_submenu) menu.addSeparator() @@ -414,7 +444,7 @@ def process_tray_menu_update(main_window) -> None: case True: main_window.profile_submenu.clear() for profile_name in find_all_profiles(): - action = QAction("Apply " + profile_name, main_window) + action = QAction("Apply " + build_profile_label(profile_name), main_window) action.triggered.connect(lambda checked, bound_profile_name=profile_name: process_profile_apply_from_tray(main_window, bound_profile_name)) main_window.profile_submenu.addAction(action) return None @@ -554,10 +584,10 @@ def process_application_options_load(main_window) -> None: saved = parser_instance.get("Options", option_key, fallback=get_option_default_value(option_key)) main_window.options_widgets[option_key].setCurrentText(saved) last_profile = parser_instance.get("Profile", "last_active_profile", fallback=DEFAULT_PROFILE) - match main_window.profile_selector.findText(last_profile) >= 0: + match main_window.profile_selector.findText(build_profile_label(last_profile)) >= 0: case True: main_window.profile_selector.blockSignals(True) - main_window.profile_selector.setCurrentText(last_profile) + main_window.profile_selector.setCurrentText(build_profile_label(last_profile)) main_window.profile_selector.blockSignals(False) main_window.current_profile = last_profile case False: diff --git a/src/volt/checks.rs b/src/volt/checks.rs index 052ad2e..f9ff050 100644 --- a/src/volt/checks.rs +++ b/src/volt/checks.rs @@ -1,7 +1,9 @@ use std::sync::RwLock; use crate::config::parse_settings; +use crate::config::sanitize_name; use crate::consts::CadenceChoice; +use crate::consts::DEFAULT_PROFILE; use crate::consts::FEATURE_ANISOTROPY; use crate::consts::FRAME_LIMIT_MIN; use crate::consts::MethodChoice; @@ -93,6 +95,16 @@ const SURFACE_ALPHA: [&str; 1] = ["opaque"]; const SURFACE_MIN_IMAGES: u32 = 4; const SURFACE_MAX_IMAGES: u32 = 0; const SURFACE_SECTION: &str = "[wayland]\npresent_modes = \"mailbox;fifo\"\ncomposite_alphas = \"opaque\"\nmin_image_count = \"4\"\nmax_image_count = \"0\"\n"; +const DEFAULT_NAME_MIXED: &str = "Default"; +const RESERVED_NAME_MIXED: &str = "Probe"; +const PLAIN_NAME: &str = "myprofile"; + +#[test] +fn reads_one_spelling_of_the_default_profile_name() { + assert_eq!(sanitize_name(DEFAULT_NAME_MIXED), DEFAULT_PROFILE); + assert_eq!(sanitize_name(RESERVED_NAME_MIXED), DEFAULT_PROFILE); + assert_eq!(sanitize_name(PLAIN_NAME), PLAIN_NAME); +} #[test] fn keeps_the_application_value_when_nothing_is_forced() { diff --git a/src/volt/config.rs b/src/volt/config.rs index cddc59e..a6e2fbf 100644 --- a/src/volt/config.rs +++ b/src/volt/config.rs @@ -241,19 +241,32 @@ pub(crate) fn parse_settings(text: &str) -> Settings { } } +fn reserved_name(raw: &str) -> bool { + RESERVED_PROFILES + .iter() + .any(|name| raw.eq_ignore_ascii_case(name)) +} + fn name_is_valid(raw: &str) -> bool { !raw.is_empty() && !raw.contains('/') && !raw.contains('\\') && !raw.contains("..") && !raw.contains('\0') - && !RESERVED_PROFILES.contains(&raw) + && !reserved_name(raw) && raw.chars().all(|ch| ch.is_ascii_graphic()) } +fn folded_name(raw: &str) -> String { + match raw.eq_ignore_ascii_case(DEFAULT_PROFILE) { + true => DEFAULT_PROFILE.into(), + false => raw.into(), + } +} + pub(crate) fn sanitize_name(raw: &str) -> String { match name_is_valid(raw) { - true => raw.into(), + true => folded_name(raw), false => { log_at(LogLevel::Warn, "invalid profile name, using default profile"); DEFAULT_PROFILE.into()