loader.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. * Copyright (C) 2015 Malte Veerman <maldela@halloarsch.de>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU Lesser General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public License along
  15. * with this program; if not, write to the Free Software Foundation, Inc.,
  16. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. *
  18. */
  19. #include "loader.h"
  20. #include "hwmon.h"
  21. #include <QtCore/QFile>
  22. #include <QtCore/QDir>
  23. #include <QtCore/QTextStream>
  24. #include <QtCore/QTimer>
  25. #include <QtCore/QDebug>
  26. #include <KAuth/KAuthExecuteJob>
  27. #define HWMON_PATH "/sys/class/hwmon"
  28. namespace Fancontrol
  29. {
  30. Loader::Loader(QObject *parent) : QObject(parent),
  31. m_interval(10),
  32. m_configUrl(QUrl::fromLocalFile("/etc/fancontrol")),
  33. m_error(""),
  34. m_timer(new QTimer(this))
  35. {
  36. parseHwmons();
  37. m_timer->setSingleShot(false);
  38. m_timer->start(1);
  39. connect(m_timer, SIGNAL(timeout()), this, SLOT(updateSensors()));
  40. }
  41. void Loader::parseHwmons()
  42. {
  43. QDir hwmonDir(HWMON_PATH);
  44. QStringList list;
  45. if (hwmonDir.isReadable())
  46. list = hwmonDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
  47. else if (hwmonDir.exists())
  48. {
  49. setError(QString(HWMON_PATH) + " is not readable!");
  50. return;
  51. }
  52. else
  53. {
  54. setError(QString(HWMON_PATH) + " does not exist!");
  55. return;
  56. }
  57. QStringList dereferencedList;
  58. while (!list.isEmpty())
  59. dereferencedList << QFile::symLinkTarget(hwmonDir.absoluteFilePath(list.takeFirst()));
  60. foreach (Hwmon *hwmon, m_hwmons)
  61. {
  62. if (!dereferencedList.contains(hwmon->path()))
  63. {
  64. m_hwmons.removeOne(hwmon);
  65. emit hwmonsChanged();
  66. hwmon->deleteLater();
  67. }
  68. else
  69. hwmon->initialize();
  70. }
  71. foreach (const QString &hwmonPath, dereferencedList)
  72. {
  73. bool hwmonExists = false;
  74. foreach (const Hwmon *hwmon, m_hwmons)
  75. {
  76. if (hwmon->path() == hwmonPath)
  77. {
  78. hwmonExists = true;
  79. break;
  80. }
  81. }
  82. if (!hwmonExists)
  83. {
  84. Hwmon *newHwmon = new Hwmon(hwmonPath, this);
  85. if (newHwmon->isValid())
  86. {
  87. connect(this, SIGNAL(sensorsUpdateNeeded()), newHwmon, SLOT(updateSensors()));
  88. m_hwmons << newHwmon;
  89. emit hwmonsChanged();
  90. }
  91. else
  92. delete newHwmon;
  93. }
  94. }
  95. }
  96. PwmFan * Loader::getPwmFan(const QPair<int, int> &indexPair) const
  97. {
  98. Hwmon *hwmon = m_hwmons.value(indexPair.first, Q_NULLPTR);
  99. if (!hwmon)
  100. return Q_NULLPTR;
  101. return hwmon->pwmFan(indexPair.second);
  102. }
  103. Temp * Loader::getTemp(const QPair<int, int> &indexPair) const
  104. {
  105. Hwmon *hwmon = m_hwmons.value(indexPair.first, Q_NULLPTR);
  106. if (!hwmon)
  107. return Q_NULLPTR;
  108. return hwmon->temp(indexPair.second);
  109. }
  110. QPair<int, int> Loader::getEntryNumbers(const QString &entry)
  111. {
  112. if (entry.isEmpty())
  113. {
  114. qWarning() << "Loader::getHwmonNumber(): given empty string.";
  115. return QPair<int, int>(-1, -1);
  116. }
  117. QStringList list = entry.split('/', QString::SkipEmptyParts);
  118. if (list.size() != 2)
  119. {
  120. qWarning() << "Invalid entry to parse:" << entry << "Should contain exactly one \'/\'";
  121. return QPair<int, int>(-1, -1);
  122. }
  123. QString hwmon = list.at(0);
  124. QString sensor = list.at(1);
  125. if (!hwmon.startsWith("hwmon"))
  126. {
  127. qWarning() << "Invalid entry to parse:" << entry << "Should begin with \"hwmon\"";
  128. return QPair<int, int>(-1, -1);
  129. }
  130. if (!sensor.contains(QRegExp("^(pwm|fan|temp|_input)\\d+")))
  131. {
  132. qWarning() << "Invalid entry to parse:" << entry << "\n Sensor should begin with pwm|fan|temp|_input followed by a number";
  133. return QPair<int, int>(-1, -1);
  134. }
  135. bool success;
  136. hwmon.remove("hwmon");
  137. sensor.remove(QRegExp("^(pwm|fan|temp|_input)"));
  138. int hwmonResult = hwmon.toInt(&success);
  139. if (!success)
  140. {
  141. qWarning() << "Invalid entry to parse:" << entry << "Could not convert" << hwmon << "to int";
  142. return QPair<int, int>(-1, -1);
  143. }
  144. int sensorResult = sensor.toInt(&success);
  145. if (!success)
  146. {
  147. qWarning() << "Invalid entry to parse:" << entry << "Could not convert" << sensor << "to int";
  148. return QPair<int, int>(-1, -1);
  149. }
  150. return QPair<int, int>(hwmonResult, sensorResult - 1);
  151. }
  152. void Loader::parseConfigLine(const QString &line, void (PwmFan::*memberSetFunction)(int)) const
  153. {
  154. if (!memberSetFunction)
  155. {
  156. qWarning() << "Loader::parseConfigLine(): Null for member function pointer";
  157. return;
  158. }
  159. QStringList entries = line.split(' ');
  160. foreach (const QString &entry, entries)
  161. {
  162. QStringList fanValuePair = entry.split('=');
  163. if (fanValuePair.size() == 2)
  164. {
  165. QString fanString = fanValuePair.at(0);
  166. QString valueString = fanValuePair.at(1);
  167. bool success;
  168. int value = valueString.toInt(&success);
  169. if (success)
  170. {
  171. PwmFan *fan = getPwmFan(getEntryNumbers(fanString));
  172. if (fan)
  173. (fan->*memberSetFunction)(value);
  174. }
  175. else
  176. qWarning() << valueString << "is not an int";
  177. }
  178. else
  179. qWarning() << "Invalid Entry:" << entry;
  180. }
  181. }
  182. bool Loader::load(const QUrl &url)
  183. {
  184. QString fileName;
  185. if (url.isEmpty())
  186. {
  187. qDebug() << "Given empty url. Fallback to " << m_configUrl;
  188. fileName = m_configUrl.toLocalFile();
  189. }
  190. else if (url.isLocalFile())
  191. fileName = url.toLocalFile();
  192. else
  193. {
  194. setError("Url is not a local file");
  195. return false;
  196. }
  197. QTextStream stream;
  198. QFile file(fileName);
  199. QString fileContent;
  200. if (file.open(QFile::ReadOnly | QFile::Text))
  201. {
  202. stream.setDevice(&file);
  203. fileContent = stream.readAll();
  204. }
  205. else if (file.exists())
  206. {
  207. KAuth::Action action("fancontrol.gui.helper.action");
  208. action.setHelperId("fancontrol.gui.helper");
  209. QVariantMap map;
  210. map["action"] = "read";
  211. map["filename"] = fileName;
  212. action.setArguments(map);
  213. KAuth::ExecuteJob *reply = action.execute();
  214. if (!reply->exec())
  215. {
  216. qDebug() << reply->error();
  217. setError(reply->errorString() + reply->errorText());
  218. return false;
  219. }
  220. else
  221. {
  222. fileContent = reply->data()["content"].toString();
  223. }
  224. }
  225. else
  226. {
  227. setError("File does not exist");
  228. return false;
  229. }
  230. //Disconnect hwmons for performance reasons
  231. //They get reconnected later
  232. foreach (Hwmon *hwmon, m_hwmons)
  233. {
  234. disconnect(hwmon, SIGNAL(configUpdateNeeded()), this, SLOT(createConfigFile()));
  235. foreach (QObject *pwmFan, hwmon->pwmFans())
  236. {
  237. qobject_cast<PwmFan *>(pwmFan)->reset();
  238. }
  239. }
  240. stream.setString(&fileContent);
  241. QStringList lines;
  242. do
  243. {
  244. QString line(stream.readLine());
  245. if (line.startsWith('#'))
  246. continue;
  247. int offset = line.indexOf('#');
  248. if (offset != -1)
  249. line.truncate(offset-1);
  250. line = line.simplified();
  251. lines << line;
  252. }
  253. while(!stream.atEnd());
  254. foreach (QString line, lines)
  255. {
  256. if (line.startsWith("INTERVAL="))
  257. {
  258. line.remove("INTERVAL=");
  259. bool success;
  260. int interval = line.toInt(&success);
  261. if (success)
  262. {
  263. setInterval(interval, false);
  264. }
  265. else
  266. {
  267. setError("Unable to parse interval line");
  268. return false;
  269. }
  270. }
  271. else if (line.startsWith("FCTEMPS="))
  272. {
  273. line.remove("FCTEMPS=");
  274. QStringList fctemps = line.split(' ');
  275. foreach (const QString &fctemp, fctemps)
  276. {
  277. QStringList nameValuePair = fctemp.split('=');
  278. if (nameValuePair.size() == 2)
  279. {
  280. QString pwm = nameValuePair.at(0);
  281. QString temp = nameValuePair.at(1);
  282. PwmFan *pwmPointer = getPwmFan(getEntryNumbers(pwm));
  283. Temp *tempPointer = getTemp(getEntryNumbers(temp));
  284. if (pwmPointer && tempPointer)
  285. {
  286. pwmPointer->setTemp(tempPointer);
  287. pwmPointer->setMinPwm(0);
  288. }
  289. }
  290. else
  291. qWarning() << "Invalid entry:" << fctemp;
  292. }
  293. }
  294. else if (line.startsWith("MINTEMP="))
  295. {
  296. line.remove("MINTEMP=");
  297. parseConfigLine(line, &PwmFan::setMinTemp);
  298. }
  299. else if (line.startsWith("MAXTEMP="))
  300. {
  301. line.remove("MAXTEMP=");
  302. parseConfigLine(line, &PwmFan::setMaxTemp);
  303. }
  304. else if (line.startsWith("MINSTART="))
  305. {
  306. line.remove("MINSTART=");
  307. parseConfigLine(line, &PwmFan::setMinStart);
  308. }
  309. else if (line.startsWith("MINSTOP="))
  310. {
  311. line.remove("MINSTOP=");
  312. parseConfigLine(line, &PwmFan::setMinStop);
  313. }
  314. else if (line.startsWith("MINPWM="))
  315. {
  316. line.remove("MINPWM=");
  317. parseConfigLine(line, &PwmFan::setMinPwm);
  318. }
  319. else if (line.startsWith("MAXPWM="))
  320. {
  321. line.remove("MAXPWM=");
  322. parseConfigLine(line, &PwmFan::setMaxPwm);
  323. }
  324. else if (!line.startsWith("DEVNAME=") &&
  325. !line.startsWith("DEVPATH=") &&
  326. !line.startsWith("FCFANS="))
  327. qWarning() << "Unrecognized line in config:" << line;
  328. }
  329. //Connect hwmons again
  330. foreach (Hwmon *hwmon, m_hwmons)
  331. {
  332. connect(hwmon, SIGNAL(configUpdateNeeded()), this, SLOT(createConfigFile()));
  333. }
  334. emit configUrlChanged();
  335. m_configFile = fileContent;
  336. emit configFileChanged();
  337. return true;
  338. }
  339. bool Loader::save(const QUrl &url)
  340. {
  341. QString fileName;
  342. if (url.isEmpty())
  343. {
  344. qDebug() << "Given empty url. Fallback to " << m_configUrl;
  345. fileName = m_configUrl.toLocalFile();
  346. }
  347. else if (url.isLocalFile())
  348. fileName = url.toLocalFile();
  349. else
  350. {
  351. setError("Url is not a local file");
  352. return false;
  353. }
  354. QFile file(fileName);
  355. if (file.open(QFile::WriteOnly | QFile::Text))
  356. {
  357. QTextStream stream(&file);
  358. stream << m_configFile;
  359. }
  360. else
  361. {
  362. KAuth::Action action("fancontrol.gui.helper.action");
  363. action.setHelperId("fancontrol.gui.helper");
  364. QVariantMap map;
  365. map["action"] = "write";
  366. map["filename"] = fileName;
  367. map["content"] = m_configFile;
  368. action.setArguments(map);
  369. KAuth::ExecuteJob *reply = action.execute();
  370. if (!reply->exec())
  371. {
  372. qDebug() << reply->error();
  373. setError(reply->errorString() + reply->errorText());
  374. return false;
  375. }
  376. }
  377. return true;
  378. }
  379. void Loader::createConfigFile()
  380. {
  381. QList<Hwmon *> usedHwmons;
  382. QList<PwmFan *> usedFans;
  383. foreach (Hwmon *hwmon, m_hwmons)
  384. {
  385. if (hwmon->pwmFans().size() > 0)
  386. usedHwmons << hwmon;
  387. foreach (QObject *fan, hwmon->pwmFans())
  388. {
  389. PwmFan *pwmFan = qobject_cast<PwmFan *>(fan);
  390. if (pwmFan->hasTemp() && pwmFan->temp())
  391. {
  392. usedFans << pwmFan;
  393. if (!usedHwmons.contains(pwmFan->temp()->parent()))
  394. usedHwmons << pwmFan->temp()->parent();
  395. }
  396. }
  397. }
  398. QString configFile = "# This file was created by Fancontrol-GUI \n";
  399. if (m_interval != 0)
  400. configFile += "INTERVAL=" + QString::number(m_interval) + "\n";
  401. if (!usedHwmons.isEmpty())
  402. {
  403. configFile += "DEVPATH=";
  404. foreach (Hwmon *hwmon, usedHwmons)
  405. {
  406. QString sanitizedPath = hwmon->path();
  407. sanitizedPath.remove(QRegExp("^/sys/"));
  408. sanitizedPath.remove(QRegExp("/hwmon/hwmon\\d\\s*$"));
  409. configFile += "hwmon" + QString::number(hwmon->index()) + "=" + sanitizedPath + " ";
  410. }
  411. configFile += "\n";
  412. configFile += "DEVNAME=";
  413. foreach (Hwmon *hwmon, usedHwmons)
  414. {
  415. configFile += "hwmon" + QString::number(hwmon->index()) + "=" + hwmon->name().split('.').first() + " ";
  416. }
  417. configFile += "\n";
  418. if (!usedFans.isEmpty())
  419. {
  420. configFile += "FCTEMPS=";
  421. foreach (PwmFan *pwmFan, usedFans)
  422. {
  423. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  424. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  425. configFile += "hwmon" + QString::number(pwmFan->temp()->parent()->index()) + "/";
  426. configFile += "temp" + QString::number(pwmFan->temp()->index()) + "_input ";
  427. }
  428. configFile += "\n";
  429. configFile += "FCFANS=";
  430. foreach (PwmFan *pwmFan, usedFans)
  431. {
  432. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  433. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  434. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  435. configFile += "fan" + QString::number(pwmFan->index()) + "_input ";
  436. }
  437. configFile += "\n";
  438. configFile += "MINTEMP=";
  439. foreach (PwmFan *pwmFan, usedFans)
  440. {
  441. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  442. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  443. configFile += QString::number(pwmFan->minTemp()) + " ";
  444. }
  445. configFile += "\n";
  446. configFile += "MAXTEMP=";
  447. foreach (PwmFan *pwmFan, usedFans)
  448. {
  449. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  450. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  451. configFile += QString::number(pwmFan->maxTemp()) + " ";
  452. }
  453. configFile += "\n";
  454. configFile += "MINSTART=";
  455. foreach (PwmFan *pwmFan, usedFans)
  456. {
  457. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  458. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  459. configFile += QString::number(pwmFan->minStart()) + " ";
  460. }
  461. configFile += "\n";
  462. configFile += "MINSTOP=";
  463. foreach (PwmFan *pwmFan, usedFans)
  464. {
  465. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  466. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  467. configFile += QString::number(pwmFan->minStop()) + " ";
  468. }
  469. configFile += "\n";
  470. configFile += "MINPWM=";
  471. foreach (PwmFan *pwmFan, usedFans)
  472. {
  473. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  474. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  475. configFile += QString::number(pwmFan->minPwm()) + " ";
  476. }
  477. configFile += "\n";
  478. configFile += "MAXPWM=";
  479. foreach (PwmFan *pwmFan, usedFans)
  480. {
  481. configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
  482. configFile += "pwm" + QString::number(pwmFan->index()) + "=";
  483. configFile += QString::number(pwmFan->maxPwm()) + " ";
  484. }
  485. configFile += "\n";
  486. }
  487. }
  488. if (configFile != m_configFile)
  489. {
  490. m_configFile = configFile;
  491. emit configFileChanged();
  492. }
  493. }
  494. void Loader::setInterval(int interval, bool writeNewConfig)
  495. {
  496. if (interval != m_interval)
  497. {
  498. m_interval = interval;
  499. emit intervalChanged();
  500. if (writeNewConfig)
  501. createConfigFile();
  502. }
  503. }
  504. void Loader::testFans()
  505. {
  506. for (int i=0; i<m_hwmons.size(); i++)
  507. {
  508. m_hwmons.at(i)->testFans();
  509. }
  510. }
  511. void Loader::detectSensors()
  512. {
  513. KAuth::Action action("fancontrol.gui.helper.action");
  514. action.setHelperId("fancontrol.gui.helper");
  515. QVariantMap map;
  516. map["action"] = "detectSensors";
  517. action.setArguments(map);
  518. KAuth::ExecuteJob *reply = action.execute();
  519. if (!reply->exec())
  520. {
  521. qDebug() << reply->error();
  522. setError(reply->errorString() + reply->errorText());
  523. return;
  524. }
  525. parseHwmons();
  526. }
  527. QList<QObject *> Loader::hwmons() const
  528. {
  529. QList<QObject *> list;
  530. foreach (Hwmon *hwmon, m_hwmons)
  531. {
  532. list << qobject_cast<QObject *>(hwmon);
  533. }
  534. return list;
  535. }
  536. QList<QObject *> Loader::allPwmFans() const
  537. {
  538. QList<QObject *> list;
  539. foreach (const Hwmon *hwmon, m_hwmons)
  540. {
  541. list += hwmon->pwmFans();
  542. }
  543. return list;
  544. }
  545. QList<QObject *> Loader::allTemps() const
  546. {
  547. QList<QObject *> list;
  548. foreach (const Hwmon *hwmon, m_hwmons)
  549. {
  550. list += hwmon->temps();
  551. }
  552. return list;
  553. }
  554. void Loader::setError (const QString &error)
  555. {
  556. m_error = error;
  557. emit errorChanged();
  558. qCritical() << error;
  559. }
  560. }