I've been implementing a dirty hack for a "panic" keyboard shortcut in a RHEL 8.4 environment to kill our application in the event that it hangs because we use a Mutter environment which prevents the user from being able to interact with the OS and only shows our application.
At first I looked into Gnome keyboard shortcuts, but it appears those get overridden or blocked entirely by Mutter, because that didn't work. So I repurposed some old driver code I wrote to write a standalone process that runs in the background and monitors the /dev/input/event file for keyboard events and responds to the Ctrl+Alt+Delete key sequence. When pressed, I use Qt to spawn a QMessageBox that when "Yes" is selected, it calls the script that kills and relaunches our application, and "No" just closes the dialog.
However, there's an issue where regardless of what you click, the dialog reappears. Through some testing, it appears this happens if you press Ctrl+Alt+Delete again while the dialog is visible, but I don't understand how that can happen, because the QMessageBox is modal and calling exec() on it should block the main thread, since the code immediately after reads the dialog result. So I also tried zeroing out the event data at the start of each loop to make sure a previous event isn't being read repeatedly while no events are taking place, but that had no effect either.
Can anyone tell what's going on here?
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <linux/input.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <QApplication>
#include <QMessageBox>
using namespace std;
#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) (((x - 1) / BITS_PER_LONG) + 1)
#define OFF(x) (x % BITS_PER_LONG)
#define LONG(x) (x / BITS_PER_LONG)
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
string detect();
void poll(string handler);
int main(int argc, char ** argv) {
QApplication app(argc, argv);
bool found = false;
string handler = detect();
poll(handler);
return app.exec();
}
string detect() {
int eventX = 0;
string handler = "";
bool found = false;
while(!found) {
ostringstream oss;
oss << "/dev/input/event" << eventX;
handler = oss.str();
int fd = open(handler.c_str(), O_RDONLY);
if(fd < 0) {
if((errno == EACCES) && (getuid() != 0)) {
cout << "You must run as root to detect the keyboard." << endl;
exit(1);
} else {
// No more event handler files.
break;
}
}
// Read event code bits from event handler file descriptor.
unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
memset(bit, 0, sizeof(bit));
ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
// Read key events from file descriptor.
ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), bit[EV_KEY]);
// Test to make sure the event file defines the Ctrl, Alt, and Delete key events.
if(
( test_bit( KEY_LEFTCTRL, bit[EV_KEY] ) || test_bit( KEY_RIGHTCTRL, bit[EV_KEY] ) ) &&
( test_bit( KEY_LEFTALT, bit[EV_KEY] ) || test_bit( KEY_RIGHTALT, bit[EV_KEY] ) ) &&
test_bit(KEY_DELETE, bit[EV_KEY])
) {
cout << "Keyboard Found @ " << handler << endl;
found = true;
}
close(fd);
eventX++;
}
if(!found) {
cout << "No keyboard event file found." << endl;
exit(2);
}
return handler;
}
void poll(string handler) {
ifstream keyboard(handler.c_str(), ios::in | ios::binary);
struct input_event event;
char data[sizeof(event)];
int states[2] = { 0, 0 };
bool dialogBlocked = false;
bool done = false;
while(!done) {
if(keyboard.is_open() && keyboard.good()) {
memset(data, 0, sizeof(event));
keyboard.read(data, sizeof(event));
memcpy(&event, data, sizeof(event));
if((event.code == KEY_LEFTCTRL) || (event.code == KEY_RIGHTCTRL)) {
if(event.value > 0) {
states[0] = 1;
} else {
states[0] = 0;
}
}
if((event.code == KEY_LEFTALT) || (event.code == KEY_RIGHTALT)) {
if(event.value > 0) {
states[1] = 1;
} else {
states[1] = 0;
}
}
if((event.code == KEY_DELETE) && (event.value > 0)) {
if((states[0] > 0) && (states[1] > 0) && !dialogBlocked) {
// Ctrl+Alt+Delete detected. Call killX.
dialogBlocked = true;
QMessageBox msgBox;
msgBox.setModal(true);
msgBox.setText("Do you want to reset?");
msgBox.setWindowTitle("Reset");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setStyleSheet(
"QMessageBox {"
" background: rgb(220, 220, 220);"
" color: rgb(0, 0, 0);"
"}"
"QPushButton {"
" border: 1px solid rgb(20, 20, 20);"
" border-radius: 5px;"
" padding: 2px;"
"}"
"QPushButton:enabled {"
" background: rgb(240, 240, 240);"
" color: rgb(20, 20, 20);"
"}"
"QPushButton:!enabled {"
" background: rgb(200, 200, 200);"
" color: rgb(250, 250, 250);"
"}"
);
int ret = msgBox.exec();
if(ret == QMessageBox::Yes) {
system("sudo /opt/eds/bin/killX");
}
}
}
if(dialogBlocked) {
// Wait until Ctrl+Alt keys are released to let operator open dialog again.
if((states[0] == 0) && (states[1] == 0)) {
dialogBlocked = false;
}
}
} else {
cout << "Lost connection to keyboard. Attempting to re-establish..." << endl;
bool detected = false;
if(keyboard.is_open()) keyboard.close();
while(!detected) {
string redetectedHandler = detect();
if(!redetectedHandler.empty()) {
keyboard.open(redetectedHandler.c_str(), ios::in | ios::binary);
if(keyboard.is_open()) {
cout << "Connection to keyboard re-established." << endl;
detected = true;
}
}
}
}
}
if(keyboard.is_open()) keyboard.close();
}