ckb-next  beta-v0.2.8 at branch testing
ckb-next driver for corsair devices
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
mainwindow.cpp
Go to the documentation of this file.
1 #include "ckbsettings.h"
2 #include "kbmanager.h"
3 #include "kbfirmware.h"
4 #include "mainwindow.h"
5 #include "ui_mainwindow.h"
6 #include <cstdlib>
7 #include <QSharedMemory>
8 #include <QShortcut>
9 #include <QMessageBox>
10 #include <QMenuBar>
11 #include <unistd.h>
12 
13 extern QSharedMemory appShare;
14 
15 static const QString configLabel = "Settings";
16 
18 
19 #ifdef USE_LIBAPPINDICATOR
20 extern "C" {
21  void quitIndicator(GtkMenu* menu, gpointer data) {
22  Q_UNUSED(menu);
23  MainWindow* window = static_cast<MainWindow*>(data);
24  window->quitApp();
25  }
26 
27  void restoreIndicator(GtkMenu* menu, gpointer data) {
28  Q_UNUSED(menu);
29  MainWindow* window = static_cast<MainWindow*>(data);
30  window->showWindow();
31  }
32 }
33 #endif // USE_LIBAPPINDICATOR
34 
36  QMainWindow(parent),
37  ui(new Ui::MainWindow)
38 {
39  ui->setupUi(this);
40  mainWindow = this;
41 
42  // Start device manager
43  KbManager::init(CKB_VERSION_STR);
44  connect(KbManager::kbManager(), SIGNAL(kbConnected(Kb*)), this, SLOT(addDevice(Kb*)));
45  connect(KbManager::kbManager(), SIGNAL(kbDisconnected(Kb*)), this, SLOT(removeDevice(Kb*)));
46  connect(KbManager::kbManager(), SIGNAL(versionUpdated()), this, SLOT(updateVersion()));
47  connect(KbManager::scanTimer(), SIGNAL(timeout()), this, SLOT(timerTick()));
48 
49  // Set up tray icon
50  restoreAction = new QAction(tr("Restore"), this);
51  closeAction = new QAction(tr("Quit ckb"), this);
52 #ifdef USE_LIBAPPINDICATOR
53  QString desktop = std::getenv("XDG_CURRENT_DESKTOP");
54  unityDesktop = (desktop.toLower() == "unity");
55 
56  if(unityDesktop){
57  trayIcon = 0;
58 
59  indicatorMenu = gtk_menu_new();
60  indicatorMenuRestoreItem = gtk_menu_item_new_with_label("Restore");
61  indicatorMenuQuitItem = gtk_menu_item_new_with_label("Quit ckb");
62 
63  gtk_menu_shell_append(GTK_MENU_SHELL(indicatorMenu), indicatorMenuRestoreItem);
64  gtk_menu_shell_append(GTK_MENU_SHELL(indicatorMenu), indicatorMenuQuitItem);
65 
66  g_signal_connect(indicatorMenuQuitItem, "activate",
67  G_CALLBACK(quitIndicator), this);
68  g_signal_connect(indicatorMenuRestoreItem, "activate",
69  G_CALLBACK(restoreIndicator), this);
70 
71  gtk_widget_show(indicatorMenuRestoreItem);
72  gtk_widget_show(indicatorMenuQuitItem);
73 
74  indicator = app_indicator_new("ckb", "indicator-messages", APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
75 
76  app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
77  app_indicator_set_menu(indicator, GTK_MENU(indicatorMenu));
78  app_indicator_set_icon(indicator, "ckb");
79  } else
80 #endif // USE_LIBAPPINDICATOR
81  {
82  trayIconMenu = new QMenu(this);
83  trayIconMenu->addAction(restoreAction);
84  trayIconMenu->addAction(closeAction);
85  trayIcon = new QSystemTrayIcon(QIcon(":/img/ckb-logo.png"), this);
86  trayIcon->setContextMenu(trayIconMenu);
87  connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconClicked(QSystemTrayIcon::ActivationReason)));
88  }
89  toggleTrayIcon(!CkbSettings::get("Program/SuppressTrayIcon").toBool());
90 
91 #ifdef Q_OS_MACX
92  // Make a custom "Close" menu action for OSX, as the default one brings up the "still running" popup unnecessarily
93  QMenuBar* menuBar = new QMenuBar(this);
94  setMenuBar(menuBar);
95  this->menuBar()->addMenu("ckb")->addAction(closeAction);
96 #else
97  // On linux, add a handler for Ctrl+Q
98  new QShortcut(QKeySequence("Ctrl+Q"), this, SLOT(quitApp()));
99 #endif
100 
101  connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(quitApp()));
102  connect(closeAction, SIGNAL(triggered()), this, SLOT(quitApp()));
103  connect(restoreAction, SIGNAL(triggered()), this, SLOT(showWindow()));
104  connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(stateChange(Qt::ApplicationState)));
105 
106  connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanup()));
107 
108  ui->tabWidget->addTab(settingsWidget = new SettingsWidget(this), configLabel);
109  settingsWidget->setVersion("ckb-next " CKB_VERSION_STR);
110 }
111 
112 void MainWindow::toggleTrayIcon(bool visible) {
113 #ifdef USE_LIBAPPINDICATOR
114  if(unityDesktop)
115  app_indicator_set_status(indicator, visible ? APP_INDICATOR_STATUS_ACTIVE : APP_INDICATOR_STATUS_PASSIVE);
116  else
117 #endif // USE_LIBAPPINDICATOR
118  trayIcon->setVisible(visible);
119 }
120 
121 void MainWindow::addDevice(Kb* device){
122  // Connected already?
123  foreach(KbWidget* w, kbWidgets){
124  if(w->device == device)
125  return;
126  }
127  // Add the keyboard
128  KbWidget* widget = new KbWidget(this, device);
129  kbWidgets.append(widget);
130  // Add to tabber; switch to this device if on the settings screen
131  int count = ui->tabWidget->count();
132  ui->tabWidget->insertTab(count - 1, widget, widget->name());
133  if(ui->tabWidget->currentIndex() == count)
134  ui->tabWidget->setCurrentIndex(count - 1);
135  // Update connected device count
136  updateVersion();
137 }
138 
140  foreach(KbWidget* w, kbWidgets){
141  // Remove this device from the UI
142  if(w->device == device){
143  int i = kbWidgets.indexOf(w);
144  ui->tabWidget->removeTab(i);
145  kbWidgets.removeAt(i);
146  w->deleteLater();
147  }
148  }
149  // Update connected device count
150  updateVersion();
151 }
152 
154  QString daemonVersion = KbManager::ckbDaemonVersion();
155  if(daemonVersion == DAEMON_UNAVAILABLE_STR){
156  settingsWidget->setStatus("Driver inactive");
157  return;
158  }
159  int count = kbWidgets.count();
160  // Warn if the daemon version doesn't match the GUI
161  QString daemonWarning;
162  if(daemonVersion != CKB_VERSION_STR)
163  daemonWarning = "<br /><br /><b>Warning:</b> Driver version mismatch (" + daemonVersion + "). Please upgrade ckb" + QString(KbManager::ckbDaemonVersionF() > KbManager::ckbGuiVersionF() ? "" : "-daemon") + ". If the problem persists, try rebooting.";
164  if(count == 0)
165  settingsWidget->setStatus("No devices connected" + daemonWarning);
166  else if(count == 1)
167  settingsWidget->setStatus("1 device connected" + daemonWarning);
168  else
169  settingsWidget->setStatus(QString("%1 devices connected").arg(count) + daemonWarning);
170 }
171 
173  if(!mainWindow->isVisible())
174  return;
175  foreach(KbWidget* w, kbWidgets){
176  // Display firmware upgrade notification if a new version is available
177  float version = KbFirmware::versionForBoard(w->device->features);
178  if(version > w->device->firmware.toFloat()){
179  if(w->hasShownNewFW)
180  continue;
181  w->hasShownNewFW = true;
182  w->updateFwButton();
183  // Don't run this method here because it will lock up the timer and prevent devices from working properly
184  // Use a queued invocation instead
185  metaObject()->invokeMethod(this, "showFwUpdateNotification", Qt::QueuedConnection, Q_ARG(QWidget*, w), Q_ARG(float, version));
186  // Don't display more than one of these at once
187  return;
188  }
189  }
190 }
191 
192 void MainWindow::showFwUpdateNotification(QWidget* widget, float version){
193  static bool isShowing = false;
194  if(isShowing)
195  return;
196  isShowing = true;
197  showWindow();
198  KbWidget* w = (KbWidget*)widget;
199  // Ask for update
200  if(QMessageBox::information(this, "Firmware update", tr("A new firmware is available for your %1 (v%2)\nWould you like to install it now?").arg(w->device->usbModel, QString::number(version, 'f', 2)), QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes) == QMessageBox::Yes){
201  // If accepted, switch to firmware tab and bring up update window
202  w->showLastTab();
203  ui->tabWidget->setCurrentIndex(kbWidgets.indexOf(w));
204  w->showFwUpdate();
205  }
206  isShowing = false;
207 }
208 
209 void MainWindow::closeEvent(QCloseEvent *event){
210  // If the window is hidden already or the event is non-spontaneous (can happen on OSX when using the Quit menu), accept it and close
211  if(!event->spontaneous() || isHidden()){
212  event->accept();
213  return;
214  }
215  if(!CkbSettings::get("Popups/BGWarning").toBool()){
216  QMessageBox::information(this, "ckb", "ckb will still run in the background.\nTo close it, choose Exit from the tray menu\nor click \"Quit ckb\" on the Settings screen.");
217  CkbSettings::set("Popups/BGWarning", true);
218  }
219  hide();
220  event->ignore();
221 }
222 
224  // Check shared memory for changes
225  if(appShare.lock()){
226  void* data = appShare.data();
227  QStringList commands = QString((const char*)data).split("\n");
228  // Restore PID, remove all other data
229  snprintf((char*)appShare.data(), appShare.size(), "PID %ld", (long)getpid());
230  appShare.unlock();
231  // Parse commands
232  foreach(const QString& line, commands){
233  // Old ckb option line - bring application to foreground
234  if(line == "Open")
235  showWindow();
236  if(line.startsWith("Option ")){
237  // New ckb option line
238  QString option = line.split(" ")[1];
239  if(option == "Open")
240  // Bring to foreground
241  showWindow();
242  else if(option == "Close")
243  // Quit application
244  qApp->quit();
245  }
246  }
247  }
248  // Check for firmware updates (when appropriate)
249  if(!CkbSettings::get("Program/DisableAutoFWCheck").toBool()){
251  checkFwUpdates();
252  }
253  // Poll for setting updates
255 }
256 
257 void MainWindow::iconClicked(QSystemTrayIcon::ActivationReason reason){
258  // On Linux, hide/show the app when the tray icon is clicked
259  // On OSX this just shows the menu
260 #ifndef Q_OS_MACX
261  if(reason == QSystemTrayIcon::DoubleClick || reason == QSystemTrayIcon::Trigger){
262  if(isVisible())
263  hide();
264  else
265  showWindow();
266  }
267 #endif
268 }
269 
271  showNormal();
272  raise();
273  activateWindow();
274 }
275 
276 void MainWindow::stateChange(Qt::ApplicationState state){
277  // On OSX it's possible for the app to be brought to the foreground without the window actually reappearing.
278  // We want to make sure it's shown when this happens.
279 #ifdef Q_OS_MAC
280  static quint64 lastStateChange = 0;
281  quint64 now = QDateTime::currentMSecsSinceEpoch();
282  if(state == Qt::ApplicationActive){
283  // This happens once at startup so ignore it. Also don't allow it to be called more than once every 2s.
284  if(lastStateChange != 0 && now >= lastStateChange + 2 * 1000)
285  showWindow();
286  lastStateChange = now;
287  }
288 #endif
289 }
290 
292  qApp->quit();
293 }
294 
296  foreach(KbWidget* w, kbWidgets)
297  delete w;
298  kbWidgets.clear();
299  KbManager::stop();
301 }
302 
304  cleanup();
305  delete ui;
306 }
void stateChange(Qt::ApplicationState state)
Definition: mainwindow.cpp:276
static KbManager * kbManager()
Definition: kbmanager.h:23
Ui::MainWindow * ui
Definition: mainwindow.h:75
static void stop()
Definition: kbmanager.cpp:19
void showFwUpdateNotification(QWidget *widget, float version)
Definition: mainwindow.cpp:192
void updateFwButton()
Definition: kbwidget.cpp:326
static void set(const QString &key, const QVariant &value)
void cleanup()
Definition: mainwindow.cpp:295
void checkFwUpdates()
Definition: mainwindow.cpp:172
void quitApp()
Definition: mainwindow.cpp:291
void showWindow()
Definition: mainwindow.cpp:270
void closeEvent(QCloseEvent *event)
Definition: mainwindow.cpp:209
static void cleanUp()
Definition: ckbsettings.cpp:46
static QVariant get(const QString &key, const QVariant &defaultValue=QVariant())
void toggleTrayIcon(bool visible)
Definition: mainwindow.cpp:112
void removeDevice(Kb *device)
Definition: mainwindow.cpp:139
void setStatus(const QString &text)
QString firmware
Definition: kb.h:18
QSharedMemory appShare
QMenu * trayIconMenu
Definition: mainwindow.h:53
static QTimer * scanTimer()
Definition: kbmanager.h:43
static float ckbDaemonVersionF()
Definition: kbmanager.h:31
QAction * restoreAction
Definition: mainwindow.h:43
static const QString configLabel
Definition: mainwindow.cpp:15
QString name() const
Definition: kbwidget.h:25
void updateVersion()
Definition: mainwindow.cpp:153
MainWindow(QWidget *parent=0)
Definition: mainwindow.cpp:35
bool hasShownNewFW
Definition: kbwidget.h:28
static float ckbGuiVersionF()
Definition: kbmanager.h:30
void showFwUpdate()
Definition: kbwidget.h:38
void showLastTab()
Definition: kbwidget.cpp:68
Definition: kb.h:11
static void init(QString guiVersion)
Definition: kbmanager.cpp:12
QSystemTrayIcon * trayIcon
Definition: mainwindow.h:54
static MainWindow * mainWindow
Definition: mainwindow.h:35
static bool checkUpdates()
Definition: kbfirmware.h:15
QString usbModel
Definition: kb.h:16
QTabWidget * tabWidget
Definition: ui_mainwindow.h:31
#define DAEMON_UNAVAILABLE_STR
Definition: kbmanager.h:12
void setVersion(const QString &version)
void setupUi(QMainWindow *MainWindow)
Definition: ui_mainwindow.h:33
void iconClicked(QSystemTrayIcon::ActivationReason reason)
Definition: mainwindow.cpp:257
QAction * closeAction
Definition: mainwindow.h:44
SettingsWidget * settingsWidget
Definition: mainwindow.h:40
QString features
Definition: kb.h:18
static QString ckbDaemonVersion()
Definition: kbmanager.h:27
void timerTick()
Definition: mainwindow.cpp:223
QList< KbWidget * > kbWidgets
Definition: mainwindow.h:41
Kb * device
Definition: kbwidget.h:24
void addDevice(Kb *device)
Definition: mainwindow.cpp:121
QAction * actionExit
Definition: ui_mainwindow.h:27
static float versionForBoard(const QString &features, bool waitForComplete=false)
Definition: kbfirmware.h:22