// SPDX-License-Identifier: MIT #include "hardware.h" #include "fan.h" #include "cpu.h" #include #include #include #include #include #include #include class Helper : public QObject { Q_OBJECT KAuth::ActionReply reply(const QString &error, const QVariantMap &data = {}) { if (error.isEmpty()) { auto r = KAuth::ActionReply::SuccessReply(); r.setData(data); return r; } auto r = KAuth::ActionReply::HelperErrorReply(); r.setErrorDescription(error); return r; } QString service(const QString &verb, const QString &unit = "framework-laptop-tools-fan.service") { QProcess process; process.start("/usr/bin/systemctl", {verb, unit}); if (!process.waitForFinished(25000)) { process.kill(); process.waitForFinished(); return "Service did not respond."; } return process.exitCode() == 0 ? QString() : QString::fromUtf8(process.readAllStandardError()); } public Q_SLOTS: KAuth::ActionReply inspect(const QVariantMap &) { const auto error = compatibilityError(); if (!error.isEmpty()) return reply(error); auto data = firmwareStatus(); QString cpuError; data["cpuConfig"] = readCpuConfig(cpuError); if (!cpuError.isEmpty()) data["cpuError"] = cpuError; const auto path = ecHwmon(); const auto mode = readNumber(path + "/pwm1_enable"); if (mode == std::optional(2)) data["fanMode"] = "auto"; else if (mode == std::optional(1)) { data["fanMode"] = "manual"; const auto pwm = readNumber(path + "/pwm1"); if (pwm) data["fanDuty"] = qRound(*pwm * 100 / 255); QString fanError; const auto config = readFanConfig(fanError); if (!fanError.isEmpty()) data["fanError"] = fanError; if (fanError.isEmpty()) { if (config.value("mode") == "curve") { data["fanMode"] = "curve"; data["fanCurve"] = config["curve"]; QVariantList temperatures; for (const double t : fanTemperatures(config)) temperatures.append(t); data["fanTemperatures"] = temperatures; } } } data["fanPersistent"] = QFile::exists(savedFanConfigPath); return reply({}, data); } KAuth::ActionReply configure(const QVariantMap &args) { QString error = compatibilityError(); if (!error.isEmpty()) return reply(error); const QString operation = args.value("operation").toString(); if (operation == "batch") { const auto operations = args.value("operations").toList(); if (operations.isEmpty() || operations.size() > 6) return reply("Invalid settings batch."); const QStringList allowed{"keyboard", "powerAuto", "powerBrightness", "chargeLimit", "chargeWatts", "fan", "cpuProfiles"}; for (const auto &entry : operations) if (!allowed.contains(entry.toMap().value("operation").toString())) return reply("Unknown operation in settings batch."); QVariantList completed; for (const auto &entry : operations) { const auto result = configure(entry.toMap()); if (result.failed()) return reply({}, {{"completed", completed}, {"applyError", result.errorDescription()}}); completed.append(entry); } return reply({}, {{"completed", completed}}); } bool valid; const double raw = args.value("value").toDouble(&valid); const int value = valid && raw >= 0 && raw <= 10000 && raw == int(raw) ? int(raw) : -1; if (operation == "keyboard" && value >= 0 && value <= 100) error = writeNumber("/sys/class/leds/chromeos::kbd_backlight/brightness", value); else if (operation == "cpuProfiles") { const auto config = normalizeCpuConfig(args.value("config").toMap()); error = validateCpuConfig(config); if (!error.isEmpty()) return reply(error); if (!QDir().mkpath("/etc/framework-laptop-tools")) return reply("Cannot create CPU configuration directory."); QSaveFile file(cpuConfigPath); if (!file.open(QIODevice::WriteOnly)) return reply(file.errorString()); file.setPermissions(QFile::ReadOwner | QFile::WriteOwner); const auto data = QJsonDocument::fromVariant(config).toJson(); if (file.write(data) != data.size() || !file.commit()) return reply(file.errorString()); const QString unit = "framework-laptop-tools-cpu.service"; if (hasCpuOverrides(config)) { error = service("enable", unit); if (error.isEmpty()) error = service("restart", unit); } else { error = service("disable", unit); if (error.isEmpty()) error = service("stop", unit); } if (!error.isEmpty()) error = "CPU settings were saved, but applying them failed: " + error; } else if (operation == "fan") { error = validateFan(args); if (!error.isEmpty()) return reply(error); // Restore the old worker before replacing its configuration. error = service("stop"); if (!error.isEmpty()) return reply(error); if (QFile::exists(savedFanConfigPath) && !QFile::remove(savedFanConfigPath)) return reply("Cannot remove saved fan settings."); const bool persistent = args.value("mode") != "auto"; error = service(persistent ? "enable" : "disable"); if (error.isEmpty()) error = service(persistent ? "enable" : "disable", "framework-laptop-tools-fan-resume.service"); if (!error.isEmpty()) return reply(error); if (args.value("mode") == "auto") error = restoreFan(); else { const QString path = persistent ? savedFanConfigPath : fanConfigPath; if (!QDir().mkpath(QFileInfo(path).absolutePath())) error = "Cannot create fan settings directory."; if (error.isEmpty()) { QSaveFile file(path); if (!file.open(QIODevice::WriteOnly)) return reply(file.errorString()); file.setPermissions(QFile::ReadOwner | QFile::WriteOwner); const auto data = QJsonDocument::fromVariant(args).toJson(QJsonDocument::Compact); if (file.write(data) != data.size() || !file.commit()) return reply(file.errorString()); error = service("start"); } } } else if (operation == "powerAuto") error = setFirmwareValue(operation, 0); else if (value >= 0) error = setFirmwareValue(operation, value); else error = "Unknown operation or invalid value."; return reply(error); } }; KAUTH_HELPER_MAIN("se.ajpanton.frameworktools", Helper) #include "helper.moc"