diff --git a/src/compositor-x11.c b/src/compositor-x11.c
index bda76386..2dd1aecd 100644
--- a/src/compositor-x11.c
+++ b/src/compositor-x11.c
@@ -149,6 +149,7 @@ x11_compositor_setup_xkb(struct x11_compositor *c)
 #else
 	const xcb_query_extension_reply_t *ext;
 	xcb_generic_error_t *error;
+	xcb_void_cookie_t select;
 	xcb_xkb_per_client_flags_cookie_t pcf;
 	xcb_xkb_per_client_flags_reply_t *pcf_reply;
 
@@ -162,6 +163,20 @@ x11_compositor_setup_xkb(struct x11_compositor *c)
 	}
 	c->xkb_event_base = ext->first_event;
 
+	select = xcb_xkb_select_events(c->conn,
+				       XCB_XKB_ID_USE_CORE_KBD,
+				       XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
+				       0,
+				       XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
+				       0,
+				       0,
+				       NULL);
+	error = xcb_request_check(c->conn, select);
+	if (error) {
+		weston_log("error: failed to select for XKB state events\n");
+		return;
+	}
+
 	pcf = xcb_xkb_per_client_flags(c->conn,
 				       XCB_XKB_ID_USE_CORE_KBD,
 				       XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
@@ -595,6 +610,51 @@ x11_compositor_find_output(struct x11_compositor *c, xcb_window_t window)
 	return NULL;
 }
 
+#ifdef HAVE_XCB_XKB
+static uint32_t
+get_xkb_mod_mask(struct x11_compositor *c, uint32_t in)
+{
+	struct weston_xkb_info *info = &c->base.seat->xkb_info;
+	uint32_t ret = 0;
+
+	if ((in & ShiftMask) && info->shift_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->shift_mod);
+	if ((in & LockMask) && info->caps_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->caps_mod);
+	if ((in & ControlMask) && info->ctrl_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->ctrl_mod);
+	if ((in & Mod1Mask) && info->alt_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->alt_mod);
+	if ((in & Mod2Mask) && info->mod2_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->mod2_mod);
+	if ((in & Mod3Mask) && info->mod3_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->mod3_mod);
+	if ((in & Mod4Mask) && info->super_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->super_mod);
+	if ((in & Mod5Mask) && info->mod5_mod != XKB_MOD_INVALID)
+		ret |= (1 << info->mod5_mod);
+
+	return ret;
+}
+
+static void
+update_xkb_state(struct x11_compositor *c, xcb_xkb_state_notify_event_t *state)
+{
+	struct weston_compositor *ec = &c->base;
+	struct wl_seat *seat = &ec->seat->seat;
+
+	xkb_state_update_mask(c->base.seat->xkb_state.state,
+			      get_xkb_mod_mask(c, state->baseMods),
+			      get_xkb_mod_mask(c, state->latchedMods),
+			      get_xkb_mod_mask(c, state->lockedMods),
+			      0,
+			      0,
+			      state->group);
+
+	notify_modifiers(seat, wl_display_next_serial(c->base.wl_display));
+}
+#endif
+
 static void
 x11_compositor_deliver_button_event(struct x11_compositor *c,
 				    xcb_generic_event_t *event, int state)
@@ -755,7 +815,8 @@ x11_compositor_handle_event(int fd, uint32_t mask, void *data)
 				   weston_compositor_get_time(),
 				   key_press->detail - 8,
 				   WL_KEYBOARD_KEY_STATE_PRESSED,
-				   STATE_UPDATE_AUTOMATIC);
+				   c->has_xkb ? STATE_UPDATE_NONE :
+						STATE_UPDATE_AUTOMATIC);
 			break;
 		case XCB_KEY_RELEASE:
 			/* If we don't have XKB, we need to use the lame
@@ -769,7 +830,7 @@ x11_compositor_handle_event(int fd, uint32_t mask, void *data)
 				   weston_compositor_get_time(),
 				   key_release->detail - 8,
 				   WL_KEYBOARD_KEY_STATE_RELEASED,
-				   STATE_UPDATE_AUTOMATIC);
+				   STATE_UPDATE_NONE);
 			break;
 		case XCB_BUTTON_PRESS:
 			x11_compositor_deliver_button_event(c, event, 1);
@@ -840,6 +901,16 @@ x11_compositor_handle_event(int fd, uint32_t mask, void *data)
 			break;
 		}
 
+#ifdef HAVE_XCB_XKB
+		if (c->has_xkb &&
+		    (event->response_type & ~0x80) == c->xkb_event_base) {
+			xcb_xkb_state_notify_event_t *state =
+				(xcb_xkb_state_notify_event_t *) event;
+			if (state->xkbType == XCB_XKB_STATE_NOTIFY)
+				update_xkb_state(c, state);
+		}
+#endif
+
 		count++;
 		if (prev != event)
 			free (event);