Binding ctrl+(shift+)tab in GTK3

Problem

Pressing ctrl+shift+tab, ctrl+tab, and shift+tab never fires a key-pressed-event, but is instead gobbled up internally by GTK3 and used to switch the currently focused widget.

This is especially annoying when dealing with the GtkNotebook widget, as the de-facto, cross-platform, standard for switching tabs involves, well, the tab button. The “alternative” shortcut GTK3 offers is ctrl+pageup/ctrl+pagedown but this is by all accounts, objectively inferior. It requires two hands, and switching between tab-forwards and tab-backwards mode requires moving a finger across keys (whereas the normal shortcut only requires lifting one finger).

Solution

GTK3 offers the focus signal which is emitted by the currently focused widget every time tab or shift+tab is pressed. This is intended to be overridden if the programmer is creating their own widget, but we can use it for our purposes and instead of changing the focused widget, switch the active tab in our notebook. Since the actual tab switching has to happen inside the focus callback we’ll also need key-pressed-event and key-released-event callbacks to record inside a global variable whether the control key is currently pressed or not.

bool controlPressed;

// user_data = whether this is a callback for a keydown (else a keyup)
bool control_key_checker(GtkWidget *widget, GdkEventKey *event,
    gpointer user_data)
{
    if (event->keyval == GDK_KEY_Control_L)
        controlPressed = (bool)user_data;
    
    return false;
}

bool notebook_tab_shortcut(GtkWidget *widget, GtkDirectionType direction,
    gpointer user_data)
{
    if (controlPressed)
    {
        int last    = gtk_notebook_get_n_pages(GTK_NOTEBOOK(user_data)) - 1;
        int current = gtk_notebook_get_current_page(GTK_NOTEBOOK(user_data));
        int next;
        
        if (direction)
            next = (current == 0 ? last : current - 1);
        else
            next = (current == last ? 0 : current + 1);
        
        gtk_notebook_set_current_page(GTK_NOTEBOOK(user_data), next);
    }
    
    return true;
}
int main(int argc, char *argv[])
{
    [...]
    
    g_signal_connect(window, "focus",
        G_CALLBACK(notebook_tab_shortcut), (gpointer)notebook);
    
    g_signal_connect(window, "key-press-event",
        G_CALLBACK(control_key_checker), (gpointer)true);
    
    g_signal_connect(window, "key-release-event",
        G_CALLBACK(control_key_checker), (gpointer)false);
    
    [...]
}

A full, runable, example is available here.