Search code examples
c++pdcurses

Is there any way to clear Curse's event queue?


I'm trying to get mouse location data from PDCurses, and it generally works. The problem is, if the mouse button is pressed twice before an event check happens, only one of the events will be popped from the queue. This means that the event for the second press is still in the queue, and the next time the mouse is pressed, the old location will be popped instead of the most recent. If this keeps happening, the queue starts to get backed up, and the reported mouse presses become less and less recent.

Since the only thing I'm using getch for is for mouse events (I'm using Window's GetAsyncKeyState and my own manager for keyboard events), I thought that an easy fix would just be to clear the event queue after reading a mouse event.

Unfortunately though, it doesn't seem to be that easy, since I can't find any methods that allow clearing the queue.

The only way I could think of was to set getch to non-blocking using nodelay, then repeatedly use getch, keeping the last event that was popped. That seems inefficient and inaccurate though.

Because this is potentially an XY problem, here's the function in question:

void EventHandler::getMousePos(int& x, int& y) {
    MEVENT event;
    if (nc_getmouse(&event) == OK) {
        x = event.x, y = event.y;
    }
}

EventHandler.h:

#ifndef EVENT_HANDLER_H
#define EVENT_HANDLER_H

#include <vector>
#include <atomic>
#include <thread>
#include <functional>
#include <windows.h>
#include <WinUser.h>
#include "curses.h"

#define LEFT_MOUSE VK_LBUTTON
#define RIGHT_MOUSE VK_RBUTTON
#define MIDDLE_MOUSE VK_MBUTTON

typedef std::function<void(char)> KeyHandler;
typedef std::function<void(char,long,long)> MouseHandler;

class EventHandler {

    std::thread listeningThread;

    std::atomic<bool> listening = false;

    std::vector<char> keysToCheck;
    std::vector<char> mButtonsToCheck;

    KeyHandler keyHandler = KeyHandler();
    MouseHandler mouseHandler = MouseHandler();

    void actOnPressedKeys();

public:
    EventHandler();

    ~EventHandler();

    void setKeyHandler(KeyHandler);
    void setMouseHandler(MouseHandler);

    void setKeysToListenOn(std::vector<char>);
    void setButtonsToListenOn(std::vector<char>);

    void listenForPresses(int loopMSDelay = 100);
    void stopListening();

    static void getMousePos(int& x, int& y);

};

#endif

EventHandler.cpp:

#include "EventHandler.h"

#include <thread>
#include <stdexcept>
#include <cctype>

EventHandler::EventHandler() {

}

EventHandler::~EventHandler() {
    stopListening();
    if (listeningThread.joinable()) {
        //May need to fix this. May cause the EventHandler to freeze
        // on destruction if listeningThread can't join;
        listeningThread.join();
    }
}

void EventHandler::actOnPressedKeys() {
    for (char key : keysToCheck) {
        if (GetAsyncKeyState(key)) {
            //pressedKeys.insert(key);
            keyHandler(key);
        }
    }

    for (char button : mButtonsToCheck) {
        if ( GetAsyncKeyState(button) ) {

            int x = 0, y = 0;
            getMousePos(x, y);
            mvprintw(y, x, "Button Press Detected");
            mouseHandler(button, x, y);
        }
    }
}

/*void EventHandler::actOnPressedKeys() {
    pressedKeys.forEach([](char key){
        keyHandler(key);
    });
}*/

void EventHandler::setKeyHandler(KeyHandler handler) {
    keyHandler = handler;
}

void EventHandler::setMouseHandler(MouseHandler handler) {
    mouseHandler = handler;
}

void EventHandler::setKeysToListenOn(std::vector<char> newListenKeys) {
    if (listening) {
        throw std::runtime_error::runtime_error(
            "Cannot change the listened-on keys while listening"
        );
        //This could be changed to killing the thread by setting
        // listening to false, changing the keys, then restarting
        // the listening thread. I can't see that being necessary though.
    }

    //Untested
    for (char& key : newListenKeys) {
        if (key >= 'a' && key <= 'z') {
            key += 32;
        }
    }

    keysToCheck = newListenKeys;

}

void EventHandler::setButtonsToListenOn(std::vector<char> newListenButtons) {
    if (listening) {
        throw std::runtime_error::runtime_error(
            "Cannot change the listened-on buttons while listening"
        );
    }

    mButtonsToCheck = newListenButtons;
}

void EventHandler::listenForPresses(int loopMSDelay) {
    listening = true;
    listeningThread = std::thread ([=]{
        do {
            actOnPressedKeys();
            std::this_thread::sleep_for(std::chrono::milliseconds(loopMSDelay));
        } while (listening);

    });
}

void EventHandler::stopListening() {
    listening = false;
}

void EventHandler::getMousePos(int& x, int& y) {
    MEVENT event;
    if (nc_getmouse(&event) == OK) {
        x = event.x, y = event.y;
    }
}

Solution

  • PDCurses implements flushinp, which is supposed to do what is requested. The comment and function (for win32, for example) states

    /* discard any pending keyboard or mouse input -- this is the core 
       routine for flushinp() */
    
    void PDC_flushinp(void)
    {
        PDC_LOG(("PDC_flushinp() - called\n"));
    
        FlushConsoleInputBuffer(pdc_con_in);
    }
    

    This function by the way is provided in most curses implementations. Here is a link to the manual page in ncurses, which may be helpful.