[PATCH] wmaker: revamp titlebar language button

2 views
Skip to first unread message

david.m...@gmail.com

unread,
Mar 14, 2026, 10:10:59 AM (3 days ago) Mar 14
to Window Maker Development
This patch is replacing the modelock legacy hardcoded language dropdown
icons with a compact titlebar button based on the current locale.
(it adds also a detection to xkbfile library which is required to get
the short name of the locale).
Now supports up to 4 layouts, clicking on the language button will cycle
through them (XKB officially supports up to four groups).
---
 configure.ac        |   4 +-
 m4/wm_xext_check.m4 |  32 +++++++
 src/Makefile.am     |   1 +
 src/event.c         |  17 ++--
 src/framewin.c      | 198 ++++++++++++++++++++++++++++++++++++++++----
 src/framewin.h      |   3 +-
 src/screen.c        |   7 +-
 src/window.c        |  88 +++++++++++++++++---
 src/window.h        |   5 ++
 9 files changed, 314 insertions(+), 41 deletions(-)

diff --git a/configure.ac b/configure.ac
index 7048524d..96277426 100644
--- a/configure.ac
+++ b/configure.ac
@@ -555,7 +555,9 @@ AC_ARG_ENABLE([modelock],
 m4_divert_pop([INIT_PREPARE])dnl
 
 AS_IF([test "x$enable_modelock" = "xyes"],
-    [AC_DEFINE([XKB_MODELOCK], [1], [whether XKB language MODELOCK should be enabled]) ])
+    [WM_XEXT_CHECK_XKBFILE
+     AS_IF([test "x$enable_modelock" = "xyes"],
+         [AC_DEFINE([XKB_MODELOCK], [1], [whether XKB language MODELOCK should be enabled])])])
 
 
 dnl XDND Drag-nd-Drop support
diff --git a/m4/wm_xext_check.m4 b/m4/wm_xext_check.m4
index 751d8314..d482bdd5 100644
--- a/m4/wm_xext_check.m4
+++ b/m4/wm_xext_check.m4
@@ -232,3 +232,35 @@ AC_DEFUN_ONCE([WM_XEXT_CHECK_XRANDR],
     [supported_xext], [LIBXRANDR], [], [-])dnl
 AC_SUBST([LIBXRANDR])dnl
 ]) dnl AC_DEFUN
+
+
+# WM_XEXT_CHECK_XKBFILE
+# ---------------------
+#
+# Check for the XKB File extension library (libxkbfile)
+# The check depends on variable 'enable_modelock' being either:
+#   yes  - detect, fail if not found
+#   no   - do not detect, disable support
+#
+# When found, append appropriate stuff in LIBXKBFILE, and append info to
+# the variable 'supported_xext'
+# When not found, generate an error because it's required for modelock
+AC_DEFUN_ONCE([WM_XEXT_CHECK_XKBFILE],
+[WM_LIB_CHECK([XKBFile], [-lxkbfile], [XkbRF_GetNamesProp], [$XLIBS],
+    [wm_save_CFLAGS="$CFLAGS"
+     AC_COMPILE_IFELSE([AC_LANG_PROGRAM([dnl
+@%:@include <stdio.h>
+@%:@include <X11/Xlib.h>
+@%:@include <X11/XKBlib.h>
+@%:@include <X11/extensions/XKBfile.h>
+@%:@include <X11/extensions/XKBrules.h>
+], [dnl
+  Display *dpy = NULL;
+  XkbRF_VarDefsRec vd;
+  XkbRF_GetNamesProp(dpy, NULL, &vd);])],
+        [],
+        [AC_MSG_ERROR([found $CACHEVAR but cannot compile using XKBfile header])])
+     CFLAGS="$wm_save_CFLAGS"],
+    [supported_xext], [LIBXKBFILE], [enable_modelock], [-])dnl
+AC_SUBST([LIBXKBFILE])dnl
+]) dnl AC_DEFUN
diff --git a/src/Makefile.am b/src/Makefile.am
index 8782191b..d05a9a01 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -165,6 +165,7 @@ wmaker_LDADD = \
  @XLFLAGS@ \
  @LIBXRANDR@ \
  @LIBXINERAMA@ \
