rofi  1.5.1
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-2017 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 
29 #define G_LOG_DOMAIN "Helper"
30 
31 #include <config.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <limits.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <glib.h>
40 #include <glib/gstdio.h>
41 #include <sys/types.h>
42 #include <sys/file.h>
43 #include <sys/stat.h>
44 #include <pwd.h>
45 #include <ctype.h>
46 #include <pango/pango.h>
47 #include <pango/pango-fontmap.h>
48 #include <pango/pangocairo.h>
49 #include <librsvg/rsvg.h>
50 #include "display.h"
51 #include "xcb.h"
52 #include "helper.h"
53 #include "helper-theme.h"
54 #include "settings.h"
55 #include "rofi.h"
56 #include "view.h"
57 
61 const char *const monitor_position_entries[] = {
62  "on focused monitor",
63  "on focused window",
64  "at mouse pointer",
65  "on monitor with focused window",
66  "on monitor that has mouse pointer"
67 };
69 static int stored_argc = 0;
71 static char **stored_argv = NULL;
72 
73 void cmd_set_arguments ( int argc, char **argv )
74 {
75  stored_argc = argc;
76  stored_argv = argv;
77 }
78 
88 static gboolean helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data )
89 {
90  gchar *match;
91  // Get the match
92  match = g_match_info_fetch ( info, 0 );
93  if ( match != NULL ) {
94  // Lookup the match, so we can replace it.
95  gchar *r = g_hash_table_lookup ( (GHashTable *) data, match );
96  if ( r != NULL ) {
97  // Append the replacement to the string.
98  g_string_append ( res, r );
99  }
100  // Free match.
101  g_free ( match );
102  }
103  // Continue replacement.
104  return FALSE;
105 }
106 
107 int helper_parse_setup ( char * string, char ***output, int *length, ... )
108 {
109  GError *error = NULL;
110  GHashTable *h;
111  h = g_hash_table_new ( g_str_hash, g_str_equal );
112  // By default, we insert terminal and ssh-client
113  g_hash_table_insert ( h, "{terminal}", config.terminal_emulator );
114  g_hash_table_insert ( h, "{ssh-client}", config.ssh_client );
115  // Add list from variable arguments.
116  va_list ap;
117  va_start ( ap, length );
118  while ( 1 ) {
119  char * key = va_arg ( ap, char * );
120  if ( key == (char *) 0 ) {
121  break;
122  }
123  char *value = va_arg ( ap, char * );
124  if ( value == (char *) 0 ) {
125  break;
126  }
127  g_hash_table_insert ( h, key, value );
128  }
129  va_end ( ap );
130 
131  // Replace hits within {-\w+}.
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 );
134  // Free regex.
135  g_regex_unref ( reg );
136  // Destroy key-value storage.
137  g_hash_table_destroy ( h );
138  // Parse the string into shell arguments.
139  if ( g_shell_parse_argv ( res, length, output, &error ) ) {
140  g_free ( res );
141  return TRUE;
142  }
143  g_free ( res );
144  // Throw error if shell parsing fails.
145  if ( error ) {
146  char *msg = g_strdup_printf ( "Failed to parse: '%s'\nError: '%s'", string, error->message );
147  rofi_view_error_dialog ( msg, FALSE );
148  g_free ( msg );
149  // print error.
150  g_error_free ( error );
151  }
152  return FALSE;
153 }
154 
156 {
157  for ( size_t i = 0; tokens && tokens[i]; i++ ) {
158  g_regex_unref ( (GRegex *) tokens[i]->regex );
159  g_free ( tokens[i] );
160  }
161  g_free ( tokens );
162 }
163 
164 static gchar *glob_to_regex ( const char *input )
165 {
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] == '*' ) {
171  r[i] = '.';
172  }
173  else if ( r[i + 1] == '?' ) {
174  r[i + 1] = 'S';
175  }
176  i++;
177  }
178  }
179  return r;
180 }
181 static gchar *fuzzy_to_regex ( const char * input )
182 {
183  GString *str = g_string_new ( "" );
184  gchar *r = g_regex_escape_string ( input, -1 );
185  gchar *iter;
186  int first = 1;
187  for ( iter = r; iter && *iter != '\0'; iter = g_utf8_next_char ( iter ) ) {
188  if ( first ) {
189  g_string_append ( str, "(" );
190  }
191  else {
192  g_string_append ( str, ".*(" );
193  }
194  if ( *iter == '\\' ) {
195  g_string_append_c ( str, '\\' );
196  iter = g_utf8_next_char ( iter );
197  // If EOL, break out of for loop.
198  if ( ( *iter ) == '\0' ) {
199  break;
200  }
201  }
202  g_string_append_unichar ( str, g_utf8_get_char ( iter ) );
203  g_string_append ( str, ")" );
204  first = 0;
205  }
206  g_free ( r );
207  char *retv = str->str;
208  g_string_free ( str, FALSE );
209  return retv;
210 }
211 
212 // Macro for quickly generating regex for matching.
213 static inline GRegex * R ( const char *s, int case_sensitive )
214 {
215  return g_regex_new ( s, G_REGEX_OPTIMIZE | ( ( case_sensitive ) ? 0 : G_REGEX_CASELESS ), 0, NULL );
216 }
217 
218 static rofi_int_matcher * create_regex ( const char *input, int case_sensitive )
219 {
220  GRegex * retv = NULL;
221  gchar *r;
222  rofi_int_matcher *rv = g_malloc0 ( sizeof ( rofi_int_matcher ) );
223  if ( input && input[0] == '-' ) {
224  rv->invert = 1;
225  input++;
226  }
227  switch ( config.matching_method )
228  {
229  case MM_GLOB:
230  r = glob_to_regex ( input );
231  retv = R ( r, case_sensitive );
232  g_free ( r );
233  break;
234  case MM_REGEX:
235  retv = R ( input, case_sensitive );
236  if ( retv == NULL ) {
237  r = g_regex_escape_string ( input, -1 );
238  retv = R ( r, case_sensitive );
239  g_free ( r );
240  }
241  break;
242  case MM_FUZZY:
243  r = fuzzy_to_regex ( input );
244  retv = R ( r, case_sensitive );
245  g_free ( r );
246  break;
247  default:
248  r = g_regex_escape_string ( input, -1 );
249  retv = R ( r, case_sensitive );
250  g_free ( r );
251  break;
252  }
253  rv->regex = retv;
254  return rv;
255 }
256 rofi_int_matcher **helper_tokenize ( const char *input, int case_sensitive )
257 {
258  if ( input == NULL ) {
259  return NULL;
260  }
261  size_t len = strlen ( input );
262  if ( len == 0 ) {
263  return NULL;
264  }
265 
266  char *saveptr = NULL, *token;
267  rofi_int_matcher **retv = NULL;
268  if ( !config.tokenize ) {
269  retv = g_malloc0 ( sizeof ( rofi_int_matcher* ) * 2 );
270  retv[0] = create_regex ( input, case_sensitive );
271  return retv;
272  }
273 
274  // First entry is always full (modified) stringtext.
275  int num_tokens = 0;
276 
277  // Copy the string, 'strtok_r' modifies it.
278  char *str = g_strdup ( input );
279 
280  // Iterate over tokens.
281  // strtok should still be valid for utf8.
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;
287  num_tokens++;
288  }
289  // Free str.
290  g_free ( str );
291  return retv;
292 }
293 
294 // cli arg handling
295 int find_arg ( const char * const key )
296 {
297  int i;
298 
299  for ( i = 0; i < stored_argc && strcasecmp ( stored_argv[i], key ); i++ ) {
300  ;
301  }
302 
303  return i < stored_argc ? i : -1;
304 }
305 int find_arg_str ( const char * const key, char** val )
306 {
307  int i = find_arg ( key );
308 
309  if ( val != NULL && i > 0 && i < stored_argc - 1 ) {
310  *val = stored_argv[i + 1];
311  return TRUE;
312  }
313  return FALSE;
314 }
315 
316 const char ** find_arg_strv ( const char *const key )
317 {
318  const char **retv = NULL;
319  int length = 0;
320  for ( int i = 0; i < stored_argc; i++ ) {
321  if ( i < ( stored_argc - 1 ) && strcasecmp ( stored_argv[i], key ) == 0 ) {
322  length++;
323  }
324  }
325  if ( length > 0 ) {
326  retv = g_malloc0 ( ( length + 1 ) * sizeof ( char* ) );
327  int index = 0;
328  for ( int i = 0; i < stored_argc; i++ ) {
329  if ( i < ( stored_argc - 1 ) && strcasecmp ( stored_argv[i], key ) == 0 ) {
330  retv[index++] = stored_argv[i + 1];
331  }
332  }
333  }
334  return retv;
335 }
336 
337 int find_arg_int ( const char * const key, int *val )
338 {
339  int i = find_arg ( key );
340 
341  if ( val != NULL && i > 0 && i < ( stored_argc - 1 ) ) {
342  *val = strtol ( stored_argv[i + 1], NULL, 10 );
343  return TRUE;
344  }
345  return FALSE;
346 }
347 int find_arg_uint ( const char * const key, unsigned int *val )
348 {
349  int i = find_arg ( key );
350 
351  if ( val != NULL && i > 0 && i < ( stored_argc - 1 ) ) {
352  *val = strtoul ( stored_argv[i + 1], NULL, 10 );
353  return TRUE;
354  }
355  return FALSE;
356 }
357 
358 char helper_parse_char ( const char *arg )
359 {
360  const size_t len = strlen ( arg );
361  // If the length is 1, it is not escaped.
362  if ( len == 1 ) {
363  return arg[0];
364  }
365  // If the length is 2 and the first character is '\', we unescape it.
366  if ( len == 2 && arg[0] == '\\' ) {
367  switch ( arg[1] )
368  {
369  // New line
370  case 'n': return '\n';
371  // Bell
372  case 'a': return '\a';
373  // Backspace
374  case 'b': return '\b';
375  // Tab
376  case 't': return '\t';
377  // Vertical tab
378  case 'v': return '\v';
379  // Form feed
380  case 'f': return '\f';
381  // Carriage return
382  case 'r': return '\r';
383  // Forward slash
384  case '\\': return '\\';
385  // 0 line.
386  case '0': return '\0';
387  default:
388  break;
389  }
390  }
391  if ( len > 2 && arg[0] == '\\' && arg[1] == 'x' ) {
392  return (char) strtol ( &arg[2], NULL, 16 );
393  }
394  g_warning ( "Failed to parse character string: \"%s\"", arg );
395  // for now default to newline.
396  return '\n';
397 }
398 
399 int find_arg_char ( const char * const key, char *val )
400 {
401  int i = find_arg ( key );
402 
403  if ( val != NULL && i > 0 && i < ( stored_argc - 1 ) ) {
404  *val = helper_parse_char ( stored_argv[i + 1] );
405  return TRUE;
406  }
407  return FALSE;
408 }
409 
410 PangoAttrList *helper_token_match_get_pango_attr ( RofiHighlightColorStyle th, rofi_int_matcher**tokens, const char *input, PangoAttrList *retv )
411 {
412  // Do a tokenized match.
413  if ( tokens ) {
414  for ( int j = 0; tokens[j]; j++ ) {
415  GMatchInfo *gmi = NULL;
416  if ( tokens[j]->invert ) {
417  continue;
418  }
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++ ) {
423  int start, end;
424  g_match_info_fetch_pos ( gmi, index, &start, &end );
425  if ( th.style & ROFI_HL_BOLD ) {
426  PangoAttribute *pa = pango_attr_weight_new ( PANGO_WEIGHT_BOLD );
427  pa->start_index = start;
428  pa->end_index = end;
429  pango_attr_list_insert ( retv, pa );
430  }
431  if ( th.style & ROFI_HL_UNDERLINE ) {
432  PangoAttribute *pa = pango_attr_underline_new ( PANGO_UNDERLINE_SINGLE );
433  pa->start_index = start;
434  pa->end_index = end;
435  pango_attr_list_insert ( retv, pa );
436  }
437  if ( th.style & ROFI_HL_STRIKETHROUGH ) {
438  PangoAttribute *pa = pango_attr_strikethrough_new ( TRUE );
439  pa->start_index = start;
440  pa->end_index = end;
441  pango_attr_list_insert ( retv, pa );
442  }
443  if ( th.style & ROFI_HL_SMALL_CAPS ) {
444  PangoAttribute *pa = pango_attr_variant_new ( PANGO_VARIANT_SMALL_CAPS );
445  pa->start_index = start;
446  pa->end_index = end;
447  pango_attr_list_insert ( retv, pa );
448  }
449  if ( th.style & ROFI_HL_ITALIC ) {
450  PangoAttribute *pa = pango_attr_style_new ( PANGO_STYLE_ITALIC );
451  pa->start_index = start;
452  pa->end_index = end;
453  pango_attr_list_insert ( retv, pa );
454  }
455  if ( th.style & ROFI_HL_COLOR ) {
456  PangoAttribute *pa = pango_attr_foreground_new (
457  th.color.red * 65535,
458  th.color.green * 65535,
459  th.color.blue * 65535 );
460  pa->start_index = start;
461  pa->end_index = end;
462  pango_attr_list_insert ( retv, pa );
463  }
464  }
465  g_match_info_next ( gmi, NULL );
466  }
467  g_match_info_free ( gmi );
468  }
469  }
470  return retv;
471 }
472 
473 int helper_token_match ( rofi_int_matcher* const *tokens, const char *input )
474 {
475  int match = TRUE;
476  // Do a tokenized match.
477  if ( tokens ) {
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;
481  }
482  }
483  return match;
484 }
485 
486 int execute_generator ( const char * cmd )
487 {
488  char **args = NULL;
489  int argv = 0;
490  helper_parse_setup ( config.run_command, &args, &argv, "{cmd}", cmd, (char *) 0 );
491 
492  int fd = -1;
493  GError *error = NULL;
494  g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &fd, NULL, &error );
495 
496  if ( error != NULL ) {
497  char *msg = g_strdup_printf ( "Failed to execute: '%s'\nError: '%s'", cmd, error->message );
498  rofi_view_error_dialog ( msg, FALSE );
499  g_free ( msg );
500  // print error.
501  g_error_free ( error );
502  fd = -1;
503  }
504  g_strfreev ( args );
505  return fd;
506 }
507 
508 int create_pid_file ( const char *pidfile )
509 {
510  if ( pidfile == NULL ) {
511  return -1;
512  }
513 
514  int fd = g_open ( pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
515  if ( fd < 0 ) {
516  g_warning ( "Failed to create pid file: '%s'.", pidfile );
517  return -1;
518  }
519  // Set it to close the File Descriptor on exit.
520  int flags = fcntl ( fd, F_GETFD, NULL );
521  flags = flags | FD_CLOEXEC;
522  if ( fcntl ( fd, F_SETFD, flags, NULL ) < 0 ) {
523  g_warning ( "Failed to set CLOEXEC on pidfile." );
524  remove_pid_file ( fd );
525  return -1;
526  }
527  // Try to get exclusive write lock on FD
528  int retv = flock ( fd, LOCK_EX | LOCK_NB );
529  if ( retv != 0 ) {
530  g_warning ( "Failed to set lock on pidfile: Rofi already running?" );
531  g_warning ( "Got error: %d %s", retv, g_strerror ( errno ) );
532  remove_pid_file ( fd );
533  return -1;
534  }
535  if ( ftruncate ( fd, (off_t) 0 ) == 0 ) {
536  // Write pid, not needed, but for completeness sake.
537  char buffer[64];
538  int length = snprintf ( buffer, 64, "%i", getpid () );
539  ssize_t l = 0;
540  while ( l < length ) {
541  l += write ( fd, &buffer[l], length - l );
542  }
543  }
544  return fd;
545 }
546 
547 void remove_pid_file ( int fd )
548 {
549  if ( fd >= 0 ) {
550  if ( close ( fd ) ) {
551  g_warning ( "Failed to close pidfile: '%s'", g_strerror ( errno ) );
552  }
553  }
554 }
555 
556 gboolean helper_validate_font ( PangoFontDescription *pfd, const char *font )
557 {
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 );
563  return FALSE;
564  }
565  return TRUE;
566 }
567 
575 {
576  int found_error = FALSE;
577  GString *msg = g_string_new (
578  "<big><b>The configuration failed to validate:</b></big>\n" );
579 
580  if ( config.matching ) {
581  if ( g_strcmp0 ( config.matching, "regex" ) == 0 ) {
583  }
584  else if ( g_strcmp0 ( config.matching, "glob" ) == 0 ) {
586  }
587  else if ( g_strcmp0 ( config.matching, "fuzzy" ) == 0 ) {
589  }
590  else if ( g_strcmp0 ( config.matching, "normal" ) == 0 ) {
592  }
593  else {
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",
595  config.matching );
596  found_error = 1;
597  }
598  }
599 
600  if ( config.element_height < 1 ) {
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",
604  found_error = TRUE;
605  }
606  if ( config.menu_columns == 0 ) {
607  g_string_append_printf ( msg, "\t<b>config.menu_columns</b>=%d is invalid. You need at least one visible column.\n",
609  config.menu_columns = 1;
610  found_error = TRUE;
611  }
612  if ( config.menu_width == 0 ) {
613  g_string_append_printf ( msg, "<b>config.menu_width</b>=0 is invalid. You cannot have a window with no width." );
614  config.menu_columns = 50;
615  found_error = TRUE;
616  }
617  if ( !( config.location >= 0 && config.location <= 8 ) ) {
618  g_string_append_printf ( msg, "\t<b>config.location</b>=%d is invalid. Value should be between %d and %d.\n",
619  config.location, 0, 8 );
621  found_error = 1;
622  }
623 
624  // Check size
625  {
626  workarea mon;
627  if ( !monitor_active ( &mon ) ) {
628  const char *name = config.monitor;
629  if ( name && name[0] == '-' ) {
630  int index = name[1] - '0';
631  if ( index < 5 && index > 0 ) {
632  name = monitor_position_entries[index - 1];
633  }
634  }
635  g_string_append_printf ( msg, "\t<b>config.monitor</b>=%s Could not find monitor.\n", name );
636  found_error = TRUE;
637  }
638  }
639 
640  if ( config.menu_font ) {
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 );
647  config.menu_font = NULL;
648  found_error = TRUE;
649  }
650  pango_font_description_free ( pfd );
651  }
652 
653  if ( g_strcmp0 ( config.monitor, "-3" ) == 0 ) {
654  // On -3, set to location 1.
655  config.location = 1;
656  config.fullscreen = 0;
657  }
658 
659  if ( found_error ) {
660  g_string_append ( msg, "Please update your configuration." );
661  rofi_add_error_message ( msg );
662  return TRUE;
663  }
664 
665  g_string_free ( msg, TRUE );
666  return FALSE;
667 }
668 
669 char *rofi_expand_path ( const char *input )
670 {
671  char **str = g_strsplit ( input, G_DIR_SEPARATOR_S, -1 );
672  for ( unsigned int i = 0; str && str[i]; i++ ) {
673  // Replace ~ with current user homedir.
674  if ( str[i][0] == '~' && str[i][1] == '\0' ) {
675  g_free ( str[i] );
676  str[i] = g_strdup ( g_get_home_dir () );
677  }
678  // If other user, ask getpwnam.
679  else if ( str[i][0] == '~' ) {
680  struct passwd *p = getpwnam ( &( str[i][1] ) );
681  if ( p != NULL ) {
682  g_free ( str[i] );
683  str[i] = g_strdup ( p->pw_dir );
684  }
685  }
686  else if ( i == 0 ) {
687  char * s = str[i];
688  if ( input[0] == G_DIR_SEPARATOR ) {
689  str[i] = g_strdup_printf ( "%s%s", G_DIR_SEPARATOR_S, s );
690  g_free ( s );
691  }
692  }
693  }
694  char *retv = g_build_filenamev ( str );
695  g_strfreev ( str );
696  return retv;
697 }
698 
700 #define MIN3( a, b, c ) ( ( a ) < ( b ) ? ( ( a ) < ( c ) ? ( a ) : ( c ) ) : ( ( b ) < ( c ) ? ( b ) : ( c ) ) )
701 
702 unsigned int levenshtein ( const char *needle, const glong needlelen, const char *haystack, const glong haystacklen )
703 {
704  if ( needlelen == G_MAXLONG ) {
705  // String to long, we cannot handle this.
706  return UINT_MAX;
707  }
708  unsigned int column[needlelen + 1];
709  for ( glong y = 0; y < needlelen; y++ ) {
710  column[y] = y;
711  }
712  // Removed out of the loop, otherwise static code analyzers think it is unset.. silly but true.
713  // old loop: for ( glong y = 0; y <= needlelen; y++)
714  column[needlelen] = needlelen;
715  for ( glong x = 1; x <= haystacklen; x++ ) {
716  const char *needles = needle;
717  column[0] = x;
718  gunichar haystackc = g_utf8_get_char ( haystack );
719  if ( !config.case_sensitive ) {
720  haystackc = g_unichar_tolower ( haystackc );
721  }
722  for ( glong y = 1, lastdiag = x - 1; y <= needlelen; y++ ) {
723  gunichar needlec = g_utf8_get_char ( needles );
724  if ( !config.case_sensitive ) {
725  needlec = g_unichar_tolower ( needlec );
726  }
727  unsigned int olddiag = column[y];
728  column[y] = MIN3 ( column[y] + 1, column[y - 1] + 1, lastdiag + ( needlec == haystackc ? 0 : 1 ) );
729  lastdiag = olddiag;
730  needles = g_utf8_next_char ( needles );
731  }
732  haystack = g_utf8_next_char ( haystack );
733  }
734  return column[needlelen];
735 }
736 
737 char * rofi_latin_to_utf8_strdup ( const char *input, gssize length )
738 {
739  gsize slength = 0;
740  return g_convert_with_fallback ( input, length, "UTF-8", "latin1", "\uFFFD", NULL, &slength, NULL );
741 }
742 
743 gchar *rofi_escape_markup ( gchar *text )
744 {
745  if ( text == NULL ) {
746  return NULL;
747  }
748  gchar *ret = g_markup_escape_text ( text, -1 );
749  g_free ( text );
750  return ret;
751 }
752 
753 char * rofi_force_utf8 ( const gchar *data, ssize_t length )
754 {
755  if ( data == NULL ) {
756  return NULL;
757  }
758  const char *end;
759  GString *string;
760 
761  if ( g_utf8_validate ( data, length, &end ) ) {
762  return g_memdup ( data, length + 1 );
763  }
764  string = g_string_sized_new ( length + 16 );
765 
766  do {
767  /* Valid part of the string */
768  g_string_append_len ( string, data, end - data );
769  /* Replacement character */
770  g_string_append ( string, "\uFFFD" );
771  length -= ( end - data ) + 1;
772  data = end + 1;
773  } while ( !g_utf8_validate ( data, length, &end ) );
774 
775  if ( length ) {
776  g_string_append_len ( string, data, length );
777  }
778 
779  return g_string_free ( string, FALSE );
780 }
781 
782 /****
783  * FZF like scorer
784  */
785 
787 #define FUZZY_SCORER_MAX_LENGTH 256
788 
789 #define MIN_SCORE ( INT_MIN / 2 )
790 
791 #define LEADING_GAP_SCORE -4
792 
793 #define GAP_SCORE -5
794 
795 #define WORD_START_SCORE 50
796 
797 #define NON_WORD_SCORE 40
798 
799 #define CAMEL_SCORE ( WORD_START_SCORE + GAP_SCORE - 1 )
800 
801 #define CONSECUTIVE_SCORE ( WORD_START_SCORE + GAP_SCORE )
802 
803 #define PATTERN_NON_START_MULTIPLIER 1
804 
805 #define PATTERN_START_MULTIPLIER 2
806 
811 {
812  /* Lower case */
814  /* Upper case */
816  /* Number */
818  /* non word character */
820 };
821 
827 static enum CharClass rofi_scorer_get_character_class ( gunichar c )
828 {
829  if ( g_unichar_islower ( c ) ) {
830  return LOWER;
831  }
832  if ( g_unichar_isupper ( c ) ) {
833  return UPPER;
834  }
835  if ( g_unichar_isdigit ( c ) ) {
836  return DIGIT;
837  }
838  return NON_WORD;
839 }
840 
849 static int rofi_scorer_get_score_for ( enum CharClass prev, enum CharClass curr )
850 {
851  if ( prev == NON_WORD && curr != NON_WORD ) {
852  return WORD_START_SCORE;
853  }
854  if ( ( prev == LOWER && curr == UPPER ) ||
855  ( prev != DIGIT && curr == DIGIT ) ) {
856  return CAMEL_SCORE;
857  }
858  if ( curr == NON_WORD ) {
859  return NON_WORD_SCORE;
860  }
861  return 0;
862 }
863 
891 int rofi_scorer_fuzzy_evaluate ( const char *pattern, glong plen, const char *str, glong slen )
892 {
893  if ( slen > FUZZY_SCORER_MAX_LENGTH ) {
894  return -MIN_SCORE;
895  }
896  glong pi, si;
897  // whether we are aligning the first character of pattern
898  gboolean pfirst = TRUE;
899  // whether the start of a word in pattern
900  gboolean pstart = TRUE;
901  // score for each position
902  int *score = g_malloc_n ( slen, sizeof ( int ) );
903  // dp[i]: maximum value by aligning pattern[0..pi] to str[0..si]
904  int *dp = g_malloc_n ( slen, sizeof ( int ) );
905  // uleft: value of the upper left cell; ulefts: maximum value of uleft and cells on the left. The arbitrary initial
906  // values suppress warnings.
907  int uleft = 0, ulefts = 0, left, lefts;
908  const gchar *pit = pattern, *sit;
909  enum CharClass prev = NON_WORD;
910  for ( si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char ( sit ) ) {
911  enum CharClass cur = rofi_scorer_get_character_class ( g_utf8_get_char ( sit ) );
912  score[si] = rofi_scorer_get_score_for ( prev, cur );
913  prev = cur;
914  dp[si] = MIN_SCORE;
915  }
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 ) ) {
919  pstart = TRUE;
920  continue;
921  }
922  lefts = MIN_SCORE;
923  for ( si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char ( sit ) ) {
924  left = dp[si];
925  lefts = MAX ( lefts + GAP_SCORE, left );
926  sc = g_utf8_get_char ( sit );
928  ? pc == sc
929  : g_unichar_tolower ( pc ) == g_unichar_tolower ( sc ) ) {
930  int t = score[si] * ( pstart ? PATTERN_START_MULTIPLIER : PATTERN_NON_START_MULTIPLIER );
931  dp[si] = pfirst
932  ? LEADING_GAP_SCORE * si + t
933  : MAX ( uleft + CONSECUTIVE_SCORE, ulefts + t );
934  }
935  else {
936  dp[si] = MIN_SCORE;
937  }
938  uleft = left;
939  ulefts = lefts;
940  }
941  pfirst = pstart = FALSE;
942  }
943  lefts = MIN_SCORE;
944  for ( si = 0; si < slen; si++ ) {
945  lefts = MAX ( lefts + GAP_SCORE, dp[si] );
946  }
947  g_free ( score );
948  g_free ( dp );
949  return -lefts;
950 }
951 
963 int utf8_strncmp ( const char* a, const char* b, size_t n )
964 {
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 );
970  g_free ( na );
971  g_free ( nb );
972  return r;
973 }
974 
975 gboolean helper_execute ( const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context )
976 {
977  gboolean retv = TRUE;
978  GError *error = NULL;
979 
980  GSpawnChildSetupFunc child_setup = NULL;
981  gpointer user_data = NULL;
982 
983  display_startup_notification ( context, &child_setup, &user_data );
984 
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 );
988  rofi_view_error_dialog ( msg, FALSE );
989  g_free ( msg );
990  // print error.
991  g_error_free ( error );
992  retv = FALSE;
993  }
994 
995  // Free the args list.
996  g_strfreev ( args );
997  return retv;
998 }
999 
1000 gboolean helper_execute_command ( const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context )
1001 {
1002  char **args = NULL;
1003  int argc = 0;
1004 
1005  if ( run_in_term ) {
1006  helper_parse_setup ( config.run_shell_command, &args, &argc, "{cmd}", cmd, (char *) 0 );
1007  }
1008  else {
1009  helper_parse_setup ( config.run_command, &args, &argc, "{cmd}", cmd, (char *) 0 );
1010  }
1011 
1012  if ( context != NULL ) {
1013  if ( context->name == NULL ) {
1014  context->name = args[0];
1015  }
1016  if ( context->binary == NULL ) {
1017  context->binary = args[0];
1018  }
1019  if ( context->description == NULL ) {
1020  gsize l = strlen ( "Launching '' via rofi" ) + strlen ( cmd ) + 1;
1021  gchar *description = g_newa ( gchar, l );
1022 
1023  g_snprintf ( description, l, "Launching '%s' via rofi", cmd );
1024  context->description = description;
1025  }
1026  if ( context->command == NULL ) {
1027  context->command = cmd;
1028  }
1029  }
1030 
1031  return helper_execute ( wd, args, "", cmd, context );
1032 }
1033 
1034 char *helper_get_theme_path ( const char *file )
1035 {
1036  char *filename = rofi_expand_path ( file );
1037  g_debug ( "Opening theme, testing: %s\n", filename );
1038  if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1039  return filename;
1040  }
1041  g_free ( filename );
1042 
1043  if ( g_str_has_suffix ( file, ".rasi" ) ) {
1044  filename = g_strdup ( file );
1045  }
1046  else {
1047  filename = g_strconcat ( file, ".rasi", NULL );
1048  }
1049  // Check config directory.
1050  const char *cpath = g_get_user_config_dir ();
1051  if ( cpath ) {
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 );
1056  return themep;
1057  }
1058  g_free ( themep );
1059  }
1060  const char * datadir = g_get_user_data_dir ();
1061  if ( datadir ) {
1062  char *theme_path = g_build_filename ( datadir, "rofi", "themes", filename, NULL );
1063  g_debug ( "Opening theme, testing: %s\n", theme_path );
1064  if ( theme_path ) {
1065  if ( g_file_test ( theme_path, G_FILE_TEST_EXISTS ) ) {
1066  g_free ( filename );
1067  return theme_path;
1068  }
1069  g_free ( theme_path );
1070  }
1071  }
1072 
1073  char *theme_path = g_build_filename ( THEME_DIR, filename, NULL );
1074  if ( theme_path ) {
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 );
1078  return theme_path;
1079  }
1080  g_free ( theme_path );
1081  }
1082  return filename;
1083 }
1084 
1085 cairo_surface_t* cairo_image_surface_create_from_svg ( const gchar* file, int height )
1086 {
1087  GError *error = NULL;
1088  cairo_surface_t *surface = NULL;
1089  RsvgHandle * handle;
1090 
1091  handle = rsvg_handle_new_from_file ( file, &error );
1092  if ( G_LIKELY ( handle != NULL ) ) {
1093  RsvgDimensionData dimensions;
1094  // Update DPI.
1095  rsvg_handle_set_dpi ( handle, config.dpi );
1096  // Get size.
1097  rsvg_handle_get_dimensions ( handle, &dimensions );
1098  // Create cairo surface in the right size.
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 );
1109  }
1110 
1111  rsvg_handle_close ( handle, &error );
1112  g_object_unref ( handle );
1113 
1115  if ( G_UNLIKELY ( failed ) ) {
1116  g_warning ( "Failed to render file: '%s'", file );
1117  cairo_surface_destroy ( surface );
1118  surface = NULL;
1119  }
1120  }
1121  if ( G_UNLIKELY ( error != NULL ) ) {
1122  g_warning ( "Failed to render SVG file: '%s': %s", file, error->message );
1123  g_error_free ( error );
1124  }
1125 
1126  return surface;
1127 }
1128 
1129 static void parse_pair ( char *input, rofi_range_pair *item )
1130 {
1131  int index = 0;
1132  const char * const sep = "-";
1133  for ( char *token = strsep ( &input, sep ); token != NULL; token = strsep ( &input, sep ) ) {
1134  if ( index == 0 ) {
1135  item->start = item->stop = (unsigned int) strtoul ( token, NULL, 10 );
1136  index++;
1137  }
1138  else {
1139  if ( token[0] == '\0' ) {
1140  item->stop = 0xFFFFFFFF;
1141  }
1142  else{
1143  item->stop = (unsigned int) strtoul ( token, NULL, 10 );
1144  }
1145  }
1146  }
1147 }
1148 void parse_ranges ( char *input, rofi_range_pair **list, unsigned int *length )
1149 {
1150  char *endp;
1151  if ( input == NULL ) {
1152  return;
1153  }
1154  const char *const sep = ",";
1155  for ( char *token = strtok_r ( input, sep, &endp ); token != NULL; token = strtok_r ( NULL, sep, &endp ) ) {
1156  // Make space.
1157  *list = g_realloc ( ( *list ), ( ( *length ) + 1 ) * sizeof ( struct rofi_range_pair ) );
1158  // Parse a single pair.
1159  parse_pair ( token, &( ( *list )[*length] ) );
1160 
1161  ( *length )++;
1162  }
1163 }
1182 void rofi_output_formatted_line ( const char *format, const char *string, int selected_line, const char *filter )
1183 {
1184  for ( int i = 0; format && format[i]; i++ ) {
1185  if ( format[i] == 'i' ) {
1186  fprintf ( stdout, "%d", selected_line );
1187  }
1188  else if ( format[i] == 'd' ) {
1189  fprintf ( stdout, "%d", ( selected_line + 1 ) );
1190  }
1191  else if ( format[i] == 's' ) {
1192  fputs ( string, stdout );
1193  }
1194  else if ( format[i] == 'q' ) {
1195  char *quote = g_shell_quote ( string );
1196  fputs ( quote, stdout );
1197  g_free ( quote );
1198  }
1199  else if ( format[i] == 'f' ) {
1200  if ( filter ) {
1201  fputs ( filter, stdout );
1202  }
1203  }
1204  else if ( format[i] == 'F' ) {
1205  if ( filter ) {
1206  char *quote = g_shell_quote ( filter );
1207  fputs ( quote, stdout );
1208  g_free ( quote );
1209  }
1210  }
1211  else {
1212  fputc ( format[i], stdout );
1213  }
1214  }
1215  fputc ( '\n', stdout );
1216  fflush ( stdout );
1217 }
unsigned int levenshtein(const char *needle, const glong needlelen, const char *haystack, const glong haystacklen)
Definition: helper.c:702
#define PATTERN_START_MULTIPLIER
Definition: helper.c:805
char * monitor
Definition: settings.h:133
static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr)
Definition: helper.c:849
static enum CharClass rofi_scorer_get_character_class(gunichar c)
Definition: helper.c:827
static gchar * glob_to_regex(const char *input)
Definition: helper.c:164
WindowLocation location
Definition: settings.h:94
int find_arg_char(const char *const key, char *val)
Definition: helper.c:399
unsigned int tokenize
Definition: settings.h:131
unsigned int case_sensitive
Definition: settings.h:112
PangoAttrList * helper_token_match_get_pango_attr(RofiHighlightColorStyle th, rofi_int_matcher **tokens, const char *input, PangoAttrList *retv)
Definition: helper.c:410
int utf8_strncmp(const char *a, const char *b, size_t n)
Definition: helper.c:963
double blue
Definition: rofi-types.h:121
Definition: xcb.h:99
char * matching
Definition: settings.h:129
unsigned int menu_columns
Definition: settings.h:61
cairo_surface_t * cairo_image_surface_create_from_svg(const gchar *file, int height)
Definition: helper.c:1085
double green
Definition: rofi-types.h:119
void display_startup_notification(RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data)
Definition: xcb.c:452
RofiHighlightStyle style
Definition: rofi-types.h:143
double red
Definition: rofi-types.h:117
int config_sanity_check(void)
Definition: helper.c:574
const char *const monitor_position_entries[]
Definition: helper.c:61
int find_arg_uint(const char *const key, unsigned int *val)
Definition: helper.c:347
char * helper_get_theme_path(const char *file)
Definition: helper.c:1034
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition: helper.c:1000
int find_arg_str(const char *const key, char **val)
Definition: helper.c:305
int find_arg_int(const char *const key, int *val)
Definition: helper.c:337
const char ** find_arg_strv(const char *const key)
Definition: helper.c:316
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
Definition: helper.c:975
int dpi
Definition: settings.h:148
static int stored_argc
Definition: helper.c:69
rofi_int_matcher ** helper_tokenize(const char *input, int case_sensitive)
Definition: helper.c:256
static GRegex * R(const char *s, int case_sensitive)
Definition: helper.c:213
#define MIN3(a, b, c)
Definition: helper.c:700
char * run_command
Definition: settings.h:81
CharClass
Definition: helper.c:810
Definition: helper.c:817
void remove_pid_file(int fd)
Definition: helper.c:547
unsigned long long count
Definition: view.c:115
#define FUZZY_SCORER_MAX_LENGTH
Definition: helper.c:787
int menu_width
Definition: settings.h:57
const gchar * binary
Definition: helper.h:271
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:473
#define NON_WORD_SCORE
Definition: helper.c:797
char * run_shell_command
Definition: settings.h:83
static gchar * fuzzy_to_regex(const char *input)
Definition: helper.c:181
char * rofi_latin_to_utf8_strdup(const char *input, gssize length)
Definition: helper.c:737
static rofi_int_matcher * create_regex(const char *input, int case_sensitive)
Definition: helper.c:218
int rofi_view_error_dialog(const char *msg, int markup)
Definition: view.c:1726
char * menu_font
Definition: settings.h:63
char * ssh_client
Definition: settings.h:77
void rofi_add_error_message(GString *str)
Definition: rofi.c:86
void cmd_set_arguments(int argc, char **argv)
Definition: helper.c:73
MatchingMethod matching_method
Definition: settings.h:130
char * rofi_expand_path(const char *input)
Definition: helper.c:669
unsigned int stop
Definition: rofi-types.h:226
#define MIN_SCORE
Definition: helper.c:789
static char ** stored_argv
Definition: helper.c:71
const gchar * command
Definition: helper.h:281
const gchar * description
Definition: helper.h:273
Definition: helper.c:815
static gboolean helper_eval_cb(const GMatchInfo *info, GString *res, gpointer data)
Definition: helper.c:88
MenuFlags flags
Definition: view.c:107
int create_pid_file(const char *pidfile)
Definition: helper.c:508
int execute_generator(const char *cmd)
Definition: helper.c:486
#define CONSECUTIVE_SCORE
Definition: helper.c:801
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition: helper.c:107
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length)
Definition: helper.c:1148
#define CAMEL_SCORE
Definition: helper.c:799
int monitor_active(workarea *mon)
Definition: xcb.c:680
int element_height
Definition: settings.h:116
static void parse_pair(char *input, rofi_range_pair *item)
Definition: helper.c:1129
char * rofi_force_utf8(const gchar *data, ssize_t length)
Definition: helper.c:753
gchar * rofi_escape_markup(gchar *text)
Definition: helper.c:743
void helper_tokenize_free(rofi_int_matcher **tokens)
Definition: helper.c:155
const gchar * name
Definition: helper.h:269
unsigned int start
Definition: rofi-types.h:225
#define GAP_SCORE
Definition: helper.c:793
#define WORD_START_SCORE
Definition: helper.c:795
void rofi_output_formatted_line(const char *format, const char *string, int selected_line, const char *filter)
Definition: helper.c:1182
Settings config
int find_arg(const char *const key)
Definition: helper.c:295
char * pidfile
Definition: rofi.c:79
char helper_parse_char(const char *arg)
Definition: helper.c:358
#define PATTERN_NON_START_MULTIPLIER
Definition: helper.c:803
#define LEADING_GAP_SCORE
Definition: helper.c:791
unsigned int fullscreen
Definition: settings.h:144
char * terminal_emulator
Definition: settings.h:75
Definition: helper.c:813
workarea mon
Definition: view.c:111
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str, glong slen)
Definition: helper.c:891
gboolean helper_validate_font(PangoFontDescription *pfd, const char *font)
Definition: helper.c:556