How to capture the global keys in Ubuntu in Qt application. I need to handle the keys like Ctrl, Shift even my qt application is minimized state also. Looks like LibQxt supports this. But as per my understanding this library won't have any support from Qt4. I am using Qt5.7. Do we have any other way to do this ?
This can be achieved using x11/xcb. The idea is to listen to a specific keyboard shortcut, system-wide, using the XGrabKey function from xlib, then catching the corresponding xcb event in the overridden nativeEventFilter method of a QAbstractNativeEventFilter subclass.
As an example, let's activate an application minimized window, using the Ctrl-A shortcut from anywhere.
The project must reference the x11extra qt module, and link the x11 library:
QT += x11extras
LIBS += -lX11
This is the filter header:
#include <QAbstractNativeEventFilter>
#include <QWidget>
class EventFilter : public QAbstractNativeEventFilter
{
public:
void setup(QWidget *target);
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
private:
int keycode;
QWidget * target;
};
and this is the implementation:
#include <xcb/xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <QtX11Extras/QX11Info>
void EventFilter::setup(QWidget *target)
{
this->target = target;
Display * display = QX11Info::display();
unsigned int modifiers = ControlMask;
keycode = XKeysymToKeycode(display, XK_A);
XGrabKey(display, keycode, modifiers, DefaultRootWindow(display), 1, GrabModeAsync, GrabModeAsync);
}
bool EventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *)
{
if (eventType == "xcb_generic_event_t")
{
xcb_generic_event_t* xcbevent = static_cast<xcb_generic_event_t *>(message);
switch(xcbevent->response_type)
{
case XCB_KEY_PRESS:
xcb_key_press_event_t * keypress_event = static_cast<xcb_key_press_event_t *>(message);
if(keypress_event->state & XCB_MOD_MASK_CONTROL)
{
if(keypress_event->detail == keycode)
{
qDebug() << "ACTIVATING ...";
target->activateWindow();
}
}
}
}
return false;
}
In a main, we create a widget on the fly, instantiate, install and setup our filter.
#include "eventfilter.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setGeometry(100, 100, 400, 300);
w.show();
EventFilter filter;
a.installNativeEventFilter(&filter);
filter.setup(&w);
return a.exec();
}
If the user minimize the widget, or the widget is deactivated, the Ctrl-A shortcut should prompt it back to the foreground.
Please notice that the XGrabKey call from the setup method could fail if some other x client grabbed the same key combination, already.
Also notice that extra modifier masks will be delivered in the event state field if some lock keys are on (e.g. on my system the pressed caps lock yelds an extra XCB_MOD_MASK_LOCK, and the num lock an extra XCB_MOD_MASK_2).
Related
I am using Qt Creator 4.5.2 (Qt 5.9.5, GCC 7.3.0 64-bit) and running on Ubuntu 18.04.
I just customized the 'QTimeEdit' widget and it worked. Now, I need to intercept the event(s) for the 'Up' and 'Down' arrow (the arrow to change the current time). But, I don't know which event it is.
I already installed the 'evenFilter' and could catch all the events. I printed out all of them. But, I did not see an event trigger when I clicked the 'Up' or 'Down' arrow (The time did get changed). I assumed it was 'keyPressEvent' but apparently it was not since it did not trigger this method when I clicked the 'Up' or 'Down' arrow.
Any idea what events for the 'Up' or 'Down' arrow in the 'QTimeEdit' widget?
void MyTimeEdit::keyPressEvent(QKeyEvent *e)
{
qInfo() << "Key Press";
QTimeEdit::keyPressEvent(e);
}
bool MyTimeEdit::eventFilter(QObject *watched, QEvent *event)
{
if(watched == lineEdit())
qInfo() << "Event Type: " << event->type();
return QTimeEdit::eventFilter(watched, event);
}
If you click with the mouse then the event cannot be caused by a keyboard key.
In this case if you want to detect when you click on the "Up" or "Down" arrow of the QTimeEdit then you must use the mousePressEvent method:
#include <QApplication>
#include <QMouseEvent>
#include <QStyleOptionSpinBox>
#include <QTimeEdit>
#include <QDebug>
class TimeEdit: public QTimeEdit{
public:
using QTimeEdit::QTimeEdit;
protected:
void mousePressEvent(QMouseEvent *event){
QTimeEdit::mousePressEvent(event);
QStyleOptionSpinBox opt;
initStyleOption(&opt);
QStyle::SubControls control = style()->hitTestComplexControl(
QStyle::CC_SpinBox, &opt, event->pos(), this);
if(control == QStyle::SC_SpinBoxUp)
qDebug() << "up";
else if (control == QStyle::SC_SpinBoxDown) {
qDebug() << "down";
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TimeEdit w;
w.show();
return a.exec();
}
But keep in mind that not only the clicks do change the value of what the QTimeEdit shows but also the keyboard, the wheel, etc., so if you want to cover all those cases it is better to track override the stepBy() method
#include <QApplication>
#include <QTimeEdit>
#include <QDebug>
class TimeEdit: public QTimeEdit{
public:
using QTimeEdit::QTimeEdit;
void stepBy(int steps){
qDebug() << steps;
QTimeEdit::stepBy(steps);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TimeEdit w;
w.show();
return a.exec();
}
I migrated some code from Qt 5.6.0 to 5.12.0. Suprisingly, I'm getting lots of warnings related to QWindowsWindow::setGeometry. Whenever a dialog is shown on top of another, I get this warning.
I could isolate the problem in a MCVE, it's very simple and minimal, all parenting look good, however, we get the warning when button is pressed:
QWindowsWindow::setGeometry: Unable to set geometry 132x30+682+303 on QWidgetWindow/'QDialogClassWindow'. Resulting geometry: 132x42+682+303 (frame: 4, 28, 4, 4, custom margin: 0, 0, 0, 0, minimum size: 116x42, maximum size: 16777215x16777215).
main.cpp:
#include <QApplication>
#include "mainframe.h"
#include <qDebug>
void MessageOutput( QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
qDebug() << msg;
}
int main( int argc, char* argv[] )
{
QApplication app(argc, argv);
qInstallMessageHandler(MessageOutput);
MainFrame wnd;
wnd.show();
return app.exec();
}
mainframe.h:
#include <QMainWindow>
class QPushButton;
class MainFrame : public QMainWindow
{
Q_OBJECT
public:
MainFrame();
public slots:
void showPopup();
private:
QPushButton* button;
};
mainframe.cpp:
#include "mainframe.h"
#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
MainFrame::MainFrame()
{
QWidget* widget = new QWidget( this );
widget->setLayout( new QVBoxLayout( widget ) );
QPushButton* pTextButton = new QPushButton( "Show popup", widget );
widget->layout()->addWidget( pTextButton );
connect( pTextButton, SIGNAL(clicked()), this, SLOT(showPopup()) );
setCentralWidget( widget );
}
void MainFrame::showPopup()
{
QDialog dlg( this );
dlg.setLayout( new QVBoxLayout() );
dlg.layout()->addWidget( new QLabel("popup message",&dlg) );
dlg.exec();
}
I see the issue under Windows 7 and 10. Am I doing anything wrong?
I know the warning can be removed by setting setMinimumSize (see https://stackoverflow.com/a/31231069/3336423), but why should we do this for every widget we create? Is there a way to fix that for good?
As you mentioned, this problem occurs only in Windows: the QWindowsWindow class is part of the windows platform plugin. Looking at Qt's source code (qwindowswindow.cpp#QWindowsWindow::setGeometry) there is no direct way to pause that specific message.
The only global solution I can think of right now is to filter the warning messages using a message handler:
void myMessageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
if (type != QtWarningMsg || !msg.startsWith("QWindowsWindow::setGeometry")) {
QByteArray localMsg = msg.toLocal8Bit();
fprintf(stdout, localMsg.constData());
}
}
int main(int argc, char* argv[])
{
qInstallMessageHandler(myMessageOutput);
QApplication a(argc, argv);
// ...
}
UPDATE
One of the problems is that Windows adds its own buttons to the frame. In your example the dialog adds three buttons: the system button (the icon, top-left corner), the help button and the close button. The help and close buttons have a fixed size, which happens to be larger than the QDialog's frame (which is computed as the maximum between the requested size and minimumSize). This then generates the warning: your requested size doesn't match the one created by Windows:
If you remove the help button, for example (dlg.setWindowFlags(dlg.windowFlags() & ~Qt::WindowContextHelpButtonHint);), the warning disappears without setting a minimum size for the window. A manual action must be taken for each dialog displayed, but I think it is easier to automatize than the minimum size (through a factory maybe?):
The issue was reported to Qt:
https://bugreports.qt.io/browse/QTBUG-73258
To the code in OP is OK, it's just a Qt bug.
It's marked as "P2 Important", so hopefully it should be fixed in a next release.
Update: It's still not fixed in Qt 6.2.2...
I have QDialogButtonBox buttons on my dialog which are Ok and Cancel button pair. I have implemented accepted() signal to process when 'Ok' button is pressed but I want to abort quitting dialog if the directory path is invalid.
void SettingsDialog::on_buttonBox_accepted()
{
QDir path( ui->lineEditRootPath->text() );
if ( path.exists( ui->lineEditRootPath->text() ) )
{
QSettings settings; // save settings to registry
settings.setValue(ROOT_PATH, ui->lineEditRootPath->text() );
}
else
{
// abort cancelling the dialog here
}
}
Can the dialog quitting be abort from this handler? Do I have to implement the above code in some other signal? Do I have to use simple button to accomplish this instead of QDialogButtonBox?
This issue comes from the dialog template bundled with Qt Creator. When you create an empty dialog with buttons, the .ui file has connections between the button box and and the underlying dialog. They are created behind your back, so to speak:
So, there really is no problem, since the button box doesn't actually accept the dialog. You must accept the dialog, if you don't then the dialog stays open.
The simple fix is to remove the default connection(s).
Other Nitpicks
You should not use the QDir::exists(const QString &) overload - it won't work. You already provided the path to dir's constructor. Simply use exists().
Thus:
void SettingsDialog::on_buttonBox_accepted()
{
QDir path(ui->lineEditRootPath->text());
if (!path.exists()) return;
QSettings settings; // save settings to registry
settings.setValue(ROOT_PATH, ui->lineEditRootPath->text());
accept(); // accepts the dialog, closing it
}
You could also use the static QFileInfo::exists:
void SettingsDialog::on_buttonBox_accepted()
{
if (! QFileInfo.exists(ui->lineEditRootPath->text()) return;
...
}
Finally, it's probably a nice idea to provide some sort of feedback to the user when an input is invalid. In C++11, that's quite easy to do:
#include <QApplication>
#include <QFileInfo>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QGridLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog dialog;
QLineEdit edit("/");
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
QGridLayout layout(&dialog);
layout.addWidget(&edit, 0, 0);
layout.addWidget(&buttons, 1, 0);
QObject::connect(&buttons, &QDialogButtonBox::accepted, [&]{
if (!QFileInfo::exists(edit.text())) return;
//...
dialog.accept();
});
QObject::connect(&buttons, &QDialogButtonBox::rejected, [&]{ dialog.reject(); });
QObject::connect(&edit, &QLineEdit::textChanged, [&](const QString&){
if (QFileInfo::exists(edit.text()))
edit.setStyleSheet("");
else
edit.setStyleSheet("* { background: red; }");
});
dialog.show();
return a.exec();
}
After some testing, you've realized that the users have a bad tendency to enter paths that might be on disconnected network volumes. When you attempt to check if they exist, it blocks the GUI just so that the OS can politely tell you "umm, nope".
The solution is to perform the check in a worker thread, so that if it blocks, the UI will not be directly affected. If the worker thread blocks, the path editor background will turn yellow. If the path doesn't exist, the background will turn red and the OK button will be disabled.
One bit of code requires some explanation: QObject::connect(&checker, &Checker::exists, &app, [&](...){...}) connects the checker's signal to a lambda in the thread context of the application object. Since checker's signals are emitted in the checker's thread, without the context (&app), the code would be executed in the checker's thread. We definitely don't want that, the GUI changes must be executed in the main thread. The simplest way to do it is to pass one object we surely know lives in the main thread: the application instance. If you don't pass the proper context, e.g. QObject::connect(&checker, &Checker::exists, [&](...){...})), you'll get undefined behavior and a crash.
#include <QApplication>
#include <QFileInfo>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QThread>
#include <QTimer>
class Thread : public QThread {
using QThread::run; // final
public:
~Thread() { quit(); wait(); }
};
class Checker : public QObject {
Q_OBJECT
public:
Q_SIGNAL void exists(bool, const QString & path);
Q_SLOT void check(const QString & path) { emit exists(QFileInfo::exists(path), path); }
};
int main(int argc, char *argv[])
{
bool pathExists = true;
QApplication app(argc, argv);
QDialog dialog;
QLineEdit edit("/");
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
QGridLayout layout(&dialog);
layout.addWidget(&edit, 0, 0);
layout.addWidget(&buttons, 1, 0);
QTimer checkTimer;
Checker checker;
Thread checkerThread;
checker.moveToThread(&checkerThread);
checkerThread.start();
checkTimer.setInterval(500);
checkTimer.setSingleShot(true);
QObject::connect(&buttons, &QDialogButtonBox::accepted, [&]{
if (!pathExists) return;
//...
dialog.accept();
});
QObject::connect(&buttons, &QDialogButtonBox::rejected, [&]{ dialog.reject(); });
QObject::connect(&edit, &QLineEdit::textChanged, &checker, &Checker::check);
QObject::connect(&edit, &QLineEdit::textChanged, &checkTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
QObject::connect(&checkTimer, &QTimer::timeout, [&]{ edit.setStyleSheet("background: yellow"); });
QObject::connect(&checker, &Checker::exists, &app, [&](bool ok, const QString & path){
if (path != edit.text()) return; // stale result
checkTimer.stop();
edit.setStyleSheet(ok ? "" : "background: red");
buttons.button(QDialogButtonBox::Ok)->setEnabled(ok);
pathExists = ok;
});
dialog.show();
return app.exec();
}
#include "main.moc"
I'm building a simple C++ application on Mac OS X 10.9 with Qt 5.2.1 using CMake (without MOC).
I am starting the executable from the command-line. The problem is that the menu bar is not showing up at all, the Terminal menu bar is still visible but not clickable. When I switch windows temporarily and then come back to the window of this application, I at least see the standard "application" menu with "About". The "About" action is now working and shows the dialog. The toolbar button also works as expected.
What else I tried (and didn't work):
using the pre-defined menuBar()
use setMenuBar()
new menuBar(0)
menubar->setVisible(true)
When I check the isVisible() it returns false, also if I set it to visible in the line before.
I wonder whether the lack of using MOC can be the reason for this?
Below I attached a reduced example.
#include <QtGui>
#include <QtWidgets>
class MainWindow : public QMainWindow {
public:
MainWindow();
private:
void create_actions_();
void create_menus_();
void create_toolbar_();
void about_();
QMenuBar* menu_bar_;
QMenu* file_menu_;
QMenu* help_menu_;
QToolBar* file_toolbar_;
QAction* action_about_;
};
MainWindow::MainWindow() {
resize(800, 600);
create_actions_();
create_menus_();
create_toolbar_();
}
void MainWindow::create_actions_() {
action_about_ = new QAction(tr("About"), this);
connect(action_about_, &QAction::triggered, this, &MainWindow::about_);
}
void MainWindow::create_menus_() {
menu_bar_ = new QMenuBar(this);
file_menu_ = menu_bar_->addMenu(tr("&File"));
menu_bar_->addSeparator();
help_menu_ = menu_bar_->addMenu(tr("&Help"));
help_menu_->addAction(action_about_);
menu_bar_->setNativeMenuBar(true);
}
void MainWindow::create_toolbar_() {
file_toolbar_ = addToolBar(tr("File"));
file_toolbar_->addAction(action_about_);
file_toolbar_->setIconSize(QSize(16, 16));
}
void MainWindow::about_() {
QMessageBox::about(this, tr("About"), tr("FooBar"));
}
int main(int argc, char **argv) {
QApplication app(argc, argv);
MainWindow main_window;
main_window.show();
const int exit_code = app.exec();
return exit_code;
}
CMakeLists.txt
FIND_PACKAGE(Qt5Core)
FIND_PACKAGE(Qt5Gui)
FIND_PACKAGE(Qt5OpenGL)
FIND_PACKAGE(Qt5Widgets)
FIND_PACKAGE(Qt5Declarative)
FIND_PACKAGE(Qt5MacExtras)
ADD_EXECUTABLE(main main.cc)
qt5_use_modules(main Core Gui Widgets Declarative MacExtras)
Thanks a lot in advance!
OK, solved the problem myself. It appears you cannot add a separator to the menubar.
Removing the menu_bar_->addSeparator(); solved the problem.
There is a QApplication::lastWindowClosed() signal. The Qt docs say:
This signal is emitted from QApplication::exec()
when the last visible primary window [...] is closed.
However, I used QApplication::processEvents() instead of QApplication::exec() in a loop. See this minimal example. (save as qapp.h, it must end on .h, and run qmake -project && qmake && make)
#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QMainWindow>
int exitprogram = 0;
class MyMainWindow : public QMainWindow
{
Q_OBJECT
public slots:
void request_exit() {
qDebug() << "exit requested";
exitprogram = 1;
}
};
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyMainWindow w;
QObject::connect(&app, SIGNAL(lastWindowClosed()),
&w, SLOT(request_exit()));
w.show();
while(!exitprogram)
{
app.processEvents(QEventLoop::AllEvents, 20);
}
}
Is there still a good way to find out, or to even get a signal, if the last such window is being closed?
Whatever reason you have for using processEvents in place of exec is wrong. The two are not equivalent. exec() will, for example, process the deferred deletion events, while processEvents will not. As you've just found out, the lastWindowClosed signal is not emitted either. This should be telling you right there that you're doing it wrong.
The idiomatic Qt way of doing something each time the event loop goes for another iteration, is to use a zero-timeout timer. Those are virtual timers that do not use operating system resources, they are an internal Qt construct.
The example below illustrates the following:
Use of a zero-timeout timer within a QObject.
Use of the State Machine Framework to manage the state of the application. We have three states:
sWindows is the state when the application windows are still shown. The application is set not to quit on the last window being closed.
sSetup is the state reached when the last of the windows was closed. In this state we ask our Object to send its notification signal with the number of times it executed the zero-timeout timer. This will set the proper count in the message label (C++11 code) or in the count label (legacy code). The state machine automatically transitions to the following state.
sMessage is the state when the message labels are shown, and the application is set to quit upon the last window being closed.
The use of a state machine leads to declarative code: you tell the state machine how to behave, without implementing all of the behavior. You only have to implement the behaviors that are specific to your application and not already provided by Qt. The objects that the state machine manages can be very much decoupled, and the code that declares the behavior of the machine is cohesive - it can be all in one function, instead of being spread around. This is considered to be good software design.
Do note that the zero-timeout timer is very diligent: it will force your handler code to execute constantly whenever the event loop is empty. This will force 100% CPU consumption on the core where the GUI thread happens to be executing. If you have nothing to do, you should stop() the timer.
Qt 5 C++11 Code
// https://github.com/KubaO/stackoverflown/tree/master/questions/close-process-19343325
#include <QtWidgets>
int main(int argc, char** argv)
{
QApplication app{argc, argv};
QLabel widget{"Close me :)"};
QLabel message{"Last window was closed"};
int counter = 0;
auto worker = [&]{
counter++;
};
QTimer workerTimer;
QObject::connect(&workerTimer, &QTimer::timeout, worker);
workerTimer.start(0);
QStateMachine machine;
QState sWindows{&machine};
QState sSetup {&machine};
QState sMessage{&machine};
sWindows.assignProperty(qApp, "quitOnLastWindowClosed", false);
sWindows.addTransition(qApp, &QGuiApplication::lastWindowClosed, &sSetup);
QObject::connect(&sSetup, &QState::entered, [&]{
workerTimer.stop();
message.setText(QString("Last window was closed. Count was %1.").arg(counter));
});
sSetup.addTransition(&sMessage);
sMessage.assignProperty(&message, "visible", true);
sMessage.assignProperty(qApp, "quitOnLastWindowClosed", true);
machine.setInitialState(&sWindows);
machine.start();
widget.show();
return app.exec();
}
Qt 4/5 C++11 Code
#include <QApplication>
#include <QLabel>
#include <QStateMachine>
#include <QBasicTimer>
class Object : public QObject {
Q_OBJECT
QBasicTimer m_timer;
int m_counter = 0;
protected:
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() == m_timer.timerId())
m_counter ++;
}
public:
Object(QObject * parent = 0) : QObject{parent} {
m_timer.start(0, this);
}
Q_SLOT void stop() const {
m_timer.stop();
emit countedTo(m_counter);
}
Q_SIGNAL void countedTo(int) const;
};
int main(int argc, char** argv)
{
QApplication app{argc, argv};
Object object;
QLabel widget{"Close me :)"};
QLabel message{"Last window was closed"};
QLabel count;
QStateMachine machine;
QState sWindows{&machine};
QState sSetup{&machine};
QState sMessage{&machine};
sWindows.assignProperty(qApp, "quitOnLastWindowClosed", false);
sWindows.addTransition(qApp, "lastWindowClosed()", &sSetup);
object.connect(&sSetup, SIGNAL(entered()), SLOT(stop()));
count.connect(&object, SIGNAL(countedTo(int)), SLOT(setNum(int)));
sSetup.addTransition(&sMessage);
sMessage.assignProperty(&message, "visible", true);
sMessage.assignProperty(&count, "visible", true);
sMessage.assignProperty(qApp, "quitOnLastWindowClosed", true);
machine.setInitialState(&sWindows);
machine.start();
widget.show();
return app.exec();
}
#include "main.moc"
I modified your code and now it works. I override closeEvent
==> test.cpp <==
#include "windows.hpp"
int exitprogram = 0;
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyMainWindow w;
w.show();
while(!exitprogram)
{
app.processEvents(QEventLoop::AllEvents, 20);
}
}
==> test.pro <==
TEMPLATE = app
TARGET = test
QT += widgets
INCLUDEPATH += .
HEADERS += windows.hpp
# Input
SOURCES += test.cpp
==> windows.hpp <==
#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QMainWindow>
#include <QCloseEvent>
extern int exitprogram;
class MyMainWindow : public QMainWindow
{
Q_OBJECT
public slots:
void closeEvent(QCloseEvent *event) override {
exitprogram = 1;
event->accept();
}
};