From 88011d5009764936e9b687b38dbea8a0bbcfe1d6 Mon Sep 17 00:00:00 2001 From: Jerzy Kozera Date: Sun, 20 Jan 2013 20:47:19 +0000 Subject: [PATCH] Alt+Space hotkey, tray icon --- README.md | 2 +- zeal/main.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++--- zeal/mainwindow.cpp | 68 +++++++++++++++++++++++++++++++++++++ zeal/mainwindow.h | 8 +++++ 4 files changed, 155 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index de64e07..61879b8 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Do `tar -jxvf file.tar.bz2` in docsets directory to enable given docset. ## TODO - * Alt+Space / configurable hotkey for showing Zeal + tray icon + * Configuration (customisable hotkey instead of hardcoded Alt+Space, remember window size, etc.) * Search enhancements - some ideas: 1. Allow selecting subset of docsets to search in. 2. Substring match in case current startswith matching doesn't return anything. diff --git a/zeal/main.cpp b/zeal/main.cpp index 9e2d83c..907171e 100644 --- a/zeal/main.cpp +++ b/zeal/main.cpp @@ -1,11 +1,84 @@ #include "mainwindow.h" #include +#include + +#include +#include +#include +#include + +using namespace std; + +void run_grabkey(); int main(int argc, char *argv[]) { - QApplication a(argc, argv); - MainWindow w; - w.show(); - - return a.exec(); + if(argc > 1 && argv[1] == string("--grabkey")) { + run_grabkey(); + return -1; // infinite loop - shouldn't terminate + } else { + // detect already running instance + QLocalSocket socket; + socket.connectToServer(serverName); + if (socket.waitForConnected(500)) { + cerr << "Already running. Terminating." << endl; + return -1; // Exit already a process running + } + + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); + } +} + +void run_grabkey() +{ + Display *display = XOpenDisplay(NULL); + + unsigned int AltMask = 0; + + XModifierKeymap *xmk = XGetModifierMapping(display); + if(xmk) { + KeyCode altKeyCode = XKeysymToKeycode(display, XK_Alt_L); + KeyCode *c = xmk->modifiermap; + int m, k; + for(m = 0; m < 8; m++) { + for(k = 0; k < xmk->max_keypermod; k++, c++) + { + if(*c == NoSymbol) { + continue; + } + if(*c == altKeyCode) { + AltMask = (1 << m); + } + } + } + XFreeModifiermap (xmk); + } + + if(AltMask == 0) { + cout << "failed" << endl; + } else { + cout << "ok" << endl; + } + + KeyCode hotKey = XKeysymToKeycode(display, XStringToKeysym("space")); + XGrabKey(display, hotKey, AnyModifier, DefaultRootWindow(display), True, GrabModeSync, GrabModeSync); + + XEvent e; + for(;;) + { + XNextEvent(display, &e); + if(e.type == KeyPress){ + if(e.xkey.keycode == hotKey && e.xkey.state & AltMask) + { + cout << "1" << endl; + XAllowEvents(display, AsyncKeyboard, e.xkey.time); + } else { + XAllowEvents(display, ReplayKeyboard, e.xkey.time); + XFlush(display); + } + } + } } diff --git a/zeal/mainwindow.cpp b/zeal/mainwindow.cpp index fa945e1..b63a22f 100644 --- a/zeal/mainwindow.cpp +++ b/zeal/mainwindow.cpp @@ -9,12 +9,48 @@ using namespace std; #include #include +#include +#include #include +const QString serverName = "zeal_process_running"; + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + // server for detecting already running instances + localServer = new QLocalServer(this); + connect(localServer, &QLocalServer::newConnection, [&]() { + bringToFront(); + }); + QLocalServer::removeServer(serverName); // remove in case previous instance crashed + localServer->listen(serverName); + + // initialise icons + setWindowIcon(QIcon::fromTheme("edit-find")); + createTrayIcon(); + + // initialise key grabber + keyGrabber.setParent(this); + keyGrabber.start(qApp->applicationFilePath(), {"--grabkey"}); + connect(&keyGrabber, &QProcess::readyRead, [&]() { + char buf[100]; + keyGrabber.readLine(buf, sizeof(buf)); + if(QString(buf) == "failed\n") { + QMessageBox::warning(this, "grabkey process init failed", + QString("Failed to grab keyboard - Alt+Space will not work.")); + } + //first = false; + if(QString(buf) == "1\n") { + if(isVisible()) hide(); + else { + bringToFront(); + } + } + }); + + // initialise docsets auto dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation); auto dataDir = QDir(dataLocation); if(!dataDir.cd("docsets")) { @@ -28,6 +64,8 @@ MainWindow::MainWindow(QWidget *parent) : } } } + + // initialise ui ui->setupUi(this); ui->lineEdit->setTreeView(ui->treeView); ui->treeView->setModel(&zealList); @@ -53,4 +91,34 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() { delete ui; + delete localServer; + keyGrabber.terminate(); + keyGrabber.waitForFinished(500); +} + +void MainWindow::createTrayIcon() +{ + auto trayIconMenu = new QMenu(this); + auto quitAction = trayIconMenu->addAction("&Quit"); + connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); + auto trayIcon = new QSystemTrayIcon(this); + trayIcon->setContextMenu(trayIconMenu); + trayIcon->setIcon(QIcon::fromTheme("edit-find")); + trayIcon->setToolTip("Zeal"); + connect(trayIcon, &QSystemTrayIcon::activated, [&](QSystemTrayIcon::ActivationReason reason) { + if(reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) { + if(isVisible()) hide(); + else show(); + } + }); + trayIcon->show(); +} + +void MainWindow::bringToFront() +{ + show(); + setWindowState( (windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + raise(); + activateWindow(); + ui->lineEdit->setFocus(); } diff --git a/zeal/mainwindow.h b/zeal/mainwindow.h index b0ecdfb..05c0f72 100644 --- a/zeal/mainwindow.h +++ b/zeal/mainwindow.h @@ -2,6 +2,8 @@ #define MAINWINDOW_H #include +#include +#include #include "zeallistmodel.h" #include "zealsearchmodel.h" @@ -9,6 +11,8 @@ namespace Ui { class MainWindow; } +extern const QString serverName; + class MainWindow : public QMainWindow { Q_OBJECT @@ -18,9 +22,13 @@ public: ~MainWindow(); private: + void bringToFront(); Ui::MainWindow *ui; ZealListModel zealList; ZealSearchModel zealSearch; + QProcess keyGrabber; + QLocalServer *localServer; + void createTrayIcon(); }; #endif // MAINWINDOW_H