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