diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
index 09320dfb..fb916daf 100644
--- a/i3-config-wizard/main.c
+++ b/i3-config-wizard/main.c
@@ -57,7 +57,7 @@ enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME;
 enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
 
 static char *config_path;
-static xcb_connection_t *conn;
+xcb_connection_t *conn;
 static xcb_get_modifier_mapping_reply_t *modmap_reply;
 static uint32_t font_id;
 static uint32_t font_bold_id;
@@ -424,6 +424,7 @@ int main(int argc, char *argv[]) {
 
     xcb_get_modifier_mapping_cookie_t modmap_cookie;
     modmap_cookie = xcb_get_modifier_mapping(conn);
+    symbols = xcb_key_symbols_alloc(conn);
 
     /* Place requests for the atoms we need as soon as possible */
     #define xmacro(atom) \
@@ -437,11 +438,7 @@ int main(int argc, char *argv[]) {
     if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
         errx(EXIT_FAILURE, "Could not get modifier mapping\n");
 
-    /* XXX: we should refactor xcb_get_numlock_mask so that it uses the
-     * modifier mapping we already have */
-    xcb_get_numlock_mask(conn);
-
-    symbols = xcb_key_symbols_alloc(conn);
+    xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
 
     font_id = get_font_id(conn, pattern, &font_height);
     font_bold_id = get_font_id(conn, patternbold, &font_bold_height);
diff --git a/i3-config-wizard/xcb.c b/i3-config-wizard/xcb.c
index 4f8eb4da..753568e3 100644
--- a/i3-config-wizard/xcb.c
+++ b/i3-config-wizard/xcb.c
@@ -104,58 +104,3 @@ int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
 
         return result;
 }
-
-/*
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn) {
-    xcb_key_symbols_t *keysyms;
-    xcb_get_modifier_mapping_cookie_t cookie;
-    xcb_get_modifier_mapping_reply_t *reply;
-    xcb_keycode_t *modmap;
-    int mask, i;
-    const int masks[8] = { XCB_MOD_MASK_SHIFT,
-                           XCB_MOD_MASK_LOCK,
-                           XCB_MOD_MASK_CONTROL,
-                           XCB_MOD_MASK_1,
-                           XCB_MOD_MASK_2,
-                           XCB_MOD_MASK_3,
-                           XCB_MOD_MASK_4,
-                           XCB_MOD_MASK_5 };
-
-    /* Request the modifier map */
-    cookie = xcb_get_modifier_mapping_unchecked(conn);
-
-    /* Get the keysymbols */
-    keysyms = xcb_key_symbols_alloc(conn);
-
-    if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
-        xcb_key_symbols_free(keysyms);
-        return;
-    }
-
-    modmap = xcb_get_modifier_mapping_keycodes(reply);
-
-    /* Get the keycode for numlock */
-#ifdef OLD_XCB_KEYSYMS_API
-    xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-#else
-    /* For now, we only use the first keysymbol. */
-    xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-    if (numlock_syms == NULL)
-        return;
-    xcb_keycode_t numlock = *numlock_syms;
-    free(numlock_syms);
-#endif
-
-    /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
-    for (mask = 0; mask < 8; mask++)
-        for (i = 0; i < reply->keycodes_per_modifier; i++)
-            if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
-                xcb_numlock_mask = masks[mask];
-
-    xcb_key_symbols_free(keysyms);
-    free(reply);
-}
-
diff --git a/i3-config-wizard/xcb.h b/i3-config-wizard/xcb.h
index c2ecedf8..1e5d2d2a 100644
--- a/i3-config-wizard/xcb.h
+++ b/i3-config-wizard/xcb.h
@@ -12,10 +12,5 @@ extern unsigned int xcb_numlock_mask;
 
 xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
 int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-/**
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn);
 
 #endif
diff --git a/include/libi3.h b/include/libi3.h
index f4cd2160..ecb72cb5 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -8,6 +8,7 @@
 #include <stdarg.h>
 #include <xcb/xcb.h>
 #include <xcb/xproto.h>
+#include <xcb/xcb_keysyms.h>
 
 /**
  * Try to get the socket path from X11 and return NULL if it doesn’t work.
@@ -121,4 +122,25 @@ char *strndup(const char *str, size_t n);
 
 #endif
 
+/**
+ * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the
+ * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function initiates one round-trip. Use get_mod_mask_for() directly if
+ * you already have the modifier mapping and key symbols.
+ *
+ */
+uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols);
+
+/**
+ * Returns the modifier mask (XCB_MOD_MASK_*) for the given keysymbol, for
+ * example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function does not initiate any round-trips.
+ *
+ */
+uint32_t get_mod_mask_for(uint32_t keysym,
+                           xcb_key_symbols_t *symbols,
+                           xcb_get_modifier_mapping_reply_t *modmap_reply);
+
 #endif
diff --git a/include/xcb.h b/include/xcb.h
index 9660939d..3670bbcd 100644
--- a/include/xcb.h
+++ b/include/xcb.h
@@ -97,13 +97,6 @@ void fake_absolute_configure_notify(Con *con);
  */
 void send_take_focus(xcb_window_t window);
 
