From e1f40d7e11ab1db3707f3ae05c650f8aacdbe5ab Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Mon, 9 Jan 2012 23:53:27 +0400 Subject: [PATCH] DropDown mode --- CMakeLists.txt | 1 + qterminal_drop.desktop | 49 ++ src/CMakeLists.txt | 19 + src/forms/propertiesdialog.ui | 78 +++- src/icons.qrc | 2 + src/icons/locked.png | Bin 0 -> 561 bytes src/icons/notlocked.png | Bin 0 -> 541 bytes src/main.cpp | 23 +- src/mainwindow.cpp | 148 +++++- src/mainwindow.h | 16 +- src/properties.cpp | 17 + src/properties.h | 6 + src/propertiesdialog.cpp | 10 + src/tabwidget.cpp | 2 +- src/termwidget.cpp | 7 +- src/third-party/globalshortcutmanager.cpp | 91 ++++ src/third-party/globalshortcutmanager.h | 48 ++ src/third-party/globalshortcutmanager_mac.cpp | 416 +++++++++++++++++ src/third-party/globalshortcutmanager_win.cpp | 205 ++++++++ src/third-party/globalshortcutmanager_x11.cpp | 439 ++++++++++++++++++ src/third-party/globalshortcuttrigger.h | 63 +++ 21 files changed, 1626 insertions(+), 14 deletions(-) create mode 100644 qterminal_drop.desktop create mode 100644 src/icons/locked.png create mode 100644 src/icons/notlocked.png create mode 100644 src/third-party/globalshortcutmanager.cpp create mode 100644 src/third-party/globalshortcutmanager.h create mode 100644 src/third-party/globalshortcutmanager_mac.cpp create mode 100644 src/third-party/globalshortcutmanager_win.cpp create mode 100644 src/third-party/globalshortcutmanager_x11.cpp create mode 100644 src/third-party/globalshortcuttrigger.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d2b985b4..32285571 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ subdirs( src ) install(FILES qterminal.desktop DESTINATION share/applications) +install(FILES qterminal_drop.desktop DESTINATION share/applications) # make dist custom target diff --git a/qterminal_drop.desktop b/qterminal_drop.desktop new file mode 100644 index 00000000..0da61bc2 --- /dev/null +++ b/qterminal_drop.desktop @@ -0,0 +1,49 @@ +[Desktop Entry] +Type=Application +Exec=qterminal --drop +Terminal=false +Categories=Accessories;System Tools;Utilities;System +Icon=terminal + +Name=QTerminal drop down +GenericName=Drop-down Terminal + +GenericName[bg]=Падащ терминал +GenericName[ca]=Terminal desplegable +GenericName[ca@valencia]=Terminal desplegable +GenericName[cs]=Vysouvací terminál +GenericName[da]=Terminal der ruller ned +GenericName[de]=Aufklapp-Terminal +GenericName[en_GB]=Drop-down Terminal +GenericName[es]=Terminal de menú desplegable +GenericName[et]=Lahtikeriv terminal +GenericName[fr]=Terminal déroulant +GenericName[hr]=Spuštajući terminal +GenericName[hu]=Legördülő terminál +GenericName[it]=Terminale a discesa +GenericName[ja]=ドロップダウン式ターミナル +GenericName[km]=ស្ថានីយ​ទម្លាក់​ចុះ +GenericName[ko]=위에서 내려오는 터미널 +GenericName[nb]=Nedtrekksterminal +GenericName[nds]=Utklapp-Konsool +GenericName[nl]=Uitvouwbare terminalemulator +GenericName[pa]=ਲਟਕਦਾ ਟਰਮੀਨਲ +GenericName[pt]=Terminal Deslizante +GenericName[pt_BR]=Terminal suspenso +GenericName[ro]=Terminal derulant + +GenericName[ru]=Выпадающий терминал +Comment[ru]=Вападающий эмулятор терминала. + +GenericName[sk]=Rozbaľovací terminál +GenericName[sv]=Rullgardinsterminal +GenericName[th]=เทอร์มินัลแบบหย่อนลง +GenericName[tr]=Yukarıdan Açılan Uçbirim +GenericName[uk]=Спадний термінал +GenericName[x-test]=xxDrop-down Terminalxx +GenericName[zh_CN]=拉幕式终端 +GenericName[zh_TW]=下拉式終端機 + +Comment=A drop-down terminal emulator. + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2cb1c65..1fa2c3a7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,14 +8,33 @@ set( QTERM_SRC termwidgetholder.cpp properties.cpp propertiesdialog.cpp + third-party/globalshortcutmanager.cpp ) +if(WIN32) + set(QTERM_SRC + ${QTERM_SRC} + third-party/globalshortcutmanager_win.cpp + ) +elseif(APPLE) + set(QTERM_SRC + ${QTERM_SRC} + third-party/globalshortcutmanager_mac.cpp + ) +else() + set(QTERM_SRC + ${QTERM_SRC} + third-party/globalshortcutmanager_x11.cpp + ) +endif() + set( QTERM_MOC_SRC mainwindow.h tabwidget.h termwidget.h termwidgetholder.h propertiesdialog.h + third-party/globalshortcuttrigger.h ) set( QTERM_UI_SRC diff --git a/src/forms/propertiesdialog.ui b/src/forms/propertiesdialog.ui index 2e0b6318..416b5d9c 100644 --- a/src/forms/propertiesdialog.ui +++ b/src/forms/propertiesdialog.ui @@ -59,6 +59,11 @@ Shortcuts + + + DropDown + + @@ -67,7 +72,7 @@ QFrame::Box - 0 + 5 @@ -362,6 +367,77 @@ + + + + + + Show on start + + + + + + + Size + + + + + + + + Height %: + + + + + + + + + + Width %: + + + + + + + + + + + + + + + + + Shortcut: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 78 + + + + + + diff --git a/src/icons.qrc b/src/icons.qrc index 7d287535..b0c40178 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -8,5 +8,7 @@ icons/document-close.png icons/application-exit.png icons/qterminal.png + icons/locked.png + icons/notlocked.png diff --git a/src/icons/locked.png b/src/icons/locked.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d26f96527c5e246f60af7bf27f2bd89d44f542 GIT binary patch literal 561 zcmV-10?z%3P)N*h9Kr56x%7OHK=LYnWvB7z$9g!_JJ=XKR&1j6ffyX~}EEkBt| zrvI)d5{b9PV$tn%I`gHozDGfjmm(*--R|${bovV`X0ut(w(Xyu=jlsl&5snsB;+{G zjD@u+*)R-uKA+Rl2^B=qb)6ByG)>6+h`52z`1SKAK0EDWbTz?n_yGIX1A&n;x3GVt z;o$uVzSqad7pjC6fv@1R$f;{+t_5}(2?f~J0mESI?kU(kd?${NjgKHzh{Z@oCJ1s3 zsHzX5fE1n}TB0b}$UhZTU7o(FU?cyzKn=7JVKO8wa z+`G^J2SgMHps!_1qDe^NOCBYjD0mLQB^77S=*ieyeXw>ywDHewKT;DbA7&VS}I{2(G1tUxpx zJrt^-eB^gKGYeR9DwVpd z*Xxu_CT}B=$Wb^PK8(lXr9z=VnM|f=XJ!>_R4SD|uRT%}WpnKFyv`;4QmOQ6XPjpw zOln2bG;gg|GfdOe$0pDB&1N%g7)IO9IP$Iy4s>0oL?WS2`LQyy$S6*qO}A84rJo*$ zKLGT8`hw^?dyzb${k=Q1AG&C@OgudgAwcjg&SNd4Ah5lQt853yr)hyNO>AHhhkXW= zfesoF79_x{0-8qfuS)O-w>V?LDG5_$VQx-fG!kI(49KztA>fc}19yoe;aAO!$g=(Q zKNUFj&js>Cn-J)B186^a(Rwk_=?vK;Ecra>OCAK5Rel~!zXw7H4*=D{Jfkpzv6y#e f$h>_qurJpqz#AT>du&JO00000NkvXXu0mjf;1As@ literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index b8b37e74..2308c67b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,13 +29,14 @@ #define out -const char* const short_options = "vhw:e:"; +const char* const short_options = "vhw:e:d"; const struct option long_options[] = { {"version", 0, NULL, 'v'}, {"help", 0, NULL, 'h'}, {"workdir", 1, NULL, 'w'}, {"execute", 1, NULL, 'e'}, + {"drop", 0, NULL, 'd'}, {NULL, 0, NULL, 0} }; @@ -48,6 +49,7 @@ void print_usage_and_exit(int code) puts("-v|--version Prints application version and exits"); puts("-w|--workdir Start session with specified work directory"); puts("-e|--execute Execute command instead of shell"); + puts("-d|--drop Start in Drop Mode, like Yakuake or Tilda"); puts("\nHomepage: http://qterminal.sourceforge.net/"); puts("Feature requests, bug reports etc please send to: \n"); exit(code); @@ -59,9 +61,10 @@ void print_version_and_exit(int code=0) exit(code); } -void parse_args(int argc, char* argv[], QString& workdir, QString & shell_command) +void parse_args(int argc, char* argv[], QString& workdir, QString & shell_command, out bool& dropMode) { int next_option; + dropMode = false; do{ next_option = getopt_long(argc, argv, short_options, long_options, NULL); switch(next_option) @@ -74,6 +77,9 @@ void parse_args(int argc, char* argv[], QString& workdir, QString & shell_comman case 'e': shell_command = QString(optarg); break; + case 'd': + dropMode = true; + break; case '?': print_usage_and_exit(1); case 'v': @@ -88,13 +94,20 @@ int main(int argc, char *argv[]) setenv("TERM", "xterm", 1); QApplication app(argc, argv); QString workdir, shell_command; - parse_args(argc, argv, workdir, shell_command); + bool dropMode; + parse_args(argc, argv, workdir, shell_command, dropMode); if (workdir.isEmpty()) workdir = QDir::homePath(); - MainWindow widget(workdir, shell_command); - widget.show(); + MainWindow widget(workdir, shell_command, dropMode); + if (!widget.dropMode() || + Properties::Instance()->dropShowOnStart + ) + { + widget.show(); + } return app.exec(); + qDebug() << Q_FUNC_INFO; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b3e36caa..0dc8875b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,13 +26,20 @@ #include "config.h" #include "properties.h" #include "propertiesdialog.h" +#include "third-party/globalshortcutmanager.h" +#define QSS_COMMON "QTabWidget::pane {border: none;}\n" +#define QSS_DROP "MainWindow {border: 1px solid rgba(0, 0, 0, 50%);}\n" +#define QSS_WINDOW "" MainWindow::MainWindow(const QString& work_dir, const QString& command, + bool dropMode, QWidget * parent, Qt::WindowFlags f) - : QMainWindow(parent,f) + : QMainWindow(parent,f), + m_dropLockButton(0), + m_dropMode(dropMode) { setupUi(this); @@ -43,8 +50,16 @@ MainWindow::MainWindow(const QString& work_dir, connect(actQuit, SIGNAL(triggered()), SLOT(close())); connect(actProperties, SIGNAL(triggered()), SLOT(actProperties_triggered())); - restoreGeometry(Properties::Instance()->mainWindowGeometry); - restoreState(Properties::Instance()->mainWindowState); + //setContentsMargins(0, 0, 0, 0); + if (m_dropMode) { + this->enableDropMode(); + setStyleSheet(QSS_COMMON QSS_DROP); + } + else { + restoreGeometry(Properties::Instance()->mainWindowGeometry); + restoreState(Properties::Instance()->mainWindowState); + setStyleSheet(QSS_COMMON QSS_WINDOW); + } connect(consoleTabulator, SIGNAL(quit_notification()), SLOT(quit())); consoleTabulator->setWorkDirectory(work_dir); @@ -73,6 +88,33 @@ MainWindow::~MainWindow() { } +void MainWindow::enableDropMode() +{ + setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint); + + m_dropLockButton = new QToolButton(this); + consoleTabulator->setCornerWidget(m_dropLockButton, Qt::BottomRightCorner); + m_dropLockButton->setCheckable(true); + m_dropLockButton->connect(m_dropLockButton, SIGNAL(clicked(bool)), this, SLOT(setKeepOpen(bool))); + setKeepOpen(Properties::Instance()->dropKeepOpen); + m_dropLockButton->setAutoRaise(true); + + setDropShortcut(Properties::Instance()->dropShortCut); + realign(); +} + +void MainWindow::setDropShortcut(QKeySequence dropShortCut) +{ + static QKeySequence oldShortCut; + GlobalShortcutManager* shortcutManager =GlobalShortcutManager::instance(); + + if (!oldShortCut.isEmpty()) + shortcutManager->disconnect(oldShortCut, this, SLOT(showHide())); + + shortcutManager->connect(dropShortCut, this, SLOT(showHide())); + oldShortCut = dropShortCut; +} + void MainWindow::setup_ActionsMenu_Actions() { QSettings settings(QDir::homePath()+"/.qterminal", QSettings::IniFormat); @@ -199,6 +241,7 @@ void MainWindow::setup_WindowMenu_Actions() toggleBorder->setCheckable(true); connect(toggleBorder, SIGNAL(triggered()), this, SLOT(toggleBorderless())); menu_Window->addAction(toggleBorder); + toggleBorder->setVisible(!m_dropMode); toggleTabbar = new QAction(tr("Toggle TabBar"), this); //toggleTabbar->setObjectName("toggle_TabBar"); @@ -291,6 +334,7 @@ void MainWindow::toggleBorderless() show(); setWindowState(Qt::WindowActive); /* don't loose focus on the window */ Properties::Instance()->borderless = toggleBorder->isChecked(); + realign(); } void MainWindow::closeEvent(QCloseEvent *ev) @@ -356,7 +400,67 @@ void MainWindow::propertiesChanged() setWindowOpacity(Properties::Instance()->appOpacity/100.0); consoleTabulator->setTabPosition((QTabWidget::TabPosition)Properties::Instance()->tabsPos); consoleTabulator->propertiesChanged(); + setDropShortcut(Properties::Instance()->dropShortCut); Properties::Instance()->saveSettings(); + realign(); +} + +void MainWindow::realign() +{ + if (m_dropMode) + { + QRect desktop = QApplication::desktop()->availableGeometry(); + QRect geometry = QRect(0, 0, + desktop.width() * Properties::Instance()->dropWidht / 100, + desktop.height() * Properties::Instance()->dropHeight / 100 + ); + geometry.moveCenter(desktop.center()); + geometry.setTop(0); + + setGeometry(geometry); + } + + int l=0, t=0, r=0, b=0; + + switch (consoleTabulator->tabPosition()) + { + case QTabWidget::North: + if (!Properties::Instance()->tabBarless) + { + t = 4; + b = 3; + } + setStyleSheet(QSS_COMMON QSS_DROP "QTabWidget::tab-bar { left: 5px; } QTabWidget::right-corner { right: 3px; bottom: 2px; }"); + break; + + case QTabWidget::South: + if (!Properties::Instance()->tabBarless) + { + b = 4; + } + setStyleSheet(QSS_COMMON QSS_DROP "QTabWidget::tab-bar { left: 5px; } QTabWidget::right-corner { right: 3px; top: 2px; }"); + break; + + case QTabWidget::West: + if (!Properties::Instance()->tabBarless) + { + l = 4; + b = 3; + } + setStyleSheet(QSS_COMMON QSS_DROP); + break; + + case QTabWidget::East: + if (!Properties::Instance()->tabBarless) + { + r = 4; + b = 3; + } + setStyleSheet(QSS_COMMON QSS_DROP); + break; + + } + setContentsMargins(l, t, r, b); } void MainWindow::updateActionGroup(QAction *a) @@ -365,3 +469,41 @@ void MainWindow::updateActionGroup(QAction *a) tabPosition->actions().at(Properties::Instance()->tabsPos)->setChecked(true); } } + +void MainWindow::showHide() +{ + if (isVisible()) + hide(); + else + { + show(); + activateWindow(); + } +} + +void MainWindow::setKeepOpen(bool value) +{ + Properties::Instance()->dropKeepOpen = value; + if (!m_dropLockButton) + return; + + if (value) + m_dropLockButton->setIcon(QIcon(":/icons/locked.png")); + else + m_dropLockButton->setIcon(QIcon(":/icons/notlocked.png")); + + m_dropLockButton->setChecked(value); +} + +bool MainWindow::event(QEvent *event) +{ + if (event->type() == QEvent::WindowDeactivate) + { + if (m_dropMode && + !Properties::Instance()->dropKeepOpen && + qApp->activeWindow() == 0 + ) + hide(); + } + return QMainWindow::event(event); +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 278e5781..54f6cdc2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -24,6 +24,7 @@ #include "ui_qterminal.h" #include +class QToolButton; class MainWindow : public QMainWindow , private Ui::mainWindow { @@ -31,9 +32,12 @@ class MainWindow : public QMainWindow , private Ui::mainWindow public: MainWindow(const QString& work_dir, const QString& command, + bool dropMode, QWidget * parent = 0, Qt::WindowFlags f = 0); ~MainWindow(); + bool dropMode() { return m_dropMode; } + protected slots: void on_consoleTabulator_currentChanged(int); void quit(); @@ -45,6 +49,12 @@ protected slots: void toggleBorderless(); void toggleTabBar(); + void showHide(); + void setKeepOpen(bool value); + +protected: + bool event(QEvent* event); + private: QActionGroup *tabPosition, *scrollBarPosition; QMenu *tabPosMenu, *scrollPosMenu; @@ -55,6 +65,10 @@ protected slots: void setup_WindowMenu_Actions(); void closeEvent(QCloseEvent*); - + void enableDropMode(); + QToolButton *m_dropLockButton; + bool m_dropMode; + void realign(); + void setDropShortcut(QKeySequence dropShortCut); }; #endif //MAINWINDOW_H diff --git a/src/properties.cpp b/src/properties.cpp index 832c4cff..5a8b8a68 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -89,6 +89,14 @@ void Properties::loadSettings() borderless = settings.value("Borderless", false).toBool(); tabBarless = settings.value("TabBarless", false).toBool(); askOnExit = settings.value("AskOnExit", true).toBool(); + + settings.beginGroup("DropMode"); + dropShortCut = QKeySequence(settings.value("ShortCut", "F12").toString()); + dropKeepOpen = settings.value("KeepOpen", false).toBool(); + dropShowOnStart = settings.value("ShowOnStart", true).toBool(); + dropWidht = settings.value("Width", 70).toInt(); + dropHeight = settings.value("Height", 45).toInt(); + settings.endGroup(); } void Properties::saveSettings() @@ -139,5 +147,14 @@ void Properties::saveSettings() settings.setValue("Borderless", borderless); settings.setValue("TabBarless", tabBarless); settings.setValue("AskOnExit", askOnExit); + + settings.beginGroup("DropMode"); + settings.setValue("ShortCut", dropShortCut.toString()); + settings.setValue("KeepOpen", dropKeepOpen); + settings.setValue("ShowOnStart", dropShowOnStart); + settings.setValue("Width", dropWidht); + settings.setValue("Height", dropHeight); + settings.endGroup(); + } diff --git a/src/properties.h b/src/properties.h index d4910f70..84bc8c06 100644 --- a/src/properties.h +++ b/src/properties.h @@ -45,6 +45,12 @@ class Properties bool askOnExit; + QKeySequence dropShortCut; + bool dropKeepOpen; + bool dropShowOnStart; + int dropWidht; + int dropHeight; + QMap< QString, QAction * > actions; void loadSettings(); diff --git a/src/propertiesdialog.cpp b/src/propertiesdialog.cpp index 7581376d..a21295c2 100644 --- a/src/propertiesdialog.cpp +++ b/src/propertiesdialog.cpp @@ -65,6 +65,11 @@ PropertiesDialog::PropertiesDialog(QWidget *parent) historyLimited->setChecked(Properties::Instance()->historyLimited); historyUnlimited->setChecked(!Properties::Instance()->historyLimited); historyLimitedTo->setValue(Properties::Instance()->historyLimitedTo); + + dropShowOnStartCheckBox->setChecked(Properties::Instance()->dropShowOnStart); + dropHeightSpinBox->setValue(Properties::Instance()->dropHeight); + dropWidthSpinBox->setValue(Properties::Instance()->dropWidht); + dropShortCutEdit->setText(Properties::Instance()->dropShortCut.toString()); } @@ -105,6 +110,11 @@ void PropertiesDialog::apply() Properties::Instance()->saveSettings(); + Properties::Instance()->dropShowOnStart = dropShowOnStartCheckBox->isChecked(); + Properties::Instance()->dropHeight = dropHeightSpinBox->value(); + Properties::Instance()->dropWidht = dropWidthSpinBox->value(); + Properties::Instance()->dropShortCut = QKeySequence(dropShortCutEdit->text()); + emit propertiesChanged(); } diff --git a/src/tabwidget.cpp b/src/tabwidget.cpp index 73ab0943..81c05af1 100644 --- a/src/tabwidget.cpp +++ b/src/tabwidget.cpp @@ -38,7 +38,7 @@ TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent), tabNumerator(0) * the tabs in Safari or Leopard's Terminal.app . * I love this! */ - setDocumentMode(true); + //setDocumentMode(true); tabBar()->setUsesScrollButtons(true); diff --git a/src/termwidget.cpp b/src/termwidget.cpp index f6563676..729fde29 100644 --- a/src/termwidget.cpp +++ b/src/termwidget.cpp @@ -199,7 +199,8 @@ TermWidget::TermWidget(const QString & wdir, const QString & shell, QWidget * pa m_term = new TermWidgetImpl(wdir, shell, this); setFocusProxy(m_term); m_layout = new QVBoxLayout; - m_layout->setContentsMargins(3, 3, 3, 3); + //m_layout->setContentsMargins(3, 3, 3, 3); + m_layout->setContentsMargins(0, 0, 0, 0); setLayout(m_layout); m_layout->addWidget(m_term); @@ -238,7 +239,7 @@ void TermWidget::term_splitCollapse() void TermWidget::term_termGetFocus() { qDebug() << "get focus" << this << this->size(); - m_border = palette().color(QPalette::Highlight); + //m_border = palette().color(QPalette::Highlight); emit termGetFocus(this); update(); } @@ -246,7 +247,7 @@ void TermWidget::term_termGetFocus() void TermWidget::term_termLostFocus() { qDebug() << "lost focus" << this; - m_border = palette().color(QPalette::Window); + //m_border = palette().color(QPalette::Window); update(); } diff --git a/src/third-party/globalshortcutmanager.cpp b/src/third-party/globalshortcutmanager.cpp new file mode 100644 index 00000000..10f0dc5a --- /dev/null +++ b/src/third-party/globalshortcutmanager.cpp @@ -0,0 +1,91 @@ +/* + * globalshortcutmanager.cpp - Class managing global shortcuts + * Copyright (C) 2006 - 2007 Maciej Niedzielski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// this code was copied from Psi (http://psi-im.org) + +#include "globalshortcutmanager.h" + +#include + +#include "globalshortcuttrigger.h" + +/** + * \brief Constructs new GlobalShortcutManager. + */ +GlobalShortcutManager::GlobalShortcutManager() + : QObject(QCoreApplication::instance()) +{ +} + +GlobalShortcutManager* GlobalShortcutManager::instance_ = 0; + +/** + * \brief Returns the instance of GlobalShortcutManager. + */ +GlobalShortcutManager* GlobalShortcutManager::instance() +{ + if (!instance_) + instance_ = new GlobalShortcutManager(); + return instance_; +} + +/** + * \brief Connects a key sequence with a slot. + * \param key, global shortcut to be connected + * \param receiver, object which should receive the notification + * \param slot, the SLOT() of the \a receiver which should be triggerd if the \a key is activated + */ +void GlobalShortcutManager::connect(const QKeySequence& key, QObject* receiver, const char* slot) +{ + KeyTrigger* t = instance()->triggers_[key]; + if (!t) { + t = new KeyTrigger(key); + instance()->triggers_.insert(key, t); + } + + QObject::connect(t, SIGNAL(activated()), receiver, slot); +} + +/** + * \brief Disonnects a key sequence from a slot. + * \param key, global shortcut to be disconnected + * \param receiver, object which \a slot is about to be disconnected + * \param slot, the SLOT() of the \a receiver which should no longer be triggerd if the \a key is activated + */ +void GlobalShortcutManager::disconnect(const QKeySequence& key, QObject* receiver, const char* slot) +{ + KeyTrigger* t = instance()->triggers_[key]; + if (!t) { + return; + } + + QObject::disconnect(t, SIGNAL(activated()), receiver, slot); + + if (!t->isUsed()) { + delete instance()->triggers_.take(key); + } +} + +void GlobalShortcutManager::clear() +{ + foreach (KeyTrigger* t, instance()->triggers_) + delete t; + instance()->triggers_.clear(); +} diff --git a/src/third-party/globalshortcutmanager.h b/src/third-party/globalshortcutmanager.h new file mode 100644 index 00000000..333773cc --- /dev/null +++ b/src/third-party/globalshortcutmanager.h @@ -0,0 +1,48 @@ +/* + * globalshortcutmanager.h - Class managing global shortcuts + * Copyright (C) 2006-2007 Maciej Niedzielski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// this code was copied from Psi (http://psi-im.org) + +#ifndef GLOBALSHORTCUTMANAGER_H +#define GLOBALSHORTCUTMANAGER_H + +#include +#include +#include + +class QObject; +class KeyTrigger; + +class GlobalShortcutManager : public QObject +{ +public: + static GlobalShortcutManager* instance(); + static void connect(const QKeySequence& key, QObject* receiver, const char* slot); + static void disconnect(const QKeySequence& key, QObject* receiver, const char* slot); + static void clear(); + +private: + GlobalShortcutManager(); + static GlobalShortcutManager* instance_; + class KeyTrigger; + QMap triggers_; +}; + +#endif diff --git a/src/third-party/globalshortcutmanager_mac.cpp b/src/third-party/globalshortcutmanager_mac.cpp new file mode 100644 index 00000000..d247c261 --- /dev/null +++ b/src/third-party/globalshortcutmanager_mac.cpp @@ -0,0 +1,416 @@ +/* + * globalshortcutmanager_mac.cpp - Mac OS X implementation of global shortcuts + * Copyright (C) 2003-2007 Eric Smith, Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// this code was copied from Psi (http://psi-im.org) + +#include "globalshortcutmanager.h" +#include "globalshortcuttrigger.h" + +// TODO: +// - don't invoke hotkey if there is a modal dialog? +// - do multi-mapping, like the x11 version + +#include + +#include + +class MacKeyTrigger +{ +public: + virtual ~MacKeyTrigger() {} + virtual void activate() = 0; + virtual bool isAccepted(int id) const = 0; +}; + +class MacKeyTriggerManager : public QObject +{ +public: + static MacKeyTriggerManager* instance() + { + if(!instance_) + instance_ = new MacKeyTriggerManager(); + return instance_; + } + + void addTrigger(MacKeyTrigger* trigger) + { + triggers_ << trigger; + } + + void removeTrigger(MacKeyTrigger* trigger) + { + triggers_.removeAll(trigger); + } + +private: + MacKeyTriggerManager() + : QObject(QCoreApplication::instance()) + { + initAscii2KeyCodeTable(&key_codes_); + hot_key_function_ = NewEventHandlerUPP(hotKeyHandler); + EventTypeSpec type; + type.eventClass = kEventClassKeyboard; + type.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(hot_key_function_, 1, &type, this, NULL); + } + + /** + * Callback function invoked when the user hits a hot-key. + */ + static pascal OSStatus hotKeyHandler(EventHandlerCallRef /*nextHandler*/, EventRef theEvent, void* userData) + { + EventHotKeyID hkID; + GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkID); + static_cast(userData)->activated(hkID.id); + return noErr; + } + + void activated(int id) + { + foreach(MacKeyTrigger* trigger, triggers_) { + if (trigger->isAccepted(id)) { + trigger->activate(); + break; + } + } + } + + static MacKeyTriggerManager* instance_; + QList triggers_; + + typedef struct + { + short kchrID; + Str255 KCHRname; + short transtable[256]; + } Ascii2KeyCodeTable; + + enum { + kTableCountOffset = 256 + 2, + kFirstTableOffset = 256 + 4, + kTableSize = 128 + }; + + static EventHandlerUPP hot_key_function_; + static Ascii2KeyCodeTable key_codes_; + +private: + /** + * initAscii2KeyCodeTable initializes the ascii to key code + * look up table using the currently active KCHR resource. This + * routine calls GetResource so it cannot be called at interrupt time. + */ + static OSStatus initAscii2KeyCodeTable(Ascii2KeyCodeTable* ttable) + { + unsigned char* theCurrentKCHR, *ithKeyTable; + short count, i, j, resID; + Handle theKCHRRsrc; + ResType rType; + + // set up our table to all minus ones + for (i = 0; i < 256; i++) + ttable->transtable[i] = -1; + + // find the current kchr resource ID + ttable->kchrID = (short)GetScriptVariable(smCurrentScript, smScriptKeys); + + // get the current KCHR resource + theKCHRRsrc = GetResource('KCHR', ttable->kchrID); + if (theKCHRRsrc == NULL) + return resNotFound; + GetResInfo(theKCHRRsrc, &resID, &rType, ttable->KCHRname); + + // dereference the resource + theCurrentKCHR = (unsigned char *)(*theKCHRRsrc); + + // get the count from the resource + count = *(short*)(theCurrentKCHR + kTableCountOffset); + + // build inverse table by merging all key tables + for (i = 0; i < count; i++) { + ithKeyTable = theCurrentKCHR + kFirstTableOffset + (i * kTableSize); + for (j = 0; j < kTableSize; j++) { + if (ttable->transtable[ ithKeyTable[j] ] == -1) + ttable->transtable[ ithKeyTable[j] ] = j; + } + } + + return noErr; + } + + /** + * validateAscii2KeyCodeTable verifies that the ascii to key code + * lookup table is synchronized with the current KCHR resource. If + * it is not synchronized, then the table is re-built. This routine calls + * GetResource so it cannot be called at interrupt time. + * + * Should probably call this at some point, in case the user has switched keyboard + * layouts while we were running. + */ + static OSStatus validateAscii2KeyCodeTable(Ascii2KeyCodeTable* ttable, Boolean* wasChanged) + { + short theID; + theID = (short) GetScriptVariable(smCurrentScript, smScriptKeys); + if (theID != ttable->kchrID) { + *wasChanged = true; + return initAscii2KeyCodeTable(ttable); + } + else { + *wasChanged = false; + return noErr; + } + } + + /** + * asciiToKeyCode looks up the ascii character in the key + * code look up table and returns the virtual key code for that + * letter. If there is no virtual key code for that letter, then + * the value -1 will be returned. + */ + static short asciiToKeyCode(Ascii2KeyCodeTable* ttable, short asciiCode) + { + if (asciiCode >= 0 && asciiCode <= 255) + return ttable->transtable[asciiCode]; + else return false; + } + + /** + * Not used. + */ + static char keyCodeToAscii(short virtualKeyCode) + { + unsigned long state; + long keyTrans; + char charCode; + Ptr kchr; + state = 0; + kchr = (Ptr)GetScriptVariable(smCurrentScript, smKCHRCache); + keyTrans = KeyTranslate(kchr, virtualKeyCode, &state); + charCode = keyTrans; + if (!charCode) + charCode = (keyTrans >> 16); + return charCode; + } + +private: + struct Qt_Mac_Keymap + { + int qt_key; + int mac_key; + }; + + static Qt_Mac_Keymap qt_keymap[]; + +public: + static bool convertKeySequence(const QKeySequence& ks, quint32* _key, quint32* _mod) + { + int code = ks[0]; + + quint32 mod = 0; + if (code & Qt::META) + mod |= controlKey; + if (code & Qt::SHIFT) + mod |= shiftKey; + if (code & Qt::CTRL) + mod |= cmdKey; + if (code & Qt::ALT) + mod |= optionKey; + + code &= ~Qt::KeyboardModifierMask; + quint32 key = 0; + for (int n = 0; qt_keymap[n].qt_key != Qt::Key_unknown; ++n) { + if (qt_keymap[n].qt_key == code) { + key = qt_keymap[n].mac_key; + break; + } + } + if (key == 0) { + key = asciiToKeyCode(&key_codes_, code & 0xffff); + } + + if (_mod) + *_mod = mod; + if (_key) + *_key = key; + + return true; + } +}; + +MacKeyTriggerManager* MacKeyTriggerManager::instance_ = NULL; +EventHandlerUPP MacKeyTriggerManager::hot_key_function_ = NULL; +MacKeyTriggerManager::Ascii2KeyCodeTable MacKeyTriggerManager::key_codes_; + +class GlobalShortcutManager::KeyTrigger::Impl : public MacKeyTrigger +{ +private: + KeyTrigger* trigger_; + EventHotKeyRef hotKey_; + int id_; + static int nextId; + +public: + /** + * Constructor registers the hotkey. + */ + Impl(GlobalShortcutManager::KeyTrigger* t, const QKeySequence& ks) + : trigger_(t) + , id_(0) + { + MacKeyTriggerManager::instance()->addTrigger(this); + + quint32 key, mod; + if (MacKeyTriggerManager::convertKeySequence(ks, &key, &mod)) { + EventHotKeyID hotKeyID; + hotKeyID.signature = 'QtHK'; + hotKeyID.id = nextId; + + OSStatus ret = RegisterEventHotKey(key, mod, hotKeyID, GetApplicationEventTarget(), 0, &hotKey_); + if (ret != 0) { + qWarning("RegisterEventHotKey(%d, %d): %d", key, mod, (int)ret); + return; + } + + id_ = nextId++; + } + } + + /** + * Destructor unregisters the hotkey. + */ + ~Impl() + { + MacKeyTriggerManager::instance()->removeTrigger(this); + + if (id_) + UnregisterEventHotKey(hotKey_); + } + + void activate() + { + emit trigger_->activated(); + } + + bool isAccepted(int id) const + { + return id_ == id; + } +}; + +/* + * The following table is from Apple sample-code. + * Apple's headers don't appear to define any constants for the virtual key + * codes of special keys, but these constants are somewhat documented in the chart at + * + * + * The constants on the chartappear to be the same values as are used in Apple's iGetKeys + * sample. + * . + * + * See also . + */ +MacKeyTriggerManager::Qt_Mac_Keymap +MacKeyTriggerManager::qt_keymap[] = { + { Qt::Key_Escape, 0x35 }, + { Qt::Key_Tab, 0x30 }, + { Qt::Key_Backtab, 0 }, + { Qt::Key_Backspace, 0x33 }, + { Qt::Key_Return, 0x24 }, + { Qt::Key_Enter, 0x4c }, // Return & Enter are different on the Mac + { Qt::Key_Insert, 0 }, + { Qt::Key_Delete, 0x75 }, + { Qt::Key_Pause, 0 }, + { Qt::Key_Print, 0 }, + { Qt::Key_SysReq, 0 }, + { Qt::Key_Clear, 0x47 }, + { Qt::Key_Home, 0x73 }, + { Qt::Key_End, 0x77 }, + { Qt::Key_Left, 0x7b }, + { Qt::Key_Up, 0x7e }, + { Qt::Key_Right, 0x7c }, + { Qt::Key_Down, 0x7d }, + { Qt::Key_PageUp, 0x74 }, // Page Up + { Qt::Key_PageDown, 0x79 }, // Page Down + { Qt::Key_Shift, 0x38 }, + { Qt::Key_Control, 0x3b }, + { Qt::Key_Meta, 0x37 }, // Command + { Qt::Key_Alt, 0x3a }, // Option + { Qt::Key_CapsLock, 57 }, + { Qt::Key_NumLock, 0 }, + { Qt::Key_ScrollLock, 0 }, + { Qt::Key_F1, 0x7a }, + { Qt::Key_F2, 0x78 }, + { Qt::Key_F3, 0x63 }, + { Qt::Key_F4, 0x76 }, + { Qt::Key_F5, 0x60 }, + { Qt::Key_F6, 0x61 }, + { Qt::Key_F7, 0x62 }, + { Qt::Key_F8, 0x64 }, + { Qt::Key_F9, 0x65 }, + { Qt::Key_F10, 0x6d }, + { Qt::Key_F11, 0x67 }, + { Qt::Key_F12, 0x6f }, + { Qt::Key_F13, 0x69 }, + { Qt::Key_F14, 0x6b }, + { Qt::Key_F15, 0x71 }, + { Qt::Key_F16, 0 }, + { Qt::Key_F17, 0 }, + { Qt::Key_F18, 0 }, + { Qt::Key_F19, 0 }, + { Qt::Key_F20, 0 }, + { Qt::Key_F21, 0 }, + { Qt::Key_F22, 0 }, + { Qt::Key_F23, 0 }, + { Qt::Key_F24, 0 }, + { Qt::Key_F25, 0 }, + { Qt::Key_F26, 0 }, + { Qt::Key_F27, 0 }, + { Qt::Key_F28, 0 }, + { Qt::Key_F29, 0 }, + { Qt::Key_F30, 0 }, + { Qt::Key_F31, 0 }, + { Qt::Key_F32, 0 }, + { Qt::Key_F33, 0 }, + { Qt::Key_F34, 0 }, + { Qt::Key_F35, 0 }, + { Qt::Key_Super_L, 0 }, + { Qt::Key_Super_R, 0 }, + { Qt::Key_Menu, 0 }, + { Qt::Key_Hyper_L, 0 }, + { Qt::Key_Hyper_R, 0 }, + { Qt::Key_Help, 0x72 }, + { Qt::Key_Direction_L, 0 }, + { Qt::Key_Direction_R, 0 }, + + { Qt::Key_unknown, 0 } +}; + +int GlobalShortcutManager::KeyTrigger::Impl::nextId = 1; + +GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence& key) +{ + d = new Impl(this, key); +} + +GlobalShortcutManager::KeyTrigger::~KeyTrigger() +{ + delete d; + d = 0; +} diff --git a/src/third-party/globalshortcutmanager_win.cpp b/src/third-party/globalshortcutmanager_win.cpp new file mode 100644 index 00000000..4db25fca --- /dev/null +++ b/src/third-party/globalshortcutmanager_win.cpp @@ -0,0 +1,205 @@ +/* + * globalshortcutmanager_win.cpp - Windows implementation of global shortcuts + * Copyright (C) 2003-2007 Justin Karneges, Maciej Niedzielski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// this code was copied from Psi (http://psi-im.org) + +#include "globalshortcutmanager.h" +#include "globalshortcuttrigger.h" + +#include + +#include + +class GlobalShortcutManager::KeyTrigger::Impl : public QWidget +{ +public: + /** + * Constructor registers the hotkey. + */ + Impl(GlobalShortcutManager::KeyTrigger* t, const QKeySequence& ks) + : trigger_(t) + , id_(0) + { + UINT mod, key; + if (convertKeySequence(ks, &mod, &key)) + if (RegisterHotKey(winId(), nextId, mod, key)) + id_ = nextId++; + } + + /** + * Destructor unregisters the hotkey. + */ + ~Impl() + { + if (id_) + UnregisterHotKey(winId(), id_); + } + + /** + * Triggers activated() signal when the hotkey is activated. + */ + bool winEvent(MSG* m, long* result) + { + if (m->message == WM_HOTKEY && m->wParam == id_) { + emit trigger_->activated(); + return true; + } + return QWidget::winEvent(m, result); + } + +private: + KeyTrigger* trigger_; + int id_; + static int nextId; + +private: + struct Qt_VK_Keymap + { + int key; + UINT vk; + }; + static Qt_VK_Keymap qt_vk_table[]; + + static bool convertKeySequence(const QKeySequence& ks, UINT* mod_, UINT* key_) + { + int code = ks; + + UINT mod = 0; + if (code & Qt::META) + mod |= MOD_WIN; + if (code & Qt::SHIFT) + mod |= MOD_SHIFT; + if (code & Qt::CTRL) + mod |= MOD_CONTROL; + if (code & Qt::ALT) + mod |= MOD_ALT; + + UINT key = 0; + code &= ~Qt::KeyboardModifierMask; + if (code >= 0x20 && code <= 0x7f) + key = code; + else { + for (int n = 0; qt_vk_table[n].key != Qt::Key_unknown; ++n) { + if (qt_vk_table[n].key == code) { + key = qt_vk_table[n].vk; + break; + } + } + if (!key) + return false; + } + + if (mod) + *mod_ = mod; + if (key) + *key_ = key; + + return true; + } +}; + +GlobalShortcutManager::KeyTrigger::Impl::Qt_VK_Keymap +GlobalShortcutManager::KeyTrigger::Impl::qt_vk_table[] = { + { Qt::Key_Escape, VK_ESCAPE }, + { Qt::Key_Tab, VK_TAB }, + { Qt::Key_Backtab, 0 }, + { Qt::Key_Backspace, VK_BACK }, + { Qt::Key_Return, VK_RETURN }, + { Qt::Key_Enter, VK_RETURN }, + { Qt::Key_Insert, VK_INSERT }, + { Qt::Key_Delete, VK_DELETE }, + { Qt::Key_Pause, VK_PAUSE }, + { Qt::Key_Print, VK_PRINT }, + { Qt::Key_SysReq, 0 }, + { Qt::Key_Clear, VK_CLEAR }, + { Qt::Key_Home, VK_HOME }, + { Qt::Key_End, VK_END }, + { Qt::Key_Left, VK_LEFT }, + { Qt::Key_Up, VK_UP }, + { Qt::Key_Right, VK_RIGHT }, + { Qt::Key_Down, VK_DOWN }, + { Qt::Key_PageUp, VK_PRIOR }, + { Qt::Key_PageDown, VK_NEXT }, + { Qt::Key_Shift, VK_SHIFT }, + { Qt::Key_Control, VK_CONTROL }, + { Qt::Key_Meta, VK_LWIN }, + { Qt::Key_Alt, VK_MENU }, + { Qt::Key_CapsLock, VK_CAPITAL }, + { Qt::Key_NumLock, VK_NUMLOCK }, + { Qt::Key_ScrollLock, VK_SCROLL }, + { Qt::Key_F1, VK_F1 }, + { Qt::Key_F2, VK_F2 }, + { Qt::Key_F3, VK_F3 }, + { Qt::Key_F4, VK_F4 }, + { Qt::Key_F5, VK_F5 }, + { Qt::Key_F6, VK_F6 }, + { Qt::Key_F7, VK_F7 }, + { Qt::Key_F8, VK_F8 }, + { Qt::Key_F9, VK_F9 }, + { Qt::Key_F10, VK_F10 }, + { Qt::Key_F11, VK_F11 }, + { Qt::Key_F12, VK_F12 }, + { Qt::Key_F13, VK_F13 }, + { Qt::Key_F14, VK_F14 }, + { Qt::Key_F15, VK_F15 }, + { Qt::Key_F16, VK_F16 }, + { Qt::Key_F17, VK_F17 }, + { Qt::Key_F18, VK_F18 }, + { Qt::Key_F19, VK_F19 }, + { Qt::Key_F20, VK_F20 }, + { Qt::Key_F21, VK_F21 }, + { Qt::Key_F22, VK_F22 }, + { Qt::Key_F23, VK_F23 }, + { Qt::Key_F24, VK_F24 }, + { Qt::Key_F25, 0 }, + { Qt::Key_F26, 0 }, + { Qt::Key_F27, 0 }, + { Qt::Key_F28, 0 }, + { Qt::Key_F29, 0 }, + { Qt::Key_F30, 0 }, + { Qt::Key_F31, 0 }, + { Qt::Key_F32, 0 }, + { Qt::Key_F33, 0 }, + { Qt::Key_F34, 0 }, + { Qt::Key_F35, 0 }, + { Qt::Key_Super_L, 0 }, + { Qt::Key_Super_R, 0 }, + { Qt::Key_Menu, 0 }, + { Qt::Key_Hyper_L, 0 }, + { Qt::Key_Hyper_R, 0 }, + { Qt::Key_Help, 0 }, + { Qt::Key_Direction_L, 0 }, + { Qt::Key_Direction_R, 0 }, + + { Qt::Key_unknown, 0 }, +}; + +int GlobalShortcutManager::KeyTrigger::Impl::nextId = 1; + +GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence& key) +{ + d = new Impl(this, key); +} + +GlobalShortcutManager::KeyTrigger::~KeyTrigger() +{ + delete d; + d = 0; +} diff --git a/src/third-party/globalshortcutmanager_x11.cpp b/src/third-party/globalshortcutmanager_x11.cpp new file mode 100644 index 00000000..e09be2f8 --- /dev/null +++ b/src/third-party/globalshortcutmanager_x11.cpp @@ -0,0 +1,439 @@ +/* + * globalshortcutmanager_x11.cpp - X11 implementation of global shortcuts + * Copyright (C) 2003-2007 Justin Karneges, Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// this code was copied from Psi (http://psi-im.org) + +#include "globalshortcutmanager.h" +#include "globalshortcuttrigger.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef KeyPress +// defined by X11 headers +const int XKeyPress = KeyPress; +const int XKeyRelease = KeyRelease; +#undef KeyPress +#endif + +class X11KeyTrigger +{ +public: + virtual ~X11KeyTrigger() {} + virtual void activate() = 0; + virtual bool isAccepted(int qkey) const = 0; +}; + +class X11KeyTriggerManager : public QObject +{ +public: + static X11KeyTriggerManager* instance() + { + if(!instance_) + instance_ = new X11KeyTriggerManager(); + return instance_; + } + + void addTrigger(X11KeyTrigger* trigger) + { + triggers_ << trigger; + } + + void removeTrigger(X11KeyTrigger* trigger) + { + triggers_.removeAll(trigger); + } + + struct Qt_XK_Keygroup + { + char num; + int sym[3]; + }; + +protected: + // reimplemented + bool eventFilter(QObject* o, QEvent* e) + { + if(e->type() == QEvent::KeyPress) { + QKeyEvent* k = static_cast(e); + int qkey = k->key(); + if (k->modifiers() & Qt::ShiftModifier) + qkey |= Qt::SHIFT; + if (k->modifiers() & Qt::ControlModifier) + qkey |= Qt::CTRL; + if (k->modifiers() & Qt::AltModifier) + qkey |= Qt::ALT; + if (k->modifiers() & Qt::MetaModifier) + qkey |= Qt::META; + + foreach(X11KeyTrigger* trigger, triggers_) { + if (trigger->isAccepted(qkey)) { + trigger->activate(); + return true; + } + } + } + + return QObject::eventFilter(o, e); + } + +private: + X11KeyTriggerManager() + : QObject(QCoreApplication::instance()) + { + QCoreApplication::instance()->installEventFilter(this); + } + + static X11KeyTriggerManager* instance_; + QList triggers_; + +private: + struct Qt_XK_Keymap + { + int key; + Qt_XK_Keygroup xk; + }; + + static Qt_XK_Keymap qt_xk_table[]; + static long alt_mask; + static long meta_mask; + static long super_mask; + static long hyper_mask; + static long numlock_mask; + static bool haveMods; + + // adapted from qapplication_x11.cpp + static void ensureModifiers() + { + if (haveMods) + return; + + Display* appDpy = QX11Info::display(); + XModifierKeymap* map = XGetModifierMapping(appDpy); + if (map) { + // XKeycodeToKeysym helper code adapeted from xmodmap + int min_keycode, max_keycode, keysyms_per_keycode = 1; + XDisplayKeycodes (appDpy, &min_keycode, &max_keycode); + XFree(XGetKeyboardMapping (appDpy, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode)); + + int i, maskIndex = 0, mapIndex = 0; + for (maskIndex = 0; maskIndex < 8; maskIndex++) { + for (i = 0; i < map->max_keypermod; i++) { + if (map->modifiermap[mapIndex]) { + KeySym sym; + int symIndex = 0; + do { + sym = XKeycodeToKeysym(appDpy, map->modifiermap[mapIndex], symIndex); + symIndex++; + } while ( !sym && symIndex < keysyms_per_keycode); + if (alt_mask == 0 && (sym == XK_Alt_L || sym == XK_Alt_R)) { + alt_mask = 1 << maskIndex; + } + if (meta_mask == 0 && (sym == XK_Meta_L || sym == XK_Meta_R)) { + meta_mask = 1 << maskIndex; + } + if (super_mask == 0 && (sym == XK_Super_L || sym == XK_Super_R)) { + super_mask = 1 << maskIndex; + } + if (hyper_mask == 0 && (sym == XK_Hyper_L || sym == XK_Hyper_R)) { + hyper_mask = 1 << maskIndex; + } + if (numlock_mask == 0 && (sym == XK_Num_Lock)) { + numlock_mask = 1 << maskIndex; + } + } + mapIndex++; + } + } + + XFreeModifiermap(map); + + // logic from qt source see gui/kernel/qkeymapper_x11.cpp + if (meta_mask == 0 || meta_mask == alt_mask) { + // no meta keys... s,meta,super, + meta_mask = super_mask; + if (meta_mask == 0 || meta_mask == alt_mask) { + // no super keys either? guess we'll use hyper then + meta_mask = hyper_mask; + } + } + } + else { + // assume defaults + alt_mask = Mod1Mask; + meta_mask = Mod4Mask; + } + + haveMods = true; + } + +public: + static bool convertKeySequence(const QKeySequence& ks, unsigned int* _mod, Qt_XK_Keygroup* _kg) + { + int code = ks; + ensureModifiers(); + + unsigned int mod = 0; + if (code & Qt::META) + mod |= meta_mask; + if (code & Qt::SHIFT) + mod |= ShiftMask; + if (code & Qt::CTRL) + mod |= ControlMask; + if (code & Qt::ALT) + mod |= alt_mask; + + Qt_XK_Keygroup kg; + kg.num = 0; + kg.sym[0] = 0; + code &= ~Qt::KeyboardModifierMask; + + bool found = false; + for (int n = 0; qt_xk_table[n].key != Qt::Key_unknown; ++n) { + if (qt_xk_table[n].key == code) { + kg = qt_xk_table[n].xk; + found = true; + break; + } + } + + if (!found) { + // try latin1 + if (code >= 0x20 && code <= 0x7f) { + kg.num = 1; + kg.sym[0] = code; + } + } + + if (!kg.num) + return false; + + if (_mod) + *_mod = mod; + if (_kg) + *_kg = kg; + + return true; + } + + static QList ignModifiersList() + { + QList ret; + if (numlock_mask) { + ret << 0 << LockMask << numlock_mask << (LockMask | numlock_mask); + } + else { + ret << 0 << LockMask; + } + return ret; + } +}; + +X11KeyTriggerManager* X11KeyTriggerManager::instance_ = NULL; + +class GlobalShortcutManager::KeyTrigger::Impl : public X11KeyTrigger +{ +private: + KeyTrigger* trigger_; + int qkey_; + + struct GrabbedKey { + int code; + uint mod; + }; + QList grabbedKeys_; + + static bool failed; + static int XGrabErrorHandler(Display *, XErrorEvent *) + { + qWarning("failed to grab key"); + failed = true; + return 0; + } + + void bind(int keysym, unsigned int mod) + { + int code = XKeysymToKeycode(QX11Info::display(), keysym); + + // don't grab keys with empty code (because it means just the modifier key) + if (keysym && !code) + return; + + failed = false; + XErrorHandler savedErrorHandler = XSetErrorHandler(XGrabErrorHandler); + WId w = QX11Info::appRootWindow(); + foreach(long mask_mod, X11KeyTriggerManager::ignModifiersList()) { + XGrabKey(QX11Info::display(), code, mod | mask_mod, w, False, GrabModeAsync, GrabModeAsync); + GrabbedKey grabbedKey; + grabbedKey.code = code; + grabbedKey.mod = mod | mask_mod; + grabbedKeys_ << grabbedKey; + } + XSync(QX11Info::display(), False); + XSetErrorHandler(savedErrorHandler); + } + +public: + /** + * Constructor registers the hotkey. + */ + Impl(GlobalShortcutManager::KeyTrigger* t, const QKeySequence& ks) + : trigger_(t) + , qkey_(ks) + { + X11KeyTriggerManager::instance()->addTrigger(this); + + X11KeyTriggerManager::Qt_XK_Keygroup kg; + unsigned int mod; + if (X11KeyTriggerManager::convertKeySequence(ks, &mod, &kg)) + for (int n = 0; n < kg.num; ++n) + bind(kg.sym[n], mod); + } + + /** + * Destructor unregisters the hotkey. + */ + ~Impl() + { + X11KeyTriggerManager::instance()->removeTrigger(this); + + foreach(GrabbedKey key, grabbedKeys_) + XUngrabKey(QX11Info::display(), key.code, key.mod, QX11Info::appRootWindow()); + } + + void activate() + { + emit trigger_->activated(); + } + + bool isAccepted(int qkey) const + { + return qkey_ == qkey; + } +}; + +bool GlobalShortcutManager::KeyTrigger::Impl::failed; +long X11KeyTriggerManager::alt_mask = 0; +long X11KeyTriggerManager::meta_mask = 0; +long X11KeyTriggerManager::super_mask = 0; +long X11KeyTriggerManager::hyper_mask = 0; +long X11KeyTriggerManager::numlock_mask = 0; +bool X11KeyTriggerManager::haveMods = false; + +X11KeyTriggerManager::Qt_XK_Keymap +X11KeyTriggerManager::qt_xk_table[] = { + { Qt::Key_Escape, {1, { XK_Escape }}}, + { Qt::Key_Tab, {2, { XK_Tab, XK_KP_Tab }}}, + { Qt::Key_Backtab, {1, { XK_ISO_Left_Tab }}}, + { Qt::Key_Backspace, {1, { XK_BackSpace }}}, + { Qt::Key_Return, {1, { XK_Return }}}, + { Qt::Key_Enter, {1, { XK_KP_Enter }}}, + { Qt::Key_Insert, {2, { XK_Insert, XK_KP_Insert }}}, + { Qt::Key_Delete, {3, { XK_Delete, XK_KP_Delete, XK_Clear }}}, + { Qt::Key_Pause, {1, { XK_Pause }}}, + { Qt::Key_Print, {1, { XK_Print }}}, + { Qt::Key_SysReq, {1, { XK_Sys_Req }}}, + { Qt::Key_Clear, {1, { XK_KP_Begin }}}, + { Qt::Key_Home, {2, { XK_Home, XK_KP_Home }}}, + { Qt::Key_End, {2, { XK_End, XK_KP_End }}}, + { Qt::Key_Left, {2, { XK_Left, XK_KP_Left }}}, + { Qt::Key_Up, {2, { XK_Up, XK_KP_Up }}}, + { Qt::Key_Right, {2, { XK_Right, XK_KP_Right }}}, + { Qt::Key_Down, {2, { XK_Down, XK_KP_Down }}}, + { Qt::Key_PageUp, {2, { XK_Prior, XK_KP_Prior }}}, + { Qt::Key_PageDown, {2, { XK_Next, XK_KP_Next }}}, + { Qt::Key_Shift, {3, { XK_Shift_L, XK_Shift_R, XK_Shift_Lock }}}, + { Qt::Key_Control, {2, { XK_Control_L, XK_Control_R }}}, + { Qt::Key_Meta, {2, { XK_Meta_L, XK_Meta_R }}}, + { Qt::Key_Alt, {2, { XK_Alt_L, XK_Alt_R }}}, + { Qt::Key_CapsLock, {1, { XK_Caps_Lock }}}, + { Qt::Key_NumLock, {1, { XK_Num_Lock }}}, + { Qt::Key_ScrollLock, {1, { XK_Scroll_Lock }}}, + { Qt::Key_Space, {2, { XK_space, XK_KP_Space }}}, + { Qt::Key_Equal, {2, { XK_equal, XK_KP_Equal }}}, + { Qt::Key_Asterisk, {2, { XK_asterisk, XK_KP_Multiply }}}, + { Qt::Key_Plus, {2, { XK_plus, XK_KP_Add }}}, + { Qt::Key_Comma, {2, { XK_comma, XK_KP_Separator }}}, + { Qt::Key_Minus, {2, { XK_minus, XK_KP_Subtract }}}, + { Qt::Key_Period, {2, { XK_period, XK_KP_Decimal }}}, + { Qt::Key_Slash, {2, { XK_slash, XK_KP_Divide }}}, + { Qt::Key_F1, {1, { XK_F1 }}}, + { Qt::Key_F2, {1, { XK_F2 }}}, + { Qt::Key_F3, {1, { XK_F3 }}}, + { Qt::Key_F4, {1, { XK_F4 }}}, + { Qt::Key_F5, {1, { XK_F5 }}}, + { Qt::Key_F6, {1, { XK_F6 }}}, + { Qt::Key_F7, {1, { XK_F7 }}}, + { Qt::Key_F8, {1, { XK_F8 }}}, + { Qt::Key_F9, {1, { XK_F9 }}}, + { Qt::Key_F10, {1, { XK_F10 }}}, + { Qt::Key_F11, {1, { XK_F11 }}}, + { Qt::Key_F12, {1, { XK_F12 }}}, + { Qt::Key_F13, {1, { XK_F13 }}}, + { Qt::Key_F14, {1, { XK_F14 }}}, + { Qt::Key_F15, {1, { XK_F15 }}}, + { Qt::Key_F16, {1, { XK_F16 }}}, + { Qt::Key_F17, {1, { XK_F17 }}}, + { Qt::Key_F18, {1, { XK_F18 }}}, + { Qt::Key_F19, {1, { XK_F19 }}}, + { Qt::Key_F20, {1, { XK_F20 }}}, + { Qt::Key_F21, {1, { XK_F21 }}}, + { Qt::Key_F22, {1, { XK_F22 }}}, + { Qt::Key_F23, {1, { XK_F23 }}}, + { Qt::Key_F24, {1, { XK_F24 }}}, + { Qt::Key_F25, {1, { XK_F25 }}}, + { Qt::Key_F26, {1, { XK_F26 }}}, + { Qt::Key_F27, {1, { XK_F27 }}}, + { Qt::Key_F28, {1, { XK_F28 }}}, + { Qt::Key_F29, {1, { XK_F29 }}}, + { Qt::Key_F30, {1, { XK_F30 }}}, + { Qt::Key_F31, {1, { XK_F31 }}}, + { Qt::Key_F32, {1, { XK_F32 }}}, + { Qt::Key_F33, {1, { XK_F33 }}}, + { Qt::Key_F34, {1, { XK_F34 }}}, + { Qt::Key_F35, {1, { XK_F35 }}}, + { Qt::Key_Super_L, {1, { XK_Super_L }}}, + { Qt::Key_Super_R, {1, { XK_Super_R }}}, + { Qt::Key_Menu, {1, { XK_Menu }}}, + { Qt::Key_Hyper_L, {1, { XK_Hyper_L }}}, + { Qt::Key_Hyper_R, {1, { XK_Hyper_R }}}, + { Qt::Key_Help, {1, { XK_Help }}}, + { Qt::Key_Direction_L, {0, { 0 }}}, + { Qt::Key_Direction_R, {0, { 0 }}}, + + { Qt::Key_unknown, {0, { 0 }}}, +}; + +GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence& key) +{ + d = new Impl(this, key); +} + +GlobalShortcutManager::KeyTrigger::~KeyTrigger() +{ + delete d; + d = 0; +} diff --git a/src/third-party/globalshortcuttrigger.h b/src/third-party/globalshortcuttrigger.h new file mode 100644 index 00000000..fec42047 --- /dev/null +++ b/src/third-party/globalshortcuttrigger.h @@ -0,0 +1,63 @@ +/* + * globalshortcuttrigger.h - Helper class activating global shortcut + * Copyright (C) 2006-2007 Maciej Niedzielski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// this code was copied from Psi (http://psi-im.org) + +#ifndef GLOBALSHORTCUTTRIGGER_H +#define GLOBALSHORTCUTTRIGGER_H + +#include "globalshortcutmanager.h" +#include + +class GlobalShortcutManager::KeyTrigger : public QObject +{ + Q_OBJECT +public: + /** + * Is there any slot connected to this hotkey? + */ + bool isUsed() const + { + return QObject::receivers(SIGNAL(activated())) > 0; + } + +signals: + void activated(); + +private: + /** + * Registers the \a key. + */ + KeyTrigger(const QKeySequence& key); + /** + * Unregisters the key. + */ + ~KeyTrigger(); + + friend class GlobalShortcutManager; + + /** + * Platform-specific helper + */ + class Impl; + Impl* d; +}; + +#endif