+ @LIBXKBFILE@ \
  @XLIBS@ \
  @LIBM@ \
  @INTLIBS@
diff --git a/src/event.c b/src/event.c
index 0fcbf0e7..a62586b7 100644
--- a/src/event.c
+++ b/src/event.c
@@ -593,10 +593,12 @@ static void handleExtensions(XEvent * event)
  handleShapeNotify(event);
  }
 #endif
- if (w_global.xext.xkb.supported && event->type == w_global.xext.xkb.event_base) {
+ if (w_global.xext.xkb.supported && event->type >= w_global.xext.xkb.event_base
+ && event->type <= w_global.xext.xkb.event_base + 255) {
  XkbEvent *xkbevent = (XkbEvent *) event;
+ int xkb_type = xkbevent->any.xkb_type;
 
- if (xkbevent->any.xkb_type == XkbNewKeyboardNotify) {
+ if (xkb_type == XkbNewKeyboardNotify) {
  int j;
  WScreen *scr;
 
@@ -607,8 +609,10 @@ static void handleExtensions(XEvent * event)
  }
 #ifdef KEEP_XKB_LOCK_STATUS
  else {
- if (wPreferences.modelock && (xkbevent->any.xkb_type == XkbIndicatorStateNotify)) {
- handleXkbIndicatorStateNotify((XkbEvent *) event);
+ /* Listen not only for IndicatorStateNotify but also for StateNotify
+  * which is commonly emitted on group (layout) changes. */
+ if (wPreferences.modelock && (xkb_type == XkbIndicatorStateNotify || xkb_type == XkbStateNotify)) {
+ handleXkbIndicatorStateNotify(xkbevent);
  }
  }
 #endif /*KEEP_XKB_LOCK_STATUS */
@@ -1324,6 +1328,8 @@ static void handleXkbIndicatorStateNotify(XkbEvent *event)
  if (wwin->frame->languagemode != staterec.group) {
  wwin->frame->last_languagemode = wwin->frame->languagemode;
  wwin->frame->languagemode = staterec.group;
+ wWindowGetLanguageLabel(wwin->frame->languagemode, wwin->frame->language_label);
+ wFrameWindowUpdateLanguageButton(wwin->frame);
  }
 #ifdef XKB_BUTTON_HINT
  if (wwin->frame->titlebar) {
@@ -1967,7 +1973,8 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent
  wwin->frame->languagemode = wwin->frame->last_languagemode;
  wwin->frame->last_languagemode = staterec.group;
  XkbLockGroup(dpy, XkbUseCoreKbd, wwin->frame->languagemode);
-
+ /* Update the language label text */
+ wWindowGetLanguageLabel(wwin->frame->languagemode, wwin->frame->language_label);
  }
  }
  break;
diff --git a/src/framewin.c b/src/framewin.c
index e72d881d..ac3fc747 100644
--- a/src/framewin.c
+++ b/src/framewin.c
@@ -54,7 +54,7 @@ static void resizebarMouseDown(WObjDescriptor * desc, XEvent * event);
 static void checkTitleSize(WFrameWindow * fwin);
 
 static void paintButton(WCoreWindow * button, WTexture * texture,
- unsigned long color, WPixmap * image, int pushed);
+ unsigned long color, WPixmap * image, int pushed, int from_xpm);
 
 static void updateTitlebar(WFrameWindow * fwin);
 
@@ -98,6 +98,7 @@ WFrameWindow *wFrameWindowCreate(WScreen * scr, int wlevel, int x, int y,
 #ifdef KEEP_XKB_LOCK_STATUS
  fwin->languagemode = XkbGroup1Index;
  fwin->last_languagemode = XkbGroup2Index;
+ wWindowGetLanguageLabel(fwin->languagemode, fwin->language_label);
 #endif
 
  fwin->depth = depth;
@@ -145,6 +146,7 @@ void wFrameWindowUpdateBorders(WFrameWindow * fwin, int flags)
  theight = *fwin->title_min_height;
  } else {
  theight = 0;
+ fwin->flags.titlebar = 0;
  }
 
  if (wPreferences.new_style == TS_NEW) {
@@ -536,6 +538,8 @@ static void updateTitlebar(WFrameWindow * fwin)
 #ifdef XKB_BUTTON_HINT
  else {
  int bsize = theight - 7;
+ if (wPreferences.new_style == TS_NEXT)
+ bsize -= 1;
  if (fwin->flags.hide_left_button || !fwin->left_button || fwin->flags.lbutton_dont_fit) {
  if (fwin->language_button)
  wCoreConfigure(fwin->language_button, 3, (theight - bsize) / 2,
@@ -950,6 +954,10 @@ void wFrameWindowPaint(WFrameWindow * fwin)
  remakeTexture(fwin, i);
  }
  }
+#ifdef XKB_BUTTON_HINT
+ if (wPreferences.modelock)
+ wFrameWindowUpdateLanguageButton(fwin);
+#endif
  }
 
  if (fwin->flags.need_texture_change) {
@@ -1023,7 +1031,8 @@ void wFrameWindowPaint(WFrameWindow * fwin)
  allButtons = 0;
  }
 #ifdef XKB_BUTTON_HINT
- fwin->languagebutton_image = scr->b_pixmaps[WBUT_XKBGROUP1 + fwin->languagemode];
+ if (fwin->flags.language_button && !fwin->languagebutton_image[0])
+ wFrameWindowUpdateLanguageButton(fwin);
 #endif
 
  if (fwin->title) {
@@ -1228,10 +1237,145 @@ int wFrameWindowChangeTitle(WFrameWindow *fwin, const char *new_title)
 }
 
 #ifdef XKB_BUTTON_HINT
+
+static int wFrameWindowSetLanguageButtonImages(WFrameWindow *fwin, Pixmap *pixmaps, int count)
+{
+ int i;
+ for (i = 0; i < count; i++) {
+ WPixmap *wp = wPixmapCreate(pixmaps[i], None);
+ if (!wp) {
+ int j;
+ XFreePixmap(dpy, pixmaps[i]);
+ for (j = i + 1; j < count; j++)
+ XFreePixmap(dpy, pixmaps[j]);
+ return 0;
+ }
+ wp->client_owned = 0;
+ wp->client_owned_mask = 0;
+ if (fwin->languagebutton_image[i] && !fwin->languagebutton_image[i]->shared)
+ wPixmapDestroy(fwin->languagebutton_image[i]);
+ fwin->languagebutton_image[i] = wp;
+ }
+ return 1;
+}
+
 void wFrameWindowUpdateLanguageButton(WFrameWindow * fwin)
 {
- paintButton(fwin->language_button, fwin->title_texture[fwin->flags.state],
-     WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->languagebutton_image, True);
+ WScreen *scr = fwin->screen_ptr;
+ WCoreWindow *button = fwin->language_button;
+ GC gc = scr->copy_gc;
+ int i, text_width, text_height;
+ int border_thickness = 3;
+ int key_width, key_height;
+ int group_index;
+ Pixmap tmp[2]; /* focused, unfocused */
+
+ if (!fwin->flags.titlebar || fwin->core->descriptor.parent_type != WCLASS_WINDOW)
+ return;
+
+ if (!button || fwin->language_label[0] == '\0')
+ return;
+
+ group_index = fwin->languagemode;
+ if (group_index < 0 || group_index >= 4)
+ return;
+
+ /* Calculate text dimensions */
+ int small_px = WMFontHeight(*fwin->font) * 55 / 100;
+ WMFont *small = WMBoldSystemFontOfSize(scr->wmscreen, small_px);
+ text_width = WMWidthOfString(small, fwin->language_label, strlen(fwin->language_label));
+ text_height = WMFontHeight(small);
+
+ key_width = button->width - border_thickness;
+ key_height = button->height - border_thickness;
+
+ /* Ensure dimensions are valid */
+ if (key_width < 1 || key_height < 1) {
+ WMReleaseFont(small);
+ return;
+ }
+
+ /* Create temporary pixmaps for immediate drawing */
+ tmp[0] = XCreatePixmap(dpy, button->window, 2*key_width, key_height, scr->w_depth);
+ if (tmp[0] == None) {
+ WMReleaseFont(small);
+ return;
+ }
+
+ tmp[1] = None;
+ if (wPreferences.new_style == TS_NEW) {
+ tmp[1] = XCreatePixmap(dpy, button->window, 2*key_width, key_height, scr->w_depth);
+ if (tmp[1] == None) {
+ XFreePixmap(dpy, tmp[0]);
+ WMReleaseFont(small);
+ return;
+ }
+ }
+
+ /* Reset GC to ensure clean state for drawing */
+ XSetClipMask(dpy, gc, None);
+
+ /* Draw the language label centered in the button */
+ int text_x = (key_width - text_width) / 2;
+ int text_y = (key_height - text_height) / 2;
+
+ /* Fill the color pixmap depending on the style  */
+ if (wPreferences.new_style == TS_NEW) {
+ for (i = 0; i < 2; i++) {
+ if (fwin->title_texture[i]->any.type != WTEX_SOLID && fwin->title_back[i] != None) {
+ XCopyArea(dpy, fwin->languagebutton_back[i], tmp[i], scr->copy_gc,
+   0, 0, button->width, button->height, -1, -1);
+ } else {
+ unsigned long bg_pixel = fwin->title_texture[i]->solid.normal.pixel;
+ XSetForeground(dpy, gc, bg_pixel);
+ XFillRectangle(dpy, tmp[i], gc, 0, 0, key_width, key_height);
+ }
+ WMDrawString(scr->wmscreen, tmp[i], fwin->title_color[i],
+  small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
+ }
+ } else if (wPreferences.new_style == TS_OLD) {
+ unsigned long bg_pixel = scr->widget_texture->normal.pixel;
+ XSetForeground(dpy, gc, bg_pixel);
+ XFillRectangle(dpy, tmp[0], gc, 0, 0, key_width, key_height);
+ WMDrawString(scr->wmscreen, tmp[0], WMBlackColor(scr->wmscreen),
+  small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
+ } else {
+ unsigned long bg_pixel = scr->widget_texture->dark.pixel;
+ XSetForeground(dpy, gc, bg_pixel);
+ XFillRectangle(dpy, tmp[0], gc, 0, 0, key_width, key_height);
+ WMColor *silver = WMCreateRGBColor(scr->wmscreen, 0xc0c0, 0xc0c0, 0xc0c0, True);
+ WMDrawString(scr->wmscreen, tmp[0], silver,
+  small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
+ WMReleaseColor(silver);
+ }
+
+ /* pushed button next to normal for easy access when painting */
+ text_x = key_width + (key_width - text_width) / 2;
+ if (wPreferences.new_style == TS_NEW) {
+ XSetForeground(dpy, gc, scr->white_pixel);
+ for (i = 0; i < 2; i++) {
+ XFillRectangle(dpy, tmp[i], gc, key_width, 0, key_width, key_height);
+ WMDrawString(scr->wmscreen, tmp[i], WMBlackColor(scr->wmscreen),
+  small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
+ }
+ } else if (wPreferences.new_style == TS_OLD) {
+ XSetForeground(dpy, gc, scr->white_pixel);
+ XFillRectangle(dpy, tmp[0], gc, key_width, 0, key_width, key_height);
+ WMDrawString(scr->wmscreen, tmp[0], WMDarkGrayColor(scr->wmscreen),
+ small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
+ } else {
+ unsigned long bg_pixel = scr->widget_texture->dark.pixel;
+ WMColor *silver = WMCreateRGBColor(scr->wmscreen, 0xc0c0, 0xc0c0, 0xc0c0, True);
+ XSetForeground(dpy, gc, bg_pixel);
+ XFillRectangle(dpy, tmp[0], gc, key_width, 0, key_width, key_height);
+ WMDrawString(scr->wmscreen, tmp[0], silver,
+  small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
+ WMReleaseColor(silver);
+ }
+
+ WMReleaseFont(small);
+
+ wFrameWindowSetLanguageButtonImages(fwin, tmp, (wPreferences.new_style == TS_NEW) ? 2 : 1);
 }
 #endif /* XKB_BUTTON_HINT */
 
@@ -1286,7 +1430,7 @@ static void checkTitleSize(WFrameWindow * fwin)
  fwin->flags.incomplete_title = 0;
 }
 
-static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long color, WPixmap * image, int pushed)
+static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long color, WPixmap * image, int pushed, int from_xpm)
 {
  WScreen *scr = button->screen_ptr;
  GC copy_gc = scr->copy_gc;
@@ -1364,8 +1508,12 @@ static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long
  } else {
  if (wPreferences.new_style == TS_OLD) {
  XSetForeground(dpy, copy_gc, scr->dark_pixel);
- XFillRectangle(dpy, button->window, copy_gc, 0, 0,
-        button->width, button->height);
+ if (from_xpm)
+ XFillRectangle(dpy, button->window, copy_gc, 0, 0,
+ button->width, button->height);
+ else
+ XCopyArea(dpy, image->image, button->window, copy_gc,
+ left, 0, width, image->height, x, y);
  } else {
  XSetForeground(dpy, copy_gc, scr->black_pixel);
  XCopyArea(dpy, image->image, button->window, copy_gc,
@@ -1379,7 +1527,11 @@ static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long
  XSetForeground(dpy, copy_gc, color);
  XSetBackground(dpy, copy_gc, texture->any.color.pixel);
  }
- XFillRectangle(dpy, button->window, copy_gc, 0, 0, button->width, button->height);
+ if (from_xpm)
+ XFillRectangle(dpy, button->window, copy_gc, 0, 0, button->width, button->height);
+ else
+ XCopyArea(dpy, image->image, button->window, copy_gc,
+ left, 0, width, image->height, x, y);
  }
  }
 }
@@ -1394,18 +1546,23 @@ static void handleButtonExpose(WObjDescriptor * desc, XEvent * event)
 
 #ifdef XKB_BUTTON_HINT
  if (button == fwin->language_button) {
- if (!fwin->flags.hide_language_button)
+ if (!fwin->flags.hide_language_button) {
+ /* map focused and pfocused states to focus language button image */
+ int lb_index = wPreferences.new_style == TS_NEW && (fwin->flags.state == 1) ? 1 : 0;
+
  paintButton(button, fwin->title_texture[fwin->flags.state],
      WMColorPixel(fwin->title_color[fwin->flags.state]),
-     fwin->languagebutton_image, False);
+     fwin->languagebutton_image[lb_index],
+     False, False);
+ }
  } else
 #endif
  if (button == fwin->left_button)
  paintButton(button, fwin->title_texture[fwin->flags.state],
-     WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->lbutton_image, False);
+     WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->lbutton_image, False, True);
  else
  paintButton(button, fwin->title_texture[fwin->flags.state],
-     WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->rbutton_image, False);
+     WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->rbutton_image, False, True);
 }
 
 static void titlebarMouseDown(WObjDescriptor * desc, XEvent * event)
@@ -1437,7 +1594,7 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
  WCoreWindow *button = desc->self;
  WPixmap *image;
  XEvent ev;
- int done = 0, execute = 1;
+ int done = 0, execute = 1, from_xpm = True;
  WTexture *texture;
  unsigned long pixel;
  int clickButton = event->xbutton.button;
@@ -1458,13 +1615,18 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
  if (button == fwin->language_button) {
  if (!wPreferences.modelock)
  return;
- image = fwin->languagebutton_image;
+
+ /* map focused and pfocused states to focus language button image */
+ int lb_index = wPreferences.new_style == TS_NEW && (fwin->flags.state == 1) ? 1 : 0;
+
+ image = fwin->languagebutton_image[lb_index];
+ from_xpm = False;
  }
 #endif
 
  pixel = WMColorPixel(fwin->title_color[fwin->flags.state]);
  texture = fwin->title_texture[fwin->flags.state];
- paintButton(button, texture, pixel, image, True);
+ paintButton(button, texture, pixel, image, True, from_xpm);
 
  while (!done) {
  WMMaskEvent(dpy, LeaveWindowMask | EnterWindowMask | ButtonReleaseMask
@@ -1472,12 +1634,12 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
  switch (ev.type) {
  case LeaveNotify:
  execute = 0;
- paintButton(button, texture, pixel, image, False);
+ paintButton(button, texture, pixel, image, False, from_xpm);
  break;
 
  case EnterNotify:
  execute = 1;
- paintButton(button, texture, pixel, image, True);
+ paintButton(button, texture, pixel, image, True, from_xpm);
  break;
 
  case ButtonPress:
@@ -1492,7 +1654,7 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
  WMHandleEvent(&ev);
  }
  }
- paintButton(button, texture, pixel, image, False);
+ paintButton(button, texture, pixel, image, False, from_xpm);
 
  if (execute) {
  if (button == fwin->left_button) {
diff --git a/src/framewin.h b/src/framewin.h
index 8b0a53ca..1e034755 100644
--- a/src/framewin.h
+++ b/src/framewin.h
@@ -80,7 +80,7 @@ typedef struct WFrameWindow {
     WPixmap *lbutton_image;
     WPixmap *rbutton_image;
 #ifdef XKB_BUTTON_HINT
-    WPixmap *languagebutton_image;
+    WPixmap *languagebutton_image[2];    /* focused, unfocused */
 #endif
 
     union WTexture **title_texture;
@@ -93,6 +93,7 @@ typedef struct WFrameWindow {
 #ifdef KEEP_XKB_LOCK_STATUS
     int languagemode;
     int last_languagemode;
+    char language_label[3];        /* 2-letter language code */
 #endif /* KEEP_XKB_LOCK_STATUS */
 
     /* thing that uses this frame. passed as data to callbacks */
diff --git a/src/screen.c b/src/screen.c
index 009622dd..172a6078 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -658,11 +658,10 @@ WScreen *wScreenInit(int screen_number)
  XSelectInput(dpy, scr->root_win, event_mask);
 
 #ifdef KEEP_XKB_LOCK_STATUS
- /* Only GroupLock doesn't work correctly in my system since right-alt
-  * can change mode while holding it too - ]d
-  */
  if (w_global.xext.xkb.supported)
- XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask|XkbNewKeyboardNotifyMask, XkbIndicatorStateNotifyMask|XkbNewKeyboardNotifyMask);
+ XkbSelectEvents(dpy, XkbUseCoreKbd,
+ XkbIndicatorStateNotifyMask | XkbStateNotifyMask | XkbNewKeyboardNotifyMask,
+ XkbIndicatorStateNotifyMask | XkbStateNotifyMask | XkbNewKeyboardNotifyMask);
 #else
  if (w_global.xext.xkb.supported)
  XkbSelectEvents(dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
diff --git a/src/window.c b/src/window.c
index 71d14a63..032ead46 100644
--- a/src/window.c
+++ b/src/window.c
@@ -24,6 +24,7 @@
 
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include <X11/Xatom.h>
 #ifdef USE_XSHAPE
 #include <X11/extensions/shape.h>
 #endif
@@ -38,6 +39,12 @@
 #include <string.h>
 #include <stdint.h>
 #include <math.h>
+#include <ctype.h>
+
+#ifdef XKB_BUTTON_HINT
+#include <X11/extensions/XKBfile.h>  // Required for XkbRF_VarDefsRec
+#include <X11/extensions/XKBrules.h> // Required for XkbRF_GetNamesProp
+#endif
 
 /* For getting mouse wheel mappings from WINGs */
 #include <WINGs/WINGs.h>
@@ -2304,14 +2311,6 @@ void wWindowUpdateButtonImages(WWindow *wwin)
  fwin->lbutton_image = scr->b_pixmaps[WBUT_ICONIFY];
  }
  }
-#ifdef XKB_BUTTON_HINT
- if (!WFLAGP(wwin, no_language_button)) {
- if (fwin->languagebutton_image && !fwin->languagebutton_image->shared)
- wPixmapDestroy(fwin->languagebutton_image);
-
- fwin->languagebutton_image = scr->b_pixmaps[WBUT_XKBGROUP1 + fwin->languagemode];
- }
-#endif
 
  /* close button */
 
@@ -3145,6 +3144,37 @@ static void windowCloseDblClick(WCoreWindow *sender, void *data, XEvent *event)
 }
 
 #ifdef XKB_BUTTON_HINT
+/* Helper function to extract the 2-letter language code for a given XKB group index */
+void wWindowGetLanguageLabel(int group_index, char *label)
+{
+ XkbRF_VarDefsRec vd;
+ /* Default to empty - will fallback to pixmap if we can't get the label */
+ label[0] = '\0';
+
+ if (XkbRF_GetNamesProp(dpy, NULL, &vd) && vd.layout) {
+ int i;
+ char *layout_list = strdup(vd.layout);
+ char *tok = strtok(layout_list, ",");
+
+ /* Iterate to the requested group index */
+ for (i = 0; i < group_index && tok != NULL; i++) {
+ tok = strtok(NULL, ",");
+ }
+
+ if (tok) {
+ /* Copy exactly the first two bytes, then format: first uppercase, second lowercase */
+ strncpy(label, tok, 2);
+ label[2] = '\0';
+ if (label[0])
+ label[0] = (char) toupper((unsigned char) label[0]);
+ if (label[1])
+ label[1] = (char) tolower((unsigned char) label[1]);
+ }
+
+ free(layout_list);
+ }
+}
+
 static void windowLanguageClick(WCoreWindow *sender, void *data, XEvent *event)
 {
  WWindow *wwin = data;
@@ -3158,11 +3188,45 @@ static void windowLanguageClick(WCoreWindow *sender, void *data, XEvent *event)
  if (event->xbutton.button != Button1 && event->xbutton.button != Button3)
  return;
  tl = wwin->frame->languagemode;
- wwin->frame->languagemode = wwin->frame->last_languagemode;
- wwin->frame->last_languagemode = tl;
+
+ /* Try to advance to the next available XKB group */
+ XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+ int newgroup = -1;
+ if (desc && desc->names) {
+ int i;
+ const int MAX_GROUPS = 4; /* typical XKB max groups */
+ for (i = 1; i <= MAX_GROUPS; i++) {
+ int cand = (tl + i) % MAX_GROUPS;
+ Atom a = desc->names->groups[cand];
+ if (a != None) {
+ /* Use XGetAtomName to ensure the atom actually has a name */
+ char *nm = XGetAtomName(dpy, a);
+ if (nm && nm[0] != '\0') {
+ newgroup = cand;
+ XFree(nm);
+ break;
+ }
+ if (nm)
+ XFree(nm);
+ }
+ }
+ }
+
+ if (newgroup >= 0) {
+ wwin->frame->last_languagemode = tl;
+ wwin->frame->languagemode = newgroup;
+ XkbLockGroup(dpy, XkbUseCoreKbd, wwin->frame->languagemode);
+ } else {
+ /* fallback to previous toggle behaviour for setups with only two
+  * groups or when group info is not available */
+ wwin->frame->languagemode = wwin->frame->last_languagemode;
+ wwin->frame->last_languagemode = tl;
+ XkbLockGroup(dpy, XkbUseCoreKbd, wwin->frame->languagemode);
+ }
+ /* Update label */
+ wWindowGetLanguageLabel(wwin->frame->languagemode, wwin->frame->language_label);
+
  wSetFocusTo(scr, wwin);
- wwin->frame->languagebutton_image =
-     wwin->frame->screen_ptr->b_pixmaps[WBUT_XKBGROUP1 + wwin->frame->languagemode];
  wFrameWindowUpdateLanguageButton(wwin->frame);
  if (event->xbutton.button == Button3)
  return;
diff --git a/src/window.h b/src/window.h
index 0481b46b..5da5d897 100644
--- a/src/window.h
+++ b/src/window.h
@@ -405,4 +405,9 @@ void wWindowDeleteSavedState(WMagicNumber id);
 Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured);
 
 void wWindowSetOmnipresent(WWindow *wwin, Bool flag);
+
+#ifdef XKB_BUTTON_HINT
+void wWindowGetLanguageLabel(int group_index, char *label);
+#endif
+
 #endif
--
2.43.0

0001-wmaker-revamp-titlebar-language-button.patch
Reply all
Reply to author
Forward
0 new messages