-/**
- * Finds out which modifier mask is the one for numlock, as the user may
- * change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn);
-
 /**
  * Raises the given window (typically client->frame) above all other windows
  *
diff --git a/libi3/get_mod_mask.c b/libi3/get_mod_mask.c
new file mode 100644
index 00000000..76549220
--- /dev/null
+++ b/libi3/get_mod_mask.c
@@ -0,0 +1,81 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009-2011 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+
+/*
+ * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the
+ * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function initiates one round-trip. Use get_mod_mask_for() directly if
+ * you already have the modifier mapping and key symbols.
+ *
+ */
+uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols) {
+    xcb_get_modifier_mapping_cookie_t cookie;
+    xcb_get_modifier_mapping_reply_t *modmap_r;
+
+    xcb_flush(conn);
+
+    /* Get the current modifier mapping (this is blocking!) */
+    cookie = xcb_get_modifier_mapping(conn);
+    if (!(modmap_r = xcb_get_modifier_mapping_reply(conn, cookie, NULL)))
+        return 0;
+
+    uint32_t result = get_mod_mask_for(keysym, symbols, modmap_r);
+    free(modmap_r);
+    return result;
+}
+
+/*
+ * Returns the modifier mask (XCB_MOD_MASK_*) for the given keysymbol, for
+ * example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function does not initiate any round-trips.
+ *
+ */
+uint32_t get_mod_mask_for(uint32_t keysym,
+                           xcb_key_symbols_t *symbols,
+                           xcb_get_modifier_mapping_reply_t *modmap_reply) {
+    xcb_keycode_t *codes, *modmap;
+    xcb_keycode_t mod_code;
+
+    modmap = xcb_get_modifier_mapping_keycodes(modmap_reply);
+
+    /* Get the list of keycodes for the given symbol */
+    if (!(codes = xcb_key_symbols_get_keycode(symbols, keysym)))
+        return 0;
+
+    /* Loop through all modifiers (Mod1-Mod5, Shift, Control, Lock) */
+    for (int mod = 0; mod < 8; mod++)
+        for (int j = 0; j < modmap_reply->keycodes_per_modifier; j++) {
+            /* Store the current keycode (for modifier 'mod') */
+            mod_code = modmap[(mod * modmap_reply->keycodes_per_modifier) + j];
+            /* Check if that keycode is in the list of previously resolved
+             * keycodes for our symbol. If so, return the modifier mask. */
+            for (xcb_keycode_t *code = codes; *code; code++) {
+                if (*code != mod_code)
+                    continue;
+
+                free(codes);
+                /* This corresponds to the XCB_MOD_MASK_* constants */
+                return (1 << mod);
+            }
+        }
+
+    return 0;
+}
diff --git a/src/handlers.c b/src/handlers.c
index 0695f2d0..29dcdc70 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -286,7 +286,7 @@ static int handle_mapping_notify(xcb_mapping_notify_event_t *event) {
     DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
     xcb_refresh_keyboard_mapping(keysyms, event);
 
-    xcb_get_numlock_mask(conn);
+    xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
     ungrab_all_keys(conn);
     translate_keysyms();
diff --git a/src/main.c b/src/main.c
index 958ea653..52c9e2cd 100644
--- a/src/main.c
+++ b/src/main.c
@@ -178,7 +178,7 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
     xcb_key_symbols_free(keysyms);
     keysyms = xcb_key_symbols_alloc(conn);
 
-    xcb_get_numlock_mask(conn);
+    xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
     ungrab_all_keys(conn);
     DLOG("Re-grabbing...\n");
@@ -520,7 +520,7 @@ int main(int argc, char *argv[]) {
 
     keysyms = xcb_key_symbols_alloc(conn);
 
-    xcb_get_numlock_mask(conn);
+    xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
     translate_keysyms();
     grab_all_keys(conn, false);
diff --git a/src/xcb.c b/src/xcb.c
index 587f18b3..d9891bfc 100644
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -173,60 +173,6 @@ void send_take_focus(xcb_window_t window) {
     free(event);
 }
 
-/*
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn) {
-    xcb_key_symbols_t *keysyms;
-    xcb_get_modifier_mapping_cookie_t cookie;
-    xcb_get_modifier_mapping_reply_t *reply;
-    xcb_keycode_t *modmap;
-    int mask, i;
-    const int masks[8] = { XCB_MOD_MASK_SHIFT,
-                           XCB_MOD_MASK_LOCK,
-                           XCB_MOD_MASK_CONTROL,
-                           XCB_MOD_MASK_1,
-                           XCB_MOD_MASK_2,
-                           XCB_MOD_MASK_3,
-                           XCB_MOD_MASK_4,
-                           XCB_MOD_MASK_5 };
-
-    /* Request the modifier map */
-    cookie = xcb_get_modifier_mapping(conn);
-
-    /* Get the keysymbols */
-    keysyms = xcb_key_symbols_alloc(conn);
-
-    if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
-        xcb_key_symbols_free(keysyms);
-        return;
-    }
-
-    modmap = xcb_get_modifier_mapping_keycodes(reply);
-
-    /* Get the keycode for numlock */
-#ifdef OLD_XCB_KEYSYMS_API
-    xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-#else
-    /* For now, we only use the first keysymbol. */
-    xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-    if (numlock_syms == NULL)
-        return;
-    xcb_keycode_t numlock = *numlock_syms;
-    free(numlock_syms);
-#endif
-
-    /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
-    for (mask = 0; mask < 8; mask++)
-        for (i = 0; i < reply->keycodes_per_modifier; i++)
-            if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
-                xcb_numlock_mask = masks[mask];
-
-    xcb_key_symbols_free(keysyms);
-    free(reply);
-}
-
 /*
  * Raises the given window (typically client->frame) above all other windows
  *