From 2a3a7e70419f1f3941e73fe8f369ba7ba78da4a1 Mon Sep 17 00:00:00 2001 From: Vladislav Nepogodin Date: Mon, 18 Nov 2024 17:16:12 +0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=20scx:=20use=20scx=5Floader=20to?= =?UTF-8?q?=20start/stop=20scheds=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Take into consideration the scx_loader configuration: Load the configuration or use default values if it is not present. Following this, the user has the option to manually edit the configuration file using their preferred text editor. If the scx.service is currently running or enabled, disable it to avoid any potential conflicts with scx_loader. --- CMakeLists.txt | 7 +- config-option-lib/src/lib.rs | 2 + config-option-lib/src/scx_loader_config.rs | 373 +++++++++++++++++++++ src/conf-window.cpp | 1 - src/schedext-window.cpp | 201 ++++++----- src/schedext-window.hpp | 4 + src/scx_utils.cpp | 168 ++++++++++ src/scx_utils.hpp | 116 +++++++ 8 files changed, 783 insertions(+), 89 deletions(-) create mode 100644 config-option-lib/src/scx_loader_config.rs create mode 100644 src/scx_utils.cpp create mode 100644 src/scx_utils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bee4b42..17129d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ include(CPM) find_package(Python3 REQUIRED COMPONENTS Interpreter) find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) -find_package(Qt6 COMPONENTS Widgets LinguistTools Concurrent REQUIRED) +find_package(Qt6 COMPONENTS Widgets LinguistTools Concurrent DBus REQUIRED) pkg_check_modules( LIBALPM REQUIRED @@ -119,6 +119,7 @@ qt_add_executable(${PROJECT_NAME} "${CMAKE_BINARY_DIR}/compile_options.hpp" src/config-options.hpp src/config-options.cpp src/conf-window.hpp src/conf-window.cpp + src/scx_utils.hpp src/scx_utils.cpp src/schedext-window.hpp src/schedext-window.cpp src/conf-patches-page.hpp src/conf-patches-page.ui src/conf-options-page.hpp src/conf-options-page.ui @@ -140,9 +141,9 @@ enable_sanitizers(project_options) include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}) corrosion_import_crate(MANIFEST_PATH "config-option-lib/Cargo.toml" FLAGS "${CARGO_FLAGS}") -corrosion_add_cxxbridge(config-option-lib-cxxbridge CRATE config_option_lib MANIFEST_PATH "config-option-lib/Cargo.toml" FILES lib.rs) +corrosion_add_cxxbridge(config-option-lib-cxxbridge CRATE config_option_lib MANIFEST_PATH "config-option-lib/Cargo.toml" FILES lib.rs scx_loader_config.rs) -target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options Qt6::Widgets Qt6::Concurrent Threads::Threads fmt::fmt frozen::frozen config-option-lib-cxxbridge PkgConfig::LIBALPM PkgConfig::LIBGLIB) +target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options Qt6::Widgets Qt6::Concurrent Qt6::DBus Threads::Threads fmt::fmt frozen::frozen config-option-lib-cxxbridge PkgConfig::LIBALPM PkgConfig::LIBGLIB) option(ENABLE_UNITY "Enable Unity builds of projects" OFF) if(ENABLE_UNITY) diff --git a/config-option-lib/src/lib.rs b/config-option-lib/src/lib.rs index b4231e9..9152786 100644 --- a/config-option-lib/src/lib.rs +++ b/config-option-lib/src/lib.rs @@ -14,6 +14,8 @@ // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +pub mod scx_loader_config; + use std::fs; use std::io::Write; diff --git a/config-option-lib/src/scx_loader_config.rs b/config-option-lib/src/scx_loader_config.rs new file mode 100644 index 0000000..78d1ef4 --- /dev/null +++ b/config-option-lib/src/scx_loader_config.rs @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2024 Vladislav Nepogodin + +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2. + +use std::collections::HashMap; +use std::fs; +use std::io::Write; +use std::path::Path; + +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; + +#[cxx::bridge(namespace = "scx_loader")] +mod ffi { + extern "Rust" { + type Config; + + /// Initialize config from config path, if the file doesn't exist config with default values + /// will be created. + fn init_config_file(config_path: &str) -> Result>; + + /// Write the config to the file. + fn write_config_file(&self, filepath: &str) -> Result<()>; + + /// Retrieves default scheduler if set, overwise returns Err + fn get_default_scheduler(&self) -> Result; + + /// Retrieves default mode if set, overwise returns Auto + fn get_default_mode(&self) -> u32; + + /// Get the scx flags for the given sched mode + fn get_scx_flags_for_mode( + &self, + supported_sched: &str, + sched_mode: u32, + ) -> Result>; + + /// Set the default scheduler with default mode + fn set_scx_sched_with_mode(&mut self, supported_sched: &str, sched_mode: u32) + -> Result<()>; + } +} + +// TODO(vnepogodin): instead of copying code from scx_loader, the scx_loader should be crate with +// provided functions and traits which we call here + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SupportedSched { + #[serde(rename = "scx_bpfland")] + Bpfland, + #[serde(rename = "scx_rusty")] + Rusty, + #[serde(rename = "scx_lavd")] + Lavd, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum SchedMode { + /// Default values for the scheduler + Auto = 0, + /// Applies flags for better gaming experience + Gaming = 1, + /// Applies flags for lower power usage + PowerSave = 2, + /// Starts scheduler in low latency mode + LowLatency = 3, +} + +impl TryFrom for SchedMode { + type Error = anyhow::Error; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(SchedMode::Auto), + 1 => Ok(SchedMode::Gaming), + 2 => Ok(SchedMode::PowerSave), + 3 => Ok(SchedMode::LowLatency), + _ => anyhow::bail!("SchedMode with such value doesn't exist"), + } + } +} + +#[derive(Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(default)] +pub struct Config { + pub default_sched: Option, + pub default_mode: Option, + pub scheds: HashMap, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Sched { + pub auto_mode: Option>, + pub gaming_mode: Option>, + pub lowlatency_mode: Option>, + pub powersave_mode: Option>, +} + +fn init_config_file(config_path: &str) -> Result> { + let config = init_config(config_path).context("Failed to initialize config")?; + Ok(Box::new(config)) +} + +impl Config { + fn write_config_file(&self, filepath: &str) -> Result<()> { + let toml_content = toml::to_string(self)?; + + let mut file_obj = fs::File::create(filepath)?; + file_obj.write_all(toml_content.as_bytes())?; + + Ok(()) + } + + fn get_default_scheduler(&self) -> Result { + if let Some(default_sched) = &self.default_sched { + Ok(get_name_from_scx(default_sched).to_owned()) + } else { + anyhow::bail!("Default scheduler is not set") + } + } + + fn get_default_mode(&self) -> u32 { + if let Some(sched_mode) = &self.default_mode { + sched_mode.clone() as u32 + } else { + SchedMode::Auto as u32 + } + } + + fn get_scx_flags_for_mode( + &self, + supported_sched: &str, + sched_mode: u32, + ) -> Result> { + let scx_sched = get_scx_from_str(supported_sched)?; + let sched_mode: SchedMode = sched_mode.try_into()?; + let args = get_scx_flags_for_mode(self, &scx_sched, sched_mode); + Ok(args) + } + + fn set_scx_sched_with_mode(&mut self, supported_sched: &str, sched_mode: u32) -> Result<()> { + let scx_sched = get_scx_from_str(supported_sched)?; + let sched_mode: SchedMode = sched_mode.try_into()?; + + self.default_sched = Some(scx_sched); + self.default_mode = Some(sched_mode); + + Ok(()) + } +} + +/// Initialize config from first found config path, overwise fallback to default config +pub fn init_config(config_path: &str) -> Result { + if Path::new(config_path).exists() { + parse_config_file(config_path) + } else { + Ok(get_default_config()) + } +} + +pub fn parse_config_file(filepath: &str) -> Result { + let file_content = fs::read_to_string(filepath)?; + parse_config_content(&file_content) +} + +fn parse_config_content(file_content: &str) -> Result { + if file_content.is_empty() { + anyhow::bail!("The config file is empty!") + } + let config: Config = toml::from_str(file_content)?; + Ok(config) +} + +pub fn get_default_config() -> Config { + Config { + default_sched: None, + default_mode: Some(SchedMode::Auto), + scheds: HashMap::from([ + ("scx_bpfland".to_string(), get_default_sched_for_config(&SupportedSched::Bpfland)), + ("scx_rusty".to_string(), get_default_sched_for_config(&SupportedSched::Rusty)), + ("scx_lavd".to_string(), get_default_sched_for_config(&SupportedSched::Lavd)), + ]), + } +} + +/// Get the scx flags for the given sched mode +pub fn get_scx_flags_for_mode( + config: &Config, + scx_sched: &SupportedSched, + sched_mode: SchedMode, +) -> Vec { + if let Some(sched_config) = config.scheds.get(get_name_from_scx(scx_sched)) { + let scx_flags = extract_scx_flags_from_config(sched_config, &sched_mode); + + // try to exact flags from config, otherwise fallback to hardcoded default + scx_flags.unwrap_or({ + get_default_scx_flags_for_mode(scx_sched, sched_mode) + .into_iter() + .map(String::from) + .collect() + }) + } else { + get_default_scx_flags_for_mode(scx_sched, sched_mode) + .into_iter() + .map(String::from) + .collect() + } +} + +/// Extract the scx flags from config +fn extract_scx_flags_from_config( + sched_config: &Sched, + sched_mode: &SchedMode, +) -> Option> { + match sched_mode { + SchedMode::Gaming => sched_config.gaming_mode.clone(), + SchedMode::LowLatency => sched_config.lowlatency_mode.clone(), + SchedMode::PowerSave => sched_config.powersave_mode.clone(), + SchedMode::Auto => sched_config.auto_mode.clone(), + } +} + +/// Get Sched object for configuration object +fn get_default_sched_for_config(scx_sched: &SupportedSched) -> Sched { + Sched { + auto_mode: Some( + get_default_scx_flags_for_mode(scx_sched, SchedMode::Auto) + .into_iter() + .map(String::from) + .collect(), + ), + gaming_mode: Some( + get_default_scx_flags_for_mode(scx_sched, SchedMode::Gaming) + .into_iter() + .map(String::from) + .collect(), + ), + lowlatency_mode: Some( + get_default_scx_flags_for_mode(scx_sched, SchedMode::LowLatency) + .into_iter() + .map(String::from) + .collect(), + ), + powersave_mode: Some( + get_default_scx_flags_for_mode(scx_sched, SchedMode::PowerSave) + .into_iter() + .map(String::from) + .collect(), + ), + } +} + +/// Get the default scx flags for the given sched mode +fn get_default_scx_flags_for_mode(scx_sched: &SupportedSched, sched_mode: SchedMode) -> Vec<&str> { + match scx_sched { + SupportedSched::Bpfland => match sched_mode { + SchedMode::Gaming => vec!["-m", "performance"], + SchedMode::LowLatency => vec!["-k", "-s", "5000", "-l", "5000"], + SchedMode::PowerSave => vec!["-m", "powersave"], + SchedMode::Auto => vec![], + }, + SupportedSched::Lavd => match sched_mode { + SchedMode::Gaming | SchedMode::LowLatency => vec!["--performance"], + SchedMode::PowerSave => vec!["--powersave"], + // NOTE: potentially adding --auto in future + SchedMode::Auto => vec![], + }, + // scx_rusty doesn't support any of these modes + SupportedSched::Rusty => vec![], + } +} + +/// Get the scx trait from the given scx name or return error if the given scx name is not supported +fn get_scx_from_str(scx_name: &str) -> Result { + match scx_name { + "scx_bpfland" => Ok(SupportedSched::Bpfland), + "scx_rusty" => Ok(SupportedSched::Rusty), + "scx_lavd" => Ok(SupportedSched::Lavd), + _ => Err(anyhow::anyhow!("{scx_name} is not supported")), + } +} + +/// Get the scx name from the given scx trait +fn get_name_from_scx(supported_sched: &SupportedSched) -> &'static str { + match supported_sched { + SupportedSched::Bpfland => "scx_bpfland", + SupportedSched::Rusty => "scx_rusty", + SupportedSched::Lavd => "scx_lavd", + } +} + +#[cfg(test)] +mod tests { + use crate::scx_loader_config::*; + + #[test] + fn test_default_config() { + let config_str = r#" +default_mode = "Auto" + +[scheds.scx_bpfland] +auto_mode = [] +gaming_mode = ["-m", "performance"] +lowlatency_mode = ["-k", "-s", "5000", "-l", "5000"] +powersave_mode = ["-m", "powersave"] + +[scheds.scx_rusty] +auto_mode = [] +gaming_mode = [] +lowlatency_mode = [] +powersave_mode = [] + +[scheds.scx_lavd] +auto_mode = [] +gaming_mode = ["--performance"] +lowlatency_mode = ["--performance"] +powersave_mode = ["--powersave"] +"#; + + let parsed_config = parse_config_content(config_str).expect("Failed to parse config"); + let expected_config = get_default_config(); + + assert_eq!(parsed_config, expected_config); + } + + #[test] + fn test_simple_fallback_config_flags() { + let config_str = r#" +default_mode = "Auto" +"#; + + let parsed_config = parse_config_content(config_str).expect("Failed to parse config"); + + let bpfland_flags = + get_scx_flags_for_mode(&parsed_config, &SupportedSched::Bpfland, SchedMode::Gaming); + let expected_flags = + get_default_scx_flags_for_mode(&SupportedSched::Bpfland, SchedMode::Gaming); + assert_eq!(bpfland_flags.iter().map(|x| x.as_str()).collect::>(), expected_flags); + } + + #[test] + fn test_sched_fallback_config_flags() { + let config_str = r#" +default_mode = "Auto" + +[scheds.scx_lavd] +auto_mode = ["--help"] +"#; + + let parsed_config = parse_config_content(config_str).expect("Failed to parse config"); + + let lavd_flags = + get_scx_flags_for_mode(&parsed_config, &SupportedSched::Lavd, SchedMode::Gaming); + let expected_flags = + get_default_scx_flags_for_mode(&SupportedSched::Lavd, SchedMode::Gaming); + assert_eq!(lavd_flags.iter().map(|x| x.as_str()).collect::>(), expected_flags); + + let lavd_flags = + get_scx_flags_for_mode(&parsed_config, &SupportedSched::Lavd, SchedMode::Auto); + assert_eq!(lavd_flags.iter().map(|x| x.as_str()).collect::>(), vec!["--help"]); + } + + #[test] + fn test_empty_config() { + let config_str = ""; + let result = parse_config_content(config_str); + assert!(result.is_err()); + } +} diff --git a/src/conf-window.cpp b/src/conf-window.cpp index a069d07..ead2b35 100644 --- a/src/conf-window.cpp +++ b/src/conf-window.cpp @@ -212,7 +212,6 @@ auto get_pkgext_value_from_makepkgconf() noexcept -> std::string { return pkgext_val; } - auto prepare_func_names(std::vector parse_lines, std::string_view pkgver_str) noexcept -> std::vector { using namespace std::string_view_literals; diff --git a/src/schedext-window.cpp b/src/schedext-window.cpp index 5320da5..7160c17 100644 --- a/src/schedext-window.cpp +++ b/src/schedext-window.cpp @@ -19,6 +19,7 @@ // NOLINTBEGIN(bugprone-unhandled-exception-at-new) #include "schedext-window.hpp" +#include "scx_utils.hpp" #include "utils.hpp" #include @@ -38,6 +39,7 @@ #pragma GCC diagnostic ignored "-Wconversion" #endif +#include #include #include @@ -67,6 +69,14 @@ auto read_kernel_file(std::string_view file_path) noexcept -> std::string { return file_content; } +void spawn_child_process(QString&& cmd, QStringList&& args) noexcept { + QProcess child_proc; + child_proc.start(std::move(cmd), std::move(args)); + if (!child_proc.waitForFinished() || child_proc.exitCode() != 0) { + qWarning() << "child process failed with exit code: " << child_proc.exitCode(); + } +} + auto get_current_scheduler() noexcept -> std::string { using namespace std::string_view_literals; @@ -83,6 +93,16 @@ auto get_current_scheduler() noexcept -> std::string { return current_sched; } +auto is_scx_loader_service_enabled() noexcept -> bool { + using namespace std::string_view_literals; + return utils::exec("systemctl is-enabled scx_loader"sv) == "enabled"sv; +} + +auto is_scx_loader_service_active() noexcept -> bool { + using namespace std::string_view_literals; + return utils::exec("systemctl is-active scx_loader"sv) == "active"sv; +} + auto is_scx_service_enabled() noexcept -> bool { using namespace std::string_view_literals; return utils::exec("systemctl is-enabled scx"sv) == "enabled"sv; @@ -93,60 +113,39 @@ auto is_scx_service_active() noexcept -> bool { return utils::exec("systemctl is-active scx"sv) == "active"sv; } -enum class SchedMode : std::uint8_t { - /// Default values for the scheduler - Auto = 0, - /// Applies flags for better gaming experience - Gaming = 1, - /// Applies flags for lower power usage - PowerSave = 2, - /// Starts scheduler in low latency mode - LowLatency = 3, -}; - -constexpr auto get_scx_mode_from_str(std::string_view scx_mode) noexcept -> SchedMode { - using namespace std::string_view_literals; +void disable_scx_loader_service() noexcept { + if (is_scx_loader_service_enabled()) { + spawn_child_process("/usr/bin/systemctl", {"disable", "--now", "-f", "scx_loader"}); + fmt::print("Disabling scx_loader service\n"); + } else if (is_scx_loader_service_active()) { + spawn_child_process("/usr/bin/systemctl", {"stop", "-f", "scx_loader"}); + fmt::print("Stoping scx_loader service\n"); + } +} - if (scx_mode == "gaming"sv) { - return SchedMode::Gaming; - } else if (scx_mode == "lowlatency"sv) { - return SchedMode::LowLatency; - } else if (scx_mode == "powersave"sv) { - return SchedMode::PowerSave; +void disable_scx_service() noexcept { + if (is_scx_service_enabled()) { + spawn_child_process("/usr/bin/systemctl", {"disable", "--now", "-f", "scx"}); + fmt::print("Disabling scx service\n"); + } else if (is_scx_service_active()) { + spawn_child_process("/usr/bin/systemctl", {"stop", "-f", "scx"}); + fmt::print("Stoping scx service\n"); } - return SchedMode::Auto; } -constexpr auto get_scx_flags(std::string_view scx_sched, SchedMode scx_mode) noexcept -> std::string_view { +constexpr auto get_scx_mode_from_str(std::string_view scx_mode) noexcept -> scx::SchedMode { using namespace std::string_view_literals; - // Map the selected performance profile to the different scheduler - // options. - // - // NOTE: only scx_bpfland and scx_lavd are supported for now. - if (scx_mode == SchedMode::Auto) { - } else if (scx_mode == SchedMode::Gaming) { - if (scx_sched == "scx_bpfland"sv) { - return "-m performance"sv; - } else if (scx_sched == "scx_lavd"sv) { - return "--performance"sv; - } - } else if (scx_mode == SchedMode::LowLatency) { - if (scx_sched == "scx_bpfland"sv) { - return "-k -s 5000 -l 5000"sv; - } else if (scx_sched == "scx_lavd"sv) { - return "--performance"sv; - } - } else if (scx_mode == SchedMode::PowerSave) { - if (scx_sched == "scx_bpfland"sv) { - return "-m powersave"sv; - } else if (scx_sched == "scx_lavd"sv) { - return "--powersave"sv; - } + if (scx_mode == "gaming"sv) { + return scx::SchedMode::Gaming; + } else if (scx_mode == "lowlatency"sv) { + return scx::SchedMode::LowLatency; + } else if (scx_mode == "powersave"sv) { + return scx::SchedMode::PowerSave; } - - return {}; + return scx::SchedMode::Auto; } + } // namespace SchedExtWindow::SchedExtWindow(QWidget* parent) @@ -156,20 +155,33 @@ SchedExtWindow::SchedExtWindow(QWidget* parent) setAttribute(Qt::WA_NativeWindow); setWindowFlags(Qt::Window); // for the close, min and max buttons + { + auto loader_config = scx::loader::Config::init_config(m_config_path); + if (loader_config.has_value()) { + m_scx_config = std::make_unique(std::move(*loader_config)); + } else { + QMessageBox::critical(this, "CachyOS Kernel Manager", tr("Cannot initialize scx_loader configuration")); + return; + } + } + // Selecting the scheduler - QStringList sched_names; - sched_names << "scx_bpfland" - << "scx_central" - << "scx_lavd" - << "scx_layered" - << "scx_nest" - << "scx_qmap" - << "scx_rlfifo" - << "scx_rustland" - << "scx_rusty" - << "scx_simple" - << "scx_userland"; - m_ui->schedext_combo_box->addItems(sched_names); + auto supported_scheds = scx::loader::get_supported_scheds(); + if (supported_scheds.has_value()) { + m_ui->schedext_combo_box->addItems(*supported_scheds); + } else { + QMessageBox::critical(this, "CachyOS Kernel Manager", tr("Cannot get information from scx_loader!\nIs it working?\nThis is needed for the app to work properly")); + + // hide all components which depends on scheduler management + m_ui->schedext_combo_box->setHidden(true); + m_ui->scheduler_select_label->setHidden(true); + + m_ui->schedext_profile_combo_box->setHidden(true); + m_ui->scheduler_profile_select_label->setHidden(true); + + m_ui->schedext_flags_edit->setHidden(true); + m_ui->scheduler_set_flags_label->setHidden(true); + } // Selecting the performance profile QStringList sched_profiles; @@ -213,13 +225,7 @@ void SchedExtWindow::on_disable() noexcept { using namespace std::string_view_literals; // TODO(vnepogodin): refactor that - if (is_scx_service_enabled()) { - QProcess::startDetached("/usr/bin/pkexec", {"/usr/bin/systemctl", "disable", "--now", "scx"}); - fmt::print("Disabling scx\n"); - } else if (is_scx_service_active()) { - QProcess::startDetached("/usr/bin/pkexec", {"/usr/bin/systemctl", "stop", "scx"}); - fmt::print("Stoping scx\n"); - } + disable_scx_loader_service(); m_ui->disable_button->setEnabled(true); m_ui->apply_button->setEnabled(true); @@ -245,33 +251,58 @@ void SchedExtWindow::on_apply() noexcept { m_ui->disable_button->setEnabled(false); m_ui->apply_button->setEnabled(false); - const auto service_cmd = []() -> std::string_view { - using namespace std::string_view_literals; - if (!is_scx_service_enabled()) { - return "enable --now"sv; - } - return "restart"sv; - }(); - - static constexpr auto get_scx_flags_sed = [](std::string_view scx_sched, - SchedMode scx_mode, - std::string_view scx_extra_flags) -> std::string { - const auto scx_base_flags = get_scx_flags(scx_sched, scx_mode); - return fmt::format(R"(-e 's/^\s*#\?\s*SCX_FLAGS=.*$/SCX_FLAGS="{} {}"/')", scx_base_flags, scx_extra_flags); - }; + // stop/disable 'scx.service' if its running/enabled on the system, + // overwise it will conflict + disable_scx_service(); // TODO(vnepogodin): refactor that const auto& current_selected = m_ui->schedext_combo_box->currentText().toStdString(); const auto& current_profile = m_ui->schedext_profile_combo_box->currentText().toStdString(); const auto& extra_flags = m_ui->schedext_flags_edit->text().trimmed().toStdString(); - const auto& scx_mode = get_scx_mode_from_str(current_profile); - const auto& scx_flags_sed = get_scx_flags_sed(current_selected, scx_mode, extra_flags); + const auto& scx_mode = get_scx_mode_from_str(current_profile); - const auto& sed_cmd = fmt::format("sed -e 's/SCX_SCHEDULER=.*/SCX_SCHEDULER={}/' {} -i /etc/default/scx && systemctl {} scx", current_selected, scx_flags_sed, service_cmd); + auto sched_args = QStringList(); + if (auto scx_flags_for_mode = m_scx_config->scx_flags_for_mode(current_selected, scx_mode); scx_flags_for_mode) { + if (!scx_flags_for_mode->empty()) { + sched_args << std::move(*scx_flags_for_mode); + } + } else { + QMessageBox::critical(this, "CachyOS Kernel Manager", tr("Cannot get scx flags from scx_loader configuration!")); + } + + // NOTE: maybe we should also take into consideration these custom flags, + // but then the question how it should be displayed/handled + if (!extra_flags.empty()) { + sched_args << QString::fromStdString(extra_flags).split(' '); + } + + fmt::print("Applying scx '{}' with args: {}\n", current_selected, sched_args.join(' ').toStdString()); + auto sched_reply = scx::loader::switch_scheduler_with_args(current_selected, sched_args); + if (!sched_reply) { + qDebug() << "Failed to switch '" << current_selected << "' with args:" << sched_args; + } + + // enable scx_loader service if not enabled yet, it fully replaces scx.service + if (!is_scx_loader_service_enabled()) { + fmt::print("Enabling scx_loader service\n"); + spawn_child_process("/usr/bin/systemctl", {"enable", "-f", "scx_loader"}); + } + + // change default scheduler and default scheduler mode + if (!m_scx_config->set_scx_sched_with_mode(current_selected, scx_mode)) { + QMessageBox::critical(this, "CachyOS Kernel Manager", tr("Cannot set default scx scheduler with mode! Scheduler %1 with mode %2").arg(QString::fromStdString(current_selected), QString::fromStdString(current_profile))); + } + + // write scx_loader configuration to the temp file + const auto tmp_config_path = std::string{"/tmp/scx_loader.toml"}; + if (!m_scx_config->write_config_file(tmp_config_path)) { + QMessageBox::critical(this, "CachyOS Kernel Manager", tr("Cannot write scx_loader config to file")); + } - QProcess::startDetached("/usr/bin/pkexec", {"/usr/bin/bash", "-c", QString::fromStdString(sed_cmd)}); - fmt::print("Applying scx {}\n", current_selected); + // copy scx_loader configuration from the temp file to the actual path with root permissions + auto config_path = QString::fromStdString(std::string(m_config_path)); + spawn_child_process("/usr/bin/pkexec", {QStringLiteral("/usr/bin/cp"), QString::fromStdString(tmp_config_path), config_path}); m_ui->disable_button->setEnabled(true); m_ui->apply_button->setEnabled(true); diff --git a/src/schedext-window.hpp b/src/schedext-window.hpp index d3be41d..4200832 100644 --- a/src/schedext-window.hpp +++ b/src/schedext-window.hpp @@ -39,6 +39,8 @@ #include +#include "scx_utils.hpp" + #include #include #include @@ -68,6 +70,8 @@ class SchedExtWindow final : public QMainWindow { void on_disable() noexcept; void on_sched_changed() noexcept; + const std::string_view m_config_path{"/etc/scx_loader.toml"}; + scx::loader::ConfigPtr m_scx_config; std::vector m_previously_set_options{}; std::unique_ptr m_ui = std::make_unique(); QTimer* m_sched_timer = nullptr; diff --git a/src/scx_utils.cpp b/src/scx_utils.cpp new file mode 100644 index 0000000..63e93ba --- /dev/null +++ b/src/scx_utils.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2024 Vladislav Nepogodin +// +// This file is part of CachyOS kernel manager. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "scx_utils.hpp" + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wdeprecated-this-capture" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#pragma GCC diagnostic ignored "-Wuseless-cast" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wsuggest-final-types" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#include +#include +#include + +#include "config-option-lib-cxxbridge/scx_loader_config.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#include + +namespace { + +auto convert_rust_vec_string(auto&& rust_vec) -> std::vector { + std::vector res_vec{}; + res_vec.reserve(rust_vec.size()); + for (auto&& rust_vec_el : rust_vec) { + res_vec.emplace_back(std::string{rust_vec_el}); + } + return res_vec; +} + +auto convert_std_vec_into_stringlist(std::vector&& std_vec) -> QStringList { + QStringList flags; + flags.reserve(static_cast(std_vec.size())); + for (auto&& vec_el : std_vec) { + flags << QString::fromStdString(vec_el); + } + return flags; +} + +} // namespace + +namespace scx::loader { + +auto get_supported_scheds() noexcept -> std::optional { + QDBusMessage message = QDBusMessage::createMethodCall( + "org.scx.Loader", + "/org/scx/Loader", + "org.freedesktop.DBus.Properties", + "Get"); + message << "org.scx.Loader" << "SupportedSchedulers"; + QDBusMessage reply = QDBusConnection::systemBus().call(message); + if (reply.type() == QDBusMessage::ErrorMessage) { + fmt::print(stderr, "Failed to get supported schedulers: {}\n", reply.errorMessage().toStdString()); + return std::nullopt; + } + + return reply.arguments().at(0).value().variant().toStringList(); +} + +auto get_current_scheduler() noexcept -> std::optional { + QDBusMessage message = QDBusMessage::createMethodCall( + "org.scx.Loader", + "/org/scx/Loader", + "org.freedesktop.DBus.Properties", + "Get"); + message << "org.scx.Loader" << "CurrentScheduler"; + QDBusMessage reply = QDBusConnection::systemBus().call(message); + if (reply.type() == QDBusMessage::ErrorMessage) { + fmt::print(stderr, "Failed to get current scheduler: {}\n", reply.errorMessage().toStdString()); + return std::nullopt; + } + + return reply.arguments().at(0).value().variant().toString(); +} + +auto switch_scheduler_with_args(std::string_view scx_sched, QStringList sched_args) noexcept -> bool { + QDBusMessage message = QDBusMessage::createMethodCall( + "org.scx.Loader", + "/org/scx/Loader", + "org.scx.Loader", + "SwitchSchedulerWithArgs"); + message << QString::fromStdString(std::string{scx_sched}) << sched_args; + QDBusMessage reply = QDBusConnection::systemBus().call(message); + if (reply.type() == QDBusMessage::ErrorMessage) { + fmt::print(stderr, "Failed to switch scheduler with args: {}\n", reply.errorMessage().toStdString()); + return false; + } + return true; +} + +auto Config::init_config(std::string_view filepath) noexcept -> std::optional { + try { + const ::rust::Str filepath_rust(filepath.data(), filepath.size()); + auto&& config = Config(scx_loader::init_config_file(filepath_rust)); + return std::make_optional(std::move(config)); + } catch (const std::exception& e) { + fmt::print(stderr, "Failed to parse init config: {}\n", e.what()); + } + return std::nullopt; +} + +auto Config::scx_flags_for_mode(std::string_view scx_sched, SchedMode sched_mode) noexcept -> std::optional { + try { + const ::rust::Str scx_sched_rust(scx_sched.data(), scx_sched.size()); + auto rust_vec = m_config->get_scx_flags_for_mode(scx_sched_rust, static_cast(sched_mode)); + auto flags_vec = convert_rust_vec_string(std::move(rust_vec)); + return convert_std_vec_into_stringlist(std::move(flags_vec)); + } catch (const std::exception& e) { + fmt::print(stderr, "Failed to get scx flag for the mode: {}\n", e.what()); + } + return std::nullopt; +} + +auto Config::set_scx_sched_with_mode(std::string_view scx_sched, SchedMode sched_mode) noexcept -> bool { + try { + const ::rust::Str scx_sched_rust(scx_sched.data(), scx_sched.size()); + m_config->set_scx_sched_with_mode(scx_sched_rust, static_cast(sched_mode)); + return true; + } catch (const std::exception& e) { + fmt::print(stderr, "Failed to set default scx scheduler with mode: {}\n", e.what()); + } + return false; +} + +auto Config::write_config_file(std::string_view filepath) noexcept -> bool { + try { + const ::rust::Str filepath_rust(filepath.data(), filepath.size()); + m_config->write_config_file(filepath_rust); + return true; + } catch (const std::exception& e) { + fmt::print(stderr, "Failed to set default scx scheduler with mode: {}\n", e.what()); + } + return false; +} + +} // namespace scx::loader diff --git a/src/scx_utils.hpp b/src/scx_utils.hpp new file mode 100644 index 0000000..a0661e2 --- /dev/null +++ b/src/scx_utils.hpp @@ -0,0 +1,116 @@ +// Copyright (C) 2024 Vladislav Nepogodin +// +// This file is part of CachyOS kernel manager. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef SCX_UTILS_HPP +#define SCX_UTILS_HPP + +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wdeprecated-this-capture" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#pragma GCC diagnostic ignored "-Wuseless-cast" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wsuggest-final-types" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#include + +#include "rust/cxx.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +namespace scx_loader { +struct Config; +} + +namespace scx { + +enum class SchedMode : std::uint8_t { + /// Default values for the scheduler + Auto = 0, + /// Applies flags for better gaming experience + Gaming = 1, + /// Applies flags for lower power usage + PowerSave = 2, + /// Starts scheduler in low latency mode + LowLatency = 3, +}; + +} // namespace scx + +namespace scx::loader { + +// Gets supported schedulers by scx_loader +auto get_supported_scheds() noexcept -> std::optional; + +// Gets currently running scheduler by scx_loader +auto get_current_scheduler() noexcept -> std::optional; + +// Switches scheduler with specified args +auto switch_scheduler_with_args(std::string_view scx_sched, QStringList sched_args) noexcept -> bool; + +/// @brief Manages configuration of scx_loader. +/// +/// This structure holds pointer to object from Rust code, which represents +/// the actual Config structure. +class Config { + public: + /// @brief Initializes config from config path, if the file + /// doesn't exist config with default values will be created. + static auto init_config(std::string_view filepath) noexcept -> std::optional; + + /// @brief Writes the config to the file. + auto write_config_file(std::string_view filepath) noexcept -> bool; + + /// @brief Returns the scx flags for the given sched mode. + auto scx_flags_for_mode(std::string_view scx_sched, SchedMode sched_mode) noexcept -> std::optional; + + /// @brief Sets the default scheduler with default mode. + auto set_scx_sched_with_mode(std::string_view scx_sched, SchedMode sched_mode) noexcept -> bool; + + // explicitly deleted + Config() = delete; + + private: + explicit Config(::rust::Box<::scx_loader::Config>&& config) : m_config(std::move(config)) { } + + /// @brief A pointer to the Config structure from Rust code. + ::rust::Box<::scx_loader::Config> m_config; +}; + +/// @brief A unique pointer to a Config object. +using ConfigPtr = std::unique_ptr; + +} // namespace scx::loader + +#endif // SCX_UTILS_HPP