-1

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();
}
Darin Beaudreau
  • 375
  • 7
  • 30

1 Answers1

-1

You are never setting done to true, and by this you have a infinite loop which reopens the dialog regardless if you clicked on the yes option.

Also your I am not sure what system("sudo /opt/eds/bin/killX"); is intended to do. If you intend to kill the current process then it does not do the job. For simplicity sake you can call std::terminate then indeed, you would not have to set done to true, since you are exiting the loop abnormally.

So you have 2 options:

                    if(ret == QMessageBox::Yes) {
                        system("sudo /opt/eds/bin/killX");
                        done = true;
                    }

or

#include <exception>

....

                if(ret == QMessageBox::Yes) {
                    std::terminate();
                }
Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • The ```done``` variable is for the main event loop and does not matter, as this process is not supposed to exit when Ctrl+Alt+Delete is pressed. Also, the killX script is not an issue, as it kills the process as it is supposed to. The problem is only the dialog relaunching itself. I could not call std::terminate, as the program above is not the process I am trying to kill... the killX script terminates another process unrelated to this one. The process is separate because the process I am trying to kill is locking up and this is a keyboard shortcut for recovery. – Darin Beaudreau Feb 13 '23 at 14:15
  • Well you can argue all you want, if you remove all that key board handling code and get to the real minimized version of the code, you will see that you just have a while loop that indefinitely will create a new QMessageBox and display it. I know this not only by looking at your code but by executing that minimized version. Just change your code to the first version I provided as it will answer and be a solution to your question. The rest is your broken understanding of your keyboard code. If you not happy with the correct answer, I can't help you, but don't wonder that you won't be helped. – Superlokkus Feb 13 '23 at 19:02
  • I'm not arguing. I'm telling you that you're wrong about how this works. The while loop not ending is INTENTIONAL. This process should not end. I don't know what wasn't clear about that. It should continue to poll the event file after a Ctrl+Alt+Delete is detected and respond each time it is pressed. Each iteration of that loop is one key event, not one Ctrl+Alt+Delete event. Once Delete is pressed, it makes a system call to a script that kills the hung process. The only thing "not working" is it can spawn multiple dialogs in a row if you press CAD while the modal dialog is still open. – Darin Beaudreau Feb 14 '23 at 20:42
  • Well that behavior is not what you described in your question. And not reproducible by me. See [mre] – Superlokkus Feb 15 '23 at 12:56