Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package jgmenu for openSUSE:Factory checked 
in at 2026-09-17 15:18:07
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/jgmenu (Old)
 and      /work/SRC/openSUSE:Factory/.jgmenu.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "jgmenu"

Thu Sep 17 15:18:07 2026 rev:23 rq:1378240 version:4.7.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/jgmenu/jgmenu.changes    2026-08-11 
17:13:50.481670863 +0200
+++ /work/SRC/openSUSE:Factory/.jgmenu.new.383539/jgmenu.changes        
2026-09-17 15:19:48.470957524 +0200
@@ -1,0 +2,18 @@
+Wed Sep 16 05:07:18 UTC 2026 - Michael Vetter <[email protected]>
+
+- Update to 4.7.0:
+  Added:
+  * Support multi-line menu items #259
+  * Add caret (cursor) to search widget #268
+  * Add the following configuration options:
+    + respect_workarea to optionally ignore the _NET_WORKAREA when
+      position_mode=pointer #227 #260
+    + sep_text_height to specify the height of separators with text
+      (defined by ^sep(text)) #261
+    + focus_on_open to focus the first selectable item when opening the
+      menu #263
+  Fixed:
+  * Fix NetBSD build errors #269 #272
+  * Fix search widget pango markup handling #237 #262
+
+-------------------------------------------------------------------

Old:
----
  v4.6.0.tar.gz

New:
----
  v4.7.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ jgmenu.spec ++++++
--- /var/tmp/diff_new_pack.ciC88S/_old  2026-09-17 15:19:49.204988320 +0200
+++ /var/tmp/diff_new_pack.ciC88S/_new  2026-09-17 15:19:49.206988403 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           jgmenu
-Version:        4.6.0
+Version:        4.7.0
 Release:        0
 Summary:        Small X11 menu intended to be used with openbox and tint2
 License:        GPL-2.0-only

++++++ v4.6.0.tar.gz -> v4.7.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/Makefile new/jgmenu-4.7.0/Makefile
--- old/jgmenu-4.6.0/Makefile   2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/Makefile   2026-09-15 21:09:14.000000000 +0200
@@ -13,14 +13,14 @@
 -include config.mk
 include Makefile.inc
 
-jgmenu:         CFLAGS += `pkg-config cairo pango pangocairo $(RSVG_LIB) 
--cflags` $(RSVG_FLAGS)
+jgmenu:         CFLAGS += `pkg-config libpng cairo pango pangocairo 
$(RSVG_LIB) --cflags` $(RSVG_FLAGS)
+jgmenu:         CFLAGS += -pthread
 jgmenu-ob:      CFLAGS += `pkg-config --cflags libxml-2.0`
 jgmenu-obtheme: CFLAGS += `pkg-config --cflags libxml-2.0`
 jgmenu-config:  CFLAGS += `pkg-config --cflags glib-2.0`
 jgmenu-apps:    CFLAGS += `pkg-config --cflags glib-2.0`
 
-jgmenu:         LIBS   += `pkg-config x11 xrandr cairo pango pangocairo 
$(RSVG_LIB) --libs` $(RSVG_FLAGS)
-jgmenu:         LIBS   += -pthread -lpng
+jgmenu:         LIBS   += `pkg-config x11 xrandr libpng cairo pango pangocairo 
$(RSVG_LIB) --libs` $(RSVG_FLAGS)
 jgmenu-ob:      LIBS   += `pkg-config --libs libxml-2.0`
 jgmenu-obtheme: LIBS   += `pkg-config --libs libxml-2.0`
 jgmenu-config:  LIBS   += `pkg-config --libs glib-2.0`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/Makefile.inc 
new/jgmenu-4.7.0/Makefile.inc
--- old/jgmenu-4.6.0/Makefile.inc       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/Makefile.inc       2026-09-15 21:09:14.000000000 +0200
@@ -39,3 +39,9 @@
 endif
 
 MAKEFLAGS += --no-print-directory
+
+ifdef SECTIONS
+# Find unused functions
+CFLAGS    += -ffunction-sections
+LDFLAGS   += -Wl,--gc-sections -Wl,--print-gc-sections
+endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/NEWS.md new/jgmenu-4.7.0/NEWS.md
--- old/jgmenu-4.6.0/NEWS.md    2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/NEWS.md    2026-09-15 21:09:14.000000000 +0200
@@ -8,6 +8,7 @@
 
 | Date       | Release Notes                     |
 |------------|-----------------------------------|
+| 2026-09-15 | [v4.7.0](docs/relnotes/4.7.0.txt) |
 | 2026-08-07 | [v4.6.0](docs/relnotes/4.6.0.txt) |
 | 2024-12-06 | [v4.5.0](docs/relnotes/4.5.0.txt) |
 | 2022-10-30 | [v4.4.1](docs/relnotes/4.4.1.txt) |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/docs/manual/jgmenu.1.md 
new/jgmenu-4.7.0/docs/manual/jgmenu.1.md
--- old/jgmenu-4.6.0/docs/manual/jgmenu.1.md    2026-08-07 20:18:13.000000000 
+0200
+++ new/jgmenu-4.7.0/docs/manual/jgmenu.1.md    2026-09-15 21:09:14.000000000 
+0200
@@ -440,6 +440,10 @@
     jgmenu during the boot process and then sending a `killall -SIGUSR1 jgmenu`
     to show the menu.
 
