<commit_before>diff --git i/config.def.h w/config.def.h
index 6f05dce..05343a2 100644
--- i/config.def.h
+++ w/config.def.h
@@ -178,7 +178,10 @@ static const char *colorname[] = {
 	"#839496",  /* FIXED: solarized base0/12 */
 	"#000000",
-	"#eeeeee",
-	"#555555",
+	"#555555",  // = defaultrcs (reverse cursor / during selection)
+	"#eeeeee",  // grp0(en) = white = defaultcs (normal cursor)
+	"#009eee",  // grp1(ru) = cyan
+	"#ee9e00",  // grp2(ua) = yellow
+	"#9e00ee",  // grp3(xx) = magenta
 };
 
 /* Terminal colors for alternate (light) palette */
@@ -210,8 +210,11 @@ static const char *altcolorname[] = {
 	"#000000",
 	"#839496",  /* FIXED: solarized base0/12 */
-	"#555555",
-	"#eeeeee",
+	"#eeeeee",  // = defaultrcs (reverse cursor / during selection)
+	"#222222",  // grp0(en) = dim white = defaultcs (normal cursor)
+	"#006aaa",  // grp1(ru) = dim cyan
+	"#aa6a00",  // grp2(ua) = dim yellow
+	"#6a00aa",  // grp3(xx) = dim magenta
 };
 
 /*
  * Default colors (colorname index)
@@ -226,8 +226,9 @@ 
  */
 unsigned int defaultfg = 256;
 unsigned int defaultbg = 257;
-unsigned int defaultcs = 258;
-unsigned int defaultrcs = 259;
+unsigned int defaultrcs = 258;
+const unsigned int xkbbasecolor = 259;
+unsigned int defaultcs = xkbbasecolor;
 
 /*
  * Default shape of cursor
diff --git i/st.h w/st.h
index fa2eddf..5600282 100644
--- i/st.h
+++ w/st.h
@@ -76,4 +76,5 @@ void *xmalloc(size_t);
 extern unsigned int tabspaces;
 extern unsigned int defaultfg;
 extern unsigned int defaultbg;
+extern const unsigned int xkbbasecolor;
 extern unsigned int defaultcs;
diff --git i/st.c w/st.c
index a1e7d23..27a1a57 100644
--- i/st.c
+++ w/st.c
@@ -2755,6 +2755,16 @@ draw(void)
 		xximspot(term.ocx, term.ocy);
 }
 
+void
+redrawcursor(void)
+{
+	if (!xstartdraw())
+		return;
+	xdrawcursor(term.c.x, term.c.y, term.line[term.c.y][term.c.x],
+	    term.ocx, term.ocy, term.line[term.ocy][term.ocx],
+	    term.line[term.ocy], term.col);
+}
+
 void
 redraw(void)
 {
diff --git i/st.h w/st.h
index f5934b8..5600282 100644
--- i/st.h
+++ w/st.h
@@ -87,6 +87,7 @@ typedef union {
 } Arg;
 
 void die(const char *, ...);
+void redrawcursor(void);
 void redraw(void);
 void draw(void);
 
diff --git i/x.c w/x.c
index cee7c50..8df71f8 100644
--- i/x.c
+++ w/x.c
@@ -2061,6 +2061,14 @@ focus(XEvent *ev)
 		xseturgency(0);
 		if (IS_SET(MODE_FOCUS))
 			ttywrite("\033[I", 3, 0);
+		XkbStateRec xkbstate;
+		if (XkbGetState(xw.dpy, XkbUseCoreKbd, &xkbstate) == Success) {
+			int grp = xkbstate.group;
+			if (grp < 0 || grp > 3)
+				die("Xkb unlikely invalid lang group: %d\n", grp);
+			defaultcs = xkbbasecolor + grp;
+			redrawcursor();
+		}
 	} else {
 		if (xw.ime.xic)
 			XUnsetICFocus(xw.ime.xic);
@@ -2248,6 +2248,20 @@ run(void)
 	FcPatternGetDouble(dc.font.match->pattern, FC_SIZE, 0, &defaultrelfontsize);
 	refreshxrandr(0);
 
+	int xkb_major = XkbMajorVersion;
+	int xkb_minor = XkbMinorVersion;
+	if (!XkbLibraryVersion(&xkb_major, &xkb_minor)) {
+		die("Xkb library (%d.%d) is incompatible with runtime (%d.%d)\n",
+		    XkbMajorVersion, XkbMinorVersion, xkb_major, xkb_minor);
+	}
+	int xkb_opcode, xkb_event_base, xkb_error_base;
+	if (!XkbQueryExtension(xw.dpy, &xkb_opcode, &xkb_event_base, &xkb_error_base, &xkb_major, &xkb_minor)) {
+		die("Xkb server extension is not available (or incompatible)\n");
+	}
+	if (!XkbSelectEventDetails(xw.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask)) {
+		die("Xkb subscribe to events gone wrong\n");
+	}
+
 	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
 	cresize(w, h);
 
@@ -2280,6 +2297,27 @@ run(void)
 			if (XFilterEvent(&ev, None))
 				continue;
 			if (LASTEvent <= ev.type) {
+				if (xkb_event_base == ev.type) {
+					// NOTE: keep cursor unchanged for other background windows
+					// INFO: xkb events are broadcasted regardless of kbd focus
+					if (!(IS_SET(MODE_FOCUSED)))
+						continue;
+					if (XkbStateNotify != ((XkbAnyEvent *)&ev)->xkb_type) {
+						fprintf(stderr, "Xkb unexpected event\n");
+						continue;
+					}
+					// MAYBE:ENH: map color to lang code "en" instead of index
+					//   SEE: libkbdd.c:569: _kbdd_group_names_initialize
+					int grp = ((XkbStateNotifyEvent*)&ev)->group;
+					// DEBUG: fprintf(stderr, "Xkb lang group[0-3]: %d\n", grp);
+					if (grp < 0 || grp > 3) {
+						fprintf(stderr, "Xkb invalid lang group: %d\n", grp);
+						continue;
+					}
+					defaultcs = xkbbasecolor + grp;
+					redrawcursor();
+					continue;<commit_msg>[st] * chg my-xkblangcolor-0.8.4-20211224-2f6e597.diff  +60/-25<commit_after>diff --git i/config.def.h w/config.def.h
index 6f05dce..05343a2 100644
--- i/config.def.h
+++ w/config.def.h
@@ -178,7 +178,10 @@ static const char *colorname[] = {
 	"#839496",  /* FIXED: solarized base0/12 */
 	"#000000",
