Event handling in GLFW
Many modern OSs are event-based – the programs don’t just sit there and ask the OS over and over if any mouse or keyboard input has occurred or if the window has been moved, minimized, or resized. All these events are stored in an event queue and must be handled by the application code. If you never request the events of that queue, your application window won’t even close in a proper manner, that is, it can only be killed using Task Manager.
You can find the example code for these additions in the 04_event_handling
folder.
Let’s have a look at how GLFW handles the events from the OS.
The GLFW event queue handling
You have already seen a bit of the event handling in the code for the Window
class – we used these two GLFW calls to close our window and end the application:
int glfwWindowShouldClose(GLFWwindow *win);
void glfwPollEvents();
The first call, glfwWindowShouldClose()
, checks whether an application window should be closed. This event is generated after the user clicks on the top-right close icon of the window. We are using this as a condition to step out of our while()
loop, end the mainLoop()
method of the Window
class, and start the cleanup process.
Important note
The call to glfwPollEvents()
is required in order to empty the event queue. It will also run any configured callbacks. If you forget this call, your window will do nothing, not even close down.
You should call glfwPollEvents()
at the end of the main loop to process the newly arrived events.
There is another call to clear the event queue and fire the callbacks:
void glfwWaitEvents();
This one puts the thread to sleep and waits until at least one event has been generated for the window. Usually, this is used in non-interactive applications that are waiting for any input from the user.
Mixing the C++ classes and the C callbacks
A simple starting point is to react to the window close request and just output a message to the user. To get this to work, we need two parts – the function called by GLFW and a call that sets the function as a callback.
This sounds easy to do, but only at first glance. As GLFW is pure C code, it has no knowledge about C++ classes, member functions, the this
pointer, and all the other moving parts. However, there are some solutions to this.
The first way is that we could use a static function of our Window
class as it is technically similar to a C function. At the moment, we won’t use more than one application window, but if we add support for a pop-out window later, we might be in trouble with the static class function. It is the same for all objects of that class, and as it can only access static members, you have to take extra steps to avoid even more trouble when starting with multithreaded code.
So, let’s consider the second way and use a “free” function, outside the class, to dispatch the call to the C++ class. However, instead of having to define two separate functions for every callback, we will use a Lambda.
Lambda functions
A lambda is a small piece of code, running as an anonymous function. It has no visible name, takes the number and types of arguments from its definition, and runs the code. Internally, the lambda function is converted into a small class by your compiler; there is no magic applied here. It’s only a convenient way to help reduce the code you write. If you want to know more about lambda functions, you can find a link to a tutorial in the Additional resources section.
The authors of GLFW are aware of this problem and have added a small helper to every window that might be created – a pointer that can be set and read by the user:
void glfwSetWindowUserPointer(GLFWwindow *win, void *ptr);
You can store any arbitrary data in the user pointer – it doesn’t have to be the this
pointer of the class object, but it is only a pointer and must be accessible by your code. We will use it to store the pointer in our C++ Window
object, and inside the lambda, this pointer will be read and used just like in any other C++ call.
The callback function itself looks a bit weird if you have never used C-style callbacks:
GLFWwindowclosefun glfwSetWindowCloseCallback (GLFWwindow *window, GLFWwindowclosefun callback);
It requires a pointer to a function and returns either NULL
, if this is the first call, or the pointer to a previously set callback function. You could change this callback during runtime, which means moving to a different dialog to display any unsaved changes.
The last part of the puzzle is the window close function, which is called by the callback:
typedef void(* GLFWwindowclosefun) (GLFWwindow *window)
The GLFWwindowclosefun
function is created using typedef
, just like the other functions used for callbacks. This is done to avoid writing the expression in the second braces every time we use the function. As this is still C code, sadly, no modern C++ enhancements are available to change it.
And this is how you should put all the parts together – by adding the following lines to the init()
function of the Window.cpp
file:
glfwSetWindowUserPointer(mWindow, this);
glfwSetWindowCloseCallback(mWindow, [](GLFWwindow *win) {
auto thisWindow = static_cast<Window*>(
glfwGetWindowUserPointer(win));
thisWindow->handleWindowCloseEvents();
});
Here, the lambda is introduced by the square brackets, []
, followed by the parameters the function takes. You could even capture some data from the outside of the function using the brackets, making it available, like in normal functions. We can’t use this capturing method for C-style callbacks, as such captures are not compatible with a function pointer.
Inside the lambda function, we can retrieve the user pointer set by glfwSetWindowUserPointer()
, cast it back to a pointer to an instance of our Window
class (this is our application window), and call the member function to handle the event. The function does not need to get the GLFWwindow
parameter, as we already saved it as a private member in the Window
class. The result of glfwSetWindowCloseCallback()
can be safely ignored. It returns the address of the callback function that was set in a previous call. This is the first call in the code, so it will simply return NULL
.
The class member function needs to be added to Window.cpp
:
void Window::handleWindowCloseEvents() {
Logger::log(1, "%s: Window got close event... bye!\n",
__FUNCTION__);
}
Currently, the handleWindowCloseEvents()
function just prints out a log line and does nothing else. But this is the perfect place to check whether the user really wants to quit or if unsaved changes have been made.
This function has to be declared in the Window.h
header file, too:
private:
void handleWindowCloseEvents();
If you start the compiled code and close the window, you should get an output like this:
init: Window successfully initialized
handleWindowCloseEvents: Window got close event... bye!
cleanup: Terminating Window
You can check the other events in the GLFW documentation and add other callback functions plus the respective lambdas. Additionally, you can check the example code for more calls – it has simple support for window movement, minimizing and maximizing, and printing out some log messages when the events are processed.
Important note
Some OSs stall the window content update if your application window has been moved or resized. So, don’t be alarmed if this happens – it is not a bug in your code. Workarounds are available to keep the window content updated on these window events, and you can check the GLFW documentation to find a way to solve this.
Now that our application window behaves in the way we would expect, we should add methods for a user to control what happens in our program.