Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package wofi for openSUSE:Factory checked in at 2025-08-18 16:08:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/wofi (Old) and /work/SRC/openSUSE:Factory/.wofi.new.1085 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "wofi" Mon Aug 18 16:08:38 2025 rev:8 rq:1300017 version:1.5.1 Changes: -------- --- /work/SRC/openSUSE:Factory/wofi/wofi.changes 2024-02-20 21:13:44.212505407 +0100 +++ /work/SRC/openSUSE:Factory/.wofi.new.1085/wofi.changes 2025-08-18 16:08:55.924619219 +0200 @@ -1,0 +2,41 @@ +Fri Aug 8 06:37:03 UTC 2025 - Jan Baier <jba...@suse.com> + +- update to 1.5.1: + * dmenu mode will now auto-close with status 0 if no entries are provided and + custom entry is not allowed + * Added the API function wofi_no_custom_entry() + * update_surface_size() no longer performs a resize if width or height is 0 + * Create a `strict-contains` matching mode for a more obvious sort order. + * Added {} to close_on_focus_loss if statement + * add close_on_focus_loss optionfor wofi to close when it loses focus + * Add CLI option to hide the search bar + * fix: Call update_surface_size() inside hide_search_first() + * Added drun-ignore_metadata + * iswlower() and iswupper() are now used when fuzzy matching so that the + score is correctly computed when using non-ascii characters + * Proper unicode support in match.c so case insensitive matching works on + non-ascii characters + * Added additional information about the semantics of shift vs non-shift + modifiers + * Moved the list of modifier names to the top of wofi-keys(7) to make it + easier to find + * Added documentation explicitly specifying how to use modifier keys + * Switched out the string literal "Up" for key_default to match the rest of + the key definitions + * Added documentation for how to bind multiple key combinations to a single + action + * Fixed incorrect key binding listed in wofi(5) for key_backward + * Hiding the search bar now disables it so you can't type into it. Fixes #234 + * Added --no-custom-entry + * Greatly simplified the line size code, it works a lot better than before + * Added the use_search_box option + * Fixed hide_search=true for surfaces that don't use percent sizing + * The hide_search config option now behaves correctly. It does exactly the + same thing as key_hide_search but on launch. This now also allows you to + un-hide the search with key_hide_search if wofi is launched with + hide_search=true + * Fixed #216 arrow key issue. This was caused by the fix to #184. Both issues + should now be resolved + * Fixed segfault when cache is broken #213 + +------------------------------------------------------------------- Old: ---- v1.4.1.tar.gz New: ---- v1.5.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ wofi.spec ++++++ --- /var/tmp/diff_new_pack.JUpe69/_old 2025-08-18 16:08:56.688650942 +0200 +++ /var/tmp/diff_new_pack.JUpe69/_new 2025-08-18 16:08:56.692651108 +0200 @@ -1,7 +1,7 @@ # # spec file for package wofi # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: wofi -Version: 1.4.1 +Version: 1.5.1 Release: 0 Summary: Launcher for wlroots compositors License: GPL-3.0-only ++++++ v1.4.1.tar.gz -> v1.5.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/.hg_archival.txt new/wofi-v1.5.1/.hg_archival.txt --- old/wofi-v1.4.1/.hg_archival.txt 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/.hg_archival.txt 2025-07-31 06:23:40.000000000 +0200 @@ -1,4 +1,4 @@ repo: 1c71dcd9c6a6dd54601820ce069e7c3ed7e946ca -node: 1e89e8a94806ef2dd27dbf79b4fcc9902f89c422 +node: fa80dc83c18c63f06bc42599436c90ae9e12c844 branch: default -tag: v1.4.1 +tag: v1.5.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/.hgignore new/wofi-v1.5.1/.hgignore --- old/wofi-v1.4.1/.hgignore 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/.hgignore 2025-07-31 06:23:40.000000000 +0200 @@ -4,5 +4,3 @@ ^\.hgrepos$ ^build$ ^debug$ -^Debug$ -^noasan$ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/.hgtags new/wofi-v1.5.1/.hgtags --- old/wofi-v1.4.1/.hgtags 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/.hgtags 2025-07-31 06:23:40.000000000 +0200 @@ -9,3 +9,5 @@ 84e91980936bf85a854cee6881398cff9d27fce4 v1.2.4 1c32143a8460e01559e141c291500a6f4ddcf18c v1.3 eab2b31e805564012e4f71920a8f87ba5f9f798c v1.4 +1e89e8a94806ef2dd27dbf79b4fcc9902f89c422 v1.4.1 +7373d243116703780ba88d32d635a05ffa2177b7 v1.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/inc/match.h new/wofi-v1.5.1/inc/match.h --- old/wofi-v1.4.1/inc/match.h 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/inc/match.h 2025-07-31 06:23:40.000000000 +0200 @@ -29,6 +29,7 @@ enum matching_mode { MATCHING_MODE_CONTAINS, + MATCHING_MODE_STRICT_CONTAINS, MATCHING_MODE_MULTI_CONTAINS, MATCHING_MODE_FUZZY }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/inc/wofi_api.h new/wofi-v1.5.1/inc/wofi_api.h --- old/wofi-v1.4.1/inc/wofi_api.h 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/inc/wofi_api.h 2025-07-31 06:23:40.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Scoopta + * Copyright (C) 2020-2025 Scoopta * This file is part of Wofi * Wofi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -48,6 +48,8 @@ bool wofi_allow_markup(void); +bool wofi_no_custom_entry(void); + uint64_t wofi_get_image_size(void); uint64_t wofi_get_window_scale(void); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/man/wofi-keys.7 new/wofi-v1.5.1/man/wofi-keys.7 --- old/wofi-v1.4.1/man/wofi-keys.7 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/man/wofi-keys.7 2025-07-31 06:23:40.000000000 +0200 @@ -3,7 +3,23 @@ wofi \- Key names for custom binds .SH DESCRIPTION -This is a list of the key names that can be used for custom binding. These are taken from gdk/gdkkeysyms.h with exception to modifiers. Certain keys cannot have the shift modifier attached as holding shift while using these keys causes a completely different key press to be registered. For example Shift\-j is invalid as holding shift while pressing j changes the key into J so Shift-J should be registered as the key instead of Shift\-j. This is the case with all alphanumeric chars as well as Tab which turns into ISO_Left_Tab. +This is a list of the key names that can be used for custom binding. These are taken from gdk/gdkkeysyms.h with exception to modifiers. + +Modifiers can be attached to keys by prefixing the key with the modifier name separated by a \-. For example Shift\-J or Ctrl\-j. Only one modifier can be used in a given bind. + +Certain keys cannot have the shift modifier attached as holding shift while using these keys causes a completely different key press to be registered. +For example Shift\-j is invalid as holding shift while pressing j changes the key into J so Shift\-J should be registered as the key instead of Shift\-j. +This is the case with all alphanumeric chars as well as Tab which turns into ISO_Left_Tab. This additionally means that non-shift modifiers such as Ctrl MUST be used with the non-shift variant of the key. +For example Ctrl\-J is not valid, a lowercase(non-shift) j must be used for Ctrl and Alt. + +Additionally it is possible to have multiple binds for one action by comma separating the binds. For example binding an action to Ctrl\-j,Ctrl\-h will map both key combinations to that one action. + +.SH MODIFIER NAMES +.B Shift +.br +.B Ctrl +.br +.B Alt .SH KEY NAMES .B BackSpace @@ -4543,10 +4559,3 @@ .B LogWindowTree .br .B LogGrabInfo - -.SH MODIFIER NAMES -.B Shift -.br -.B Ctrl -.br -.B Alt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/man/wofi.1 new/wofi-v1.5.1/man/wofi.1 --- old/wofi-v1.4.1/man/wofi.1 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/man/wofi.1 2025-07-31 06:23:40.000000000 +0200 @@ -71,11 +71,14 @@ .B \-e, \-\-exec\-search Activiating a search with enter will execute the search not the first result. .TP +.B \-E, \-\-no\-custom\-entry +Only entries will be submitted to modes, never the content of the search box. +.TP .B \-b, \-\-hide\-scroll Hides the scroll bars. .TP .B \-M, \-\-matching=\fIMODE\fR -Specifies the matching mode, it can be either contains, multi-contains, or fuzzy, default is contains. +Specifies the matching mode, it can be either contains, strict-contains, multi-contains, or fuzzy, default is contains. .TP .B \-i, \-\-insensitive Enables case insensitive search. @@ -115,7 +118,9 @@ .TP .B \-r, \-\-pre\-display\-cmd If set, the selectable entry won't be displayed as-is, but will instead be displayed based on the output of this command, which can be anything. Suggested to use with \fB"echo %s | some_cmd"\fR or \fB"some_cmd %s"\fR, as the string gets replaced in a printf-like fashion. This will not affect the output of wofi once a selection has been done, allowing you to display something else than the original output. - +.TP +.B \-j, \-\-hide\-search +Hides the search bar. .SH CONFIGURATION Wofi has 3 main files used for configuration. All files are completely optional. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/man/wofi.5 new/wofi-v1.5.1/man/wofi.5 --- old/wofi-v1.4.1/man/wofi.5 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/man/wofi.5 2025-07-31 06:23:40.000000000 +0200 @@ -73,11 +73,14 @@ .B exec_search=\fIBOOL\fR If true activiating a search with enter will execute the search not the first result, default is false. .TP +.B no_custom_entry=\fIBOOL\fR +If true only entries will be submitted to modes, never the content of the search box, default is false. +.TP .B hide_scroll=\fIBOOL\fR If true hides the scroll bars, default is false. .TP .B matching=\fIMODE\fR -Specifies the matching mode, it can be either contains, multi-contains, or fuzzy, default is contains. +Specifies the matching mode, it can be either contains, strict-contains, multi-contains, or fuzzy, default is contains. .TP .B insensitive=\fIBOOL\fR If true enables case insensitive search, default is false. @@ -146,7 +149,7 @@ Specifies the key to use in order to move forward. Default is Tab. See \fBwofi\-keys\fR(7) for the key codes. .TP .B key_backward=\fIKEY\fR -Specifies the key to use in order to move backward. Default is ISO_Left_Tab(Shift+Tab). See \fBwofi\-keys\fR(7) for the key codes. +Specifies the key to use in order to move backward. Default is Shift-ISO_Left_Tab(Shift+Tab). See \fBwofi\-keys\fR(7) for the key codes. .TP .B key_submit=\fIKEY\fR Specifies the key to use in order to submit an action. Default is Return. See \fBwofi\-keys\fR(7) for the key codes. @@ -181,6 +184,9 @@ .B hide_search=\fIBOOL\fR Specifies whether the search bar should be hidden. Default is false. .TP +.B close_on_focus_loss=\fIBOOL\fR +Specifies whether to quit wofi when it loses focus. Default is false. +.TP .B dynamic_lines=\fIBOOL\fR Specifies whether wofi should be dynamically shrunk to fit the number of visible lines or if it should always stay the same size. Default is false. .TP @@ -195,6 +201,9 @@ .TP .B pre_display_exec=\fIBOOL\fR This modifies the behavior of pre_display_cmd and causes the command in question to be directly executed via fork/exec rather than through the shell. +.TP +.B use_search_box=\fIBOOL\fR +Specifies whether or not wofi should use a GtkSearchEntry or a regular GtkEntry. The search entry has a little search icon and a clear text button that the regular entry lacks. Default is true .SH CSS SELECTORS Any GTK widget can be selected by using the name of its CSS node, these however might change with updates and are not guaranteed to stay constant. Wofi also provides certain widgets with names and classes which can be referenced from CSS to give access to the most important widgets easily. \fBwofi\fR(7) contains the current widget layout used by wofi so if you want to get into CSS directly using GTK widget names look there for info. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/man/wofi.7 new/wofi-v1.5.1/man/wofi.7 --- old/wofi-v1.4.1/man/wofi.7 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/man/wofi.7 2025-07-31 06:23:40.000000000 +0200 @@ -59,6 +59,9 @@ .TP .B print_desktop_file=\fIBOOL\fR If true the path to the desktop file and the name of the corresponding action(if present) will be printed to stdout instead of invoking it, default is false. +.TP +.B ignore_metadata=\fIBOOL\fR +If true only the application's name will be searched rather than the normal behavior of searching a large amount of metadata, including category and description, default is false. .SH DRUN When images are enabled drun mode will pull icon themes however being a GTK app it's possible you'll need to run gtk\-update\-icon\-cache to get them to apply. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/meson.build new/wofi-v1.5.1/meson.build --- old/wofi-v1.4.1/meson.build 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/meson.build 2025-07-31 06:23:40.000000000 +0200 @@ -1,4 +1,4 @@ -project('wofi', 'c', version : 'v1.4.1', default_options : ['c_std=c99', 'buildtype=release', 'warning_level=2']) +project('wofi', 'c', version : 'v1.5.1', default_options : ['c_std=c99', 'buildtype=release', 'warning_level=2']) cc = meson.get_compiler('c') pkgcfg = import('pkgconfig') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/modes/dmenu.c new/wofi-v1.5.1/modes/dmenu.c --- old/wofi-v1.4.1/modes/dmenu.c 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/modes/dmenu.c 2025-07-31 06:23:40.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 Scoopta + * Copyright (C) 2019-2025 Scoopta * This file is part of Wofi * Wofi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -65,7 +65,9 @@ if(!isatty(STDIN_FILENO)) { char* line = NULL; size_t size = 0; + bool has_line = false; while(getdelim(&line, &size, separator[0], stdin) != -1) { + has_line = true; char* delim = strchr(line, separator[0]); if(delim != NULL) { *delim = 0; @@ -76,6 +78,12 @@ map_put(entry_map, line, "true"); } free(line); + + if(!has_line && wofi_no_custom_entry()) { + wofi_exit(0); + } + } else if(wofi_no_custom_entry()) { + wofi_exit(0); } if(!print_line_num) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/modes/drun.c new/wofi-v1.5.1/modes/drun.c --- old/wofi-v1.4.1/modes/drun.c 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/modes/drun.c 2025-07-31 06:23:40.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 Scoopta + * Copyright (C) 2019-2025 Scoopta * This file is part of Wofi * Wofi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ #include <gtk/gtk.h> #include <gio/gdesktopappinfo.h> -static const char* arg_names[] = {"print_command", "display_generic", "disable_prime", "print_desktop_file"}; +static const char* arg_names[] = {"print_command", "display_generic", "disable_prime", "print_desktop_file", "ignore_metadata"}; static struct mode* mode; @@ -45,6 +45,7 @@ static bool display_generic; static bool disable_prime; static bool print_desktop_file; +static bool ignore_metadata; static char* get_search_text(char* file) { GDesktopAppInfo* info = g_desktop_app_info_new_from_filename(file); @@ -65,12 +66,19 @@ } } - char* ret = utils_concat(7, name, file, - exec == NULL ? "" : exec, - description == NULL ? "" : description, - categories == NULL ? "" : categories, - keywords_str, - generic_name == NULL ? "" : generic_name); + char* ret; + + if(ignore_metadata) { + ret = strdup(name); + } else { + ret = utils_concat(7, name, file, + exec == NULL ? "" : exec, + description == NULL ? "" : description, + categories == NULL ? "" : categories, + keywords_str, + generic_name == NULL ? "" : generic_name); + } + free(keywords_str); return ret; } @@ -340,6 +348,7 @@ display_generic = strcmp(config_get(config, "display_generic", "false"), "true") == 0; disable_prime = strcmp(config_get(config, "disable_prime", "false"), "true") == 0; print_desktop_file = strcmp(config_get(config, "print_desktop_file", "false"), "true") == 0; + ignore_metadata = strcmp(config_get(config, "ignore_metadata", "false"), "true") == 0; entries = map_init(); struct wl_list* cache = wofi_read_cache(mode); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/src/main.c new/wofi-v1.5.1/src/main.c --- old/wofi-v1.4.1/src/main.c 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/src/main.c 2025-07-31 06:23:40.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2020 Scoopta + * Copyright (C) 2019-2025 Scoopta * This file is part of Wofi * Wofi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -82,6 +82,7 @@ printf("--term\t\t\t-t\tSpecifies the terminal to use when running in a term\n"); printf("--password\t\t-P\tRuns in password mode\n"); printf("--exec-search\t\t-e\tMakes enter always use the search contents not the first result\n"); + printf("--no-custom-entry\t-E\tOnly entries will be submitted to modes, never the content of the search box\n"); printf("--hide-scroll\t\t-b\tHides the scroll bars\n"); printf("--matching\t\t-M\tSets the matching method, default is contains\n"); printf("--insensitive\t\t-i\tAllows case insensitive searching\n"); @@ -97,6 +98,7 @@ printf("--search\t\t-Q\tSearch for something immediately on open\n"); printf("--monitor\t\t-o\tSets the monitor to open on\n"); printf("--pre-display-cmd\t-r\tRuns command for the displayed entries, without changing the output. %%s for the real string\n"); + printf("--hide-search\t\t-j\tHides the search bar\n"); exit(0); } @@ -342,6 +344,12 @@ .val = 'e' }, { + .name = "no-custom-entry", + .has_arg = no_argument, + .flag = NULL, + .val = 'E' + }, + { .name = "hide-scroll", .has_arg = no_argument, .flag = NULL, @@ -432,6 +440,12 @@ .val = 'r' }, { + .name = "hide-search", + .has_arg = no_argument, + .flag = NULL, + .val = 'j' + }, + { .name = NULL, .has_arg = 0, .flag = NULL, @@ -455,6 +469,7 @@ char* terminal = NULL; char* password_char = "false"; char* exec_search = NULL; + char* no_custom_entry = NULL; char* hide_scroll = NULL; char* matching = NULL; char* insensitive = NULL; @@ -468,13 +483,14 @@ char* search = NULL; char* monitor = NULL; char* pre_display_cmd = NULL; + char* hide_search = NULL; struct wl_list options; wl_list_init(&options); struct option_node* node; int opt; - while((opt = getopt_long(argc, argv, "hfc:s:C:dS:W:H:p:x:y:nImk:t:P::ebM:iqvl:aD:L:w:O:GQ:o:r:", opts, NULL)) != -1) { + while((opt = getopt_long(argc, argv, "hfc:s:C:dS:W:H:p:x:y:nImk:t:P::eEbM:iqvl:aD:L:w:O:GQ:o:r:j", opts, NULL)) != -1) { switch(opt) { case 'h': print_usage(argv); @@ -538,6 +554,9 @@ case 'e': exec_search = "true"; break; + case 'E': + no_custom_entry = "true"; + break; case 'b': hide_scroll = "true"; break; @@ -586,6 +605,9 @@ case 'r': pre_display_cmd = optarg; break; + case 'j': + hide_search = "true"; + break; } } @@ -742,6 +764,9 @@ if(exec_search != NULL) { map_put(config, "exec_search", exec_search); } + if(no_custom_entry != NULL) { + map_put(config, "no_custom_entry", no_custom_entry); + } if(hide_scroll != NULL) { map_put(config, "hide_scroll", hide_scroll); } @@ -778,6 +803,9 @@ if(pre_display_cmd != NULL) { map_put(config, "pre_display_cmd", pre_display_cmd); } + if(hide_search != NULL) { + map_put(config, "hide_search", hide_search); + } struct sigaction sigact = {0}; sigact.sa_handler = sig; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/src/match.c new/wofi-v1.5.1/src/match.c --- old/wofi-v1.4.1/src/match.c 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/src/match.c 2025-07-31 06:23:40.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Scoopta + * Copyright (C) 2019-2025 Scoopta * This file is part of Wofi * Wofi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,9 +15,11 @@ along with Wofi. If not, see <http://www.gnu.org/licenses/>. */ -#include <ctype.h> #include <match.h> -#include <string.h> + +#include <wchar.h> +#include <wctype.h> +#include <stdlib.h> // leading gap #define SCORE_GAP_LEADING -0.005 @@ -48,32 +50,59 @@ #define max(a, b) (((a) > (b)) ? (a) : (b)) +static wchar_t* wcscasestr(const wchar_t* haystack, const wchar_t* needle) { + wchar_t* ret = NULL; + + size_t needle_idx = 0; + for(size_t count = 0; haystack[count] != 0; ++count) { + if(towlower(haystack[count]) == towlower(needle[needle_idx])) { + if(ret == NULL) { + ret = (wchar_t*) haystack + count; + } + ++needle_idx; + } else { + ret = NULL; + needle_idx = 0; + } + + if(needle[needle_idx] == 0) { + break; + } + } + + if(needle[needle_idx] != 0) { + return NULL; + } + + return ret; +} + // matching -static bool contains_match(const char* filter, const char* text, bool insensitive) { - if(filter == NULL || strcmp(filter, "") == 0) { +static bool contains_match(const wchar_t* filter, const wchar_t* text, bool insensitive) { + if(filter == NULL || wcscmp(filter, L"") == 0) { return true; } if(text == NULL) { return false; } if(insensitive) { - return strcasestr(text, filter) != NULL; + return wcscasestr(text, filter) != NULL; } else { - return strstr(text, filter) != NULL; + return wcsstr(text, filter) != NULL; } } -static char* strcasechr(const char* s,char c, bool insensitive) { +static wchar_t* wcscasechr(const wchar_t* s, wchar_t c, bool insensitive) { if(insensitive) { - const char accept[3] = {tolower(c), toupper(c), 0}; - return strpbrk(s, accept); + const wchar_t accept[3] = {towlower(c), towupper(c), 0}; + return wcspbrk(s, accept); } else { - return strchr(s, c); + return wcschr(s, c); } } -static bool fuzzy_match(const char* filter, const char* text, bool insensitive) { - if(filter == NULL || strcmp(filter, "") == 0) { +static bool fuzzy_match(const wchar_t* filter, const wchar_t* text, bool insensitive) { + if(filter == NULL || wcscmp(filter, L"") == 0) { return true; } if(text == NULL) { @@ -82,9 +111,9 @@ // we just check that all the characters (ignoring case) are in the // search text possibly case insensitively in the correct order while(*filter != 0) { - char nch = *filter++; + wchar_t nch = *filter++; - if(!(text = strcasechr(text, nch, insensitive))) { + if(!(text = wcscasechr(text, nch, insensitive))) { return false; } text++; @@ -92,19 +121,19 @@ return true; } -static bool multi_contains_match(const char* filter, const char* text, bool insensitive) { - if(filter == NULL || strcmp(filter, "") == 0) { +static bool multi_contains_match(const wchar_t* filter, const wchar_t* text, bool insensitive) { + if(filter == NULL || wcscmp(filter, L"") == 0) { return true; } if(text == NULL) { return false; } - char new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; - strncpy(new_filter, filter, sizeof(new_filter)); - new_filter[sizeof(new_filter) - 1] = '\0'; - char* token; - char* rest = new_filter; - while((token = strtok_r(rest, " ", &rest))) { + wchar_t new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; + wcsncpy(new_filter, filter, sizeof(new_filter) / sizeof(wchar_t)); + new_filter[(sizeof(new_filter) / sizeof(wchar_t)) - 1] = L'\0'; + wchar_t* token; + wchar_t* rest = new_filter; + while((token = wcstok(rest, L" ", &rest))) { if(contains_match(token, text, insensitive) == false) { return false; } @@ -114,16 +143,35 @@ bool match_for_matching_mode(const char* filter, const char* text, enum matching_mode matching, bool insensitive) { + size_t filter_len = filter == NULL ? 1 : mbstowcs(NULL, filter, 0) + 1; + size_t text_len = text == NULL ? 1 : mbstowcs(NULL, text, 0) + 1; + + wchar_t wfilter[filter_len]; + wchar_t wtext[text_len]; + + if(filter != NULL) { + mbstowcs(wfilter, filter, filter_len); + } else { + wfilter[0] = 0; + } + + if(text != NULL) { + mbstowcs(wtext, text, text_len); + } else { + wtext[0] = 0; + } + bool retval; switch(matching) { case MATCHING_MODE_MULTI_CONTAINS: - retval = multi_contains_match(filter, text, insensitive); + retval = multi_contains_match(wfilter, wtext, insensitive); break; case MATCHING_MODE_CONTAINS: - retval = contains_match(filter, text, insensitive); + case MATCHING_MODE_STRICT_CONTAINS: + retval = contains_match(wfilter, wtext, insensitive); break; case MATCHING_MODE_FUZZY: - retval = fuzzy_match(filter, text, insensitive); + retval = fuzzy_match(wfilter, wtext, insensitive); break; default: return false; @@ -134,25 +182,24 @@ // end matching // fuzzy matching -static void precompute_bonus(const char* haystack, score_t* match_bonus) { +static void precompute_bonus(const wchar_t* haystack, score_t* match_bonus) { /* Which positions are beginning of words */ - int m = strlen(haystack); - char last_ch = '\0'; + int m = wcslen(haystack); + wchar_t last_ch = L'\0'; for(int i = 0; i < m; i++) { - char ch = haystack[i]; + wchar_t ch = haystack[i]; score_t score = 0; - if(isalnum(ch)) { - if(!last_ch || last_ch == '/') { + if(iswalnum(ch)) { + if(!last_ch || last_ch == L'/') { score = SCORE_MATCH_SLASH; - } else if(last_ch == '-' || last_ch == '_' || - last_ch == ' ') { + } else if(last_ch == L'-' || last_ch == L'_' || + last_ch == L' ') { score = SCORE_MATCH_WORD; - } else if(last_ch >= 'a' && last_ch <= 'z' && - ch >= 'A' && ch <= 'Z') { + } else if(iswlower(last_ch) && iswupper(ch)) { /* CamelCase */ score = SCORE_MATCH_CAPITAL; - } else if(last_ch == '.') { + } else if(last_ch == L'.') { score = SCORE_MATCH_DOT; } } @@ -162,9 +209,9 @@ } } -static inline bool match_with_case(char a, char b, bool insensitive) { +static inline bool match_with_case(wchar_t a, wchar_t b, bool insensitive) { if(insensitive) { - return tolower(a) == tolower(b); + return towlower(a) == towlower(b); } else { return a == b; } @@ -172,7 +219,7 @@ static inline void match_row(int row, score_t* curr_D, score_t* curr_M, const score_t* last_D, const score_t* last_M, - const char* needle, const char* haystack, int n, int m, score_t* match_bonus, bool insensitive) { + const wchar_t* needle, const wchar_t* haystack, int n, int m, score_t* match_bonus, bool insensitive) { int i = row; score_t prev_score = SCORE_MIN; @@ -228,12 +275,12 @@ // Also, the reference algorithm does not take into account case sensitivity // which has been implemented here. -static score_t fuzzy_score(const char* haystack, const char* needle, bool insensitive) { +static score_t fuzzy_score(const wchar_t* haystack, const wchar_t* needle, bool insensitive) { if(*needle == 0) return SCORE_MIN; - int n = strlen(needle); - int m = strlen(haystack); + int n = wcslen(needle); + int m = wcslen(haystack); score_t match_bonus[m]; precompute_bonus(haystack, match_bonus); @@ -278,7 +325,7 @@ // end fuzzy matching // sorting -static int fuzzy_sort(const char* text1, const char* text2, const char* filter, bool insensitive) { +static int fuzzy_sort(const wchar_t* text1, const wchar_t* text2, const wchar_t* filter, bool insensitive) { bool match1 = fuzzy_match(filter, text1, insensitive); bool match2 = fuzzy_match(filter, text2, insensitive); // both filters match do fuzzy scoring @@ -309,7 +356,7 @@ // we sort based on how early in the string all the matches are. // if there are matches for each. -static int multi_contains_sort(const char* text1, const char* text2, const char* filter, bool insensitive) { +static int multi_contains_sort(const wchar_t* text1, const wchar_t* text2, const wchar_t* filter, bool insensitive) { // sum of string positions of each match int t1_count = 0; int t2_count = 0; @@ -317,20 +364,20 @@ bool t1_match = true; bool t2_match = true; - char new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; - strncpy(new_filter, filter, sizeof(new_filter)); - new_filter[sizeof(new_filter) - 1] = '\0'; - - char* token; - char* rest = new_filter; - while((token = strtok_r(rest, " ", &rest))) { - char* str1, *str2; + wchar_t new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; + wcsncpy(new_filter, filter, sizeof(new_filter) / sizeof(wchar_t)); + new_filter[(sizeof(new_filter) / sizeof(wchar_t)) - 1] = L'\0'; + + wchar_t* token; + wchar_t* rest = new_filter; + while((token = wcstok(rest, L" ", &rest))) { + wchar_t* str1, *str2; if(insensitive) { - str1 = strcasestr(text1, token); - str2 = strcasestr(text2, token); + str1 = wcscasestr(text1, token); + str2 = wcscasestr(text2, token); } else { - str1 = strstr(text1, token); - str2 = strstr(text2, token); + str1 = wcsstr(text1, token); + str2 = wcsstr(text2, token); } t1_match = t1_match && str1 != NULL; t2_match = t2_match && str2 != NULL; @@ -355,15 +402,16 @@ return 0; } } -static int contains_sort(const char* text1, const char* text2, const char* filter, bool insensitive) { - char* str1, *str2; + +static int contains_sort(const wchar_t* text1, const wchar_t* text2, const wchar_t* filter, bool insensitive) { + wchar_t* str1, *str2; if(insensitive) { - str1 = strcasestr(text1, filter); - str2 = strcasestr(text2, filter); + str1 = wcscasestr(text1, filter); + str2 = wcscasestr(text2, filter); } else { - str1 = strstr(text1, filter); - str2 = strstr(text2, filter); + str1 = wcsstr(text1, filter); + str2 = wcsstr(text2, filter); } bool tx1 = str1 == text1; bool tx2 = str2 == text2; @@ -387,18 +435,71 @@ } } +static int strict_contains_sort(const wchar_t* text1, const wchar_t* text2, const wchar_t* filter, bool insensitive) { + wchar_t* str1, *str2; + + if(insensitive) { + str1 = wcscasestr(text1, filter); + str2 = wcscasestr(text2, filter); + } else { + str1 = wcsstr(text1, filter); + str2 = wcsstr(text2, filter); + } + bool txc1 = str1 != NULL; + bool txc2 = str2 != NULL; + + if(txc1 && txc2) { + return 0; + } else if(txc1) { + return -1; + } else if(txc2) { + return 1; + } else { + return 0; + } +} + int sort_for_matching_mode(const char* text1, const char* text2, int fallback, enum matching_mode match_type, const char* filter, bool insensitive) { + size_t text1_len = text1 == NULL ? 1 : mbstowcs(NULL, text1, 0) + 1; + size_t text2_len = text2 == NULL ? 1 : mbstowcs(NULL, text2, 0) + 1; + size_t filter_len = filter == NULL ? 1 : mbstowcs(NULL, filter, 0) + 1; + + wchar_t wtext1[text1_len]; + wchar_t wtext2[text2_len]; + wchar_t wfilter[filter_len]; + + if(text1 != NULL) { + mbstowcs(wtext1, text1, text1_len); + } else { + wtext1[0] = 0; + } + + if(text2 != NULL) { + mbstowcs(wtext2, text2, text2_len); + } else { + wtext2[0] = 0; + } + + if(filter != NULL) { + mbstowcs(wfilter, filter, filter_len); + } else { + wfilter[0] = 0; + } + int primary = 0; switch(match_type) { case MATCHING_MODE_MULTI_CONTAINS: - primary = multi_contains_sort(text1, text2, filter, insensitive); + primary = multi_contains_sort(wtext1, wtext2, wfilter, insensitive); break; case MATCHING_MODE_CONTAINS: - primary = contains_sort(text1, text2, filter, insensitive); + primary = contains_sort(wtext1, wtext2, wfilter, insensitive); + break; + case MATCHING_MODE_STRICT_CONTAINS: + primary = strict_contains_sort(wtext1, wtext2, wfilter, insensitive); break; case MATCHING_MODE_FUZZY: - primary = fuzzy_sort(text1, text2, filter, insensitive); + primary = fuzzy_sort(wtext1, wtext2, wfilter, insensitive); break; default: return 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wofi-v1.4.1/src/wofi.c new/wofi-v1.5.1/src/wofi.c --- old/wofi-v1.4.1/src/wofi.c 2024-02-09 20:22:27.000000000 +0100 +++ new/wofi-v1.5.1/src/wofi.c 2025-07-31 06:23:40.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 Scoopta + * Copyright (C) 2019-2025 Scoopta * This file is part of Wofi * Wofi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -79,6 +79,7 @@ static char* terminal; static GtkOrientation outer_orientation; static bool exec_search; +static bool no_custom_entry; static struct map* modes; static enum matching_mode matching; static bool insensitive; @@ -109,6 +110,7 @@ static char* pre_display_cmd = NULL; static bool pre_display_exec = false; static bool single_click = false; +static bool hide_search = false; static GdkModifierType shift_mask = GDK_SHIFT_MASK; static GdkModifierType ctrl_mask = GDK_CONTROL_MASK; static GdkModifierType alt_mask = GDK_MOD1_MASK; @@ -572,7 +574,9 @@ static void update_surface_size(void) { if(lines > 0) { height = max_height * lines; - height += 5; + } + if(width == 0 || height == 0) { + return; } if(shell != NULL) { zwlr_layer_surface_v1_set_size(wlr_surface, width, height); @@ -581,17 +585,7 @@ } gtk_window_resize(GTK_WINDOW(window), width, height); - GtkAllocation alloc; - gtk_widget_get_allocated_size(entry, &alloc, NULL); - if(outer_orientation == GTK_ORIENTATION_HORIZONTAL) { - if(alloc.width > 0) { - gtk_widget_set_size_request(scroll, width - alloc.width, height); - } - } else { - if(alloc.height > 0) { - gtk_widget_set_size_request(scroll, width, height - alloc.height); - } - } + gtk_widget_set_size_request(scroll, width, height); } static void widget_allocate(GtkWidget* widget, GdkRectangle* allocation, gpointer data) { @@ -926,7 +920,16 @@ smallest_node = node; } } - char* tmp = strdup(strchr(smallest_node->line, ' ') + 1); + + char* space = strchr(smallest_node->line, ' '); + + if(space == NULL) { + free(smallest_node->line); + wl_list_remove(&smallest_node->link); + continue; + } + + char* tmp = strdup(space + 1); free(smallest_node->line); smallest_node->line = tmp; wl_list_remove(&smallest_node->link); @@ -968,6 +971,10 @@ return allow_markup; } +bool wofi_no_custom_entry(void) { + return no_custom_entry; +} + uint64_t wofi_get_image_size(void) { return image_size; } @@ -1121,8 +1128,8 @@ return sort_for_matching_mode(text1, text2, fallback, matching, filter, insensitive); } -static void select_first(void) { - GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 0); +static void select_idx(gint idx) { + GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), idx); gtk_widget_grab_focus(GTK_WIDGET(child)); gtk_flow_box_select_child(GTK_FLOW_BOX(inner_box), GTK_FLOW_BOX_CHILD(child)); } @@ -1175,7 +1182,7 @@ user_moved = true; if(outer_orientation == GTK_ORIENTATION_VERTICAL) { if(gtk_widget_has_focus(entry) || gtk_widget_has_focus(scroll)) { - select_first(); + select_idx(1); return; } } @@ -1191,7 +1198,7 @@ user_moved = true; if(outer_orientation == GTK_ORIENTATION_HORIZONTAL) { if(gtk_widget_has_focus(entry) || gtk_widget_has_focus(scroll)) { - select_first(); + select_idx(1); return; } } @@ -1201,14 +1208,14 @@ static void move_forward(void) { user_moved = true; if(gtk_widget_has_focus(entry) || gtk_widget_has_focus(scroll)) { - select_first(); + select_idx(1); return; } gtk_widget_child_focus(window, GTK_DIR_TAB_FORWARD); if(gtk_widget_has_focus(entry)) { - select_first(); + select_idx(0); } } @@ -1251,7 +1258,9 @@ } static void do_hide_search(void) { - gtk_widget_set_visible(entry, !gtk_widget_get_visible(entry)); + bool visible = gtk_widget_get_visible(entry); + gtk_widget_set_visible(entry, !visible); + gtk_widget_set_sensitive(entry, !visible); update_surface_size(); } @@ -1497,6 +1506,9 @@ gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_FOCUSED, TRUE); } else { gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE); + if (data != NULL && strcmp(config_get(data, "close_on_focus_loss", "false"), "true") == 0) { + gtk_widget_destroy(widget); + } } return FALSE; } @@ -1571,7 +1583,7 @@ arg_count = get_arg_count(); } - if(mode == NULL && no_entry != NULL && no_entry()) { + if(mode == NULL && !no_custom_entry && no_entry != NULL && no_entry()) { mode = mode_ptr->name; } @@ -1747,6 +1759,14 @@ return G_SOURCE_REMOVE; } +static gboolean hide_search_first(gpointer data) { + (void) data; + gtk_widget_set_visible(entry, !hide_search); + gtk_widget_set_sensitive(entry, !hide_search); + update_surface_size(); + return G_SOURCE_REMOVE; +} + void wofi_init(struct map* _config) { config = _config; char* width_str = config_get(config, "width", "50%"); @@ -1782,8 +1802,9 @@ terminal = map_get(config, "term"); char* password_char = map_get(config, "password_char"); exec_search = strcmp(config_get(config, "exec_search", "false"), "true") == 0; + no_custom_entry = strcmp(config_get(config, "no_custom_entry", "false"), "true") == 0; bool hide_scroll = strcmp(config_get(config, "hide_scroll", "false"), "true") == 0; - matching = config_get_mnemonic(config, "matching", "contains", 3, "contains", "multi-contains", "fuzzy"); + matching = config_get_mnemonic(config, "matching", "contains", 4, "contains", "strict-contains", "multi-contains", "fuzzy"); insensitive = strcmp(config_get(config, "insensitive", "false"), "true") == 0; parse_search = strcmp(config_get(config, "parse_search", "false"), "true") == 0; location = config_get_mnemonic(config, "location", "center", 18, @@ -1796,7 +1817,7 @@ sort_order = config_get_mnemonic(config, "sort_order", "default", 2, "default", "alphabetical"); line_wrap = config_get_mnemonic(config, "line_wrap", "off", 4, "off", "word", "char", "word_char") - 1; bool global_coords = strcmp(config_get(config, "global_coords", "false"), "true") == 0; - bool hide_search = strcmp(config_get(config, "hide_search", "false"), "true") == 0; + hide_search = strcmp(config_get(config, "hide_search", "false"), "true") == 0; char* search = map_get(config, "search"); dynamic_lines = strcmp(config_get(config, "dynamic_lines", "false"), "true") == 0; char* monitor = map_get(config, "monitor"); @@ -1820,7 +1841,7 @@ char* key_default; key_default = "Up"; - char* key_up = (i == 0) ? "Up" : config_get(config, "key_up", key_default); + char* key_up = (i == 0) ? key_default : config_get(config, "key_up", key_default); key_default = "Down"; char* key_down = (i == 0) ? key_default : config_get(config, "key_down", key_default); key_default = "Left"; @@ -1997,13 +2018,19 @@ outer_box = gtk_box_new(outer_orientation, 0); gtk_widget_set_name(outer_box, "outer-box"); gtk_container_add(GTK_CONTAINER(window), outer_box); - entry = gtk_search_entry_new(); + + bool use_search_box = strcmp(config_get(config, "use_search_box", "true"), "true") == 0; + if(use_search_box) { + entry = gtk_search_entry_new(); + } else { + entry = gtk_entry_new(); + } + g_signal_connect(entry, "size-allocate", G_CALLBACK(widget_allocate), NULL); gtk_widget_set_name(entry, "input"); gtk_entry_set_placeholder_text(GTK_ENTRY(entry), prompt); gtk_container_add(GTK_CONTAINER(outer_box), entry); - gtk_widget_set_child_visible(entry, !hide_search); if(search != NULL) { gtk_entry_set_text(GTK_ENTRY(entry), search); @@ -2044,7 +2071,7 @@ g_signal_connect(entry, "activate", G_CALLBACK(activate_search), NULL); g_signal_connect(window, "key-press-event", G_CALLBACK(key_press), NULL); g_signal_connect(window, "focus-in-event", G_CALLBACK(focus), NULL); - g_signal_connect(window, "focus-out-event", G_CALLBACK(focus), NULL); + g_signal_connect(window, "focus-out-event", G_CALLBACK(focus), config); g_signal_connect(entry, "focus-in-event", G_CALLBACK(focus_entry), NULL); g_signal_connect(entry, "focus-out-event", G_CALLBACK(focus_entry), NULL); g_signal_connect(window, "destroy", G_CALLBACK(do_exit), NULL); @@ -2063,6 +2090,8 @@ gdk_threads_add_timeout(25, do_percent_size_first, geo_str); } + gdk_threads_add_timeout(5, hide_search_first, NULL); + wl_list_init(&mode_list); pthread_create(&mode_thread, NULL, start_mode_thread, mode);