-	"#eeeeee",
-	"#555555",
+	"#555555",  // = defaultrcs (reverse cursor / during selection)
+	"#eeeeee",  // grp0(en) = white = defaultcs (normal cursor)
+	"#009eee",  // grp1(ru) = cyan
+	"#ee9e00",  // grp2(ua) = yellow
+	"#9e00ee",  // grp3(xx) = magenta
 };
 
 /* Terminal colors for alternate (light) palette */
@@ -210,8 +210,11 @@ static const char *altcolorname[] = {
 	"#000000",
 	"#839496",  /* FIXED: solarized base0/12 */
-	"#555555",
-	"#eeeeee",
+	"#eeeeee",  // = defaultrcs (reverse cursor / during selection)
+	"#222222",  // grp0(en) = dim white = defaultcs (normal cursor)
+	"#006aaa",  // grp1(ru) = dim cyan
+	"#aa6a00",  // grp2(ua) = dim yellow
+	"#6a00aa",  // grp3(xx) = dim magenta
 };
 
 /*
  * Default colors (colorname index)
@@ -226,8 +226,9 @@ 
  */
 unsigned int defaultfg = 256;
 unsigned int defaultbg = 257;
-unsigned int defaultcs = 258;
-unsigned int defaultrcs = 259;
+unsigned int defaultrcs = 258;
+const unsigned int xkbbasecolor = 259;
+unsigned int defaultcs = xkbbasecolor;
 
 /*
  * Default shape of cursor
diff --git i/st.h w/st.h
index fa2eddf..5600282 100644
--- i/st.h
+++ w/st.h
@@ -76,4 +76,5 @@ void *xmalloc(size_t);
 extern unsigned int tabspaces;
 extern unsigned int defaultfg;
 extern unsigned int defaultbg;
+extern const unsigned int xkbbasecolor;
 extern unsigned int defaultcs;
diff --git i/st.c w/st.c
index a1e7d23..27a1a57 100644
--- i/st.c
+++ w/st.c
@@ -2755,6 +2755,24 @@ draw(void)
 		xximspot(term.ocx, term.ocy);
 }
 
+void
+redrawcursor(int grp)
+{
+	// DEBUG: fprintf(stderr, "Xkb lang group[0-3]: %d\n", grp);
+	if (grp < 0 || grp > 3)
+		die("Xkb unlikely invalid lang group: %d\n", grp);
+	if (defaultcs == xkbbasecolor + grp)
+		return;  // NOTE: prevent unnecessary redraw
+	// MAYBE:ENH: map color to lang code "en" instead of index
+	//   SEE: libkbdd.c:569: _kbdd_group_names_initialize
+	defaultcs = xkbbasecolor + grp;
+	if (!xstartdraw())
+		return;
+	xdrawcursor(term.c.x, term.c.y, term.line[term.c.y][term.c.x],
+	    term.ocx, term.ocy, term.line[term.ocy][term.ocx],
+	    term.line[term.ocy], term.col);
+}
+
 void
 redraw(void)
 {
diff --git i/st.h w/st.h
index f5934b8..5600282 100644
--- i/st.h
+++ w/st.h
@@ -87,6 +87,7 @@ typedef union {
 } Arg;
 
 void die(const char *, ...);
+void redrawcursor(int);
 void redraw(void);
 void draw(void);
 
diff --git i/x.c w/x.c
index cee7c50..8df71f8 100644
--- i/x.c
+++ w/x.c
@@ -2061,6 +2061,10 @@ focus(XEvent *ev)
 		xseturgency(0);
 		if (IS_SET(MODE_FOCUS))
 			ttywrite("\033[I", 3, 0);
+		XkbStateRec xkbstate;
+		if (XkbGetState(xw.dpy, XkbUseCoreKbd, &xkbstate) == Success) {
+			redrawcursor(xkbstate.group);
+		}
 	} else {
 		if (xw.ime.xic)
 			XUnsetICFocus(xw.ime.xic);
@@ -2248,6 +2248,25 @@ run(void)
 	FcPatternGetDouble(dc.font.match->pattern, FC_SIZE, 0, &defaultrelfontsize);
 	refreshxrandr(0);
 
+	int xkb_major = XkbMajorVersion;
+	int xkb_minor = XkbMinorVersion;
+	if (!XkbLibraryVersion(&xkb_major, &xkb_minor)) {
+		die("Xkb library (%d.%d) is incompatible with runtime (%d.%d)\n",
+		    XkbMajorVersion, XkbMinorVersion, xkb_major, xkb_minor);
+	}
+	int xkb_opcode, xkb_event_base, xkb_error_base;
+	if (!XkbQueryExtension(xw.dpy, &xkb_opcode, &xkb_event_base, &xkb_error_base, &xkb_major, &xkb_minor)) {
+		die("Xkb server extension is not available (or incompatible)\n");
+	}
+	if (!XkbSelectEventDetails(xw.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask,
+				XkbGroupStateMask | XkbModifierLatchMask | XkbModifierStateMask)) {
+		die("Xkb subscribe to states gone wrong\n");
+	}
+	if (!XkbSelectEventDetails(xw.dpy, XkbUseCoreKbd, XkbControlsNotify,
+				XkbAllControlsMask, XkbControlsEnabledMask)) {
+		die("Xkb subscribe to events gone wrong\n");
+	}
+
 	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
 	cresize(w, h);
 
@@ -2280,6 +2297,53 @@ run(void)
 			if (XFilterEvent(&ev, None))
 				continue;
 			if (LASTEvent <= ev.type) {
+				if (xkb_event_base == ev.type) {
+					// NOTE: keep cursor unchanged for other background windows
+					// INFO: xkb events are broadcasted regardless of kbd focus TRY: PerClientFlags
+					if (!(IS_SET(MODE_FOCUSED)))
+						continue;
+					int const xkb_type = ((XkbAnyEvent *)&ev)->xkb_type;
+					// DEBUG: fprintf(stderr, "Xkb event: %d\n", xkb_type);
+
+					if (xkb_type == XkbStateNotify) {
+						uint const changed = ((XkbStateNotifyEvent*)&ev)->changed;
+						if (changed & XkbGroupStateMask) {
+							redrawcursor(((XkbStateNotifyEvent*)&ev)->group);
+						} else if (changed & (XkbModifierLatchMask | XkbModifierStateMask)) {
+							int const mods = ((XkbStateNotifyEvent*)&ev)->mods;
+							if (mods & (Mod3Mask | Mod5Mask)) {
+								redrawcursor(2);  // TEMP: reuse grp3 Xkb color
+							} else {
+								// NOTE: field "group" is valid even on mods change event
+								redrawcursor(((XkbStateNotifyEvent*)&ev)->group);
+							}
+						} else {
+							fprintf(stderr, "Xkb unexpected state changed: %d\n", changed);
+						}
+					} else if (xkb_type == XkbControlsNotify) {
+						uint const changes = ((XkbControlsNotifyEvent*)&ev)->enabled_ctrl_changes;
+						if (changes & XkbOverlay1Mask) {
+							uint const state = ((XkbControlsNotifyEvent*)&ev)->enabled_ctrls;
+							// DEBUG: fprintf(stderr, "Xkb ctrls: %d & %d\n", state, changes);
+							if (state & XkbOverlay1Mask) {
+								redrawcursor(3);  // TEMP: reuse grp4 Xkb color
+							} else {
+								// HACK: query active lang inof restoring last known group
+								//   FIXED: overlay release should not override group changed through overlay
+								XkbStateRec xkbstate;
+								if (XkbGetState(xw.dpy, XkbUseCoreKbd, &xkbstate) == Success) {
+									redrawcursor(xkbstate.group);
+								}
+							}
+						} else {
+							fprintf(stderr, "Xkb unexpected ctrl changes: %d\n", changes);
+							continue;
+						}
+					} else {
+						fprintf(stderr, "Xkb unexpected event\n");
+					}
+					continue;
{'fraction_non_alphanumeric': 0.1736822499534364, 'fraction_numerical': 0.053082510709629355, 'mean_word_length': 3.4248042851256697, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6643174', 'n_tokens_mistral': 5126, 'n_tokens_neox': 4411, 'n_words': 953}