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, "&", "&"); - sbuf_replace(&label_escaped, "<", "<"); - sbuf_replace(&label_escaped, ">", ">"); + + if (filter_needle_length()) { + sbuf_cpy(&label_escaped, filter_needle()); + sbuf_replace(&label_escaped, "&", "&"); + sbuf_replace(&label_escaped, "<", "<"); + sbuf_replace(&label_escaped, ">", ">"); + } 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;
