30#define G_LOG_DOMAIN "Helper"
43#include <glib/gstdio.h>
45#include <pango/pango-fontmap.h>
46#include <pango/pango.h>
47#include <pango/pangocairo.h>
61 "on focused monitor",
"on focused window",
"at mouse pointer",
62 "on monitor with focused window",
"on monitor that has mouse pointer"};
78 h = g_hash_table_new(g_str_hash, g_str_equal);
86 char *key = va_arg(ap,
char *);
87 if (key == (
char *)0) {
90 char *value = va_arg(ap,
char *);
91 if (value == (
char *)0) {
94 g_hash_table_insert(h, key, value);
100 g_hash_table_destroy(h);
102 if (g_shell_parse_argv(res, length, output, &error)) {
109 char *msg = g_strdup_printf(
"Failed to parse: '%s'\nError: '%s'",
string,
120 for (
size_t i = 0; tokens && tokens[i]; i++) {
121 g_regex_unref((GRegex *)tokens[i]->regex);
128 gchar *r = g_regex_escape_string(input, -1);
129 size_t str_l = strlen(r);
130 for (
size_t i = 0; i < str_l; i++) {
132 if (r[i + 1] ==
'*') {
134 }
else if (r[i + 1] ==
'?') {
143 GString *str = g_string_new(
"");
144 gchar *r = g_regex_escape_string(input, -1);
147 for (iter = r; iter && *iter !=
'\0'; iter = g_utf8_next_char(iter)) {
149 g_string_append(str,
"(");
151 g_string_append(str,
".*?(");
154 g_string_append_c(str,
'\\');
155 iter = g_utf8_next_char(iter);
157 if ((*iter) ==
'\0') {
161 g_string_append_unichar(str, g_utf8_get_char(iter));
162 g_string_append(str,
")");
166 char *retv = str->str;
167 g_string_free(str, FALSE);
172 gchar *r = g_regex_escape_string(input, -1);
173 char *retv = g_strconcat(
"\\b", r, NULL);
179 gunichar buf2[G_UNICHAR_MAX_DECOMPOSITION_LENGTH] = {
186 ssize_t str_size = (g_utf8_strlen(s, -1) * 6 + 2 + 1) *
sizeof(
char);
187 char *str = g_malloc0(str_size);
189 for (
const char *iter = s; iter && *iter; iter = g_utf8_next_char(iter)) {
190 gunichar uc = g_utf8_get_char(iter);
192 gsize dl = g_unichar_fully_decompose(uc, FALSE, buf2,
193 G_UNICHAR_MAX_DECOMPOSITION_LENGTH);
195 l = g_unichar_to_utf8(buf2[0], buf);
197 l = g_unichar_to_utf8(uc, buf);
199 memcpy(striter, buf, l);
207static inline GRegex *
R(
const char *s,
int case_sensitive) {
211 GRegex *r = g_regex_new(
212 str, G_REGEX_OPTIMIZE | ((case_sensitive) ? 0 : G_REGEX_CASELESS), 0,
219 s, G_REGEX_OPTIMIZE | ((case_sensitive) ? 0 : G_REGEX_CASELESS), 0, NULL);
233 retv =
R(r, case_sensitive);
237 retv =
R(input, case_sensitive);
239 r = g_regex_escape_string(input, -1);
240 retv =
R(r, case_sensitive);
246 retv =
R(r, case_sensitive);
251 retv =
R(r, case_sensitive);
255 r = g_regex_escape_string(input, -1);
256 retv =
R(r, case_sensitive);
267 size_t len = strlen(input);
272 char *saveptr = NULL, *token;
284 char *str = g_strdup(input);
288 const char *
const sep =
" ";
289 for (token = strtok_r(str, sep, &saveptr); token != NULL;
290 token = strtok_r(NULL, sep, &saveptr)) {
293 retv[num_tokens + 1] = NULL;
322 const char **retv = NULL;
330 retv = g_malloc0((length + 1) *
sizeof(
char *));
344 if (val != NULL && i > 0 && i < (
stored_argc - 1)) {
353 if (val != NULL && i > 0 && i < (
stored_argc - 1)) {
361 const size_t len = strlen(arg);
367 if (len == 2 && arg[0] ==
'\\') {
400 if (len > 2 && arg[0] ==
'\\' && arg[1] ==
'x') {
401 return (
char)strtol(&arg[2], NULL, 16);
403 g_warning(
"Failed to parse character string: \"%s\"", arg);
411 if (val != NULL && i > 0 && i < (
stored_argc - 1)) {
422 PangoAttribute *pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
423 pa->start_index = start;
425 pango_attr_list_insert(retv, pa);
427#if PANGO_VERSION_CHECK(1, 50, 0)
430 pango_attr_text_transform_new(PANGO_TEXT_TRANSFORM_UPPERCASE);
431 pa->start_index = start;
433 pango_attr_list_insert(retv, pa);
437 pango_attr_text_transform_new(PANGO_TEXT_TRANSFORM_LOWERCASE);
438 pa->start_index = start;
440 pango_attr_list_insert(retv, pa);
445 pango_attr_text_transform_new(PANGO_TEXT_TRANSFORM_CAPITALIZE);
446 pa->start_index = start;
448 pango_attr_list_insert(retv, pa);
454 PangoAttribute *pa = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
455 pa->start_index = start;
457 pango_attr_list_insert(retv, pa);
460 PangoAttribute *pa = pango_attr_strikethrough_new(TRUE);
461 pa->start_index = start;
463 pango_attr_list_insert(retv, pa);
466 PangoAttribute *pa = pango_attr_style_new(PANGO_STYLE_ITALIC);
467 pa->start_index = start;
469 pango_attr_list_insert(retv, pa);
472 PangoAttribute *pa = pango_attr_foreground_new(
474 pa->start_index = start;
476 pango_attr_list_insert(retv, pa);
479 pa = pango_attr_foreground_alpha_new(th.
color.
alpha * 65535);
480 pa->start_index = start;
482 pango_attr_list_insert(retv, pa);
490 PangoAttrList *retv) {
497 for (
int j = 0; tokens[j]; j++) {
498 GMatchInfo *gmi = NULL;
499 if (tokens[j]->invert) {
502 g_regex_match(tokens[j]->regex, input, G_REGEX_MATCH_PARTIAL, &gmi);
503 while (g_match_info_matches(gmi)) {
504 int count = g_match_info_get_match_count(gmi);
505 for (
int index = (
count > 1) ? 1 : 0; index <
count; index++) {
507 g_match_info_fetch_pos(gmi, index, &start, &end);
510 g_match_info_next(gmi, NULL);
512 g_match_info_free(gmi);
524 for (
int j = 0; match && tokens[j]; j++) {
525 match = g_regex_match(tokens[j]->regex, r, 0, NULL);
526 match ^= tokens[j]->invert;
530 for (
int j = 0; match && tokens[j]; j++) {
531 match = g_regex_match(tokens[j]->regex, input, 0, NULL);
532 match ^= tokens[j]->invert;
545 GError *error = NULL;
546 g_spawn_async_with_pipes(NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
547 NULL, NULL, &fd, NULL, &error);
550 char *msg = g_strdup_printf(
"Failed to execute: '%s'\nError: '%s'", cmd,
567 int fd = g_open(
pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
569 g_warning(
"Failed to create pid file: '%s'.",
pidfile);
573 int flags = fcntl(fd, F_GETFD, NULL);
575 if (fcntl(fd, F_SETFD,
flags, NULL) < 0) {
576 g_warning(
"Failed to set CLOEXEC on pidfile.");
581 int retv = flock(fd, LOCK_EX | LOCK_NB);
583 g_warning(
"Failed to set lock on pidfile: Rofi already running?");
584 g_warning(
"Got error: %d %s", retv, g_strerror(errno));
589 ssize_t l = read(fd, &buffer, 64);
591 pid_t pid = g_ascii_strtoll(buffer, NULL, 0);
594 retv = flock(fd, LOCK_EX | LOCK_NB);
608 if (ftruncate(fd, (off_t)0) == 0) {
611 int length = snprintf(buffer, 64,
"%i", getpid());
614 l += write(fd, &buffer[l], length - l);
623 g_warning(
"Failed to close pidfile: '%s'", g_strerror(errno));
629 const char *fam = pango_font_description_get_family(pfd);
630 int size = pango_font_description_get_size(pfd);
631 if (fam == NULL || size == 0) {
632 g_debug(
"Pango failed to parse font: '%s'", font);
633 g_debug(
"Got family: <b>%s</b> at size: <b>%d</b>", fam ? fam :
"{unknown}",
648 int found_error = FALSE;
650 g_string_new(
"<big><b>The configuration failed to validate:</b></big>\n");
660 g_string_append_printf(
662 "\t<b>config.sorting_method</b>=%s is not a valid sorting "
663 "strategy.\nValid options are: normal or fzf.\n",
682 g_string_append_printf(msg,
683 "\t<b>config.matching</b>=%s is not a valid "
684 "matching strategy.\nValid options are: glob, "
685 "regex, fuzzy, prefix or normal.\n",
692 g_string_append_printf(msg,
693 "\t<b>config.element_height</b>=%d is invalid. An "
694 "element needs to be at least 1 line high.\n",
700 g_string_append_printf(msg,
701 "\t<b>config.location</b>=%d is invalid. Value "
702 "should be between %d and %d.\n",
713 if (name && name[0] ==
'-') {
714 int index = name[1] -
'0';
715 if (index < 5 && index > 0) {
719 g_string_append_printf(
720 msg,
"\t<b>config.monitor</b>=%s Could not find monitor.\n", name);
731 g_string_append(msg,
"Please update your configuration.");
736 g_string_free(msg, TRUE);
741 char **str = g_strsplit(input, G_DIR_SEPARATOR_S, -1);
742 for (
unsigned int i = 0; str && str[i]; i++) {
744 if (str[i][0] ==
'~' && str[i][1] ==
'\0') {
746 str[i] = g_strdup(g_get_home_dir());
749 else if (str[i][0] ==
'~') {
750 struct passwd *p = getpwnam(&(str[i][1]));
753 str[i] = g_strdup(p->pw_dir);
757 if (input[0] == G_DIR_SEPARATOR) {
758 str[i] = g_strdup_printf(
"%s%s", G_DIR_SEPARATOR_S, s);
763 char *retv = g_build_filenamev(str);
769#define MIN3(a, b, c) \
770 ((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))
772unsigned int levenshtein(
const char *needle,
const glong needlelen,
773 const char *haystack,
const glong haystacklen) {
774 if (needlelen == G_MAXLONG) {
778 unsigned int column[needlelen + 1];
779 for (glong y = 0; y < needlelen; y++) {
784 column[needlelen] = needlelen;
785 for (glong x = 1; x <= haystacklen; x++) {
786 const char *needles = needle;
788 gunichar haystackc = g_utf8_get_char(haystack);
790 haystackc = g_unichar_tolower(haystackc);
792 for (glong y = 1, lastdiag = x - 1; y <= needlelen; y++) {
793 gunichar needlec = g_utf8_get_char(needles);
795 needlec = g_unichar_tolower(needlec);
797 unsigned int olddiag = column[y];
798 column[y] =
MIN3(column[y] + 1, column[y - 1] + 1,
799 lastdiag + (needlec == haystackc ? 0 : 1));
801 needles = g_utf8_next_char(needles);
803 haystack = g_utf8_next_char(haystack);
805 return column[needlelen];
810 return g_convert_with_fallback(input, length,
"UTF-8",
"latin1",
"\uFFFD",
811 NULL, &slength, NULL);
821 if (g_utf8_validate(data, length, &end)) {
822 return g_memdup(data, length + 1);
824 string = g_string_sized_new(length + 16);
828 g_string_append_len(
string, data, end - data);
830 g_string_append(
string,
"\uFFFD");
831 length -= (end - data) + 1;
833 }
while (!g_utf8_validate(data, length, &end));
836 g_string_append_len(
string, data, length);
839 return g_string_free(
string, FALSE);
847#define FUZZY_SCORER_MAX_LENGTH 256
849#define MIN_SCORE (INT_MIN / 2)
851#define LEADING_GAP_SCORE -4
855#define WORD_START_SCORE 50
857#define NON_WORD_SCORE 40
859#define CAMEL_SCORE (WORD_START_SCORE + GAP_SCORE - 1)
861#define CONSECUTIVE_SCORE (WORD_START_SCORE + GAP_SCORE)
863#define PATTERN_NON_START_MULTIPLIER 1
865#define PATTERN_START_MULTIPLIER 2
887 if (g_unichar_islower(c)) {
890 if (g_unichar_isupper(c)) {
893 if (g_unichar_isdigit(c)) {
927 gboolean pfirst = TRUE;
929 gboolean pstart = TRUE;
931 int *score = g_malloc_n(slen,
sizeof(
int));
933 int *dp = g_malloc_n(slen,
sizeof(
int));
936 int uleft = 0, ulefts = 0, left, lefts;
937 const gchar *pit = pattern, *sit;
939 for (si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char(sit)) {
945 for (pi = 0; pi < plen; pi++, pit = g_utf8_next_char(pit)) {
946 gunichar pc = g_utf8_get_char(pit), sc;
947 if (g_unichar_isspace(pc)) {
952 for (si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char(sit)) {
955 sc = g_utf8_get_char(sit);
958 : g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
969 pfirst = pstart = FALSE;
972 for (si = 0; si < slen; si++) {
992 char *na = g_utf8_normalize(a, -1, G_NORMALIZE_ALL_COMPOSE);
993 char *nb = g_utf8_normalize(b, -1, G_NORMALIZE_ALL_COMPOSE);
994 *g_utf8_offset_to_pointer(na, n) =
'\0';
995 *g_utf8_offset_to_pointer(nb, n) =
'\0';
996 int r = g_utf8_collate(na, nb);
1003 const char *error_cmd,
1005 gboolean retv = TRUE;
1006 GError *error = NULL;
1008 GSpawnChildSetupFunc child_setup = NULL;
1009 gpointer user_data = NULL;
1013 g_spawn_async(wd, args, NULL, G_SPAWN_SEARCH_PATH, child_setup, user_data,
1015 if (error != NULL) {
1016 char *msg = g_strdup_printf(
"Failed to execute: '%s%s'\nError: '%s'",
1017 error_precmd, error_cmd, error->message);
1021 g_error_free(error);
1031 gboolean run_in_term,
1048 if (context != NULL) {
1049 if (context->
name == NULL) {
1050 context->
name = args[0];
1052 if (context->
binary == NULL) {
1053 context->
binary = args[0];
1056 gsize l = strlen(
"Launching '' via rofi") + strlen(cmd) + 1;
1057 gchar *description = g_newa(gchar, l);
1059 g_snprintf(description, l,
"Launching '%s' via rofi", cmd);
1062 if (context->
command == NULL) {
1072 g_debug(
"Opening theme, testing: %s\n", filename);
1073 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1078 if (g_str_has_suffix(file, ext)) {
1079 filename = g_strdup(file);
1081 filename = g_strconcat(file, ext, NULL);
1084 const char *cpath = g_get_user_config_dir();
1086 char *themep = g_build_filename(cpath,
"rofi",
"themes", filename, NULL);
1087 g_debug(
"Opening theme, testing: %s", themep);
1088 if (themep && g_file_test(themep, G_FILE_TEST_EXISTS)) {
1096 char *themep = g_build_filename(cpath,
"rofi", filename, NULL);
1097 g_debug(
"Opening theme, testing: %s", themep);
1098 if (g_file_test(themep, G_FILE_TEST_EXISTS)) {
1104 const char *datadir = g_get_user_data_dir();
1107 g_build_filename(datadir,
"rofi",
"themes", filename, NULL);
1109 g_debug(
"Opening theme, testing: %s", theme_path);
1110 if (g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
1118 const gchar *
const *system_data_dirs = g_get_system_data_dirs();
1119 if (system_data_dirs) {
1120 for (uint_fast32_t i = 0; system_data_dirs[i] != NULL; i++) {
1121 const char *
const sdatadir = system_data_dirs[i];
1122 g_debug(
"Opening theme directory: %s", sdatadir);
1124 g_build_filename(sdatadir,
"rofi",
"themes", filename, NULL);
1126 g_debug(
"Opening theme, testing: %s", theme_path);
1127 if (g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
1136 char *theme_path = g_build_filename(THEME_DIR, filename, NULL);
1138 g_debug(
"Opening theme, testing: %s", theme_path);
1139 if (g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
1150 while (input != NULL && isblank(*input)) {
1154 if (input == NULL) {
1158 const char *sep[] = {
"-",
":"};
1159 int pythonic = (strchr(input,
':') || input[0] ==
'-') ? 1 : 0;
1162 for (
char *token = strsep(&input, sep[pythonic]); token != NULL;
1163 token = strsep(&input, sep[pythonic])) {
1165 item->
start = item->
stop = (int)strtol(token, NULL, 10);
1170 if (token[0] ==
'\0') {
1175 item->
stop = (int)strtol(token, NULL, 10);
1184 if (input == NULL) {
1187 const char *
const sep =
",";
1188 for (
char *token = strtok_r(input, sep, &endp); token != NULL;
1189 token = strtok_r(NULL, sep, &endp)) {
1194 if (
parse_pair(token, &((*list)[*length]))) {
1200 int selected_line,
const char *filter) {
1201 for (
int i = 0; format && format[i]; i++) {
1202 if (format[i] ==
'i') {
1203 fprintf(stdout,
"%d", selected_line);
1204 }
else if (format[i] ==
'd') {
1205 fprintf(stdout,
"%d", (selected_line + 1));
1206 }
else if (format[i] ==
's') {
1207 fputs(
string, stdout);
1208 }
else if (format[i] ==
'p') {
1210 pango_parse_markup(
string, -1, 0, NULL, &esc, NULL, NULL);
1215 fputs(
"invalid string", stdout);
1217 }
else if (format[i] ==
'q') {
1218 char *quote = g_shell_quote(
string);
1219 fputs(quote, stdout);
1221 }
else if (format[i] ==
'f') {
1223 fputs(filter, stdout);
1225 }
else if (format[i] ==
'F') {
1227 char *quote = g_shell_quote(filter);
1228 fputs(quote, stdout);
1232 fputc(format[i], stdout);
1235 fputc(
'\n', stdout);
1243 int num_match = g_match_info_get_match_count(info);
1245 if (num_match == 5) {
1246 match = g_match_info_fetch(info, 4);
1247 if (match != NULL) {
1249 gchar *r = g_hash_table_lookup((GHashTable *)data, match);
1252 g_string_append(res, r);
1259 else if (num_match == 4) {
1260 match = g_match_info_fetch(info, 2);
1261 if (match != NULL) {
1263 gchar *r = g_hash_table_lookup((GHashTable *)data, match);
1266 gchar *prefix = g_match_info_fetch(info, 1);
1267 g_string_append(res, prefix);
1270 g_string_append(res, r);
1272 gchar *post = g_match_info_fetch(info, 3);
1273 g_string_append(res, post);
1287 h = g_hash_table_new(g_str_hash, g_str_equal);
1289 va_start(ap,
string);
1292 char *key = va_arg(ap,
char *);
1293 if (key == (
char *)0) {
1296 char *value = va_arg(ap,
char *);
1297 g_hash_table_insert(h, key, value);
1302 g_hash_table_destroy(h);
1321 GError *error = NULL;
1325 GRegex *reg = g_regex_new(
"\\[(.*)({[-\\w]+})(.*)\\]|({[\\w-]+})",
1326 G_REGEX_UNGREEDY, 0, &error);
1327 if (error == NULL) {
1329 g_regex_replace_eval(reg,
string, -1, 0, 0,
helper_eval_cb2, h, &error);
1334 if (error != NULL) {
1335 char *msg = g_strdup_printf(
"Failed to parse: '%s'\nError: '%s'",
string,
1340 g_error_free(error);
void helper_token_match_set_pango_attr_on_style(PangoAttrList *retv, int start, int end, RofiHighlightColorStyle th)
PangoAttrList * helper_token_match_get_pango_attr(RofiHighlightColorStyle th, rofi_int_matcher **tokens, const char *input, PangoAttrList *retv)
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
char * rofi_latin_to_utf8_strdup(const char *input, gssize length)
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length)
void cmd_set_arguments(int argc, char **argv)
rofi_int_matcher ** helper_tokenize(const char *input, int case_sensitive)
int find_arg_char(const char *const key, char *val)
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
void helper_tokenize_free(rofi_int_matcher **tokens)
char helper_parse_char(const char *arg)
void rofi_output_formatted_line(const char *format, const char *string, int selected_line, const char *filter)
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
unsigned int levenshtein(const char *needle, const glong needlelen, const char *haystack, const glong haystacklen)
char * helper_string_replace_if_exists(char *string,...)
const char ** find_arg_strv(const char *const key)
int helper_parse_setup(char *string, char ***output, int *length,...)
char * helper_get_theme_path(const char *file, const char *ext)
int execute_generator(const char *cmd)
int find_arg_int(const char *const key, int *val)
char * rofi_expand_path(const char *input)
void remove_pid_file(int fd)
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str, glong slen)
int find_arg_str(const char *const key, char **val)
int find_arg_uint(const char *const key, unsigned int *val)
int find_arg(const char *const key)
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
int create_pid_file(const char *pidfile, gboolean kill_running)
int config_sanity_check(void)
char * rofi_force_utf8(const gchar *data, ssize_t length)
void rofi_add_error_message(GString *str)
int rofi_view_error_dialog(const char *msg, int markup)
#define CONSECUTIVE_SCORE
#define LEADING_GAP_SCORE
static gchar * prefix_regex(const char *input)
static gchar * glob_to_regex(const char *input)
const char *const monitor_position_entries[]
static enum CharClass rofi_scorer_get_character_class(gunichar c)
int utf8_strncmp(const char *a, const char *b, size_t n)
#define PATTERN_NON_START_MULTIPLIER
char * helper_string_replace_if_exists_v(char *string, GHashTable *h)
static char * utf8_helper_simplify_string(const char *s)
#define FUZZY_SCORER_MAX_LENGTH
static gboolean helper_eval_cb2(const GMatchInfo *info, GString *res, gpointer data)
#define PATTERN_START_MULTIPLIER
static rofi_int_matcher * create_regex(const char *input, int case_sensitive)
static GRegex * R(const char *s, int case_sensitive)
static gchar * fuzzy_to_regex(const char *input)
static gboolean parse_pair(char *input, rofi_range_pair *item)
static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr)
const gchar * description
MatchingMethod matching_method
unsigned int case_sensitive
char matching_negate_char
SortingMethod sorting_method_enum
int monitor_active(workarea *mon)
void display_startup_notification(RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data)