+`focus_on_open` = __boolean__ (default 0)
+
+:   If set to 1, jgmenu will focus the first selectable item when opening.
+
 `csv_cmd` = __string__ (default `apps`)
 
 :   Defines the command to produce the jgmenu flavoured CSV for `jgmenu`.
@@ -479,6 +483,12 @@
     :   Launch at center of screen and ignore `_NET_WORKAREA`.
         Take precedence over `menu_{v,h}align`.
 
+`respect_workarea` = __integer__ (default 1)
+
+:   Whether to respect `_NET_WORKAREA` when positioning the menu.
+    Set to 0 to ignore reserved areas such as panels while keeping
+    pointer positioning.
+
 `edge_snap_x` = __integer__ (default 30)
 
 :   Specify the distance (in pixels) from the left hand edge, within which the
@@ -659,8 +669,11 @@
 
 `sep_height` = __integer__ (default 5)
 
-:   Height of separator without text (defined by ^sep()). Separators with text
-    use `item_height`
+:   Height of separator without text (defined by ^sep()).
+
+`sep_text_height` = __integer__ (default 25)
+
+:   Height of separator with text (defined by ^sep(text)).
 
 `sep_halign` = (left | center | right) (default left)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/docs/relnotes/4.7.0.txt 
new/jgmenu-4.7.0/docs/relnotes/4.7.0.txt
--- old/jgmenu-4.6.0/docs/relnotes/4.7.0.txt    1970-01-01 01:00:00.000000000 
+0100
+++ new/jgmenu-4.7.0/docs/relnotes/4.7.0.txt    2026-09-15 21:09:14.000000000 
+0200
@@ -0,0 +1,19 @@
+jgmenu v4.7.0
+
+### Added
+
+- Support multi-line menu items @Dupond [#259]
+- Add caret (cursor) to search widget @Dupond [#268]
+- Add the following configuration options:
+  - `respect_workarea` to optionally ignore the `_NET_WORKAREA` when
+    `position_mode=pointer` @Dupond [#227] [#260]
+  - `sep_text_height` to specify the height of separators with text
+    (defined by ^sep(text)) @Dupond [#261]
+  - `focus_on_open` to focus the first selectable item when opening the
+    menu @Dupond [#263]
+
+### Fixed
+
+- Fix NetBSD build errors @johanmalm @scotthep [#269] [#272]
+- Fix `search` widget pango markup handling @Dupond [#237] [#262]
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/scripts/version-gen.sh 
new/jgmenu-4.7.0/scripts/version-gen.sh
--- old/jgmenu-4.6.0/scripts/version-gen.sh     2026-08-07 20:18:13.000000000 
+0200
+++ new/jgmenu-4.7.0/scripts/version-gen.sh     2026-09-15 21:09:14.000000000 
+0200
@@ -8,7 +8,7 @@
 # Change "default_version" before doing a `git tag -a ...`
 #
 
-default_version="4.6.0"
+default_version="4.7.0"
 
 v=$(git describe --dirty --abbrev=1 2>/dev/null)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/argv-buf.c 
new/jgmenu-4.7.0/src/argv-buf.c
--- old/jgmenu-4.6.0/src/argv-buf.c     2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/argv-buf.c     2026-09-15 21:09:14.000000000 +0200
@@ -74,8 +74,8 @@
 
        for (i = 0; i < buf->argc; i++) {
                p = buf->argv[i];
-               rtrim(&p);
-               while (isspace(*p))
+               rtrim(p);
+               while (isspace((unsigned char)*p))
                        p++;
                buf->argv[i] = p;
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/compat.c 
new/jgmenu-4.7.0/src/compat.c
--- old/jgmenu-4.6.0/src/compat.c       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/compat.c       2026-09-15 21:09:14.000000000 +0200
@@ -1,38 +1,11 @@
 /*
  * These functions have been copied from:
- * https://github.com/git/git/blob/master/compat/strcasestr.c
  * https://github.com/git/git/blob/master/compat/strlcpy.c
  */
 
 #include "compat.h"
 #include "banned.h"
 
-/* clang-format off */
-char *gitstrcasestr(const char *haystack, const char *needle)
-{
-       int nlen = strlen(needle);
-       int hlen = strlen(haystack) - nlen + 1;
-       int i;
-
-       for (i = 0; i < hlen; i++) {
-               int j;
-
-               for (j = 0; j < nlen; j++) {
-                       unsigned char c1 = haystack[i + j];
-                       unsigned char c2 = needle[j];
-
-                       if (toupper(c1) != toupper(c2))
-                               goto next;
-               }
-               return (char *)haystack + i;
-next:
-               ;
-       }
-       return NULL;
-}
-
-/* clang-format on */
-
 size_t gitstrlcpy(char *dest, const char *src, size_t size)
 {
        size_t ret = strlen(src);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/compat.h 
new/jgmenu-4.7.0/src/compat.h
--- old/jgmenu-4.6.0/src/compat.h       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/compat.h       2026-09-15 21:09:14.000000000 +0200
@@ -7,9 +7,6 @@
 #include <string.h>
 #include <ctype.h>
 
-#define strcasestr gitstrcasestr
-char *gitstrcasestr(const char *haystack, const char *needle);
-
 #define strlcpy gitstrlcpy
 size_t gitstrlcpy(char *, const char *, size_t);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/config.c 
new/jgmenu-4.7.0/src/config.c
--- old/jgmenu-4.6.0/src/config.c       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/config.c       2026-09-15 21:09:14.000000000 +0200
@@ -23,6 +23,7 @@
        config.stay_alive          = 1;
        config.persistent          = 0;
        config.hide_on_startup     = 0;
+       config.focus_on_open       = 0;
        config.csv_cmd             = xstrdup("apps");
        config.tint2_look          = 0;
        config.position_mode       = POSITION_MODE_FIXED;
@@ -68,6 +69,7 @@
        config.item_halign         = LEFT;
 
        config.sep_height          = 5;
+       config.sep_text_height = 25;
        config.sep_markup          = NULL;
        config.sep_halign          = CENTER;
 
@@ -143,6 +145,9 @@
        } else if (!strcmp(option, "hide_on_startup")) {
                xatoi(&config.hide_on_startup, value, XATOI_NONNEG,
                      "config.hide_on_startup");
+       } else if (!strcmp(option, "focus_on_open")) {
+               xatoi(&config.focus_on_open, value, XATOI_NONNEG,
+                     "config.focus_on_open");
        } else if (!strcmp(option, "csv_cmd")) {
                xfree(config.csv_cmd);
                config.csv_cmd = xstrdup(value);
@@ -175,6 +180,10 @@
                        warn("position_mode value '%s' not recognised", value);
                }
 
+       } else if (!strcmp(option, "respect_workarea")) {
+               xatoi(&config.respect_workarea, value, XATOI_NONNEG,
+                     "config.respect_workarea");
+
        } else if (!strcmp(option, "edge_snap_x")) {
                xatoi(&config.edge_snap_x, value, XATOI_NONNEG,
                      "config.edge_snap_x");
@@ -322,6 +331,9 @@
        } else if (!strcmp(option, "sep_height")) {
                xatoi(&config.sep_height, value, XATOI_NONNEG,
                      "config.sep_height");
+       } else if (!strcmp(option, "sep_text_height")) {
+               xatoi(&config.sep_text_height, value, XATOI_GT_0,
+                     "config.sep_text_height");
        } else if (!strcmp(option, "sep_markup")) {
                xfree(config.sep_markup);
                config.sep_markup = xstrdup(value);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/config.h 
new/jgmenu-4.7.0/src/config.h
--- old/jgmenu-4.6.0/src/config.h       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/config.h       2026-09-15 21:09:14.000000000 +0200
@@ -16,6 +16,7 @@
        int stay_alive;
        int persistent;
        int hide_on_startup;
+       int focus_on_open;
        char *csv_cmd;
        int tint2_look;
        enum position_mode position_mode;
@@ -60,6 +61,7 @@
        int item_border;
        enum alignment item_halign;
        int sep_height;
+       int sep_text_height;
        char *sep_markup;
        enum alignment sep_halign;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/desktop.c 
new/jgmenu-4.7.0/src/desktop.c
--- old/jgmenu-4.6.0/src/desktop.c      2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/desktop.c      2026-09-15 21:09:14.000000000 +0200
@@ -108,11 +108,11 @@
  *  (a) %% which becomes %
  *  (b) backslash escaped characters are resolved
  */
-static void strip_exec_field_codes(char **exec)
+static void strip_exec_field_codes(char *exec)
 {
-       if (!**exec || !*exec)
+       if (!*exec || !exec)
                return;
-       for (char *p = *exec; *p; p++) {
+       for (char *p = exec; *p; p++) {
                if (*p == '\\') {
                        delchar(p);
                        continue;
@@ -164,7 +164,7 @@
        }
        strlcpy(app->filename, filename, sizeof(app->filename));
        p = &app->exec[0];
-       strip_exec_field_codes(&p);
+       strip_exec_field_codes(p);
 
        if (app->tryexec[0] != '\0' && !isprog(app->tryexec))
                app->tryexec_not_in_path = true;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/filter.c 
new/jgmenu-4.7.0/src/filter.c
--- old/jgmenu-4.6.0/src/filter.c       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/filter.c       2026-09-15 21:09:14.000000000 +0200
@@ -86,7 +86,7 @@
 
        do {
                filter_backspace();
-       } while (!isspace(needle.buf[needle.len-1]) && needle.len);
+       } while (!isspace((unsigned char)needle.buf[needle.len - 1]) && 
needle.len);
 }
 
 void filter_reset(void)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/jgmenu-config.c 
new/jgmenu-4.7.0/src/jgmenu-config.c
--- old/jgmenu-4.6.0/src/jgmenu-config.c        2026-08-07 20:18:13.000000000 
+0200
+++ new/jgmenu-4.7.0/src/jgmenu-config.c        2026-09-15 21:09:14.000000000 
+0200
@@ -28,9 +28,11 @@
        { "stay_alive", "1" },
        { "persistent", "0" },
        { "hide_on_startup", "0" },
+       { "focus_on_open", "0" },
        { "csv_cmd", "apps" },
        { "tint2_look", "0" },
        { "position_mode", "fixed" },
+       { "respect_workarea", "1" },
        { "edge_snap_x", "30" },
        { "terminal_exec", "x-terminal-emulator" },
        { "terminal_args", "-e" },
@@ -68,6 +70,7 @@
        { "item_border", "0" },
        { "item_halign", "left" },
        { "sep_height", "5" },
+       { "sep_text_height", "25" },
        { "sep_halign", "left" },
        { "sep_markup", "" },
        { "font", "" },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/jgmenu.c 
new/jgmenu-4.7.0/src/jgmenu.c
--- old/jgmenu-4.6.0/src/jgmenu.c       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/jgmenu.c       2026-09-15 21:09:14.000000000 +0200
@@ -580,7 +580,7 @@
        offsetx = cairo_image_surface_get_width(p->icon) < config.icon_size ?
                  (config.icon_size - cairo_image_surface_get_width(p->icon)) / 
2 : 0;
 
-       icon_y_coord = p->area.y + (config.item_height - config.icon_size) / 2 +
+       icon_y_coord = p->area.y + (p->area.h - config.icon_size) / 2 +
                       offsety;
        if (config.item_halign != RIGHT)
                ui_insert_image(p->icon, p->area.x + config.item_padding_x +
@@ -1035,8 +1035,12 @@
                update(1);
        }
 
-       /* Remove previous selection on awake */
-       menu.sel = NULL;
+       if (config.focus_on_open) {
+               menu.sel = first_selectable();
+               menu.current_node->last_sel = menu.sel;
+       } else {
+               menu.sel = NULL;
+       }
        draw_menu();
 
        XMapWindow(ui->dpy, ui->w[ui->cur].win);
@@ -1177,6 +1181,29 @@
        snprintf(utag, UTAG_BUFSIZ, "%d,^tag(%d", i, i);
 }
 
+/**
+ * Support multi-line menu items with dynamic item height while preserving
+ * the vertical padding of single-line items.
+ */
+static int item_height(struct item *item)
+{
+       static int font_height = -1;
+       struct point point;
+       int padding;
+
+       if (font_height == -1)
+               font_height = ui_get_text_size("abcfghjklABC",
+                                              font_get()).y;
+
+       point = ui_get_text_size(item->name, font_get());
+
+       padding = config.item_height - font_height;
+
+       return point.y + padding > config.item_height ?
+              point.y + padding :
+              config.item_height;
+}
+
 static void insert_tag_item(void)
 {
        struct item *item = NULL;
@@ -1198,7 +1225,7 @@
        item->icon = NULL;
        item->tag = item->cmd + 5;
        item->selectable = 1;
-       item->area.h = config.item_height;
+       item->area.h = item_height(item);
        list_add_tail(&item->master, &menu.master);
 }
 
@@ -1208,11 +1235,11 @@
 
        if (!s)
                return;
-       p = strstr(s, "\\n");
-       if (!p)
-               return;
-       *p = ' ';
-       *(p + 1) = '\n';
+       while ((p = strstr(s, "\\n"))) {
+               *p = ' ';
+               *(p + 1) = '\n';
+               s = p + 2;
+       }
 }
 
 /**
@@ -1299,11 +1326,13 @@
                else
                        item->tag = NULL;
                item->selectable = 1;
-               item->area.h = config.item_height;
+               item->area.h = item_height(item);
                if (!strncmp(item->name, "^sep(", 5)) {
                        item->selectable = 0;
                        if (item->name[5] == '\0')
                                item->area.h = config.sep_height;
+                       else
+                               item->area.h = config.sep_text_height;
                }
                list_add_tail(&item->master, &menu.master);
        }
@@ -2770,7 +2799,12 @@
        else
                XMapRaised(ui->dpy, ui->w[ui->cur].win);
 
-       menu.sel = NULL;
+       if (config.focus_on_open) {
+               menu.sel = first_selectable();
+               menu.current_node->last_sel = menu.sel;
+       } else {
+               menu.sel = NULL;
+       }
        draw_menu();
 
        atexit(cleanup);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/list.h new/jgmenu-4.7.0/src/list.h
--- old/jgmenu-4.6.0/src/list.h 2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/list.h 2026-09-15 21:09:14.000000000 +0200
@@ -1,5 +1,6 @@
 #ifndef _LINUX_LIST_H
 #define _LINUX_LIST_H
+#include <stddef.h>
 
 /*
  * This file is copied from:
@@ -21,7 +22,7 @@
  */
 
 /*
- * Simple doubly linked list implementation.
+ * Circular doubly linked list implementation.
  *
  * Some of the internal functions ("__xxx") are useful when
  * manipulating whole lists rather than single entries, as
@@ -135,14 +136,38 @@
        new->prev->next = new;
 }
 
+/**
+ * list_replace_init - replace old entry by new one and initialize the old one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
 static inline void list_replace_init(struct list_head *old,
-                                       struct list_head *new)
+                                    struct list_head *new)
 {
        list_replace(old, new);
        INIT_LIST_HEAD(old);
 }
 
 /**
+ * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's 
position
+ * @entry1: the location to place entry2
+ * @entry2: the location to place entry1
+ */
+static inline void list_swap(struct list_head *entry1,
+                            struct list_head *entry2)
+{
+       struct list_head *pos = entry2->prev;
+
+       list_del(entry2);
+       list_replace(entry1, entry2);
+       if (pos == entry1)
+               pos = entry2;
+       list_add(entry1, pos);
+}
+
+/**
  * list_del_init - deletes entry from list and reinitialize it.
  * @entry: the element to delete from the list.
  */
@@ -176,17 +201,59 @@
 }
 
 /**
+ * list_bulk_move_tail - move a subsection of a list to its tail
+ * @head: the head that will follow our entry
+ * @first: first entry to move
+ * @last: last entry to move, can be the same as first
+ *
+ * Move all entries between @first and including @last before @head.
+ * All three entries must belong to the same linked list.
+ */
+static inline void list_bulk_move_tail(struct list_head *head,
+                                      struct list_head *first,
+                                      struct list_head *last)
+{
+       first->prev->next = last->next;
+       last->next->prev = first->prev;
+
+       head->prev->next = first;
+       first->prev = head->prev;
+
+       last->next = head;
+       head->prev = last;
+}
+
+/**
+ * list_is_first -- tests whether @list is the first entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_first(const struct list_head *list, const struct 
list_head *head)
+{
+       return list->prev == head;
+}
+
+/**
  * list_is_last - tests whether @list is the last entry in list @head
  * @list: the entry to test
  * @head: the head of the list
  */
-static inline int list_is_last(const struct list_head *list,
-                               const struct list_head *head)
+static inline int list_is_last(const struct list_head *list, const struct 
list_head *head)
 {
        return list->next == head;
 }
 
 /**
+ * list_is_head - tests whether @list is the list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_head(const struct list_head *list, const struct 
list_head *head)
+{
+       return list == head;
+}
+
+/**
  * list_empty - tests whether a list is empty
  * @head: the list to test.
  */
@@ -230,6 +297,24 @@
 }
 
 /**
+ * list_rotate_to_front() - Rotate list to specific item.
+ * @list: The desired new front of the list.
+ * @head: The head of the list.
+ *
+ * Rotates list so that @list becomes the new front of the list.
+ */
+static inline void list_rotate_to_front(struct list_head *list,
+                                       struct list_head *head)
+{
+       /*
+        * Deletes the list head from the list denoted by @head and
+        * places it as the tail of @list, this effectively rotates the
+        * list so that @list is at the front.
+        */
+       list_move_tail(head, list);
+}
+
+/**
  * list_is_singular - tests whether a list has just one entry.
  * @head: the list to test.
  */
@@ -242,7 +327,6 @@
                struct list_head *head, struct list_head *entry)
 {
        struct list_head *new_first = entry->next;
-
        list->next = head->next;
        list->next->prev = list;
        list->prev = entry;
@@ -270,15 +354,44 @@
 {
        if (list_empty(head))
                return;
-       if (list_is_singular(head) &&
-               (head->next != entry && head != entry))
+       if (list_is_singular(head) && !list_is_head(entry, head) && (entry != 
head->next))
                return;
-       if (entry == head)
+       if (list_is_head(entry, head))
                INIT_LIST_HEAD(list);
        else
                __list_cut_position(list, head, entry);
 }
 
+/**
+ * list_cut_before - cut a list into two, before given entry
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *
+ * This helper moves the initial part of @head, up to but
+ * excluding @entry, from @head to @list.  You should pass
+ * in @entry an element you know is on @head.  @list should
+ * be an empty list or a list you do not care about losing
+ * its data.
+ * If @entry == @head, all entries on @head are moved to
+ * @list.
+ */
+static inline void list_cut_before(struct list_head *list,
+                                  struct list_head *head,
+                                  struct list_head *entry)
+{
+       if (head->next == entry) {
+               INIT_LIST_HEAD(list);
+               return;
+       }
+       list->next = head->next;
+       list->next->prev = list;
+       list->prev = entry->prev;
+       list->prev->next = list;
+       head->next = entry;
+       entry->prev = head;
+}
+
 static inline void __list_splice(const struct list_head *list,
                                 struct list_head *prev,
                                 struct list_head *next)
@@ -414,6 +527,19 @@
        list_entry((pos)->member.next, typeof(*(pos)), member)
 
 /**
+ * list_next_entry_circular - get the next element in list
+ * @pos:       the type * to cursor.
+ * @head:      the list head to take the element from.
+ * @member:    the name of the list_head within the struct.
+ *
+ * Wraparound if pos is the last element (return the first element).
+ * Note, that list is expected to be not empty.
+ */
+#define list_next_entry_circular(pos, head, member) \
+       (list_is_last(&(pos)->member, head) ? \
+       list_first_entry(head, typeof(*(pos)), member) : list_next_entry(pos, 
member))
+
+/**
  * list_prev_entry - get the prev element in list
  * @pos:       the type * to cursor
  * @member:    the name of the list_head within the struct.
@@ -422,12 +548,35 @@
        list_entry((pos)->member.prev, typeof(*(pos)), member)
 
 /**
+ * list_prev_entry_circular - get the prev element in list
+ * @pos:       the type * to cursor.
+ * @head:      the list head to take the element from.
+ * @member:    the name of the list_head within the struct.
+ *
+ * Wraparound if pos is the first element (return the last element).
+ * Note, that list is expected to be not empty.
+ */
+#define list_prev_entry_circular(pos, head, member) \
+       (list_is_first(&(pos)->member, head) ? \
+       list_last_entry(head, typeof(*(pos)), member) : list_prev_entry(pos, 
member))
+
+/**
  * list_for_each       -       iterate over a list
  * @pos:       the &struct list_head to use as a loop cursor.
  * @head:      the head for your list.
  */
 #define list_for_each(pos, head) \
-       for (pos = (head)->next; pos != (head); pos = pos->next)
+       for (pos = (head)->next; !list_is_head(pos, (head)); pos = pos->next)
+
+/**
+ * list_for_each_continue - continue iteration over a list
+ * @pos:       the &struct list_head to use as a loop cursor.
+ * @head:      the head for your list.
+ *
+ * Continue to iterate over a list, continuing after the current position.
+ */
+#define list_for_each_continue(pos, head) \
+       for (pos = pos->next; !list_is_head(pos, (head)); pos = pos->next)
 
 /**
  * list_for_each_prev  -       iterate over a list backwards
@@ -435,7 +584,7 @@
  * @head:      the head for your list.
  */
 #define list_for_each_prev(pos, head) \
-       for (pos = (head)->prev; pos != (head); pos = pos->prev)
+       for (pos = (head)->prev; !list_is_head(pos, (head)); pos = pos->prev)
 
 /**
  * list_for_each_safe - iterate over a list safe against removal of list entry
@@ -444,8 +593,9 @@
  * @head:      the head for your list.
  */
 #define list_for_each_safe(pos, n, head) \
-       for (pos = (head)->next, n = pos->next; pos != (head); \
-               pos = n, n = pos->next)
+       for (pos = (head)->next, n = pos->next; \
+            !list_is_head(pos, (head)); \
+            pos = n, n = pos->next)
 
 /**
  * list_for_each_prev_safe - iterate over a list backwards safe against 
removal of list entry
@@ -455,10 +605,34 @@
  */
 #define list_for_each_prev_safe(pos, n, head) \
        for (pos = (head)->prev, n = pos->prev; \
-            pos != (head); \
+            !list_is_head(pos, (head)); \
             pos = n, n = pos->prev)
 
 /**
+ * list_count_nodes - count nodes in the list
+ * @head:      the head for your list.
+ */
+static inline size_t list_count_nodes(struct list_head *head)
+{
+       struct list_head *pos;
+       size_t count = 0;
+
+       list_for_each(pos, head)
+               count++;
+
+       return count;
+}
+
+/**
+ * list_entry_is_head - test if the entry points to the head of the list
+ * @pos:       the type * to cursor
+ * @head:      the head for your list.
+ * @member:    the name of the list_head within the struct.
+ */
+#define list_entry_is_head(pos, head, member)                          \
+       list_is_head(&pos->member, (head))
+
+/**
  * list_for_each_entry -       iterate over list of given type
  * @pos:       the type * to use as a loop cursor.
  * @head:      the head for your list.
@@ -466,7 +640,7 @@
  */
 #define list_for_each_entry(pos, head, member)                         \
        for (pos = list_first_entry(head, typeof(*pos), member);        \
-            &pos->member != (head);                                    \
+            !list_entry_is_head(pos, head, member);                    \
             pos = list_next_entry(pos, member))
 
 /**
@@ -477,7 +651,7 @@
  */
 #define list_for_each_entry_reverse(pos, head, member)                 \
        for (pos = list_last_entry(head, typeof(*pos), member);         \
-            &pos->member != (head);                                    \
+            !list_entry_is_head(pos, head, member);                    \
             pos = list_prev_entry(pos, member))
 
 /**
@@ -500,9 +674,9 @@
  * Continue to iterate over list of given type, continuing after
  * the current position.
  */
-#define list_for_each_entry_continue(pos, head, member)                        
\
+#define list_for_each_entry_continue(pos, head, member)                \
        for (pos = list_next_entry(pos, member);                        \
-            &pos->member != (head);                                    \
+            !list_entry_is_head(pos, head, member);                    \
             pos = list_next_entry(pos, member))
 
 /**
@@ -516,7 +690,7 @@
  */
 #define list_for_each_entry_continue_reverse(pos, head, member)                
\
        for (pos = list_prev_entry(pos, member);                        \
-            &pos->member != (head);                                    \
+            !list_entry_is_head(pos, head, member);                    \
             pos = list_prev_entry(pos, member))
 
 /**
@@ -527,11 +701,24 @@
  *
  * Iterate over list of given type, continuing from current position.
  */
-#define list_for_each_entry_from(pos, head, member)                    \
-       for (; &pos->member != (head);                                  \
+#define list_for_each_entry_from(pos, head, member)                    \
+       for (; !list_entry_is_head(pos, head, member);                  \
             pos = list_next_entry(pos, member))
 
 /**
+ * list_for_each_entry_from_reverse - iterate backwards over list of given type
+ *                                    from the current point
+ * @pos:       the type * to use as a loop cursor.
+ * @head:      the head for your list.
+ * @member:    the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from_reverse(pos, head, member)            \
+       for (; !list_entry_is_head(pos, head, member);                  \
+            pos = list_prev_entry(pos, member))
+
+/**
  * list_for_each_entry_safe - iterate over list of given type safe against 
removal of list entry
  * @pos:       the type * to use as a loop cursor.
  * @n:         another type * to use as temporary storage
@@ -541,7 +728,7 @@
 #define list_for_each_entry_safe(pos, n, head, member)                 \
        for (pos = list_first_entry(head, typeof(*pos), member),        \
                n = list_next_entry(pos, member);                       \
-            &pos->member != (head);                                    \
+            !list_entry_is_head(pos, head, member);                    \
             pos = n, n = list_next_entry(n, member))
 
 /**
@@ -554,10 +741,10 @@
  * Iterate over list of given type, continuing after current point,
  * safe against removal of list entry.
  */
-#define list_for_each_entry_safe_continue(pos, n, head, member)                
        \
-       for (pos = list_next_entry(pos, member),                                
\
+#define list_for_each_entry_safe_continue(pos, n, head, member)                
\
+       for (pos = list_next_entry(pos, member),                                
\
                n = list_next_entry(pos, member);                               
\
-            &pos->member != (head);                                            
\
+            !list_entry_is_head(pos, head, member);                            
\
             pos = n, n = list_next_entry(n, member))
 
 /**
@@ -570,9 +757,9 @@
  * Iterate over list of given type from current point, safe against
  * removal of list entry.
  */
-#define list_for_each_entry_safe_from(pos, n, head, member)                    
\
+#define list_for_each_entry_safe_from(pos, n, head, member)                    
\
        for (n = list_next_entry(pos, member);                                  
\
-            &pos->member != (head);                                            
\
+            !list_entry_is_head(pos, head, member);                            
\
             pos = n, n = list_next_entry(n, member))
 
 /**
@@ -588,7 +775,7 @@
 #define list_for_each_entry_safe_reverse(pos, n, head, member)         \
        for (pos = list_last_entry(head, typeof(*pos), member),         \
                n = list_prev_entry(pos, member);                       \
-            &pos->member != (head);                                    \
+            !list_entry_is_head(pos, head, member);                    \
             pos = n, n = list_prev_entry(n, member))
 
 /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/sbuf.c new/jgmenu-4.7.0/src/sbuf.c
--- old/jgmenu-4.6.0/src/sbuf.c 2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/sbuf.c 2026-09-15 21:09:14.000000000 +0200
@@ -115,7 +115,7 @@
        if (!s || !s->buf || !s->len)
                return;
        p = s->buf;
-       while (i < s->len && isspace(*p++))
+       while (i < s->len && isspace((unsigned char)*p++))
                i++;
        sbuf_shift_left(s, i);
 }
@@ -129,7 +129,7 @@
                return;
        p = s->buf + s->len - 1;
        while (i < s->len) {
-               if (!isspace(*p))
+               if (!isspace((unsigned char)*p))
                        break;
                i++;
                p--;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/util.c new/jgmenu-4.7.0/src/util.c
--- old/jgmenu-4.6.0/src/util.c 2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/util.c 2026-09-15 21:09:14.000000000 +0200
@@ -97,24 +97,24 @@
        return ret;
 }
 
-void rtrim(char **s)
+void rtrim(char *s)
 {
        size_t len;
        char *end;
 
-       len = strlen(*s);
+       len = strlen(s);
        if (!len)
                return;
-       end = *s + len - 1;
-       while (end >= *s && isspace(*end))
+       end = s + len - 1;
+       while (end >= s && isspace((unsigned char)*end))
                end--;
        *(end + 1) = '\0';
 }
 
 char *strstrip(char *s)
 {
-       rtrim(&s);
-       while (isspace(*s))
+       rtrim(s);
+       while (isspace((unsigned char)*s))
                s++;
        return s;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/util.h new/jgmenu-4.7.0/src/util.h
--- old/jgmenu-4.6.0/src/util.h 2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/util.h 2026-09-15 21:09:14.000000000 +0200
@@ -32,7 +32,7 @@
 void *xmalloc(size_t size);
 void *xrealloc(void *ptr, size_t size);
 void *xcalloc(size_t nb, size_t size);
-void rtrim(char **s);
+void rtrim(char *s);
 char *strstrip(char *s);
 int parse_config_line(char *line, char **option, char **value);
 int hex_to_dec(char c);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/widgets.c 
new/jgmenu-4.7.0/src/widgets.c
--- old/jgmenu-4.6.0/src/widgets.c      2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/widgets.c      2026-09-15 21:09:14.000000000 +0200
@@ -106,11 +106,15 @@
        int padding_left = 4;
 
        sbuf_init(&label_escaped);
-       sbuf_cpy(&label_escaped, filter_needle_length() ? filter_needle() :
-                (*w)->content);
-       sbuf_replace(&label_escaped, "&", "&amp;");
-       sbuf_replace(&label_escaped, "<", "&lt;");
-       sbuf_replace(&label_escaped, ">", "&gt;");
+
+       if (filter_needle_length()) {
+               sbuf_cpy(&label_escaped, filter_needle());
+               sbuf_replace(&label_escaped, "&", "&amp;");
+               sbuf_replace(&label_escaped, "<", "&lt;");
+               sbuf_replace(&label_escaped, ">", "&gt;");
+       } else {
+               sbuf_cpy(&label_escaped, (*w)->content);
+       }
 
        if (config.search_markup && config.search_markup[0] != '\0') {
                sbuf_prepend(&label_escaped, ">");
@@ -119,8 +123,16 @@
                sbuf_addstr(&label_escaped, "</span>");
        }
 
-       ui_insert_text(label_escaped.buf, (*w)->x + padding_left, (*w)->y,
-                      (*w)->h, (*w)->w, (*w)->fgcol, LEFT);
+       if (filter_needle_length()) {
+               ui_insert_text_with_caret(label_escaped.buf, (*w)->x + 
padding_left,
+                       (*w)->y, (*w)->h, (*w)->w, (*w)->fgcol, LEFT,
+                       CARET_AT_END);
+       } else {
+               sbuf_prepend(&label_escaped, " ");
+               ui_insert_text_with_caret(label_escaped.buf, (*w)->x + 
padding_left,
+                               (*w)->y, (*w)->h, (*w)->w, (*w)->fgcol, LEFT,
+                               CARET_AT_START);
+       }
        free(label_escaped.buf);
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/x11-ui.c 
new/jgmenu-4.7.0/src/x11-ui.c
--- old/jgmenu-4.6.0/src/x11-ui.c       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/x11-ui.c       2026-09-15 21:09:14.000000000 +0200
@@ -564,6 +564,33 @@
        pango_tab_array_free(tabs);
 }
 
+void ui_insert_text_with_caret(char *s, int x, int y, int h, int w,
+                              double *rgba, enum alignment align,
+                              enum caret_position position)
+{
+       int width;
+       int height;
+       double caret_x;
+
+       ui_insert_text(s, x, y, h, w, rgba, align);
+
+       pango_layout_get_pixel_size(ui->w[ui->cur].pangolayout, &width, 
&height);
+
+       /*
+        * Use a 2.5-pixel offset to keep the 1-pixel caret sharp.
+        * Integer offsets (e.g. 2 or 3) cause Cairo to anti-alias the
+        * line, making the caret appear wider and darker than intended.
+        */
+       if (position == CARET_AT_START)
+               caret_x = x + 2.5;
+       else
+               caret_x = x + width + 2.5;
+
+       ui_draw_line(caret_x, y + (h - height) / 2,
+                    caret_x, y + (h + height) / 2,
+                    1.0, rgba);
+}
+
 struct point ui_get_text_size(const char *str, const char *fontdesc)
 {
        cairo_surface_t *cs;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/x11-ui.h 
new/jgmenu-4.7.0/src/x11-ui.h
--- old/jgmenu-4.6.0/src/x11-ui.h       2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/x11-ui.h       2026-09-15 21:09:14.000000000 +0200
@@ -66,8 +66,15 @@
 void ui_draw_rectangle_gradient(double x, double y, double w, double h, double 
radius, double line_width,
                                int fill, double *top_rgba, double *bot_rgba, 
enum alignment align);
 void ui_draw_line(double x0, double y0, double x1, double y1, double 
line_width, double *rgba);
+enum caret_position {
+       CARET_AT_START,
+       CARET_AT_END
+};
 void ui_insert_text(char *s, int x, int y, int h, int w, double *rgba,
                    enum alignment align);
+void ui_insert_text_with_caret(char *s, int x, int y, int h, int w,
+                       double *rgba, enum alignment align,
+                       enum caret_position position);
 struct point ui_get_text_size(const char *str, const char *fontdesc);
 int ui_is_point_in_area(struct point p, struct area a);
 void ui_map_window(unsigned int w, unsigned int h);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jgmenu-4.6.0/src/xpm-loader.c 
new/jgmenu-4.7.0/src/xpm-loader.c
--- old/jgmenu-4.6.0/src/xpm-loader.c   2026-08-07 20:18:13.000000000 +0200
+++ new/jgmenu-4.7.0/src/xpm-loader.c   2026-09-15 21:09:14.000000000 +0200
@@ -129,9 +129,11 @@
 
 static int find_color(const char *name, struct xpm_color *color_ptr)
 {
-       struct xpm_color_entry *found;
+       const struct xpm_color_entry *found;
 
-       found = bsearch(name, xcolors, G_N_ELEMENTS(xcolors), sizeof(struct 
xpm_color_entry), compare_xcolor_entries);
+       found = bsearch(name, xcolors, G_N_ELEMENTS(xcolors),
+                       sizeof(struct xpm_color_entry),
+                       compare_xcolor_entries);
        if (!found)
                return 0;
 

Reply via email to