summaryrefslogtreecommitdiffstats
path: root/src/fbgui/fbgui.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fbgui/fbgui.cpp')
-rw-r--r--src/fbgui/fbgui.cpp492
1 files changed, 492 insertions, 0 deletions
diff --git a/src/fbgui/fbgui.cpp b/src/fbgui/fbgui.cpp
new file mode 100644
index 0000000..22a2ac6
--- /dev/null
+++ b/src/fbgui/fbgui.cpp
@@ -0,0 +1,492 @@
+#include "fbgui.h"
+#include "sysinfo.h"
+#include "loggerengine.h"
+#include "downloadmanager.h"
+#include "javascriptinterface.h"
+
+#include <iostream>
+#include <QThread>
+#include <QtWebKit>
+#include <QxtCore>
+
+QThread dmThread;
+QString logFilePath("");
+QString ipConfigFilePath("");
+QString binPath("");
+QUrl baseURL("");
+QString downloadPath("");
+int updateInterval = -1;
+QString fileToTriggerURL("");
+QString serialLocation("");
+QString sessionID("");
+int debugMode = -1;
+
+//-------------------------------------------------------------------------------------------
+/**
+ * A constructor.
+ *
+ * The constructor of the fbgui class. It initializes the main objects
+ * which are needed while the program is running.
+ * The appearance of the webView is here also defined.
+ *
+ * @see JavascriptInterface
+ * @see DownloadManager
+ */
+fbgui::fbgui() {
+}
+fbgui::~fbgui() {
+ dmThread.quit();
+}
+
+
+
+/**
+ * init function.
+ */
+void fbgui::init() {
+ // start fbgui
+ qxtLog->debug() << "Initializing fbgui...";
+
+ setupLayout();
+ createActions();
+
+ // initialize javascript interface
+ JavascriptInterface* jsi = new JavascriptInterface(
+ _webView->page()->mainFrame());
+ QObject::connect(jsi, SIGNAL(quitFbgui()), this, SLOT(close()));
+ QObject::connect(jsi, SIGNAL(shutDownClient()), this,
+ SLOT(performShutDown()));
+ QObject::connect(_webView->page()->mainFrame(), SIGNAL(
+ javaScriptWindowObjectCleared()), jsi, SLOT(attachToDOM()));
+
+ // initialize download manager
+ DownloadManager* dm = new DownloadManager();
+ QObject::connect(dm, SIGNAL(downloadInfo(const QString&, const double&)),
+ jsi, SLOT(downloadInfo(const QString&, const double&)));
+ QObject::connect(dm, SIGNAL(notify(const QString&)), jsi,
+ SLOT(notify(const QString&)));
+ QObject::connect(jsi, SIGNAL(requestFile(const QString&)), dm,
+ SLOT(downloadFile(const QString&)));
+ QObject::connect(
+ dm,
+ SIGNAL(updateProgress(const int&, const double&, const QString&)),
+ jsi,
+ SLOT(updateProgressBar(const int&, const double&, const QString&)));
+ QObject::connect(dm, SIGNAL(downloadQueueEmpty()), jsi, SLOT(
+ callbackOnFinished()));
+ QObject::connect(dm, SIGNAL(downloadQueueEmpty()), this, SLOT(loadSystem()));
+
+ // move download manager to its own thread
+ dm->moveToThread(&dmThread);
+ dmThread.start();
+
+ // show "waiting for internet" page until triggered.
+ if (debugMode > -1) {
+ _webView->load(QUrl("qrc:/html/preload-debug.html"));
+ } else {
+ _webView->load(QUrl("qrc:/html/preload.html"));
+ }
+
+ // watcher is not needed anymore since we guarantee internet connection with the networkDiscovery.
+ // start watching for fileToTriggerURL
+ //watchForTrigger();
+ loadURL();
+
+ // set properties
+ setWindowTitle("fbgui");
+ setAttribute(Qt::WA_QuitOnClose, true);
+ setWindowFlags(Qt::FramelessWindowHint);
+ showFullScreen();
+ this->show();
+}
+//-------------------------------------------------------------------------------------------
+// Layout / actions setup
+//-------------------------------------------------------------------------------------------
+/**
+ * This method sets the used Layout.
+ *
+ * This method sets the used Layout. Possible layout are:
+ * - browser mode: only the browser is visible
+ * - debug mode: the screen is divided into the browser and a debug
+ * out console
+ */
+void fbgui::setupLayout() {
+ // setup layout of the gui: debug split or browser
+ _webView = new QWebView(this);
+ if (debugMode == 1) {
+ // split main window in browser & debug console
+ createDebugConsole();
+ _splitter = new QSplitter(Qt::Vertical, this);
+ _splitter->addWidget(_webView);
+ _splitter->addWidget(_debugConsole);
+ setCentralWidget(_splitter);
+ } else
+ setCentralWidget(_webView);
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method enables a shortcut for closing the program.
+ * The shortcut itself is not configurable: CTRL + X
+ */
+void fbgui::createActions() {
+ _quit = new QAction(tr("&quit"), this);
+ _quit->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X));
+ this->addAction(_quit);
+ connect(_quit, SIGNAL(triggered()), this, SLOT(close()));
+}
+//-------------------------------------------------------------------------------------------
+// File system watching
+//-------------------------------------------------------------------------------------------
+/**
+ * This method sets a "watchdog" to a special file.
+ *
+ * This method sets a "watchdog" to a special file. If needed it creates the
+ * file which it has to watch over. It than connects a QFileSystemWatcher with
+ * this file. If changes happen to this file, the
+ * fbgui::checkForTrigger(const QString& dirname) method will be called.
+ *
+ */
+void fbgui::watchForTrigger() {
+ // check if fileToTriggerURL already exists
+ QFile file(fileToTriggerURL);
+ if (file.exists()) {
+ qxtLog->debug() << "[watcher] " << fileToTriggerURL << " found.";
+ // try to load URL
+ loadURL();
+ } else {
+ // create it
+ if (file.open(QIODevice::WriteOnly)) {
+ qxtLog->debug() << "[gui] Created: " << fileToTriggerURL;
+ file.close();
+ } else {
+ qxtLog->debug() << "[gui] Creation of " << fileToTriggerURL
+ << " failed!";
+ qxtLog->debug() << "[gui] Exiting in 5 seconds...";
+ QTimer::singleShot(5000, this, SLOT(close()));
+ }
+ }
+ // watch the path to trigger file
+ qxtLog->debug() << "[watcher] Watching " << fileToTriggerURL;
+ _watcher = new QFileSystemWatcher(QStringList(fileToTriggerURL), this);
+QObject::connect(_watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(prepareURLLoad()));
+
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method checks if the trigger was valid.
+ *
+ * This method checks if the trigger was valid. If yes,
+ * we have received an IP Address an can load the main screen.
+ * If not, something some error happened.
+ *
+ * @see fbgui::checkHost()
+ * @see fbgui::loadURL()
+ */
+void fbgui::prepareURLLoad() {
+ qxtLog->debug() << "[watcher] " << fileToTriggerURL << " changed!";
+ // disconnect _watcher, his job is done
+ qxtLog->debug() << "[watcher] disconnected.";
+ _watcher->disconnect(this);
+ _watcher->deleteLater();
+ // try to load URL
+ loadURL();
+}
+//-------------------------------------------------------------------------------------------
+// Preparations for URL load
+//-------------------------------------------------------------------------------------------
+/**
+ * This method checks the existance of the host.
+ *
+ * This method checks if the host exists / can be found.
+ * The host is from the URL given through the configuration.
+ */
+bool fbgui::checkHost() const {
+ QHostInfo hostInfo = QHostInfo::fromName(baseURL.host());
+ if (hostInfo.error() != QHostInfo::NoError) {
+ qxtLog->debug() << "[gui] Lookup of " << baseURL.host() << "failed.";
+ qxtLog->debug() << "[gui] Host can not be reached.";
+ return false;
+ } else {
+ qxtLog->debug() << "[gui] Lookup of " << baseURL.host() << " succeeded.";
+ return true;
+ }
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method tries loads the URL.
+ *
+ * This method loads the main screen via an POST request. If also disconnects the watcher
+ * of the file, (Watcher is set in the fbgui::watchForTrigger() method).
+ * and generates the POST data body.
+ *
+ * @see fbgui::watchForTrigger()
+ * @see fbgui::generatePOSTData()
+ */
+void fbgui::loadURL() {
+ if (checkHost()) {
+ qxtLog->debug() << "[gui] Loading URL: " << baseURL.toString() << " ...";
+
+ // Generate POST identification data needed by PBS.
+ QByteArray postData = generatePOSTData();
+ QNetworkRequest req(baseURL);
+
+ // show cursor again since user is about to interact.
+ //QWSServer::instance()->setCursorVisible(true);
+ QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(loadURLDone(bool)));
+ _webView->load(req, QNetworkAccessManager::PostOperation, postData);
+ }
+ // TODO: error page if no host.
+}
+void fbgui::loadURLDone(bool success) {
+ // done contains the success of the loading: false / true
+ if (!success) {
+ qxtLog->debug() << "[gui] Loading failed. URL: "
+ << _webView->url().toString();
+ qxtLog->debug() << "[gui] You can quit with CTRL + X ...";
+ // TODO handle failure properly...
+ } else {
+ qxtLog->debug() << "[gui] Loaded URL: " << _webView->url().toString();
+ }
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method generates the POST data body.
+ *
+ * This method generates the POST data body. The body contains the
+ * MAC address, an hardwarehash and a specific serial number.
+ * The hardwarehash is a MD5 hash over the MAC address and the
+ * mainboard serial number.
+ * The specific serial number is set at the creation of the usb boot stick.
+ * This file has to be present on the directory specified in
+ * the configuration for this to work.
+ *
+ * @see SysInfo::getMACAddress()
+ * @see SysInfo::getMainboardSerial()
+ */
+QByteArray fbgui::generatePOSTData() {
+ qxtLog->debug() << "[gui] Generating POST data...";
+ // use MAC address as base data
+ SysInfo si;
+ QByteArray data(si.getInfo("mac").toUtf8());
+ // append mainboard serial to the mac address for more unique hardwarehash
+ QByteArray mbserial(si.getInfo("mbserial").toUtf8());
+ if (!mbserial.isEmpty())
+ data.append(mbserial);
+ else {
+ qxtLog->debug()
+ << "[gui] Mainboard serial was empty. Not appending to base hash data.";
+ }
+ qxtLog->debug() << "[post] Hashing: " << data;
+ // generate MD5 hash of data
+ QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Md5);
+ qxtLog->debug() << "[post] MD5 Hash: " << hash.toHex();
+
+ // fetch serial number from usb
+ QByteArray serial;
+ QFile file(serialLocation);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qxtLog->debug() << "[post] No such file: " << file.fileName();
+ }
+ // everything ok, read data
+ serial = file.readAll();
+ file.close();
+ serial.chop(1); // chop EOF
+ qxtLog->debug() << "[post] Serial number is: " << serial;
+
+ // construct final byte array
+ QByteArray postData("mac=");
+ postData.append(si.getInfo("mac"));
+ postData.append("&hardwarehash=" + hash.toHex());
+ postData.append("&serialnumber=" + serial);
+ qxtLog->debug() << "[post] POST data: " << postData;
+ return postData;
+}
+
+//-------------------------------------------------------------------------------------------
+// Shutdown / Reboot of the client
+//-------------------------------------------------------------------------------------------
+// TODO One function for reboot and shutdown, with parameter for the action.
+// for example: doSystemCall(_REBOOT_);
+/**
+ * This method performs the shutdown of the client.
+ *
+ * This method performs the shutdown of the client. It is triggered by the
+ * JavascriptInterface::shutDownClient() signal which will be emited in the
+ * JavascriptInterface::shutDown() method.
+ * This method writes the character 'o' in /proc/sysrq-trigger
+ * which will shutdown the computer immediatly.
+ * (See linux magic keys)
+ *
+ * @see JavascriptInterface::shutDownClient()
+ * @see JavascriptInterface::shutDown()
+ */
+void fbgui::performShutDown() {
+ QFile file("/proc/sysrq-trigger");
+ if (file.open(QIODevice::WriteOnly)) {
+ file.write("o");
+ file.close();
+ } else {
+ qxtLog->debug() << "[gui] Could not open /proc/sysrq-trigger";
+ }
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method performs the reboot of the client.
+ *
+ * This method performs the reboot of the client. It is triggered by the
+ * JavascriptInterface::rebootClient() signal which will be emited in the
+ * JavascriptInterface::reboot() method.
+ * This method writes the character 'b' in /proc/sysrq-trigger
+ * which will shutdown the computer immediatly.
+ * (See linux magic keys)
+ *
+ * @see JavascriptInterface::rebootClient()
+ * @see JavascriptInterface::reboot()
+ */
+void fbgui::performReboot() {
+ QFile file("/proc/sysrq-trigger");
+ if (file.open(QIODevice::WriteOnly)) {
+ file.write("b");
+ file.close();
+ } else {
+ qxtLog->debug() << "[gui] Could not open /proc/sysrq-trigger";
+ }
+}
+//-------------------------------------------------------------------------------------------
+// Preparing Kernel Switch per kexec (initiating Stage 3)
+//-------------------------------------------------------------------------------------------
+void fbgui::loadSystem() {
+ //show loading system page.
+ //_webView->disconnect(this);
+ //QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(prepareKexec()));
+ _webView->load(QUrl("qrc:/html/loadsystem.html"));
+ QTimer::singleShot(1000, this, SLOT(prepareKexec()));
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method prepares kexec.
+ *
+ * The kernel command line file that should have been downloaded from the Preboot-Server
+ * and the ip config file (created by udhcpc) are merged into the final completed KCL.
+ *
+ * A process is then started to load the kernel, initramfs and kcl into kexec.
+ * The process tries to execute kexec -l with these parameters.
+ *
+ * If this succeeds, runKexec() is called
+ *
+ * @see fbgui::runKexec()
+ *
+ */
+void fbgui::prepareKexec() {
+
+ qxtLog->debug() << "[gui] Preparing kexec ...";
+ // try to read KCL file that was downloaded.
+ QFile file(downloadPath + "/kcl");
+ if (!file.open(QIODevice::ReadOnly)) {
+ qxtLog->debug() << "[gui] No such file: " << file.fileName();
+ }
+ // everything ok, read data.
+ QString kcl = file.readAll();
+ file.close();
+ qxtLog->debug() << "[gui] KCL from PBS: " << kcl;
+
+ // try to read ipconfig file generated by udhcpc.
+ file.setFileName("/tmp/ip_config_fbgui");
+ if (!file.open(QIODevice::ReadOnly)) {
+ qxtLog->debug() << "[gui] No such file: " << file.fileName();
+ }
+ // everything ok, read data.
+ QString ipConfig = file.readAll();
+ file.close();
+ qxtLog->debug() << "[gui] IP config: " << ipConfig;
+
+ // append ipConfig
+ kcl.append(" ip=");
+ kcl.append(ipConfig);
+ qxtLog->debug() << "[gui] Complete KCL: " << kcl;
+
+ // load the kernel + initramfs + append of kcl into kexec.
+ QProcess *process = new QProcess(this);
+ QString cmdline = "kexec -l " + downloadPath.toUtf8() + "/kernel --initrd="
+ + downloadPath.toUtf8() + "/initramfs --append=\"" + kcl.toUtf8()
+ + "\"";
+ qxtLog->debug() << "[gui] kexec cmdline: " << cmdline;
+ process->start(cmdline);
+ bool ret = process->waitForFinished();
+ if (!ret) {
+ qxtLog->debug() << "[gui] Failed to execute: " << cmdline;
+ qxtLog->debug() << "[gui] Exiting in 5 seconds...";
+ QTimer::singleShot(5000, this, SLOT(close()));
+ } else {
+ qxtLog->debug() << "[gui] Kexec load was successfull.";
+ if (debugMode < 0) {
+ // if process successfully finished, try to run kexec -e
+ runKexec();
+ } else {
+ qxtLog->debug()
+ << "[gui] Skipping execution of kexec - open debug shell.";
+ qxtLog->debug()
+ << "[gui] To start the system execute \"kexec -e\" in your shell.";
+ close();
+ }
+ }
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method tries to execute: kexec -e
+ *
+ * This method tries to execute: kexec -e
+ *
+ */
+void fbgui::runKexec() {
+ QProcess *process = new QProcess(this);
+ process->start("kexec -e");
+ if (!process->waitForStarted()) {
+ qxtLog->debug() << "[gui] Failed to execute: kexec -e";
+ qxtLog->debug() << "[gui] Exiting in 5 seconds...";
+ QTimer::singleShot(5000, this, SLOT(close()));
+ //TODO: Handle failure properly...
+ }
+}
+//-------------------------------------------------------------------------------------------
+// Debug console setup / control
+//-------------------------------------------------------------------------------------------
+/**
+ * This method creates a debug console as a widget.
+ *
+ * It is basicly a QTextEdit widget as provided by QT's Framework.
+ * An action to toggle this widget is implemented (CTRL + D).
+ *
+ * @see fbgui::toggleDebugConsole()
+ */
+void fbgui::createDebugConsole() {
+ // create the debug console widget
+ _debugConsole = new QTextEdit(this);
+ _debugConsole->setWindowFlags(Qt::FramelessWindowHint);
+ // fanciness
+ QPalette pal;
+ pal.setColor(QPalette::Base, Qt::black);
+ _debugConsole->setPalette(pal);
+ _debugConsole->setTextColor(Qt::white);
+ // enable custom logger engine
+ qxtLog->addLoggerEngine("fb_logger", new LoggerEngine_fb(_debugConsole));
+ //qxtLog->initLoggerEngine("fb_logger");
+ qxtLog->setMinimumLevel("fb_logger", QxtLogger::DebugLevel);
+ // CTRL + D toggles debug window
+ _toggleDebugConsole = new QAction(tr("&toggleDebug"), this);
+ _toggleDebugConsole->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
+ addAction(_toggleDebugConsole);
+ connect(_toggleDebugConsole, SIGNAL(triggered()), this, SLOT(
+ toggleDebugConsole()));
+}
+//-------------------------------------------------------------------------------------------
+/**
+ * This method toggles the debug console.
+ *
+ * Toggle the visibility of the debug console if the action _toggleDebugConsole is triggered.
+ *
+ * @see fbgui::createDebugConsole()
+ */
+void fbgui::toggleDebugConsole() {
+ (_debugConsole->isVisible()) ? _debugConsole->hide() : _debugConsole->show();
+}