MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); mainWindow = this; trayIconMenu = new QMenu(this); restoreAction = new QAction(tr("Restore"), this); closeAction = new QAction(tr("Quit ckb"), this); trayIconMenu->addAction(restoreAction); trayIconMenu->addAction(closeAction); trayIcon = new QSystemTrayIcon(QIcon(":/img/ckb-logo.png"), this); trayIcon->setContextMenu(trayIconMenu); if(!CkbSettings::get("Program/SuppressTrayIcon").toBool()) trayIcon->show(); #ifdef Q_OS_MACX // Make a custom "Close" menu action for OSX, as the default one brings up the "still running" popup unnecessarily QMenuBar* menuBar = new QMenuBar(this); setMenuBar(menuBar); this->menuBar()->addMenu("ckb")->addAction(closeAction); #else // On linux, add a handler for Ctrl+Q new QShortcut(QKeySequence("Ctrl+Q"), this, SLOT(quitApp())); #endif connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(quitApp())); connect(closeAction, SIGNAL(triggered()), this, SLOT(quitApp())); connect(restoreAction, SIGNAL(triggered()), this, SLOT(showWindow())); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconClicked(QSystemTrayIcon::ActivationReason))); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanup())); eventTimer = new QTimer(this); eventTimer->setTimerType(Qt::PreciseTimer); connect(eventTimer, SIGNAL(timeout()), this, SLOT(timerTick())); eventTimer->start(1000 / 60); QCoreApplication::setOrganizationName("ckb"); ui->tabWidget->addTab(settingsWidget = new SettingsWidget(this), configLabel); settingsWidget->setVersion("ckb " CKB_VERSION_STR); ckbGuiVersion = PARSE_CKB_VERSION(CKB_VERSION_STR); scanKeyboards(); }
void KbFirmware::processDownload(QNetworkReply* reply){ if(reply->error() != QNetworkReply::NoError) return; // Update last check lastCheck = lastFinished = QDateTime::currentMSecsSinceEpoch(); QByteArray data = reply->readAll(); // Don't do anything if this is the same as the last version downloaded QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256); if(hash == fwTableHash) return; fwTableHash = hash; if(hasGPG == UNKNOWN){ // Check for a GPG installation QProcess gpg; gpg.start("gpg", QStringList("--version")); gpg.waitForFinished(); if(gpg.error() == QProcess::FailedToStart) // No GPG install hasGPG = NO; else { QString output = QString::fromUtf8(gpg.readAll()); // Must support RSA keys and SHA256 if(output.contains("RSA", Qt::CaseInsensitive) && output.contains("SHA256", Qt::CaseInsensitive)) hasGPG = YES; else hasGPG = NO; } if(!hasGPG) qDebug() << "No GPG detected, signature verification disabled"; } if(hasGPG){ // If GPG is available, check the signature on the file before proceeding. QDir tmp = QDir::temp(); // Save file to a temporary path. Include PID to avoid conflicts qint64 pid = QCoreApplication::applicationPid(); QString fwPath = tmp.absoluteFilePath(QString("ckb-%1-firmware").arg(pid)); QFile firmware(fwPath); if(!firmware.open(QIODevice::WriteOnly) || firmware.write(data) != data.length()){ qDebug() << "Failed to write firmware file to temporary location, aborting firmware check"; return; } firmware.close(); // Write GPG key QString keyPath = tmp.absoluteFilePath(QString("ckb-%1-key.gpg").arg(pid)); if(!QFile::copy(":/bin/msckey.gpg", keyPath)){ firmware.remove(); qDebug() << "Failed to write GPG key to temporary location, aborting firmware check"; return; } // Check signature QProcess gpg; gpg.start("gpg", QStringList("--no-default-keyring") << "--keyring" << keyPath << "--verify" << fwPath); gpg.waitForFinished(); // Clean up temp files tmp.remove(fwPath); tmp.remove(keyPath); if(gpg.error() != QProcess::UnknownError || gpg.exitCode() != 0){ qDebug() << "GPG couldn't verify firmware signature:"; qDebug() << gpg.readAllStandardOutput(); qDebug() << gpg.readAllStandardError(); return; } // Signature good, proceed to update database } fwTable.clear(); QStringList lines = QString::fromUtf8(data).split("\n"); bool scan = false; foreach(QString line, lines){ // Collapse whitespace line.replace(QRegExp("\\s+"), " ").remove(QRegExp("^\\s")).remove(QRegExp("\\s$")); // Skip empty or commented-out lines if(line.length() == 0 || line.at(0) == '#') continue; // Don't read anything until the entries begin and don't read anything after they end if(!scan){ if(line == "!BEGIN FW ENTRIES") scan = true; else continue; } if(line == "!END FW ENTRIES") break; QStringList components = line.split(" "); if(components.length() != 7) continue; // "VENDOR-PRODUCT" QString device = components[0].toUpper() + "-" + components[1].toUpper(); FW fw; fw.fwVersion = components[2].toFloat(); // Firmware blob version fw.url = QUrl::fromPercentEncoding(components[3].toLatin1()); // URL to zip file fw.ckbVersion = PARSE_CKB_VERSION(components[4]); // Minimum ckb version fw.fileName = QUrl::fromPercentEncoding(components[5].toLatin1()); // Name of file inside zip fw.hash = QByteArray::fromHex(components[6].toLatin1()); // SHA256 of file inside zip // Update entry fwTable[device] = fw; }
void MainWindow::scanKeyboards(){ QString rootdev = devpath.arg(0); QFile connected(rootdev + "/connected"); if(!connected.open(QIODevice::ReadOnly)){ // No root controller - remove all keyboards while(ui->tabWidget->count() > 1) ui->tabWidget->removeTab(0); foreach(KbWidget* w, kbWidgets) w->deleteLater(); kbWidgets.clear(); settingsWidget->setStatus("Driver inactive"); ckbDaemonVersion = INFINITY; daemonVStr.clear(); return; } // Check daemon version QFile version(rootdev + "/version"); if(version.open(QIODevice::ReadOnly)){ daemonVStr = QString::fromUtf8(version.readLine()).trimmed(); version.close(); } else daemonVStr = "<unavailable>"; ckbDaemonVersion = PARSE_CKB_VERSION(daemonVStr); // Scan connected devices foreach(KbWidget* w, kbWidgets) w->active(false); QString line; while((line = connected.readLine().trimmed()) != ""){ QStringList components = line.trimmed().split(" "); if(components.length() < 2) continue; QString path = components[0], serial = components[1]; // Connected already? KbWidget* widget = 0; foreach(KbWidget* w, kbWidgets){ if(w->device && w->device->matches(path, serial)){ widget = w; w->active(true); break; } } if(widget) continue; // Add the keyboard widget = new KbWidget(this, path, "Devices"); if(!widget->isActive()){ delete widget; continue; } kbWidgets.append(widget); int count = ui->tabWidget->count(); ui->tabWidget->insertTab(count - 1, widget, widget->name()); if(ui->tabWidget->currentIndex() == count) ui->tabWidget->setCurrentIndex(count - 1); connect(eventTimer, SIGNAL(timeout()), widget->device, SLOT(frameUpdate())); } connected.close(); // Remove any devices not found in the connected list bool updateShown = false; foreach(KbWidget* w, kbWidgets){ if(w->isActive()){ if(!updateShown){ // Display firmware upgrade notification if a new version is available (and user has automatic updates enabled) if(CkbSettings::get("Program/DisableAutoFWCheck").toBool()) continue; float version = KbFirmware::versionForBoard(w->device->features); if(version > w->device->firmware.toFloat()){ if(w->hasShownNewFW) continue; w->hasShownNewFW = true; w->updateFwButton(); // Don't display more than one of these at once updateShown = true; // Don't run this method here because it will lock up the timer and prevent devices from working properly // Use a queued invocation instead metaObject()->invokeMethod(this, "showFwUpdateNotification", Qt::QueuedConnection, Q_ARG(QWidget*, w), Q_ARG(float, version)); } } w->saveIfNeeded(); } else { int i = kbWidgets.indexOf(w); ui->tabWidget->removeTab(i); kbWidgets.removeAt(i); w->deleteLater(); } }