29 #define G_LOG_DOMAIN "Helper" 40 #include <glib/gstdio.h> 41 #include <sys/types.h> 46 #include <pango/pango.h> 47 #include <pango/pango-fontmap.h> 48 #include <pango/pangocairo.h> 49 #include <librsvg/rsvg.h> 65 "on monitor with focused window",
66 "on monitor that has mouse pointer" 88 static gboolean
helper_eval_cb (
const GMatchInfo *info, GString *res, gpointer data )
92 match = g_match_info_fetch ( info, 0 );
93 if ( match != NULL ) {
95 gchar *r = g_hash_table_lookup ( (GHashTable *) data, match );
98 g_string_append ( res, r );
109 GError *error = NULL;
111 h = g_hash_table_new ( g_str_hash, g_str_equal );
117 va_start ( ap, length );
119 char * key = va_arg ( ap,
char * );
120 if ( key == (
char *) 0 ) {
123 char *value = va_arg ( ap,
char * );
124 if ( value == (
char *) 0 ) {
127 g_hash_table_insert ( h, key, value );
132 GRegex *reg = g_regex_new (
"{[-\\w]+}", 0, 0, NULL );
133 char *res = g_regex_replace_eval ( reg,
string, -1, 0, 0,
helper_eval_cb, h, NULL );
135 g_regex_unref ( reg );
137 g_hash_table_destroy ( h );
139 if ( g_shell_parse_argv ( res, length, output, &error ) ) {
146 char *msg = g_strdup_printf (
"Failed to parse: '%s'\nError: '%s'",
string, error->message );
150 g_error_free ( error );
157 for (
size_t i = 0; tokens && tokens[i]; i++ ) {
158 g_regex_unref ( (GRegex *) tokens[i]->regex );
159 g_free ( tokens[i] );
166 gchar *r = g_regex_escape_string ( input, -1 );
167 size_t str_l = strlen ( r );
168 for (
size_t i = 0; i < str_l; i++ ) {
169 if ( r[i] ==
'\\' ) {
170 if ( r[i + 1] ==
'*' ) {
173 else if ( r[i + 1] ==
'?' ) {
183 GString *str = g_string_new (
"" );
184 gchar *r = g_regex_escape_string ( input, -1 );
187 for ( iter = r; iter && *iter !=
'\0'; iter = g_utf8_next_char ( iter ) ) {
189 g_string_append ( str,
"(" );
192 g_string_append ( str,
".*(" );
194 if ( *iter ==
'\\' ) {
195 g_string_append_c ( str,
'\\' );
196 iter = g_utf8_next_char ( iter );
198 if ( ( *iter ) ==
'\0' ) {
202 g_string_append_unichar ( str, g_utf8_get_char ( iter ) );
203 g_string_append ( str,
")" );
207 char *retv = str->str;
208 g_string_free ( str, FALSE );
213 static inline GRegex *
R (
const char *s,
int case_sensitive )
215 return g_regex_new ( s, G_REGEX_OPTIMIZE | ( ( case_sensitive ) ? 0 : G_REGEX_CASELESS ), 0, NULL );
220 GRegex * retv = NULL;
223 if ( input && input[0] ==
'-' ) {
231 retv =
R ( r, case_sensitive );
235 retv =
R ( input, case_sensitive );
236 if ( retv == NULL ) {
237 r = g_regex_escape_string ( input, -1 );
238 retv =
R ( r, case_sensitive );
244 retv =
R ( r, case_sensitive );
248 r = g_regex_escape_string ( input, -1 );
249 retv =
R ( r, case_sensitive );
258 if ( input == NULL ) {
261 size_t len = strlen ( input );
266 char *saveptr = NULL, *token;
278 char *str = g_strdup ( input );
282 const char *
const sep =
" ";
283 for ( token = strtok_r ( str, sep, &saveptr ); token != NULL; token = strtok_r ( NULL, sep, &saveptr ) ) {
284 retv = g_realloc ( retv,
sizeof (
rofi_int_matcher* ) * ( num_tokens + 2 ) );
285 retv[num_tokens] =
create_regex ( token, case_sensitive );
286 retv[num_tokens + 1] = NULL;
309 if ( val != NULL && i > 0 && i <
stored_argc - 1 ) {
318 const char **retv = NULL;
326 retv = g_malloc0 ( ( length + 1 ) *
sizeof (
char* ) );
341 if ( val != NULL && i > 0 && i < (
stored_argc - 1 ) ) {
351 if ( val != NULL && i > 0 && i < (
stored_argc - 1 ) ) {
360 const size_t len = strlen ( arg );
366 if ( len == 2 && arg[0] ==
'\\' ) {
370 case 'n':
return '\n';
372 case 'a':
return '\a';
374 case 'b':
return '\b';
376 case 't':
return '\t';
378 case 'v':
return '\v';
380 case 'f':
return '\f';
382 case 'r':
return '\r';
384 case '\\':
return '\\';
386 case '0':
return '\0';
391 if ( len > 2 && arg[0] ==
'\\' && arg[1] ==
'x' ) {
392 return (
char) strtol ( &arg[2], NULL, 16 );
394 g_warning (
"Failed to parse character string: \"%s\"", arg );
403 if ( val != NULL && i > 0 && i < (
stored_argc - 1 ) ) {
414 for (
int j = 0; tokens[j]; j++ ) {
415 GMatchInfo *gmi = NULL;
416 if ( tokens[j]->invert ) {
419 g_regex_match ( tokens[j]->regex, input, G_REGEX_MATCH_PARTIAL, &gmi );
420 while ( g_match_info_matches ( gmi ) ) {
421 int count = g_match_info_get_match_count ( gmi );
422 for (
int index = (
count > 1 ) ? 1 : 0; index <
count; index++ ) {
424 g_match_info_fetch_pos ( gmi, index, &start, &end );
426 PangoAttribute *pa = pango_attr_weight_new ( PANGO_WEIGHT_BOLD );
427 pa->start_index = start;
429 pango_attr_list_insert ( retv, pa );
432 PangoAttribute *pa = pango_attr_underline_new ( PANGO_UNDERLINE_SINGLE );
433 pa->start_index = start;
435 pango_attr_list_insert ( retv, pa );
438 PangoAttribute *pa = pango_attr_strikethrough_new ( TRUE );
439 pa->start_index = start;
441 pango_attr_list_insert ( retv, pa );
444 PangoAttribute *pa = pango_attr_variant_new ( PANGO_VARIANT_SMALL_CAPS );
445 pa->start_index = start;
447 pango_attr_list_insert ( retv, pa );
450 PangoAttribute *pa = pango_attr_style_new ( PANGO_STYLE_ITALIC );
451 pa->start_index = start;
453 pango_attr_list_insert ( retv, pa );
456 PangoAttribute *pa = pango_attr_foreground_new (
460 pa->start_index = start;
462 pango_attr_list_insert ( retv, pa );
465 g_match_info_next ( gmi, NULL );
467 g_match_info_free ( gmi );
478 for (
int j = 0; match && tokens[j]; j++ ) {
479 match = g_regex_match ( tokens[j]->regex, input, 0, NULL );
480 match ^= tokens[j]->invert;
493 GError *error = NULL;
494 g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &fd, NULL, &error );
496 if ( error != NULL ) {
497 char *msg = g_strdup_printf (
"Failed to execute: '%s'\nError: '%s'", cmd, error->message );
501 g_error_free ( error );
514 int fd = g_open (
pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
516 g_warning (
"Failed to create pid file: '%s'.",
pidfile );
520 int flags = fcntl ( fd, F_GETFD, NULL );
522 if ( fcntl ( fd, F_SETFD,
flags, NULL ) < 0 ) {
523 g_warning (
"Failed to set CLOEXEC on pidfile." );
528 int retv = flock ( fd, LOCK_EX | LOCK_NB );
530 g_warning (
"Failed to set lock on pidfile: Rofi already running?" );
531 g_warning (
"Got error: %d %s", retv, g_strerror ( errno ) );
535 if ( ftruncate ( fd, (off_t) 0 ) == 0 ) {
538 int length = snprintf ( buffer, 64,
"%i", getpid () );
540 while ( l < length ) {
541 l += write ( fd, &buffer[l], length - l );
550 if ( close ( fd ) ) {
551 g_warning (
"Failed to close pidfile: '%s'", g_strerror ( errno ) );
558 const char *fam = pango_font_description_get_family ( pfd );
559 int size = pango_font_description_get_size ( pfd );
560 if ( fam == NULL || size == 0 ) {
561 g_debug (
"Pango failed to parse font: '%s'", font );
562 g_debug (
"Got family: <b>%s</b> at size: <b>%d</b>", fam ? fam :
"{unknown}", size );
576 int found_error = FALSE;
577 GString *msg = g_string_new (
578 "<big><b>The configuration failed to validate:</b></big>\n" );
594 g_string_append_printf ( msg,
"\t<b>config.matching</b>=%s is not a valid matching strategy.\nValid options are: glob, regex, fuzzy or normal.\n",
601 g_string_append_printf ( msg,
"\t<b>config.element_height</b>=%d is invalid. An element needs to be atleast 1 line high.\n",
607 g_string_append_printf ( msg,
"\t<b>config.menu_columns</b>=%d is invalid. You need at least one visible column.\n",
613 g_string_append_printf ( msg,
"<b>config.menu_width</b>=0 is invalid. You cannot have a window with no width." );
618 g_string_append_printf ( msg,
"\t<b>config.location</b>=%d is invalid. Value should be between %d and %d.\n",
629 if ( name && name[0] ==
'-' ) {
630 int index = name[1] -
'0';
631 if ( index < 5 && index > 0 ) {
635 g_string_append_printf ( msg,
"\t<b>config.monitor</b>=%s Could not find monitor.\n", name );
641 PangoFontDescription *pfd = pango_font_description_from_string (
config.
menu_font );
642 const char *fam = pango_font_description_get_family ( pfd );
643 int size = pango_font_description_get_size ( pfd );
644 if ( fam == NULL || size == 0 ) {
645 g_string_append_printf ( msg,
"Pango failed to parse font: '%s'\n",
config.
menu_font );
646 g_string_append_printf ( msg,
"Got font family: <b>%s</b> at size <b>%d</b>\n", fam ? fam :
"{unknown}", size );
650 pango_font_description_free ( pfd );
660 g_string_append ( msg,
"Please update your configuration." );
665 g_string_free ( msg, TRUE );
671 char **str = g_strsplit ( input, G_DIR_SEPARATOR_S, -1 );
672 for (
unsigned int i = 0; str && str[i]; i++ ) {
674 if ( str[i][0] ==
'~' && str[i][1] ==
'\0' ) {
676 str[i] = g_strdup ( g_get_home_dir () );
679 else if ( str[i][0] ==
'~' ) {
680 struct passwd *p = getpwnam ( &( str[i][1] ) );
683 str[i] = g_strdup ( p->pw_dir );
688 if ( input[0] == G_DIR_SEPARATOR ) {
689 str[i] = g_strdup_printf (
"%s%s", G_DIR_SEPARATOR_S, s );
694 char *retv = g_build_filenamev ( str );
700 #define MIN3( a, b, c ) ( ( a ) < ( b ) ? ( ( a ) < ( c ) ? ( a ) : ( c ) ) : ( ( b ) < ( c ) ? ( b ) : ( c ) ) ) 702 unsigned int levenshtein (
const char *needle,
const glong needlelen,
const char *haystack,
const glong haystacklen )
704 if ( needlelen == G_MAXLONG ) {
708 unsigned int column[needlelen + 1];
709 for ( glong y = 0; y < needlelen; y++ ) {
714 column[needlelen] = needlelen;
715 for ( glong x = 1; x <= haystacklen; x++ ) {
716 const char *needles = needle;
718 gunichar haystackc = g_utf8_get_char ( haystack );
720 haystackc = g_unichar_tolower ( haystackc );
722 for ( glong y = 1, lastdiag = x - 1; y <= needlelen; y++ ) {
723 gunichar needlec = g_utf8_get_char ( needles );
725 needlec = g_unichar_tolower ( needlec );
727 unsigned int olddiag = column[y];
728 column[y] =
MIN3 ( column[y] + 1, column[y - 1] + 1, lastdiag + ( needlec == haystackc ? 0 : 1 ) );
730 needles = g_utf8_next_char ( needles );
732 haystack = g_utf8_next_char ( haystack );
734 return column[needlelen];
740 return g_convert_with_fallback ( input, length,
"UTF-8",
"latin1",
"\uFFFD", NULL, &slength, NULL );
745 if ( text == NULL ) {
748 gchar *ret = g_markup_escape_text ( text, -1 );
755 if ( data == NULL ) {
761 if ( g_utf8_validate ( data, length, &end ) ) {
762 return g_memdup ( data, length + 1 );
764 string = g_string_sized_new ( length + 16 );
768 g_string_append_len (
string, data, end - data );
770 g_string_append (
string,
"\uFFFD" );
771 length -= ( end - data ) + 1;
773 }
while ( !g_utf8_validate ( data, length, &end ) );
776 g_string_append_len (
string, data, length );
779 return g_string_free (
string, FALSE );
787 #define FUZZY_SCORER_MAX_LENGTH 256 789 #define MIN_SCORE ( INT_MIN / 2 ) 791 #define LEADING_GAP_SCORE -4 795 #define WORD_START_SCORE 50 797 #define NON_WORD_SCORE 40 799 #define CAMEL_SCORE ( WORD_START_SCORE + GAP_SCORE - 1 ) 801 #define CONSECUTIVE_SCORE ( WORD_START_SCORE + GAP_SCORE ) 803 #define PATTERN_NON_START_MULTIPLIER 1 805 #define PATTERN_START_MULTIPLIER 2 829 if ( g_unichar_islower ( c ) ) {
832 if ( g_unichar_isupper ( c ) ) {
835 if ( g_unichar_isdigit ( c ) ) {
898 gboolean pfirst = TRUE;
900 gboolean pstart = TRUE;
902 int *score = g_malloc_n ( slen,
sizeof (
int ) );
904 int *dp = g_malloc_n ( slen,
sizeof (
int ) );
907 int uleft = 0, ulefts = 0, left, lefts;
908 const gchar *pit = pattern, *sit;
910 for ( si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char ( sit ) ) {
916 for ( pi = 0; pi < plen; pi++, pit = g_utf8_next_char ( pit ) ) {
917 gunichar pc = g_utf8_get_char ( pit ), sc;
918 if ( g_unichar_isspace ( pc ) ) {
923 for ( si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char ( sit ) ) {
926 sc = g_utf8_get_char ( sit );
929 : g_unichar_tolower ( pc ) == g_unichar_tolower ( sc ) ) {
941 pfirst = pstart = FALSE;
944 for ( si = 0; si < slen; si++ ) {
945 lefts = MAX ( lefts +
GAP_SCORE, dp[si] );
965 char *na = g_utf8_normalize ( a, -1, G_NORMALIZE_ALL_COMPOSE );
966 char *nb = g_utf8_normalize ( b, -1, G_NORMALIZE_ALL_COMPOSE );
967 *g_utf8_offset_to_pointer ( na, n ) =
'\0';
968 *g_utf8_offset_to_pointer ( nb, n ) =
'\0';
969 int r = g_utf8_collate ( na, nb );
977 gboolean retv = TRUE;
978 GError *error = NULL;
980 GSpawnChildSetupFunc child_setup = NULL;
981 gpointer user_data = NULL;
985 g_spawn_async ( wd, args, NULL, G_SPAWN_SEARCH_PATH, child_setup, user_data, NULL, &error );
986 if ( error != NULL ) {
987 char *msg = g_strdup_printf (
"Failed to execute: '%s%s'\nError: '%s'", error_precmd, error_cmd, error->message );
991 g_error_free ( error );
1005 if ( run_in_term ) {
1012 if ( context != NULL ) {
1013 if ( context->
name == NULL ) {
1014 context->
name = args[0];
1016 if ( context->
binary == NULL ) {
1017 context->
binary = args[0];
1020 gsize l = strlen (
"Launching '' via rofi" ) + strlen ( cmd ) + 1;
1021 gchar *description = g_newa ( gchar, l );
1023 g_snprintf ( description, l,
"Launching '%s' via rofi", cmd );
1026 if ( context->
command == NULL ) {
1037 g_debug (
"Opening theme, testing: %s\n", filename );
1038 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1041 g_free ( filename );
1043 if ( g_str_has_suffix ( file,
".rasi" ) ) {
1044 filename = g_strdup ( file );
1047 filename = g_strconcat ( file,
".rasi", NULL );
1050 const char *cpath = g_get_user_config_dir ();
1052 char *themep = g_build_filename ( cpath,
"rofi", filename, NULL );
1053 g_debug (
"Opening theme, testing: %s\n", themep );
1054 if ( g_file_test ( themep, G_FILE_TEST_EXISTS ) ) {
1055 g_free ( filename );
1060 const char * datadir = g_get_user_data_dir ();
1062 char *theme_path = g_build_filename ( datadir,
"rofi",
"themes", filename, NULL );
1063 g_debug (
"Opening theme, testing: %s\n", theme_path );
1065 if ( g_file_test ( theme_path, G_FILE_TEST_EXISTS ) ) {
1066 g_free ( filename );
1069 g_free ( theme_path );
1073 char *theme_path = g_build_filename ( THEME_DIR, filename, NULL );
1075 g_debug (
"Opening theme, testing: %s\n", theme_path );
1076 if ( g_file_test ( theme_path, G_FILE_TEST_EXISTS ) ) {
1077 g_free ( filename );
1080 g_free ( theme_path );
1087 GError *error = NULL;
1088 cairo_surface_t *surface = NULL;
1089 RsvgHandle * handle;
1091 handle = rsvg_handle_new_from_file ( file, &error );
1092 if ( G_LIKELY ( handle != NULL ) ) {
1093 RsvgDimensionData dimensions;
1095 rsvg_handle_set_dpi ( handle,
config.
dpi );
1097 rsvg_handle_get_dimensions ( handle, &dimensions );
1099 double scale = (double) height / dimensions.height;
1100 surface = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32,
1101 (
double) dimensions.width * scale,
1102 (double) dimensions.height * scale );
1103 gboolean failed = cairo_surface_status ( surface ) != CAIRO_STATUS_SUCCESS;
1104 if ( G_LIKELY ( failed == FALSE ) ) {
1105 cairo_t *cr = cairo_create ( surface );
1106 cairo_scale ( cr, scale, scale );
1107 failed = rsvg_handle_render_cairo ( handle, cr ) == FALSE;
1108 cairo_destroy ( cr );
1111 rsvg_handle_close ( handle, &error );
1112 g_object_unref ( handle );
1115 if ( G_UNLIKELY ( failed ) ) {
1116 g_warning (
"Failed to render file: '%s'", file );
1117 cairo_surface_destroy ( surface );
1121 if ( G_UNLIKELY ( error != NULL ) ) {
1122 g_warning (
"Failed to render SVG file: '%s': %s", file, error->message );
1123 g_error_free ( error );
1132 const char *
const sep =
"-";
1133 for (
char *token = strsep ( &input, sep ); token != NULL; token = strsep ( &input, sep ) ) {
1135 item->
start = item->
stop = (
unsigned int) strtoul ( token, NULL, 10 );
1139 if ( token[0] ==
'\0' ) {
1140 item->
stop = 0xFFFFFFFF;
1143 item->
stop = (
unsigned int) strtoul ( token, NULL, 10 );
1151 if ( input == NULL ) {
1154 const char *
const sep =
",";
1155 for (
char *token = strtok_r ( input, sep, &endp ); token != NULL; token = strtok_r ( NULL, sep, &endp ) ) {
1157 *list = g_realloc ( ( *list ), ( ( *length ) + 1 ) *
sizeof (
struct rofi_range_pair ) );
1159 parse_pair ( token, &( ( *list )[*length] ) );
1184 for (
int i = 0; format && format[i]; i++ ) {
1185 if ( format[i] ==
'i' ) {
1186 fprintf ( stdout,
"%d", selected_line );
1188 else if ( format[i] ==
'd' ) {
1189 fprintf ( stdout,
"%d", ( selected_line + 1 ) );
1191 else if ( format[i] ==
's' ) {
1192 fputs (
string, stdout );
1194 else if ( format[i] ==
'q' ) {
1195 char *quote = g_shell_quote (
string );
1196 fputs ( quote, stdout );
1199 else if ( format[i] ==
'f' ) {
1201 fputs ( filter, stdout );
1204 else if ( format[i] ==
'F' ) {
1206 char *quote = g_shell_quote ( filter );
1207 fputs ( quote, stdout );
1212 fputc ( format[i], stdout );
1215 fputc (
'\n', stdout );
unsigned int levenshtein(const char *needle, const glong needlelen, const char *haystack, const glong haystacklen)
#define PATTERN_START_MULTIPLIER
static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr)
static enum CharClass rofi_scorer_get_character_class(gunichar c)
static gchar * glob_to_regex(const char *input)
int find_arg_char(const char *const key, char *val)
unsigned int case_sensitive
PangoAttrList * helper_token_match_get_pango_attr(RofiHighlightColorStyle th, rofi_int_matcher **tokens, const char *input, PangoAttrList *retv)
int utf8_strncmp(const char *a, const char *b, size_t n)
unsigned int menu_columns
cairo_surface_t * cairo_image_surface_create_from_svg(const gchar *file, int height)
void display_startup_notification(RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data)
int config_sanity_check(void)
const char *const monitor_position_entries[]
int find_arg_uint(const char *const key, unsigned int *val)
char * helper_get_theme_path(const char *file)
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
int find_arg_str(const char *const key, char **val)
int find_arg_int(const char *const key, int *val)
const char ** find_arg_strv(const char *const key)
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
rofi_int_matcher ** helper_tokenize(const char *input, int case_sensitive)
static GRegex * R(const char *s, int case_sensitive)
void remove_pid_file(int fd)
#define FUZZY_SCORER_MAX_LENGTH
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
static gchar * fuzzy_to_regex(const char *input)
char * rofi_latin_to_utf8_strdup(const char *input, gssize length)
static rofi_int_matcher * create_regex(const char *input, int case_sensitive)
int rofi_view_error_dialog(const char *msg, int markup)
void rofi_add_error_message(GString *str)
void cmd_set_arguments(int argc, char **argv)
MatchingMethod matching_method
char * rofi_expand_path(const char *input)
static char ** stored_argv
const gchar * description
static gboolean helper_eval_cb(const GMatchInfo *info, GString *res, gpointer data)
int create_pid_file(const char *pidfile)
int execute_generator(const char *cmd)
#define CONSECUTIVE_SCORE
int helper_parse_setup(char *string, char ***output, int *length,...)
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length)
int monitor_active(workarea *mon)
static void parse_pair(char *input, rofi_range_pair *item)
char * rofi_force_utf8(const gchar *data, ssize_t length)
gchar * rofi_escape_markup(gchar *text)
void helper_tokenize_free(rofi_int_matcher **tokens)
void rofi_output_formatted_line(const char *format, const char *string, int selected_line, const char *filter)
int find_arg(const char *const key)
char helper_parse_char(const char *arg)
#define PATTERN_NON_START_MULTIPLIER
#define LEADING_GAP_SCORE
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str, glong slen)
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)