From ec115fedf7e17538037f4e85f5147b61ca68f72f Mon Sep 17 00:00:00 2001 From: David Maciejak Date: Thu, 12 Mar 2026 21:16:42 -0400 Subject: [PATCH] wmaker: revamp titlebar language button 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 +@%:@include +@%:@include +@%:@include +@%:@include +], [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 #include +#include #ifdef USE_XSHAPE #include #endif @@ -38,6 +39,12 @@ #include #include #include +#include + +#ifdef XKB_BUTTON_HINT +#include // Required for XkbRF_VarDefsRec +#include // Required for XkbRF_GetNamesProp +#endif /* For getting mouse wheel mappings from WINGs */ #include @@ -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