rofi 2.1.0
helper.c
Go to the documentation of this file.
1/*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2012 Sean Pringle <sean.pringle@gmail.com>
6 * Copyright © 2013-2023 Qball Cow <qball@gmpclient.org>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 */
28
30#include "config.h"
31#define G_LOG_DOMAIN "Helper"
32
33#include "display.h"
34#include "helper-theme.h"
35#include "helper.h"
36#include "rofi.h"
37#include "settings.h"
38#include "view.h"
39#include <ctype.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <glib.h>
43#include <glib/gstdio.h>
44#include <limits.h>
45#include <pango/pango-fontmap.h>
46#include <pango/pango.h>
47#include <pango/pangocairo.h>
48#include <pwd.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <sys/file.h>
53#include <sys/stat.h>
54#include <sys/types.h>
55#include <unistd.h>
56
57const char *const MatchingMethodStr[MM_NUM_MATCHERS] = {
58 "Normal", "Regex", "Glob", "Fuzzy", "Prefix"};
59
62 -1,
63};
65static int CurrentMatchingMethod = 0;
66
70const char *const monitor_position_entries[] = {
71 "on focused monitor", "on focused window", "at mouse pointer",
72 "on monitor with focused window", "on monitor that has mouse pointer"};
73
76char **stored_argv = NULL;
77
78char *helper_string_replace_if_exists_v(char *string, GHashTable *h);
79
96
97void cmd_set_arguments(int argc, char **argv) {
98 stored_argc = argc;
99 stored_argv = argv;
100}
101
102int helper_parse_setup(char *string, char ***output, int *length, ...) {
103 GError *error = NULL;
104 GHashTable *h;
105 h = g_hash_table_new(g_str_hash, g_str_equal);
106 // By default, we insert terminal and ssh-client
107 g_hash_table_insert(h, "{terminal}", config.terminal_emulator);
108 g_hash_table_insert(h, "{ssh-client}", config.ssh_client);
109 // Add list from variable arguments.
110 va_list ap;
111 va_start(ap, length);
112 while (1) {
113 char *key = va_arg(ap, char *);
114 if (key == (char *)0) {
115 break;
116 }
117 char *value = va_arg(ap, char *);
118 if (value == (char *)0) {
119 break;
120 }
121 g_hash_table_insert(h, key, value);
122 }
123 va_end(ap);
124
125 char *res = helper_string_replace_if_exists_v(string, h);
126 // Destroy key-value storage.
127 g_hash_table_destroy(h);
128 // Parse the string into shell arguments.
129 if (g_shell_parse_argv(res, length, output, &error)) {
130 g_free(res);
131 return TRUE;
132 }
133 g_free(res);
134 // Throw error if shell parsing fails.
135 if (error) {
136 char *msg = g_strdup_printf("Failed to parse: '%s'\nError: '%s'", string,
137 error->message);
138 rofi_view_error_dialog(msg, FALSE);
139 g_free(msg);
140 // print error.
141 g_error_free(error);
142 }
143 return FALSE;
144}
145
147 for (size_t i = 0; tokens && tokens[i]; i++) {
148 g_regex_unref((GRegex *)tokens[i]->regex);
149 g_free(tokens[i]);
150 }
151 g_free(tokens);
152}
153
154static gchar *glob_to_regex(const char *input) {
155 gchar *r = g_regex_escape_string(input, -1);
156 size_t str_l = strlen(r);
157 for (size_t i = 0; i < str_l; i++) {
158 if (r[i] == '\\') {
159 if (r[i + 1] == '*') {
160 r[i] = '.';
161 } else if (r[i + 1] == '?') {
162 r[i + 1] = 'S';
163 }
164 i++;
165 }
166 }
167 return r;
168}
169static gchar *fuzzy_to_regex(const char *input) {
170 char *retv = NULL;
171 GString *str = g_string_new("");
172 gchar *r = g_regex_escape_string(input, -1);
173 gchar *iter;
174 int first = 1;
175 for (iter = r; iter && *iter != '\0'; iter = g_utf8_next_char(iter)) {
176 if (first) {
177 g_string_append(str, "(");
178 } else {
179 g_string_append(str, ".*?(");
180 }
181 if (*iter == '\\') {
182 g_string_append_c(str, '\\');
183 iter = g_utf8_next_char(iter);
184 // If EOL, break out of for loop.
185 if ((*iter) == '\0') {
186 break;
187 }
188 }
189 g_string_append_unichar(str, g_utf8_get_char(iter));
190 g_string_append(str, ")");
191 first = 0;
192 }
193 g_free(r);
194 retv = g_string_free(str, FALSE);
195 return retv;
196}
197
198static gchar *prefix_regex(const char *input) {
199 gchar *r = g_regex_escape_string(input, -1);
200 char *retv = g_strconcat("\\b", r, NULL);
201 g_free(r);
202 return retv;
203}
204
205static char *utf8_helper_simplify_string(const char *os) {
206 char buf[6] = {
207 0,
208 };
209
210 // Normalize the string to a fully decomposed form, then filter out
211 // mark/accent characters.
212 char *s = g_utf8_normalize(os, -1, G_NORMALIZE_ALL);
213 ssize_t str_size = (g_utf8_strlen(s, -1) * 6 + 2 + 1) * sizeof(char);
214 char *str = g_malloc0(str_size);
215 char *striter = str;
216 for (const char *iter = s; iter && *iter; iter = g_utf8_next_char(iter)) {
217 gunichar uc = g_utf8_get_char(iter);
218 if (!g_unichar_ismark(uc)) {
219 int l = g_unichar_to_utf8(uc, buf);
220 memcpy(striter, buf, l);
221 striter += l;
222 }
223 }
224 g_free(s);
225
226 return str;
227}
228
229// Macro for quickly generating regex for matching.
230static inline GRegex *R(const char *s, int case_sensitive) {
232 char *str = utf8_helper_simplify_string(s);
233
234 GRegex *r = g_regex_new(
235 str, G_REGEX_OPTIMIZE | ((case_sensitive) ? 0 : G_REGEX_CASELESS), 0,
236 NULL);
237
238 g_free(str);
239 return r;
240 }
241 return g_regex_new(
242 s, G_REGEX_OPTIMIZE | ((case_sensitive) ? 0 : G_REGEX_CASELESS), 0, NULL);
243}
244
245static rofi_int_matcher *create_regex(const char *input, int case_sensitive) {
246 GRegex *retv = NULL;
247 gchar *r;
248 rofi_int_matcher *rv = g_malloc0(sizeof(rofi_int_matcher));
249 if (input && input[0] == config.matching_negate_char) {
250 rv->invert = 1;
251 input++;
252 }
253 switch (config.matching_method) {
254 case MM_GLOB:
255 r = glob_to_regex(input);
256 retv = R(r, case_sensitive);
257 g_free(r);
258 break;
259 case MM_REGEX:
260 retv = R(input, case_sensitive);
261 if (retv == NULL) {
262 r = g_regex_escape_string(input, -1);
263 retv = R(r, case_sensitive);
264 g_free(r);
265 }
266 break;
267 case MM_FUZZY:
268 r = fuzzy_to_regex(input);
269 retv = R(r, case_sensitive);
270 g_free(r);
271 break;
272 case MM_PREFIX:
273 r = prefix_regex(input);
274 retv = R(r, case_sensitive);
275 g_free(r);
276 break;
277 default:
278 r = g_regex_escape_string(input, -1);
279 retv = R(r, case_sensitive);
280 g_free(r);
281 break;
282 }
283 rv->regex = retv;
284 return rv;
285}
286rofi_int_matcher **helper_tokenize(const char *input, int case_sensitive) {
287 if (input == NULL) {
288 return NULL;
289 }
290 size_t len = strlen(input);
291 if (len == 0) {
292 return NULL;
293 }
294
295 char *saveptr = NULL, *token;
296 rofi_int_matcher **retv = NULL;
297 if (!config.tokenize) {
298 retv = g_malloc0(sizeof(rofi_int_matcher *) * 2);
299 retv[0] = create_regex(input, case_sensitive);
300 return retv;
301 }
302
303 // First entry is always full (modified) stringtext.
304 int num_tokens = 0;
305
306 // Copy the string, 'strtok_r' modifies it.
307 char *str = g_strdup(input);
308
309 // Iterate over tokens.
310 // strtok should still be valid for utf8.
311 const char *const sep = " ";
312 for (token = strtok_r(str, sep, &saveptr); token != NULL;
313 token = strtok_r(NULL, sep, &saveptr)) {
314 retv = g_realloc(retv, sizeof(rofi_int_matcher *) * (num_tokens + 2));
315 retv[num_tokens] = create_regex(token, case_sensitive);
316 retv[num_tokens + 1] = NULL;
317 num_tokens++;
318 }
319 // Free str.
320 g_free(str);
321 return retv;
322}
323
324// cli arg handling
325int find_arg(const char *const key) {
326 int i;
327
328 for (i = 0; i < stored_argc && strcasecmp(stored_argv[i], key); i++) {
329 ;
330 }
331
332 return i < stored_argc ? i : -1;
333}
334int find_arg_str(const char *const key, char **val) {
335 int i = find_arg(key);
336
337 if (val != NULL && i > 0 && i < stored_argc - 1) {
338 *val = stored_argv[i + 1];
339 return TRUE;
340 }
341 return FALSE;
342}
343
344const char **find_arg_strv(const char *const key) {
345 const char **retv = NULL;
346 int length = 0;
347 for (int i = 0; i < stored_argc; i++) {
348 if (i < (stored_argc - 1) && strcasecmp(stored_argv[i], key) == 0) {
349 length++;
350 }
351 }
352 if (length > 0) {
353 retv = g_malloc0((length + 1) * sizeof(char *));
354 int index = 0;
355 for (int i = 0; i < stored_argc; i++) {
356 if (i < (stored_argc - 1) && strcasecmp(stored_argv[i], key) == 0) {
357 retv[index++] = stored_argv[i + 1];
358 }
359 }
360 }
361 return retv;
362}
363
364int find_arg_int(const char *const key, int *val) {
365 int i = find_arg(key);
366
367 if (val != NULL && i > 0 && i < (stored_argc - 1)) {
368 *val = strtol(stored_argv[i + 1], NULL, 10);
369 return TRUE;
370 }
371 return FALSE;
372}
373int find_arg_uint(const char *const key, unsigned int *val) {
374 int i = find_arg(key);
375
376 if (val != NULL && i > 0 && i < (stored_argc - 1)) {
377 *val = strtoul(stored_argv[i + 1], NULL, 10);
378 return TRUE;
379 }
380 return FALSE;
381}
382
383char helper_parse_char(const char *arg) {
384 const size_t len = strlen(arg);
385 // If the length is 1, it is not escaped.
386 if (len == 1) {
387 return arg[0];
388 }
389 // If the length is 2 and the first character is '\', we unescape it.
390 if (len == 2 && arg[0] == '\\') {
391 switch (arg[1]) {
392 // New line
393 case 'n':
394 return '\n';
395 // Bell
396 case 'a':
397 return '\a';
398 // Backspace
399 case 'b':
400 return '\b';
401 // Tab
402 case 't':
403 return '\t';
404 // Vertical tab
405 case 'v':
406 return '\v';
407 // Form feed
408 case 'f':
409 return '\f';
410 // Carriage return
411 case 'r':
412 return '\r';
413 // Forward slash
414 case '\\':
415 return '\\';
416 // 0 line.
417 case '0':
418 return '\0';
419 default:
420 break;
421 }
422 }
423 if (len > 2 && arg[0] == '\\' && arg[1] == 'x') {
424 return (char)strtol(&arg[2], NULL, 16);
425 }
426 g_warning("Failed to parse character string: \"%s\"", arg);
427 // for now default to newline.
428 return '\n';
429}
430
431int find_arg_char(const char *const key, char *val) {
432 int i = find_arg(key);
433
434 if (val != NULL && i > 0 && i < (stored_argc - 1)) {
435 *val = helper_parse_char(stored_argv[i + 1]);
436 return TRUE;
437 }
438 return FALSE;
439}
440
441void helper_token_match_set_pango_attr_on_style(PangoAttrList *retv, int start,
442 int end,
444 if (th.style & ROFI_HL_BOLD) {
445 PangoAttribute *pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
446 pa->start_index = start;
447 pa->end_index = end;
448 pango_attr_list_insert(retv, pa);
449 }
450#if PANGO_VERSION_CHECK(1, 50, 0)
451 if (th.style & ROFI_HL_UPPERCASE) {
452 PangoAttribute *pa =
453 pango_attr_text_transform_new(PANGO_TEXT_TRANSFORM_UPPERCASE);
454 pa->start_index = start;
455 pa->end_index = end;
456 pango_attr_list_insert(retv, pa);
457 }
458 if (th.style & ROFI_HL_LOWERCASE) {
459 PangoAttribute *pa =
460 pango_attr_text_transform_new(PANGO_TEXT_TRANSFORM_LOWERCASE);
461 pa->start_index = start;
462 pa->end_index = end;
463 pango_attr_list_insert(retv, pa);
464 }
465 if (th.style & ROFI_HL_CAPITALIZE) {
466#if 0
467 PangoAttribute *pa =
468 pango_attr_text_transform_new(PANGO_TEXT_TRANSFORM_CAPITALIZE);
469 pa->start_index = start;
470 pa->end_index = end;
471 pango_attr_list_insert(retv, pa);
472#endif
473 // Disabled because of bug in pango
474 }
475#endif
476 if (th.style & ROFI_HL_UNDERLINE) {
477 PangoAttribute *pa = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
478 pa->start_index = start;
479 pa->end_index = end;
480 pango_attr_list_insert(retv, pa);
481 }
482 if (th.style & ROFI_HL_STRIKETHROUGH) {
483 PangoAttribute *pa = pango_attr_strikethrough_new(TRUE);
484 pa->start_index = start;
485 pa->end_index = end;
486 pango_attr_list_insert(retv, pa);
487 }
488 if (th.style & ROFI_HL_ITALIC) {
489 PangoAttribute *pa = pango_attr_style_new(PANGO_STYLE_ITALIC);
490 pa->start_index = start;
491 pa->end_index = end;
492 pango_attr_list_insert(retv, pa);
493 }
494 if (th.style & ROFI_HL_COLOR) {
495 PangoAttribute *pa = pango_attr_foreground_new(
496 th.color.red * 65535, th.color.green * 65535, th.color.blue * 65535);
497 pa->start_index = start;
498 pa->end_index = end;
499 pango_attr_list_insert(retv, pa);
500
501 if (th.color.alpha < 1.0) {
502 pa = pango_attr_foreground_alpha_new(th.color.alpha * 65535);
503 pa->start_index = start;
504 pa->end_index = end;
505 pango_attr_list_insert(retv, pa);
506 }
507 }
508}
509
511 rofi_int_matcher **tokens,
512 const char *input,
513 PangoAttrList *retv) {
514 // Disable highlighting for normalize match, not supported atm.
516 return retv;
517 }
518 // Do a tokenized match.
519 if (tokens) {
520 for (int j = 0; tokens[j]; j++) {
521 GMatchInfo *gmi = NULL;
522 if (tokens[j]->invert) {
523 continue;
524 }
525 g_regex_match(tokens[j]->regex, input, G_REGEX_MATCH_PARTIAL, &gmi);
526 while (g_match_info_matches(gmi)) {
527 int count = g_match_info_get_match_count(gmi);
528 for (int index = (count > 1) ? 1 : 0; index < count; index++) {
529 int start, end;
530 g_match_info_fetch_pos(gmi, index, &start, &end);
531 helper_token_match_set_pango_attr_on_style(retv, start, end, th);
532 }
533 g_match_info_next(gmi, NULL);
534 }
535 g_match_info_free(gmi);
536 }
537 }
538 return retv;
539}
540
541int helper_token_match(rofi_int_matcher *const *tokens, const char *input) {
542 int match = TRUE;
543 // Do a tokenized match.
544 if (tokens) {
546 char *r = utf8_helper_simplify_string(input);
547 for (int j = 0; match && tokens[j]; j++) {
548 match = g_regex_match(tokens[j]->regex, r, 0, NULL);
549 match ^= tokens[j]->invert;
550 }
551 g_free(r);
552 } else {
553 for (int j = 0; match && tokens[j]; j++) {
554 match = g_regex_match(tokens[j]->regex, input, 0, NULL);
555 match ^= tokens[j]->invert;
556 }
557 }
558 }
559 return match;
560}
561
562int execute_generator(const char *cmd) {
563 char **args = NULL;
564 int argv = 0;
565 helper_parse_setup(config.run_command, &args, &argv, "{cmd}", cmd, (char *)0);
566
567 int fd = -1;
568 GError *error = NULL;
569 g_spawn_async_with_pipes(NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
570 NULL, NULL, &fd, NULL, &error);
571
572 if (error != NULL) {
573 char *msg = g_strdup_printf("Failed to execute: '%s'\nError: '%s'", cmd,
574 error->message);
575 rofi_view_error_dialog(msg, FALSE);
576 g_free(msg);
577 // print error.
578 g_error_free(error);
579 fd = -1;
580 }
581 g_strfreev(args);
582 return fd;
583}
584
585int create_pid_file(const char *pidfile, gboolean kill_running) {
586 if (pidfile == NULL) {
587 return -1;
588 }
589
590 int fd = g_open(pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
591 if (fd < 0) {
592 g_warning("Failed to create pid file: '%s'.", pidfile);
593 return -1;
594 }
595 // Set it to close the File Descriptor on exit.
596 int flags = fcntl(fd, F_GETFD, NULL);
597 flags = flags | FD_CLOEXEC;
598 if (fcntl(fd, F_SETFD, flags, NULL) < 0) {
599 g_warning("Failed to set CLOEXEC on pidfile.");
600 remove_pid_file(fd);
601 return -1;
602 }
603 // Try to get exclusive write lock on FD
604 int retv = flock(fd, LOCK_EX | LOCK_NB);
605 if (retv != 0) {
606 g_warning("Failed to set lock on pidfile: Rofi already running?");
607 g_warning("Got error: %d %s", retv, g_strerror(errno));
608 if (kill_running) {
609 char buffer[64] = {
610 0,
611 };
612 ssize_t l = read(fd, &(buffer[0]), 63);
613 if (l > 1) {
614 buffer[l] = 0;
615 pid_t pid = g_ascii_strtoll(buffer, NULL, 0);
616 kill(pid, SIGTERM);
617 while (1) {
618 retv = flock(fd, LOCK_EX | LOCK_NB);
619 if (retv == 0) {
620 break;
621 }
622 g_usleep(100);
623 }
624 }
625 remove_pid_file(fd);
626 return create_pid_file(pidfile, FALSE);
627 }
628
629 remove_pid_file(fd);
630 return -1;
631 }
632 if (ftruncate(fd, (off_t)0) == 0) {
633 // Write pid, not needed, but for completeness sake.
634 char buffer[64];
635 int length = snprintf(buffer, 64, "%i", getpid());
636 ssize_t l = 0;
637 while (l < length) {
638 l += write(fd, &buffer[l], length - l);
639 }
640 }
641 return fd;
642}
643
644void remove_pid_file(int fd) {
645 if (fd >= 0) {
646 if (close(fd)) {
647 g_warning("Failed to close pidfile: '%s'", g_strerror(errno));
648 }
649 }
650}
651
652gboolean helper_validate_font(PangoFontDescription *pfd, const char *font) {
653 const char *fam = pango_font_description_get_family(pfd);
654 int size = pango_font_description_get_size(pfd);
655 if (fam == NULL || size == 0) {
656 g_debug("Pango failed to parse font: '%s'", font);
657 g_debug("Got family: <b>%s</b> at size: <b>%d</b>", fam ? fam : "{unknown}",
658 size);
659 return FALSE;
660 }
661 return TRUE;
662}
663
672 int found_error = FALSE;
673 GString *msg =
674 g_string_new("<big><b>The configuration failed to validate:</b></big>\n");
675
677 if (g_strcmp0(config.sorting_method, "normal") == 0) {
679 } else if (g_strcmp0(config.sorting_method, "levenshtein") == 0) {
681 } else if (g_strcmp0(config.sorting_method, "fzf") == 0) {
683 } else if (g_strcmp0(config.sorting_method, "fzf-v2") == 0) {
685 } else {
686 g_string_append_printf(
687 msg,
688 "\t<b>config.sorting_method</b>=%s is not a valid sorting "
689 "strategy.\nValid options are: normal, fzf or fzf-v2.\n",
691 found_error = 1;
692 }
693 }
694
695 if (config.matching) {
696 char **strv = g_strsplit(config.matching, ",", 0);
697 if (strv) {
698 int matching_method_index = 0;
699 for (char **str = strv; *str && matching_method_index < MM_NUM_MATCHERS;
700 str++) {
701 gboolean found = FALSE;
702 for (unsigned i = 0;
703 i < MM_NUM_MATCHERS && matching_method_index < MM_NUM_MATCHERS;
704 i++) {
705 if (g_ascii_strcasecmp(*str, MatchingMethodStr[i]) == 0) {
706 MatchingMethodEnabled[matching_method_index] = i;
707 matching_method_index++;
708 NUMMatchingMethodEnabled = matching_method_index;
709 if (matching_method_index == MM_NUM_MATCHERS) {
710 found_error = 1;
711 g_string_append_printf(msg,
712 "\t<b>config.matching</b> = %s to many "
713 "matching options enabled.\n",
715 }
716 found = TRUE;
717 }
718 }
719 if (!found) {
720 g_string_append_printf(msg,
721 "\t<b>config.matching</b>=%s is not a valid "
722 "matching strategy.\nValid options are: glob, "
723 "regex, fuzzy, prefix or normal.\n",
724 *str);
725 found_error = 1;
726 }
727 }
729 g_strfreev(strv);
730 }
731 }
732
733 if (config.element_height < 1) {
734 g_string_append_printf(msg,
735 "\t<b>config.element_height</b>=%d is invalid. An "
736 "element needs to be at least 1 line high.\n",
739 found_error = TRUE;
740 }
741 if (!(config.location >= 0 && config.location <= 8)) {
742 g_string_append_printf(msg,
743 "\t<b>config.location</b>=%d is invalid. Value "
744 "should be between %d and %d.\n",
745 config.location, 0, 8);
747 found_error = 1;
748 }
749
750#ifdef ENABLE_XCB
751 // Check size
752 {
753 workarea mon;
754 if (config.backend == DISPLAY_XCB && !monitor_active(&mon)) {
755 const char *name = config.monitor;
756 if (name && name[0] == '-') {
757 int index = name[1] - '0';
758 if (index < 5 && index > 0) {
759 name = monitor_position_entries[index - 1];
760 }
761 }
762 g_string_append_printf(
763 msg, "\t<b>config.monitor</b>=%s Could not find monitor.\n", name);
764 found_error = TRUE;
765 }
766 }
767#endif
768
769 if (g_strcmp0(config.monitor, "-3") == 0) {
770 // On -3, set to location 1.
771 config.location = 1;
772 }
773
774 if (found_error) {
775 g_string_append(msg, "Please update your configuration.");
777 return TRUE;
778 }
779
780 g_string_free(msg, TRUE);
781 return FALSE;
782}
783
784char *rofi_expand_path(const char *input) {
785 if (input == NULL) {
786 return NULL;
787 }
788 char **str = g_strsplit(input, G_DIR_SEPARATOR_S, -1);
789 for (unsigned int i = 0; str && str[i]; i++) {
790 // Replace ~ with current user homedir.
791 if (str[i][0] == '~' && str[i][1] == '\0') {
792 g_free(str[i]);
793 str[i] = g_strdup(g_get_home_dir());
794 }
795 // If other user, ask getpwnam.
796 else if (str[i][0] == '~') {
797 struct passwd *p = getpwnam(&(str[i][1]));
798 if (p != NULL) {
799 g_free(str[i]);
800 str[i] = g_strdup(p->pw_dir);
801 }
802 } else if (i == 0) {
803 char *s = str[i];
804 if (input[0] == G_DIR_SEPARATOR) {
805 str[i] = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, s);
806 g_free(s);
807 }
808 }
809 }
810 char *retv = g_build_filenamev(str);
811 g_strfreev(str);
812 return retv;
813}
814
816#define MIN3(a, b, c) \
817 ((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))
818
819unsigned int levenshtein(const char *needle, const glong needlelen,
820 const char *haystack, const glong haystacklen,
821 int case_sensitive) {
822 if (needlelen == G_MAXLONG) {
823 // String to long, we cannot handle this.
824 return UINT_MAX;
825 }
826 unsigned int column[needlelen + 1];
827 for (glong y = 0; y < needlelen; y++) {
828 column[y] = y;
829 }
830 // Removed out of the loop, otherwise static code analyzers think it is
831 // unset.. silly but true. old loop: for ( glong y = 0; y <= needlelen; y++)
832 column[needlelen] = needlelen;
833 for (glong x = 1; x <= haystacklen; x++) {
834 const char *needles = needle;
835 column[0] = x;
836 gunichar haystackc = g_utf8_get_char(haystack);
837 if (!case_sensitive) {
838 haystackc = g_unichar_tolower(haystackc);
839 }
840 for (glong y = 1, lastdiag = x - 1; y <= needlelen; y++) {
841 gunichar needlec = g_utf8_get_char(needles);
842 if (!case_sensitive) {
843 needlec = g_unichar_tolower(needlec);
844 }
845 unsigned int olddiag = column[y];
846 column[y] = MIN3(column[y] + 1, column[y - 1] + 1,
847 lastdiag + (needlec == haystackc ? 0 : 1));
848 lastdiag = olddiag;
849 needles = g_utf8_next_char(needles);
850 }
851 haystack = g_utf8_next_char(haystack);
852 }
853 return column[needlelen];
854}
855
856char *rofi_latin_to_utf8_strdup(const char *input, gssize length) {
857 gsize slength = 0;
858 return g_convert_with_fallback(input, length, "UTF-8", "latin1", "\uFFFD",
859 NULL, &slength, NULL);
860}
861
862char *rofi_force_utf8(const gchar *data, ssize_t length) {
863 if (data == NULL) {
864 return NULL;
865 }
866 if (length < 0) {
867 length = strlen(data);
868 }
869 const char *end;
870 GString *string;
871
872 if (g_utf8_validate(data, length, &end)) {
873 return g_strndup(data, length);
874 }
875 string = g_string_sized_new(length + 16);
876
877 do {
878 /* Valid part of the string */
879 g_string_append_len(string, data, end - data);
880 /* Replacement character */
881 g_string_append(string, "\uFFFD");
882 length -= (end - data) + 1;
883 data = end + 1;
884 } while (!g_utf8_validate(data, length, &end));
885
886 if (length) {
887 g_string_append_len(string, data, length);
888 }
889
890 return g_string_free(string, FALSE);
891}
892
893/****
894 * FZF like scorer
895 */
896
898#define FUZZY_SCORER_MAX_LENGTH 256
900#define MIN_SCORE (INT_MIN / 2)
902#define LEADING_GAP_SCORE -4
904#define GAP_SCORE -5
906#define WORD_START_SCORE 50
908#define NON_WORD_SCORE 40
910#define CAMEL_SCORE (WORD_START_SCORE + GAP_SCORE - 1)
912#define CONSECUTIVE_SCORE (WORD_START_SCORE + GAP_SCORE)
914#define PATTERN_NON_START_MULTIPLIER 1
916#define PATTERN_START_MULTIPLIER 2
917
922 /* Lower case */
924 /* Upper case */
926 /* Number */
928 /* non word character */
930};
931
938 if (g_unichar_islower(c)) {
939 return LOWER;
940 }
941 if (g_unichar_isupper(c)) {
942 return UPPER;
943 }
944 if (g_unichar_isdigit(c)) {
945 return DIGIT;
946 }
947 return NON_WORD;
948}
949
958static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr) {
959 if (prev == NON_WORD && curr != NON_WORD) {
960 return WORD_START_SCORE;
961 }
962 if ((prev == LOWER && curr == UPPER) || (prev != DIGIT && curr == DIGIT)) {
963 return CAMEL_SCORE;
964 }
965 if (curr == NON_WORD) {
966 return NON_WORD_SCORE;
967 }
968 return 0;
969}
970
971int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
972 glong slen, int case_sensitive) {
973 if (slen > FUZZY_SCORER_MAX_LENGTH) {
974 return -MIN_SCORE;
975 }
976 glong pi, si;
977 // whether we are aligning the first character of pattern
978 gboolean pfirst = TRUE;
979 // whether the start of a word in pattern
980 gboolean pstart = TRUE;
981 // score for each position
982 int *score = g_malloc_n(slen, sizeof(int));
983 // dp[i]: maximum value by aligning pattern[0..pi] to str[0..si]
984 int *dp = g_malloc_n(slen, sizeof(int));
985 // uleft: value of the upper left cell; ulefts: maximum value of uleft and
986 // cells on the left. The arbitrary initial values suppress warnings.
987 int uleft = 0, ulefts = 0, left, lefts;
988 const gchar *pit = pattern, *sit;
989 enum CharClass prev = NON_WORD;
990 for (si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char(sit)) {
991 enum CharClass cur = rofi_scorer_get_character_class(g_utf8_get_char(sit));
992 score[si] = rofi_scorer_get_score_for(prev, cur);
993 prev = cur;
994 dp[si] = MIN_SCORE;
995 }
996 for (pi = 0; pi < plen; pi++, pit = g_utf8_next_char(pit)) {
997 gunichar pc = g_utf8_get_char(pit), sc;
998 if (g_unichar_isspace(pc)) {
999 pstart = TRUE;
1000 continue;
1001 }
1002 lefts = MIN_SCORE;
1003 for (si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char(sit)) {
1004 left = dp[si];
1005 lefts = MAX(lefts + GAP_SCORE, left);
1006 sc = g_utf8_get_char(sit);
1007 if (case_sensitive ? pc == sc
1008 : g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
1009 int t = score[si] * (pstart ? PATTERN_START_MULTIPLIER
1011 dp[si] = pfirst ? LEADING_GAP_SCORE * si + t
1012 : MAX(uleft + CONSECUTIVE_SCORE, ulefts + t);
1013 } else {
1014 dp[si] = MIN_SCORE;
1015 }
1016 uleft = left;
1017 ulefts = lefts;
1018 }
1019 pfirst = pstart = FALSE;
1020 }
1021 lefts = MIN_SCORE;
1022 for (si = 0; si < slen; si++) {
1023 lefts = MAX(lefts + GAP_SCORE, dp[si]);
1024 }
1025 g_free(score);
1026 g_free(dp);
1027 return -lefts;
1028}
1029
1030/****
1031 * Port of fzf's FuzzyMatchV2 scoring algorithm.
1032 *
1033 * This follows the default fuzzy scorer from junegunn/fzf
1034 * (src/algo/algo.go), as opposed to the rofi-specific scorer above which was
1035 * only loosely inspired by fzf. The aim is to match fzf's ranking: a
1036 * Smith-Waterman style DP with bonuses for word boundaries, camelCase and
1037 * consecutive matches, and penalties for gaps.
1038 *
1039 * The implementation is split in two:
1040 * - rofi_scorer_fzf_v2_term() scores a single whitespace-free term, and is
1041 * the literal port of FuzzyMatchV2.
1042 * - rofi_scorer_fzf_v2_evaluate() is the public entry point: it normalises
1043 * and decodes the text once, then splits the pattern into whitespace-
1044 * separated terms and sums their scores (fzf's AND semantics).
1045 *
1046 * Two of fzf's optimisations are intentionally dropped for clarity, neither of
1047 * which changes the resulting score: the asciiFuzzyIndex pre-scan that narrows
1048 * the search range, and the matrix-width reduction (a full M*N matrix is used
1049 * instead of the [F[0], lastIdx] band). Match positions / backtrace are not
1050 * computed as only the score is needed for sorting.
1051 */
1052
1054#define FZF_V2_SCORE_MATCH 16
1056#define FZF_V2_SCORE_GAP_START (-3)
1058#define FZF_V2_SCORE_GAP_EXTENSION (-1)
1060#define FZF_V2_BONUS_BOUNDARY 8
1062#define FZF_V2_BONUS_NON_WORD 8
1064#define FZF_V2_BONUS_CAMEL_123 \
1065 (FZF_V2_BONUS_BOUNDARY + FZF_V2_SCORE_GAP_EXTENSION)
1066
1067#define FZF_V2_BONUS_CONSECUTIVE \
1068 (-(FZF_V2_SCORE_GAP_START + FZF_V2_SCORE_GAP_EXTENSION))
1069
1070#define FZF_V2_BONUS_FIRST_CHAR_MULTIPLIER 2
1072#define FZF_V2_BONUS_BOUNDARY_WHITE (FZF_V2_BONUS_BOUNDARY + 2)
1074#define FZF_V2_BONUS_BOUNDARY_DELIMITER (FZF_V2_BONUS_BOUNDARY + 1)
1076#define FZF_V2_MIN_SCORE (G_MININT / 2)
1077
1088
1090 if (g_unichar_islower(c)) {
1091 return FZF_V2_CHAR_LOWER;
1092 }
1093 if (g_unichar_isupper(c)) {
1094 return FZF_V2_CHAR_UPPER;
1095 }
1096 if (g_unichar_isdigit(c)) {
1097 return FZF_V2_CHAR_NUMBER;
1098 }
1099 if (g_unichar_isalpha(c)) {
1100 return FZF_V2_CHAR_LETTER;
1101 }
1102 if (g_unichar_isspace(c)) {
1103 return FZF_V2_CHAR_WHITE;
1104 }
1105 /* fzf's delimiterChars; all ASCII. */
1106 if (c < 0x80 && strchr("/,:;|", (char)c) != NULL) {
1107 return FZF_V2_CHAR_DELIMITER;
1108 }
1109 return FZF_V2_CHAR_NON_WORD;
1110}
1111
1113 enum FzfV2CharClass cur) {
1114 if (cur >= FZF_V2_CHAR_NON_WORD) {
1115 switch (prev) {
1116 case FZF_V2_CHAR_WHITE:
1121 return FZF_V2_BONUS_BOUNDARY;
1122 default:
1123 break;
1124 }
1125 }
1126 if ((prev == FZF_V2_CHAR_LOWER && cur == FZF_V2_CHAR_UPPER) ||
1127 (prev != FZF_V2_CHAR_NUMBER && cur == FZF_V2_CHAR_NUMBER)) {
1129 }
1130 switch (cur) {
1133 return FZF_V2_BONUS_NON_WORD;
1134 case FZF_V2_CHAR_WHITE:
1136 default:
1137 break;
1138 }
1139 return 0;
1140}
1141
1156static int rofi_scorer_fzf_v2_term(const gunichar *pat, glong M,
1157 const gunichar *tmatch, const int *B,
1158 glong N, int case_sensitive) {
1159 if (M == 0) {
1160 return 0;
1161 }
1162 if (M > N) {
1163 return FZF_V2_MIN_SCORE;
1164 }
1165
1166 int result;
1167 int *H = g_malloc0_n((gsize)M * N, sizeof(int));
1168 int *C = g_malloc0_n((gsize)M * N, sizeof(int));
1169 glong *F = g_malloc_n(M, sizeof(glong));
1170
1171 gunichar pchar0 = case_sensitive ? pat[0] : g_unichar_tolower(pat[0]);
1172
1173 /* Phase 1: first DP row (pattern[0]) and the greedy first-occurrence
1174 * positions F[] that bound the DP region. */
1175 int prev_h0 = 0;
1176 gboolean in_gap = FALSE;
1177 glong pidx = 0, last_idx = 0;
1178 gunichar pchar_adv = pchar0;
1179 int max_score = 0;
1180 for (glong off = 0; off < N; off++) {
1181 if (tmatch[off] == pchar_adv) {
1182 /* pchar_adv sticks at the last pattern char once the whole pattern has
1183 * been seen, so last_idx tracks the final occurrence of that char and
1184 * the DP region below covers later (possibly better) matches too. */
1185 if (pidx < M) {
1186 F[pidx] = off;
1187 pidx++;
1188 pchar_adv = case_sensitive ? pat[MIN(pidx, M - 1)]
1189 : g_unichar_tolower(pat[MIN(pidx, M - 1)]);
1190 }
1191 last_idx = off;
1192 }
1193
1194 if (tmatch[off] == pchar0) {
1195 int bonus = B[off];
1197 H[off] = sc;
1198 C[off] = 1;
1199 if (M == 1 && sc > max_score) {
1200 max_score = sc;
1201 if (bonus >= FZF_V2_BONUS_BOUNDARY) {
1202 /* Forward search: a boundary match of a 1-char pattern is optimal. */
1203 break;
1204 }
1205 }
1206 in_gap = FALSE;
1207 } else {
1208 int v = prev_h0 +
1210 H[off] = MAX(v, 0);
1211 C[off] = 0;
1212 in_gap = TRUE;
1213 }
1214 prev_h0 = H[off];
1215 }
1216 if (pidx != M) {
1217 /* pattern is not a subsequence of the text */
1218 result = FZF_V2_MIN_SCORE;
1219 } else if (M == 1) {
1220 result = max_score;
1221 } else {
1222 /* Phase 2: fill the rest of the score matrix. Each row i only spans
1223 * [F[i], last_idx]; cells outside stay 0. */
1224 for (glong i = 1; i < M; i++) {
1225 gunichar pchar = case_sensitive ? pat[i] : g_unichar_tolower(pat[i]);
1226 glong f = F[i];
1227 in_gap = FALSE;
1228 int *row = H + i * N;
1229 int *crow = C + i * N;
1230 int *diag = H + (i - 1) * N;
1231 int *cdiag = C + (i - 1) * N;
1232 for (glong j = f; j <= last_idx; j++) {
1233 int s1 = 0, s2, consecutive = 0;
1234 int hleft = (j > f) ? row[j - 1] : 0;
1235 s2 = hleft +
1237 if (pchar == tmatch[j]) {
1238 int b = B[j];
1239 s1 = diag[j - 1] + FZF_V2_SCORE_MATCH;
1240 consecutive = cdiag[j - 1] + 1;
1241 if (consecutive > 1) {
1242 int fb = B[j - consecutive + 1];
1243 if (b >= FZF_V2_BONUS_BOUNDARY && b > fb) {
1244 consecutive = 1;
1245 } else {
1246 b = MAX(b, MAX(FZF_V2_BONUS_CONSECUTIVE, fb));
1247 }
1248 }
1249 if (s1 + b < s2) {
1250 s1 += B[j];
1251 consecutive = 0;
1252 } else {
1253 s1 += b;
1254 }
1255 }
1256 crow[j] = consecutive;
1257 in_gap = s1 < s2;
1258 int sc = MAX(MAX(s1, s2), 0);
1259 if (i == M - 1 && sc > max_score) {
1260 max_score = sc;
1261 }
1262 row[j] = sc;
1263 }
1264 }
1265 result = max_score;
1266 }
1267
1268 g_free(H);
1269 g_free(C);
1270 g_free(F);
1271 return result;
1272}
1273
1274int rofi_scorer_fzf_v2_evaluate(const char *pattern, glong plen,
1275 const char *str, glong slen,
1276 int case_sensitive) {
1277 /* plen is unused (the per-term lengths are recomputed after decoding) but is
1278 * kept so this matches the rofi_scorer_fuzzy_evaluate() signature and the
1279 * call site can treat both scorers uniformly. */
1280 (void)plen;
1281 if (slen > FUZZY_SCORER_MAX_LENGTH) {
1282 return FZF_V2_MIN_SCORE;
1283 }
1284
1285 /* When normalize-match is on, score the same (accent-stripped) text the
1286 * matcher matched against, so fzf's per-rune comparison stays consistent
1287 * with rofi's matching. This is rofi's equivalent of fzf's normalizeRune
1288 * table. */
1289 char *npattern = NULL, *nstr = NULL;
1290 if (config.normalize_match) {
1291 npattern = utf8_helper_simplify_string(pattern);
1292 nstr = utf8_helper_simplify_string(str);
1293 pattern = npattern;
1294 str = nstr;
1295 }
1296
1297 glong N;
1298 gunichar *txt = g_utf8_to_ucs4_fast(str, -1, &N);
1299
1300 /* Pre-compute, once per string, the case-folded text and per-position
1301 * bonuses (both depend only on the text, not the pattern). The character
1302 * class is always taken from the original character so camelCase bonuses
1303 * survive case folding, exactly like fzf. */
1304 gunichar *tmatch = g_malloc_n(N, sizeof(gunichar));
1305 int *B = g_malloc_n(N, sizeof(int));
1306 enum FzfV2CharClass prev_class = FZF_V2_CHAR_WHITE;
1307 for (glong off = 0; off < N; off++) {
1309 tmatch[off] = case_sensitive ? txt[off] : g_unichar_tolower(txt[off]);
1310 B[off] = rofi_scorer_fzf_v2_bonus_for(prev_class, c);
1311 prev_class = c;
1312 }
1313
1314 /* fzf treats whitespace-separated terms as independent AND conditions and
1315 * sums their scores; a line matches only if every term matches. */
1316 int total = 0;
1317 gboolean any_term = FALSE;
1318 gchar **terms = g_strsplit_set(pattern, " \t\n\r\v\f", -1);
1319 for (gchar **t = terms; *t != NULL; t++) {
1320 if ((*t)[0] == '\0') {
1321 continue; /* skip empty fields from consecutive separators */
1322 }
1323 any_term = TRUE;
1324 glong tm;
1325 gunichar *pat = g_utf8_to_ucs4_fast(*t, -1, &tm);
1326 int s = rofi_scorer_fzf_v2_term(pat, tm, tmatch, B, N, case_sensitive);
1327 g_free(pat);
1328 if (s == FZF_V2_MIN_SCORE) {
1329 total = FZF_V2_MIN_SCORE;
1330 break;
1331 }
1332 total += s;
1333 }
1334 g_strfreev(terms);
1335
1336 int result = any_term ? total : 0;
1337
1338 g_free(tmatch);
1339 g_free(B);
1340 g_free(txt);
1341 g_free(npattern);
1342 g_free(nstr);
1343 return result;
1344}
1345
1347static guint16 rofi_scorer_fzf_v2_as_uint16(int val) {
1348 if (val > G_MAXUINT16) {
1349 return G_MAXUINT16;
1350 }
1351 if (val < 0) {
1352 return 0;
1353 }
1354 return (guint16)val;
1355}
1356
1358static guint16 rofi_scorer_fzf_v2_trim_length(const char *str) {
1359 const char *first = str;
1360 while (*first != '\0' && g_unichar_isspace(g_utf8_get_char(first))) {
1361 first = g_utf8_next_char(first);
1362 }
1363 const char *last = first + strlen(first);
1364 while (last > first) {
1365 const char *prev = g_utf8_prev_char(last);
1366 if (!g_unichar_isspace(g_utf8_get_char(prev))) {
1367 break;
1368 }
1369 last = prev;
1370 }
1371 return rofi_scorer_fzf_v2_as_uint16((int)g_utf8_strlen(first, last - first));
1372}
1373
1374int rofi_scorer_fzf_v2_sort_key(int score, const char *str) {
1375 /* fzf compares a [4]uint16 tuple (Result.points in src/result.go) from the
1376 * most significant element down; its default scheme fills the top two with
1377 * MaxUint16 - AsUint16(score) and TrimLength() ({byScore, byLength} in
1378 * parseScheme, src/options.go), and leaves the rest zero. Packing those two
1379 * into one integer compares the same.
1380 *
1381 * The length gets 15 bits rather than fzf's 16 to keep the widest key at
1382 * G_MAXINT: it then fits the int the weight is stored in, and no difference
1383 * between keys overflows. Nothing is lost, as the scorer rejects anything
1384 * over FUZZY_SCORER_MAX_LENGTH. */
1385 guint32 by_score = G_MAXUINT16 - rofi_scorer_fzf_v2_as_uint16(score);
1386 guint32 by_length = MIN(rofi_scorer_fzf_v2_trim_length(str), 0x7FFF);
1387 return (int)((by_score << 15) | by_length);
1388}
1389
1401int utf8_strncmp(const char *a, const char *b, size_t n) {
1402 char *na = g_utf8_normalize(a, -1, G_NORMALIZE_ALL_COMPOSE);
1403 char *nb = g_utf8_normalize(b, -1, G_NORMALIZE_ALL_COMPOSE);
1404 if (na == NULL && nb == NULL)
1405 return 0;
1406 if (na == NULL) {
1407 g_free(nb);
1408 return -1;
1409 }
1410 if (nb == NULL) {
1411 g_free(na);
1412 return 1;
1413 }
1414 size_t la = MIN(n, (size_t)g_utf8_strlen(na, -1));
1415 size_t lb = MIN(n, (size_t)g_utf8_strlen(nb, -1));
1416 *g_utf8_offset_to_pointer(na, la) = '\0';
1417 *g_utf8_offset_to_pointer(nb, lb) = '\0';
1418 int r = g_utf8_collate(na, nb);
1419 g_free(na);
1420 g_free(nb);
1421 return r;
1422}
1423
1424gboolean helper_execute(const char *wd, char **args, const char *error_precmd,
1425 const char *error_cmd,
1426 RofiHelperExecuteContext *context) {
1427 return helper_execute_env(wd, args, error_precmd, error_cmd, context, NULL);
1428}
1429gboolean helper_execute_env(const char *wd, char **args,
1430 const char *error_precmd, const char *error_cmd,
1431 RofiHelperExecuteContext *context, gchar **envp) {
1432 gboolean retv = TRUE;
1433 GError *error = NULL;
1434
1435 GSpawnChildSetupFunc child_setup = NULL;
1436 gpointer user_data = NULL;
1437
1438 display_startup_notification(context, &child_setup, &user_data);
1439
1440 g_spawn_async(wd, args, envp, G_SPAWN_SEARCH_PATH, child_setup, user_data,
1441 NULL, &error);
1442 if (error != NULL) {
1443 char *msg = g_strdup_printf("Failed to execute: '%s%s'\nError: '%s'",
1444 error_precmd, error_cmd, error->message);
1445 rofi_view_error_dialog(msg, FALSE);
1446 g_free(msg);
1447 // print error.
1448 g_error_free(error);
1449 retv = FALSE;
1450 }
1451
1452 // Free the args list.
1453 g_strfreev(args);
1454 return retv;
1455}
1456
1457gboolean helper_execute_command(const char *wd, const char *cmd,
1458 gboolean run_in_term,
1459 RofiHelperExecuteContext *context) {
1460 return helper_execute_command_env(wd, cmd, run_in_term, context, NULL);
1461}
1462
1463gboolean helper_execute_command_env(const char *wd, const char *cmd,
1464 gboolean run_in_term,
1465 RofiHelperExecuteContext *context,
1466 char **envp) {
1467 char **args = NULL;
1468 int argc = 0;
1469
1470 if (run_in_term) {
1471 helper_parse_setup(config.run_shell_command, &args, &argc, "{cmd}", cmd,
1472 (char *)0);
1473 } else {
1474 helper_parse_setup(config.run_command, &args, &argc, "{cmd}", cmd,
1475 (char *)0);
1476 }
1477
1478 if (args == NULL) {
1479 return FALSE;
1480 }
1481
1482 if (context != NULL) {
1483 if (context->name == NULL) {
1484 context->name = args[0];
1485 }
1486 if (context->binary == NULL) {
1487 context->binary = args[0];
1488 }
1489 if (context->description == NULL) {
1490 gsize l = strlen("Launching '' via rofi") + strlen(cmd) + 1;
1491 gchar *description = g_newa(gchar, l);
1492
1493 g_snprintf(description, l, "Launching '%s' via rofi", cmd);
1494 context->description = description;
1495 }
1496 if (context->command == NULL) {
1497 context->command = cmd;
1498 }
1499 }
1500
1501 return helper_execute_env(wd, args, "", cmd, context, envp);
1502}
1503
1504static char *helper_get_theme_path_check_file(const char *filename,
1505 const char *parent_file) {
1506
1507 // Check if absolute path.
1508 if (g_path_is_absolute(filename)) {
1509 g_debug("Opening theme, path is absolute: %s", filename);
1510 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1511 return g_strdup(filename);
1512 }
1513 g_debug("Opening theme, path is absolute but does not exists: %s",
1514 filename);
1515 } else {
1516 if (parent_file != NULL) {
1517 // If no absolute path specified, expand it.
1518 char *basedir = g_path_get_dirname(parent_file);
1519 char *path = g_build_filename(basedir, filename, NULL);
1520 g_free(basedir);
1521 g_debug("Opening theme, check in dir where file is included: %s", path);
1522 if (g_file_test(path, G_FILE_TEST_EXISTS)) {
1523 return path;
1524 }
1525 g_debug("Opening theme, file does not exists in dir where file is "
1526 "included: %s\n",
1527 filename);
1528 }
1529 }
1530 // Check config's themes directory.
1531 const char *cpath = g_get_user_config_dir();
1532 if (cpath) {
1533 char *themep = g_build_filename(cpath, "rofi", "themes", filename, NULL);
1534 g_debug("Opening theme, testing: %s", themep);
1535 if (themep && g_file_test(themep, G_FILE_TEST_EXISTS)) {
1536 return themep;
1537 }
1538 g_free(themep);
1539 }
1540 // Check config directory.
1541 if (cpath) {
1542 char *themep = g_build_filename(cpath, "rofi", filename, NULL);
1543 g_debug("Opening theme, testing: %s", themep);
1544 if (g_file_test(themep, G_FILE_TEST_EXISTS)) {
1545 return themep;
1546 }
1547 g_free(themep);
1548 }
1549 const char *datadir = g_get_user_data_dir();
1550 if (datadir) {
1551 char *theme_path =
1552 g_build_filename(datadir, "rofi", "themes", filename, NULL);
1553 if (theme_path) {
1554 g_debug("Opening theme, testing: %s", theme_path);
1555 if (g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
1556 return theme_path;
1557 }
1558 g_free(theme_path);
1559 }
1560 }
1561
1562 const gchar *const *system_data_dirs = g_get_system_data_dirs();
1563 if (system_data_dirs) {
1564 for (uint_fast32_t i = 0; system_data_dirs[i] != NULL; i++) {
1565 const char *const sdatadir = system_data_dirs[i];
1566 g_debug("Opening theme directory: %s", sdatadir);
1567 char *theme_path =
1568 g_build_filename(sdatadir, "rofi", "themes", filename, NULL);
1569 if (theme_path) {
1570 g_debug("Opening theme, testing: %s", theme_path);
1571 if (g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
1572 return theme_path;
1573 }
1574 g_free(theme_path);
1575 }
1576 }
1577 }
1578
1579 char *theme_path = g_build_filename(THEME_DIR, filename, NULL);
1580 if (theme_path) {
1581 g_debug("Opening theme, testing: %s", theme_path);
1582 if (g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
1583 return theme_path;
1584 }
1585 g_free(theme_path);
1586 }
1587 return NULL;
1588}
1589
1590char *helper_get_theme_path(const char *file, const char **ext,
1591 const char *parent_file) {
1592
1593 char *filename = rofi_expand_path(file);
1594 g_debug("Opening theme, testing: %s\n", filename);
1595 if (g_path_is_absolute(filename)) {
1596 g_debug("Opening theme, path is absolute: %s", filename);
1597 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1598 return filename;
1599 }
1600 }
1601 gboolean ext_found = FALSE;
1602 for (const char **i = ext; *i != NULL; i++) {
1603 if (g_str_has_suffix(file, *i)) {
1604 ext_found = TRUE;
1605 break;
1606 }
1607 }
1608 if (ext_found) {
1609 filename = rofi_expand_path(file);
1610
1611 char *retv = helper_get_theme_path_check_file(filename, parent_file);
1612 if (retv) {
1613 g_free(filename);
1614 return retv;
1615 }
1616 } else {
1617 g_assert_nonnull(ext[0]);
1618 // Iterate through extensions.
1619 char *temp = filename;
1620 for (const char **i = ext; *i != NULL; i++) {
1621 filename = g_strconcat(temp, *i, NULL);
1622 char *retv = helper_get_theme_path_check_file(filename, parent_file);
1623 if (retv) {
1624 g_free(filename);
1625 g_free(temp);
1626 return retv;
1627 }
1628 }
1629 g_free(temp);
1630 }
1631
1632 return filename;
1633}
1634
1635static gboolean parse_pair(char *input, rofi_range_pair *item) {
1636 // Skip leading blanks.
1637 while (input != NULL && isblank(*input)) {
1638 ++input;
1639 }
1640
1641 if (input == NULL) {
1642 return FALSE;
1643 }
1644
1645 const char *sep[] = {"-", ":"};
1646 int pythonic = (strchr(input, ':') || input[0] == '-') ? 1 : 0;
1647 int index = 0;
1648
1649 for (char *token = strsep(&input, sep[pythonic]); token != NULL;
1650 token = strsep(&input, sep[pythonic])) {
1651 if (index == 0) {
1652 item->start = item->stop = (int)strtol(token, NULL, 10);
1653 index++;
1654 continue;
1655 }
1656
1657 if (token[0] == '\0') {
1658 item->stop = -1;
1659 continue;
1660 }
1661
1662 item->stop = (int)strtol(token, NULL, 10);
1663 if (pythonic) {
1664 --item->stop;
1665 }
1666 }
1667 return TRUE;
1668}
1669void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length) {
1670 char *endp;
1671 if (input == NULL) {
1672 return;
1673 }
1674 const char *const sep = ",";
1675 for (char *token = strtok_r(input, sep, &endp); token != NULL;
1676 token = strtok_r(NULL, sep, &endp)) {
1677 // Make space.
1678 *list =
1679 g_realloc((*list), ((*length) + 1) * sizeof(struct rofi_range_pair));
1680 // Parse a single pair.
1681 if (parse_pair(token, &((*list)[*length]))) {
1682 (*length)++;
1683 }
1684 }
1685}
1686
1687int parse_case_sensitivity(const char *input) {
1688 int case_sensitive = config.case_sensitive;
1689 if (config.case_smart) {
1690 // By default case is false, unless the search query has a
1691 // uppercase in it?
1692 case_sensitive = FALSE;
1693 const char *end;
1694 if (g_utf8_validate(input, -1, &end)) {
1695 for (const char *c = (input); !case_sensitive && c != NULL && *c;
1696 c = g_utf8_next_char(c)) {
1697 gunichar uc = g_utf8_get_char(c);
1698 if (g_unichar_isupper(uc)) {
1699 case_sensitive = TRUE;
1700 }
1701 }
1702 }
1703 }
1704
1705 return case_sensitive;
1706}
1707
1708void rofi_output_formatted_line(const char *format, const char *string,
1709 int selected_line, const char *filter) {
1710 for (int i = 0; format && format[i]; i++) {
1711 if (format[i] == 'i') {
1712 fprintf(stdout, "%d", selected_line);
1713 } else if (format[i] == 'd') {
1714 fprintf(stdout, "%d", (selected_line + 1));
1715 } else if (format[i] == 's') {
1716 fputs(string, stdout);
1717 } else if (format[i] == 'p') {
1718 char *esc = NULL;
1719 pango_parse_markup(string, -1, 0, NULL, &esc, NULL, NULL);
1720 if (esc) {
1721 fputs(esc, stdout);
1722 g_free(esc);
1723 } else {
1724 fputs("invalid string", stdout);
1725 }
1726 } else if (format[i] == 'q') {
1727 char *quote = g_shell_quote(string);
1728 fputs(quote, stdout);
1729 g_free(quote);
1730 } else if (format[i] == 'f') {
1731 if (filter) {
1732 fputs(filter, stdout);
1733 }
1734 } else if (format[i] == 'F') {
1735 if (filter) {
1736 char *quote = g_shell_quote(filter);
1737 fputs(quote, stdout);
1738 g_free(quote);
1739 }
1740 } else {
1741 fputc(format[i], stdout);
1742 }
1743 }
1744 fputc('\n', stdout);
1745 fflush(stdout);
1746}
1747
1748static gboolean helper_eval_cb2(const GMatchInfo *info, GString *res,
1749 gpointer data) {
1750 gchar *match;
1751 // Get the match
1752 int num_match = g_match_info_get_match_count(info);
1753 // Just {text} This is inside () 5.
1754 if (num_match == 5) {
1755 match = g_match_info_fetch(info, 4);
1756 if (match != NULL) {
1757 // Lookup the match, so we can replace it.
1758 gchar *r = g_hash_table_lookup((GHashTable *)data, match);
1759 if (r != NULL) {
1760 // Append the replacement to the string.
1761 g_string_append(res, r);
1762 }
1763 // Free match.
1764 g_free(match);
1765 }
1766 }
1767 // {} with [] guard around it.
1768 else if (num_match == 4) {
1769 match = g_match_info_fetch(info, 2);
1770 if (match != NULL) {
1771 // Lookup the match, so we can replace it.
1772 gchar *r = g_hash_table_lookup((GHashTable *)data, match);
1773 if (r != NULL) {
1774 // Add (optional) prefix
1775 gchar *prefix = g_match_info_fetch(info, 1);
1776 g_string_append(res, prefix);
1777 g_free(prefix);
1778 // Append the replacement to the string.
1779 g_string_append(res, r);
1780 // Add (optional) postfix
1781 gchar *post = g_match_info_fetch(info, 3);
1782 g_string_append(res, post);
1783 g_free(post);
1784 }
1785 // Free match.
1786 g_free(match);
1787 }
1788 }
1789 // Else we have an invalid match.
1790 // Continue replacement.
1791 return FALSE;
1792}
1793
1794char *helper_string_replace_if_exists(char *string, ...) {
1795 GHashTable *h;
1796 h = g_hash_table_new(g_str_hash, g_str_equal);
1797 va_list ap;
1798 va_start(ap, string);
1799 // Add list from variable arguments.
1800 while (1) {
1801 char *key = va_arg(ap, char *);
1802 if (key == (char *)0) {
1803 break;
1804 }
1805 char *value = va_arg(ap, char *);
1806 g_hash_table_insert(h, key, value);
1807 }
1808 char *retv = helper_string_replace_if_exists_v(string, h);
1809 va_end(ap);
1810 // Destroy key-value storage.
1811 g_hash_table_destroy(h);
1812 return retv;
1813}
1814
1829char *helper_string_replace_if_exists_v(char *string, GHashTable *h) {
1830 GError *error = NULL;
1831 char *res = NULL;
1832
1833 // Replace hits within {-\w+}.
1834 GRegex *reg = g_regex_new("\\[(.*)({[-\\w]+})(.*)\\]|({[\\w-]+})",
1835 G_REGEX_UNGREEDY, 0, &error);
1836 if (error == NULL) {
1837 res =
1838 g_regex_replace_eval(reg, string, -1, 0, 0, helper_eval_cb2, h, &error);
1839 }
1840 // Free regex.
1841 g_regex_unref(reg);
1842 // Throw error if shell parsing fails.
1843 if (error != NULL) {
1844 char *msg = g_strdup_printf("Failed to parse: '%s'\nError: '%s'", string,
1845 error->message);
1846 rofi_view_error_dialog(msg, FALSE);
1847 g_free(msg);
1848 // print error.
1849 g_error_free(error);
1850 g_free(res);
1851 return NULL;
1852 }
1853 return res;
1854}
int monitor_active(workarea *mon)
Definition display.c:19
void display_startup_notification(RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data)
Definition display.c:70
struct _workarea workarea
@ WL_CENTER
Definition rofi-types.h:235
@ MM_NORMAL
Definition settings.h:39
@ MM_REGEX
Definition settings.h:40
@ MM_PREFIX
Definition settings.h:43
@ MM_FUZZY
Definition settings.h:42
@ MM_NUM_MATCHERS
Definition settings.h:44
@ MM_GLOB
Definition settings.h:41
void helper_token_match_set_pango_attr_on_style(PangoAttrList *retv, int start, int end, RofiHighlightColorStyle th)
Definition helper.c:441
PangoAttrList * helper_token_match_get_pango_attr(RofiHighlightColorStyle th, rofi_int_matcher **tokens, const char *input, PangoAttrList *retv)
Definition helper.c:510
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
Definition helper.c:652
char * rofi_latin_to_utf8_strdup(const char *input, gssize length)
Definition helper.c:856
gboolean helper_execute_command_env(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context, char **envp)
Definition helper.c:1463
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length)
Definition helper.c:1669
void cmd_set_arguments(int argc, char **argv)
Definition helper.c:97
rofi_int_matcher ** helper_tokenize(const char *input, int case_sensitive)
Definition helper.c:286
unsigned int levenshtein(const char *needle, const glong needlelen, const char *haystack, const glong haystacklen, int case_sensitive)
Definition helper.c:819
int find_arg_char(const char *const key, char *val)
Definition helper.c:431
gboolean helper_execute_env(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context, gchar **envp)
Definition helper.c:1429
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition helper.c:1457
void helper_select_next_matching_mode(void)
Definition helper.c:83
void helper_tokenize_free(rofi_int_matcher **tokens)
Definition helper.c:146
char helper_parse_char(const char *arg)
Definition helper.c:383
void rofi_output_formatted_line(const char *format, const char *string, int selected_line, const char *filter)
Definition helper.c:1708
const char * helper_get_matching_mode_str(void)
Definition helper.c:80
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
Definition helper.c:1424
int rofi_scorer_fzf_v2_evaluate(const char *pattern, glong plen, const char *str, glong slen, int case_sensitive)
Definition helper.c:1274
char * helper_string_replace_if_exists(char *string,...)
Definition helper.c:1794
const char ** find_arg_strv(const char *const key)
Definition helper.c:344
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition helper.c:102
int execute_generator(const char *cmd)
Definition helper.c:562
void helper_select_previous_matching_mode(void)
Definition helper.c:89
int rofi_scorer_fzf_v2_sort_key(int score, const char *str)
Definition helper.c:1374
int find_arg_int(const char *const key, int *val)
Definition helper.c:364
char * rofi_expand_path(const char *input)
Definition helper.c:784
void remove_pid_file(int fd)
Definition helper.c:644
int find_arg_str(const char *const key, char **val)
Definition helper.c:334
int parse_case_sensitivity(const char *input)
Definition helper.c:1687
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str, glong slen, int case_sensitive)
Definition helper.c:971
int find_arg_uint(const char *const key, unsigned int *val)
Definition helper.c:373
int find_arg(const char *const key)
Definition helper.c:325
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition helper.c:541
int create_pid_file(const char *pidfile, gboolean kill_running)
Definition helper.c:585
int config_sanity_check(void)
Definition helper.c:671
char * rofi_force_utf8(const gchar *data, ssize_t length)
Definition helper.c:862
void rofi_add_error_message(GString *str)
Definition rofi.c:97
int rofi_view_error_dialog(const char *msg, int markup)
Definition view.c:1980
#define FZF_V2_BONUS_NON_WORD
Definition helper.c:1062
static int rofi_scorer_fzf_v2_term(const gunichar *pat, glong M, const gunichar *tmatch, const int *B, glong N, int case_sensitive)
Definition helper.c:1156
static enum FzfV2CharClass rofi_scorer_fzf_v2_char_class(gunichar c)
Definition helper.c:1089
#define CONSECUTIVE_SCORE
Definition helper.c:912
#define GAP_SCORE
Definition helper.c:904
static guint16 rofi_scorer_fzf_v2_trim_length(const char *str)
Definition helper.c:1358
#define LEADING_GAP_SCORE
Definition helper.c:902
static gchar * prefix_regex(const char *input)
Definition helper.c:198
static int MatchingMethodEnabled[MM_NUM_MATCHERS]
Definition helper.c:60
char ** stored_argv
Definition helper.c:76
static char * utf8_helper_simplify_string(const char *os)
Definition helper.c:205
#define FZF_V2_BONUS_CAMEL_123
Definition helper.c:1064
#define MIN3(a, b, c)
Definition helper.c:816
#define CAMEL_SCORE
Definition helper.c:910
static gchar * glob_to_regex(const char *input)
Definition helper.c:154
#define FZF_V2_MIN_SCORE
Definition helper.c:1076
const char *const monitor_position_entries[]
Definition helper.c:70
#define FZF_V2_SCORE_MATCH
Definition helper.c:1054
static enum CharClass rofi_scorer_get_character_class(gunichar c)
Definition helper.c:937
int utf8_strncmp(const char *a, const char *b, size_t n)
Definition helper.c:1401
#define MIN_SCORE
Definition helper.c:900
#define FZF_V2_BONUS_FIRST_CHAR_MULTIPLIER
Definition helper.c:1070
#define PATTERN_NON_START_MULTIPLIER
Definition helper.c:914
#define FZF_V2_BONUS_BOUNDARY
Definition helper.c:1060
#define FZF_V2_BONUS_BOUNDARY_DELIMITER
Definition helper.c:1074
char * helper_string_replace_if_exists_v(char *string, GHashTable *h)
Definition helper.c:1829
static int rofi_scorer_fzf_v2_bonus_for(enum FzfV2CharClass prev, enum FzfV2CharClass cur)
Definition helper.c:1112
#define FZF_V2_SCORE_GAP_EXTENSION
Definition helper.c:1058
static int NUMMatchingMethodEnabled
Definition helper.c:64
#define WORD_START_SCORE
Definition helper.c:906
static char * helper_get_theme_path_check_file(const char *filename, const char *parent_file)
Definition helper.c:1504
static int CurrentMatchingMethod
Definition helper.c:65
#define FZF_V2_BONUS_BOUNDARY_WHITE
Definition helper.c:1072
#define FUZZY_SCORER_MAX_LENGTH
Definition helper.c:898
static gboolean helper_eval_cb2(const GMatchInfo *info, GString *res, gpointer data)
Definition helper.c:1748
static guint16 rofi_scorer_fzf_v2_as_uint16(int val)
Definition helper.c:1347
#define PATTERN_START_MULTIPLIER
Definition helper.c:916
char * helper_get_theme_path(const char *file, const char **ext, const char *parent_file)
Definition helper.c:1590
#define FZF_V2_BONUS_CONSECUTIVE
Definition helper.c:1067
static rofi_int_matcher * create_regex(const char *input, int case_sensitive)
Definition helper.c:245
static GRegex * R(const char *s, int case_sensitive)
Definition helper.c:230
#define NON_WORD_SCORE
Definition helper.c:908
#define FZF_V2_SCORE_GAP_START
Definition helper.c:1056
static gchar * fuzzy_to_regex(const char *input)
Definition helper.c:169
FzfV2CharClass
Definition helper.c:1079
@ FZF_V2_CHAR_LETTER
Definition helper.c:1085
@ FZF_V2_CHAR_LOWER
Definition helper.c:1083
@ FZF_V2_CHAR_NUMBER
Definition helper.c:1086
@ FZF_V2_CHAR_WHITE
Definition helper.c:1080
@ FZF_V2_CHAR_NON_WORD
Definition helper.c:1081
@ FZF_V2_CHAR_UPPER
Definition helper.c:1084
@ FZF_V2_CHAR_DELIMITER
Definition helper.c:1082
const char *const MatchingMethodStr[MM_NUM_MATCHERS]
Definition helper.c:57
CharClass
Definition helper.c:921
@ DIGIT
Definition helper.c:927
@ LOWER
Definition helper.c:923
@ NON_WORD
Definition helper.c:929
@ UPPER
Definition helper.c:925
int stored_argc
Definition helper.c:74
static gboolean parse_pair(char *input, rofi_range_pair *item)
Definition helper.c:1635
static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr)
Definition helper.c:958
struct rofi_int_matcher_t rofi_int_matcher
@ ROFI_HL_UPPERCASE
Definition rofi-types.h:64
@ ROFI_HL_STRIKETHROUGH
Definition rofi-types.h:58
@ ROFI_HL_ITALIC
Definition rofi-types.h:60
@ ROFI_HL_UNDERLINE
Definition rofi-types.h:56
@ ROFI_HL_CAPITALIZE
Definition rofi-types.h:68
@ ROFI_HL_BOLD
Definition rofi-types.h:54
@ ROFI_HL_LOWERCASE
Definition rofi-types.h:66
@ ROFI_HL_COLOR
Definition rofi-types.h:62
char * pidfile
Definition rofi.c:84
Settings config
@ DISPLAY_XCB
Definition settings.h:53
@ SORT_FZF_V2
Definition settings.h:50
@ SORT_FZF
Definition settings.h:50
@ SORT_NORMAL
Definition settings.h:50
const gchar * binary
Definition helper.h:336
const gchar * description
Definition helper.h:338
const gchar * name
Definition helper.h:334
const gchar * command
Definition helper.h:346
RofiHighlightStyle style
Definition rofi-types.h:219
WindowLocation location
Definition settings.h:110
DisplayBackend backend
Definition settings.h:105
char * matching
Definition settings.h:163
MatchingMethod matching_method
Definition settings.h:164
unsigned int tokenize
Definition settings.h:165
char * run_command
Definition settings.h:92
gboolean normalize_match
Definition settings.h:210
char * terminal_emulator
Definition settings.h:86
char * run_shell_command
Definition settings.h:94
unsigned int case_smart
Definition settings.h:144
unsigned int case_sensitive
Definition settings.h:142
char * sorting_method
Definition settings.h:126
char matching_negate_char
Definition settings.h:195
SortingMethod sorting_method_enum
Definition settings.h:124
char * ssh_client
Definition settings.h:88
int element_height
Definition settings.h:148
char * monitor
Definition settings.h:167
double blue
Definition rofi-types.h:162
double green
Definition rofi-types.h:160
double red
Definition rofi-types.h:158
double alpha
Definition rofi-types.h:164