2017-04-15 06:32:05 -04:00
/*
2015-02-12 16:34:06 -05:00
* rofi
*
* MIT / X11 License
2017-04-15 06:32:05 -04:00
* Copyright © 2012 Sean Pringle < sean . pringle @ gmail . com >
2021-06-09 08:50:39 -04:00
* Copyright © 2013 - 2021 Qball Cow < qball @ gmpclient . org >
2015-02-12 16:34:06 -05:00
*
* Permission is hereby granted , free of charge , to any person obtaining
* a copy of this software and associated documentation files ( the
* " Software " ) , to deal in the Software without restriction , including
* without limitation the rights to use , copy , modify , merge , publish ,
* distribute , sublicense , and / or sell copies of the Software , and to
* permit persons to whom the Software is furnished to do so , subject to
* the following conditions :
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS
* OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT ,
* TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
*
*/
2017-04-15 05:58:49 -04:00
2018-06-12 06:00:00 -04:00
/** The log domain for this helper. */
2017-04-15 05:58:49 -04:00
# define G_LOG_DOMAIN "Helper"
2015-02-03 02:00:33 -05:00
# include <config.h>
2014-09-06 08:57:30 -04:00
# include <stdio.h>
2014-11-15 10:26:55 -05:00
# include <stdlib.h>
# include <string.h>
2017-01-11 03:20:19 -05:00
# include <limits.h>
2015-02-03 02:00:33 -05:00
# include <unistd.h>
# include <errno.h>
# include <fcntl.h>
2015-07-30 02:57:09 -04:00
# include <glib.h>
# include <glib/gstdio.h>
2015-02-03 02:00:33 -05:00
# include <sys/types.h>
# include <sys/file.h>
2015-07-04 09:36:11 -04:00
# include <sys/stat.h>
2015-11-27 07:01:25 -05:00
# include <pwd.h>
2015-10-01 08:45:23 -04:00
# include <ctype.h>
2016-03-01 13:48:18 -05:00
# include <pango/pango.h>
# include <pango/pango-fontmap.h>
# include <pango/pangocairo.h>
2017-06-02 08:54:21 -04:00
# include "display.h"
2017-05-31 18:12:03 -04:00
# include "xcb.h"
2015-02-03 02:00:33 -05:00
# include "helper.h"
2017-02-17 08:06:31 -05:00
# include "helper-theme.h"
2016-01-07 10:01:56 -05:00
# include "settings.h"
2015-02-03 02:00:33 -05:00
# include "rofi.h"
2016-02-08 03:03:11 -05:00
# include "view.h"
2014-09-03 07:07:26 -04:00
2017-03-11 07:20:46 -05:00
/**
* Textual description of positioning rofi .
*/
2016-11-15 02:24:27 -05:00
const char * const monitor_position_entries [ ] = {
2016-08-19 12:01:26 -04:00
" on focused monitor " ,
" on focused window " ,
" at mouse pointer " ,
" on monitor with focused window " ,
" on monitor that has mouse pointer "
} ;
2016-11-15 15:54:31 -05:00
/** copy of the argc for use in commandline argument parser. */
static int stored_argc = 0 ;
/** copy of the argv pointer for use in the commandline argument parser */
2016-11-15 02:24:27 -05:00
static char * * stored_argv = NULL ;
2015-03-11 13:32:37 -04:00
2019-02-10 07:55:09 -05:00
char * helper_string_replace_if_exists_v ( char * string , GHashTable * h ) ;
2015-03-11 13:32:37 -04:00
void cmd_set_arguments ( int argc , char * * argv )
{
stored_argc = argc ;
stored_argv = argv ;
}
2014-09-03 07:07:26 -04:00
int helper_parse_setup ( char * string , char * * * output , int * length , . . . )
{
GError * error = NULL ;
GHashTable * h ;
h = g_hash_table_new ( g_str_hash , g_str_equal ) ;
2014-11-25 06:57:34 -05:00
// By default, we insert terminal and ssh-client
2014-09-03 07:07:26 -04:00
g_hash_table_insert ( h , " {terminal} " , config . terminal_emulator ) ;
g_hash_table_insert ( h , " {ssh-client} " , config . ssh_client ) ;
2014-11-25 06:57:34 -05:00
// Add list from variable arguments.
2014-09-03 07:07:26 -04:00
va_list ap ;
va_start ( ap , length ) ;
while ( 1 ) {
char * key = va_arg ( ap , char * ) ;
2017-06-13 04:15:21 -04:00
if ( key = = ( char * ) 0 ) {
2014-09-03 07:07:26 -04:00
break ;
}
char * value = va_arg ( ap , char * ) ;
2017-06-13 04:15:21 -04:00
if ( value = = ( char * ) 0 ) {
2014-09-03 07:07:26 -04:00
break ;
}
g_hash_table_insert ( h , key , value ) ;
}
va_end ( ap ) ;
2019-02-10 07:55:09 -05:00
char * res = helper_string_replace_if_exists_v ( string , h ) ;
2014-11-25 06:57:34 -05:00
// Destroy key-value storage.
2014-09-03 07:07:26 -04:00
g_hash_table_destroy ( h ) ;
2014-11-25 06:57:34 -05:00
// Parse the string into shell arguments.
2014-09-03 07:07:26 -04:00
if ( g_shell_parse_argv ( res , length , output , & error ) ) {
g_free ( res ) ;
return TRUE ;
}
g_free ( res ) ;
2014-11-25 06:57:34 -05:00
// Throw error if shell parsing fails.
2014-09-03 07:07:26 -04:00
if ( error ) {
2015-09-19 06:57:48 -04:00
char * msg = g_strdup_printf ( " Failed to parse: '%s' \n Error: '%s' " , string , error - > message ) ;
2016-02-08 03:03:11 -05:00
rofi_view_error_dialog ( msg , FALSE ) ;
2014-09-03 07:07:26 -04:00
g_free ( msg ) ;
// print error.
g_error_free ( error ) ;
}
return FALSE ;
}
2014-11-15 10:26:55 -05:00
2017-10-01 04:51:02 -04:00
void helper_tokenize_free ( rofi_int_matcher * * tokens )
2015-01-12 08:13:46 -05:00
{
2016-05-21 04:37:23 -04:00
for ( size_t i = 0 ; tokens & & tokens [ i ] ; i + + ) {
2017-09-29 02:32:09 -04:00
g_regex_unref ( ( GRegex * ) tokens [ i ] - > regex ) ;
g_free ( tokens [ i ] ) ;
2015-01-12 08:13:46 -05:00
}
2016-05-21 04:37:23 -04:00
g_free ( tokens ) ;
}
2015-01-12 08:13:46 -05:00
2016-05-21 04:37:23 -04:00
static gchar * glob_to_regex ( const char * input )
{
gchar * r = g_regex_escape_string ( input , - 1 ) ;
size_t str_l = strlen ( r ) ;
for ( size_t i = 0 ; i < str_l ; i + + ) {
if ( r [ i ] = = ' \\ ' ) {
if ( r [ i + 1 ] = = ' * ' ) {
r [ i ] = ' . ' ;
}
else if ( r [ i + 1 ] = = ' ? ' ) {
r [ i + 1 ] = ' S ' ;
}
i + + ;
}
}
return r ;
2015-01-12 08:13:46 -05:00
}
2016-08-25 02:43:40 -04:00
static gchar * fuzzy_to_regex ( const char * input )
{
GString * str = g_string_new ( " " ) ;
gchar * r = g_regex_escape_string ( input , - 1 ) ;
gchar * iter ;
int first = 1 ;
for ( iter = r ; iter & & * iter ! = ' \0 ' ; iter = g_utf8_next_char ( iter ) ) {
if ( first ) {
g_string_append ( str , " ( " ) ;
}
else {
2020-08-25 14:23:26 -04:00
g_string_append ( str , " .*?( " ) ;
2016-08-25 02:43:40 -04:00
}
2017-01-08 15:36:06 -05:00
if ( * iter = = ' \\ ' ) {
g_string_append_c ( str , ' \\ ' ) ;
2016-11-26 11:18:38 -05:00
iter = g_utf8_next_char ( iter ) ;
2016-11-30 02:41:47 -05:00
// If EOL, break out of for loop.
2017-01-08 15:36:06 -05:00
if ( ( * iter ) = = ' \0 ' ) {
break ;
}
2016-11-26 11:18:38 -05:00
}
2016-08-25 02:43:40 -04:00
g_string_append_unichar ( str , g_utf8_get_char ( iter ) ) ;
g_string_append ( str , " ) " ) ;
first = 0 ;
}
g_free ( r ) ;
char * retv = str - > str ;
g_string_free ( str , FALSE ) ;
return retv ;
}
2016-11-15 15:54:31 -05:00
2021-06-01 04:44:23 -04:00
static gchar * prefix_regex ( const char * input )
2021-05-29 07:39:31 -04:00
{
2021-06-01 04:44:23 -04:00
gchar * r = g_regex_escape_string ( input , - 1 ) ;
char * retv = g_strconcat ( " \\ b " , r , NULL ) ;
g_free ( r ) ;
2021-05-29 07:39:31 -04:00
return retv ;
}
2020-11-03 17:57:02 -05:00
static char * utf8_helper_simplify_string ( const char * s )
2020-09-30 15:56:37 -04:00
{
2020-11-03 17:57:02 -05:00
gunichar buf2 [ G_UNICHAR_MAX_DECOMPOSITION_LENGTH ] = { 0 , } ;
char buf [ 6 ] = { 0 , } ;
// Compose the string in maximally composed form.
char * str = g_malloc0 ( ( g_utf8_strlen ( s , 0 ) * 6 + 2 ) ) ;
char * striter = str ;
for ( const char * iter = s ; iter & & * iter ; iter = g_utf8_next_char ( iter ) ) {
gunichar uc = g_utf8_get_char ( iter ) ;
int l = 0 ;
gsize dl = g_unichar_fully_decompose ( uc , FALSE , buf2 , G_UNICHAR_MAX_DECOMPOSITION_LENGTH ) ;
if ( dl ) {
l = g_unichar_to_utf8 ( buf2 [ 0 ] , buf ) ;
}
else {
l = g_unichar_to_utf8 ( uc , buf ) ;
}
memcpy ( striter , buf , l ) ;
striter + = l ;
2020-09-30 15:56:37 -04:00
}
2020-11-03 17:57:02 -05:00
return str ;
2020-09-30 15:56:37 -04:00
}
2016-11-15 15:54:31 -05:00
// Macro for quickly generating regex for matching.
static inline GRegex * R ( const char * s , int case_sensitive )
{
2020-11-03 17:57:02 -05:00
if ( config . normalize_match ) {
char * str = utf8_helper_simplify_string ( s ) ;
2020-09-30 15:56:37 -04:00
2020-11-03 17:57:02 -05:00
GRegex * r = g_regex_new ( str , G_REGEX_OPTIMIZE | ( ( case_sensitive ) ? 0 : G_REGEX_CASELESS ) , 0 , NULL ) ;
2020-09-30 15:56:37 -04:00
2020-11-03 17:57:02 -05:00
g_free ( str ) ;
return r ;
}
else {
return g_regex_new ( s , G_REGEX_OPTIMIZE | ( ( case_sensitive ) ? 0 : G_REGEX_CASELESS ) , 0 , NULL ) ;
}
2016-11-15 15:54:31 -05:00
}
2017-09-29 02:32:09 -04:00
static rofi_int_matcher * create_regex ( const char * input , int case_sensitive )
2015-11-17 10:10:14 -05:00
{
2017-10-05 11:45:50 -04:00
GRegex * retv = NULL ;
gchar * r ;
rofi_int_matcher * rv = g_malloc0 ( sizeof ( rofi_int_matcher ) ) ;
2018-12-14 10:58:26 -05:00
if ( input & & input [ 0 ] = = config . matching_negate_char ) {
2017-09-29 02:32:09 -04:00
rv - > invert = 1 ;
input + + ;
}
2016-08-25 02:43:40 -04:00
switch ( config . matching_method )
{
case MM_GLOB :
r = glob_to_regex ( input ) ;
2016-11-15 15:54:31 -05:00
retv = R ( r , case_sensitive ) ;
2016-05-21 04:37:23 -04:00
g_free ( r ) ;
2016-08-25 02:43:40 -04:00
break ;
case MM_REGEX :
2016-11-15 15:54:31 -05:00
retv = R ( input , case_sensitive ) ;
2016-05-21 04:37:23 -04:00
if ( retv = = NULL ) {
2016-08-25 02:43:40 -04:00
r = g_regex_escape_string ( input , - 1 ) ;
2016-11-15 15:54:31 -05:00
retv = R ( r , case_sensitive ) ;
2016-05-21 04:37:23 -04:00
g_free ( r ) ;
2015-11-17 10:10:14 -05:00
}
2016-08-25 02:43:40 -04:00
break ;
case MM_FUZZY :
r = fuzzy_to_regex ( input ) ;
2016-11-15 15:54:31 -05:00
retv = R ( r , case_sensitive ) ;
2016-08-25 02:43:40 -04:00
g_free ( r ) ;
break ;
2021-05-29 07:39:31 -04:00
case MM_PREFIX :
2021-06-01 04:44:23 -04:00
r = prefix_regex ( input ) ;
retv = R ( r , case_sensitive ) ;
g_free ( r ) ;
2021-05-29 07:39:31 -04:00
break ;
2016-08-25 02:43:40 -04:00
default :
r = g_regex_escape_string ( input , - 1 ) ;
2016-11-15 15:54:31 -05:00
retv = R ( r , case_sensitive ) ;
2016-05-21 04:37:23 -04:00
g_free ( r ) ;
2016-08-25 02:43:40 -04:00
break ;
2015-11-17 10:10:14 -05:00
}
2017-09-29 02:32:09 -04:00
rv - > regex = retv ;
return rv ;
2015-11-17 10:10:14 -05:00
}
2017-10-01 04:51:02 -04:00
rofi_int_matcher * * helper_tokenize ( const char * input , int case_sensitive )
2014-11-15 10:26:55 -05:00
{
2016-06-16 16:23:55 -04:00
if ( input = = NULL ) {
return NULL ;
}
size_t len = strlen ( input ) ;
if ( len = = 0 ) {
2014-11-15 10:26:55 -05:00
return NULL ;
}
2017-10-05 11:45:50 -04:00
char * saveptr = NULL , * token ;
2017-09-29 02:32:09 -04:00
rofi_int_matcher * * retv = NULL ;
2015-10-04 10:37:07 -04:00
if ( ! config . tokenize ) {
2017-09-29 02:32:09 -04:00
retv = g_malloc0 ( sizeof ( rofi_int_matcher * ) * 2 ) ;
retv [ 0 ] = create_regex ( input , case_sensitive ) ;
2015-10-04 10:37:07 -04:00
return retv ;
}
2014-11-15 10:26:55 -05:00
// First entry is always full (modified) stringtext.
2015-10-04 10:37:07 -04:00
int num_tokens = 0 ;
2014-11-15 10:26:55 -05:00
// Copy the string, 'strtok_r' modifies it.
char * str = g_strdup ( input ) ;
// Iterate over tokens.
// strtok should still be valid for utf8.
2016-03-19 08:29:04 -04:00
const char * const sep = " " ;
for ( token = strtok_r ( str , sep , & saveptr ) ; token ! = NULL ; token = strtok_r ( NULL , sep , & saveptr ) ) {
2017-09-29 02:32:09 -04:00
retv = g_realloc ( retv , sizeof ( rofi_int_matcher * ) * ( num_tokens + 2 ) ) ;
retv [ num_tokens ] = create_regex ( token , case_sensitive ) ;
2014-11-15 10:26:55 -05:00
retv [ num_tokens + 1 ] = NULL ;
num_tokens + + ;
}
// Free str.
g_free ( str ) ;
return retv ;
}
// cli arg handling
2015-03-11 13:32:37 -04:00
int find_arg ( const char * const key )
2014-11-15 10:26:55 -05:00
{
int i ;
2015-03-11 13:32:37 -04:00
for ( i = 0 ; i < stored_argc & & strcasecmp ( stored_argv [ i ] , key ) ; i + + ) {
2014-11-15 10:26:55 -05:00
;
}
2015-03-11 13:32:37 -04:00
return i < stored_argc ? i : - 1 ;
2014-11-15 10:26:55 -05:00
}
2015-03-11 13:32:37 -04:00
int find_arg_str ( const char * const key , char * * val )
2014-11-15 10:26:55 -05:00
{
2015-03-11 13:32:37 -04:00
int i = find_arg ( key ) ;
2014-11-15 10:26:55 -05:00
2015-03-11 13:32:37 -04:00
if ( val ! = NULL & & i > 0 & & i < stored_argc - 1 ) {
* val = stored_argv [ i + 1 ] ;
2014-11-15 10:26:55 -05:00
return TRUE ;
}
return FALSE ;
}
2017-01-08 10:09:24 -05:00
const char * * find_arg_strv ( const char * const key )
{
2017-01-08 15:36:06 -05:00
const char * * retv = NULL ;
int length = 0 ;
2017-01-08 10:09:24 -05:00
for ( int i = 0 ; i < stored_argc ; i + + ) {
2017-04-23 09:17:15 -04:00
if ( i < ( stored_argc - 1 ) & & strcasecmp ( stored_argv [ i ] , key ) = = 0 ) {
2017-01-08 10:09:24 -05:00
length + + ;
}
}
if ( length > 0 ) {
2017-01-08 15:36:06 -05:00
retv = g_malloc0 ( ( length + 1 ) * sizeof ( char * ) ) ;
2017-01-08 10:09:24 -05:00
int index = 0 ;
for ( int i = 0 ; i < stored_argc ; i + + ) {
2017-04-27 16:59:14 -04:00
if ( i < ( stored_argc - 1 ) & & strcasecmp ( stored_argv [ i ] , key ) = = 0 ) {
2017-01-08 15:36:06 -05:00
retv [ index + + ] = stored_argv [ i + 1 ] ;
2017-01-08 10:09:24 -05:00
}
}
}
return retv ;
}
2015-03-11 13:32:37 -04:00
int find_arg_int ( const char * const key , int * val )
2014-11-15 10:26:55 -05:00
{
2015-03-11 13:32:37 -04:00
int i = find_arg ( key ) ;
2014-11-15 10:26:55 -05:00
2015-03-11 13:32:37 -04:00
if ( val ! = NULL & & i > 0 & & i < ( stored_argc - 1 ) ) {
* val = strtol ( stored_argv [ i + 1 ] , NULL , 10 ) ;
2014-11-15 10:26:55 -05:00
return TRUE ;
}
return FALSE ;
}
2015-03-11 13:32:37 -04:00
int find_arg_uint ( const char * const key , unsigned int * val )
2014-11-15 10:26:55 -05:00
{
2015-03-11 13:32:37 -04:00
int i = find_arg ( key ) ;
2014-11-15 10:26:55 -05:00
2015-03-11 13:32:37 -04:00
if ( val ! = NULL & & i > 0 & & i < ( stored_argc - 1 ) ) {
* val = strtoul ( stored_argv [ i + 1 ] , NULL , 10 ) ;
2014-11-15 10:26:55 -05:00
return TRUE ;
}
return FALSE ;
}
2015-02-17 04:31:59 -05:00
char helper_parse_char ( const char * arg )
{
2016-10-28 17:28:49 -04:00
const size_t len = strlen ( arg ) ;
2015-02-17 04:31:59 -05:00
// If the length is 1, it is not escaped.
if ( len = = 1 ) {
2015-12-02 11:46:17 -05:00
return arg [ 0 ] ;
2015-02-17 04:31:59 -05:00
}
// If the length is 2 and the first character is '\', we unescape it.
2015-12-02 11:46:17 -05:00
if ( len = = 2 & & arg [ 0 ] = = ' \\ ' ) {
2016-10-28 17:28:49 -04:00
switch ( arg [ 1 ] )
{
2015-02-17 04:31:59 -05:00
// New line
2016-10-28 17:28:49 -04:00
case ' n ' : return ' \n ' ;
2015-02-17 04:31:59 -05:00
// Bell
2016-10-28 17:28:49 -04:00
case ' a ' : return ' \a ' ;
2015-02-17 04:31:59 -05:00
// Backspace
2016-10-28 17:28:49 -04:00
case ' b ' : return ' \b ' ;
2015-02-17 04:31:59 -05:00
// Tab
2016-10-28 17:28:49 -04:00
case ' t ' : return ' \t ' ;
2015-02-17 04:31:59 -05:00
// Vertical tab
2016-10-28 17:28:49 -04:00
case ' v ' : return ' \v ' ;
2015-02-17 04:31:59 -05:00
// Form feed
2016-10-28 17:28:49 -04:00
case ' f ' : return ' \f ' ;
2015-02-17 04:31:59 -05:00
// Carriage return
2016-10-28 17:28:49 -04:00
case ' r ' : return ' \r ' ;
2015-02-17 04:31:59 -05:00
// Forward slash
2016-10-28 17:28:49 -04:00
case ' \\ ' : return ' \\ ' ;
// 0 line.
case ' 0 ' : return ' \0 ' ;
default :
break ;
2015-09-20 14:05:04 -04:00
}
2015-02-17 04:31:59 -05:00
}
2015-12-02 11:46:17 -05:00
if ( len > 2 & & arg [ 0 ] = = ' \\ ' & & arg [ 1 ] = = ' x ' ) {
return ( char ) strtol ( & arg [ 2 ] , NULL , 16 ) ;
2015-02-17 04:31:59 -05:00
}
2017-04-15 05:58:49 -04:00
g_warning ( " Failed to parse character string: \" %s \" " , arg ) ;
2015-12-02 11:46:17 -05:00
// for now default to newline.
return ' \n ' ;
2015-02-17 04:31:59 -05:00
}
2015-03-11 13:32:37 -04:00
int find_arg_char ( const char * const key , char * val )
2014-11-15 10:26:55 -05:00
{
2015-03-11 13:32:37 -04:00
int i = find_arg ( key ) ;
2014-11-15 10:26:55 -05:00
2015-03-11 13:32:37 -04:00
if ( val ! = NULL & & i > 0 & & i < ( stored_argc - 1 ) ) {
* val = helper_parse_char ( stored_argv [ i + 1 ] ) ;
2014-11-15 10:26:55 -05:00
return TRUE ;
}
return FALSE ;
}
2017-09-29 02:32:09 -04:00
PangoAttrList * helper_token_match_get_pango_attr ( RofiHighlightColorStyle th , rofi_int_matcher * * tokens , const char * input , PangoAttrList * retv )
2016-05-10 12:02:23 -04:00
{
2020-09-30 15:56:37 -04:00
// Disable highlighting for normalize match, not supported atm.
if ( config . normalize_match ) {
2020-11-03 17:57:02 -05:00
return retv ;
2020-09-30 15:56:37 -04:00
}
2016-05-10 12:02:23 -04:00
// Do a tokenized match.
if ( tokens ) {
for ( int j = 0 ; tokens [ j ] ; j + + ) {
GMatchInfo * gmi = NULL ;
2017-10-05 11:45:50 -04:00
if ( tokens [ j ] - > invert ) {
continue ;
}
2017-09-29 02:32:09 -04:00
g_regex_match ( tokens [ j ] - > regex , input , G_REGEX_MATCH_PARTIAL , & gmi ) ;
2016-05-10 12:02:23 -04:00
while ( g_match_info_matches ( gmi ) ) {
2016-08-25 02:43:40 -04:00
int count = g_match_info_get_match_count ( gmi ) ;
for ( int index = ( count > 1 ) ? 1 : 0 ; index < count ; index + + ) {
2017-01-08 18:09:02 -05:00
int start , end ;
2016-08-25 02:43:40 -04:00
g_match_info_fetch_pos ( gmi , index , & start , & end ) ;
2017-06-02 10:09:20 -04:00
if ( th . style & ROFI_HL_BOLD ) {
2017-01-08 18:09:02 -05:00
PangoAttribute * pa = pango_attr_weight_new ( PANGO_WEIGHT_BOLD ) ;
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
}
2017-06-02 10:09:20 -04:00
if ( th . style & ROFI_HL_UNDERLINE ) {
2017-01-08 18:09:02 -05:00
PangoAttribute * pa = pango_attr_underline_new ( PANGO_UNDERLINE_SINGLE ) ;
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
}
2017-06-02 10:09:20 -04:00
if ( th . style & ROFI_HL_STRIKETHROUGH ) {
2017-05-18 01:59:37 -04:00
PangoAttribute * pa = pango_attr_strikethrough_new ( TRUE ) ;
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
}
2017-06-02 10:09:20 -04:00
if ( th . style & ROFI_HL_SMALL_CAPS ) {
2017-05-22 03:24:05 -04:00
PangoAttribute * pa = pango_attr_variant_new ( PANGO_VARIANT_SMALL_CAPS ) ;
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
}
2017-06-02 10:09:20 -04:00
if ( th . style & ROFI_HL_ITALIC ) {
2017-01-08 18:09:02 -05:00
PangoAttribute * pa = pango_attr_style_new ( PANGO_STYLE_ITALIC ) ;
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
}
2017-06-02 10:09:20 -04:00
if ( th . style & ROFI_HL_COLOR ) {
2017-01-08 18:09:02 -05:00
PangoAttribute * pa = pango_attr_foreground_new (
th . color . red * 65535 ,
th . color . green * 65535 ,
th . color . blue * 65535 ) ;
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
2019-11-20 05:18:53 -05:00
2020-02-02 07:56:37 -05:00
if ( th . color . alpha < 1.0 ) {
pa = pango_attr_foreground_alpha_new ( th . color . alpha * 65535 ) ;
2019-11-20 05:18:53 -05:00
pa - > start_index = start ;
pa - > end_index = end ;
pango_attr_list_insert ( retv , pa ) ;
}
2017-01-08 18:09:02 -05:00
}
2016-08-25 02:43:40 -04:00
}
2016-05-10 12:02:23 -04:00
g_match_info_next ( gmi , NULL ) ;
}
g_match_info_free ( gmi ) ;
}
}
return retv ;
}
2015-10-05 05:38:17 -04:00
2017-09-29 02:32:09 -04:00
int helper_token_match ( rofi_int_matcher * const * tokens , const char * input )
2015-07-01 03:12:22 -04:00
{
2017-01-30 02:23:33 -05:00
int match = TRUE ;
2016-05-22 11:47:34 -04:00
// Do a tokenized match.
2017-01-30 02:23:33 -05:00
if ( tokens ) {
2020-11-03 17:57:02 -05:00
if ( config . normalize_match ) {
char * r = utf8_helper_simplify_string ( input ) ;
for ( int j = 0 ; match & & tokens [ j ] ; j + + ) {
match = g_regex_match ( tokens [ j ] - > regex , r , 0 , NULL ) ;
match ^ = tokens [ j ] - > invert ;
}
g_free ( r ) ;
2020-09-30 15:56:37 -04:00
}
2020-11-03 17:57:02 -05:00
else {
for ( int j = 0 ; match & & tokens [ j ] ; j + + ) {
match = g_regex_match ( tokens [ j ] - > regex , input , 0 , NULL ) ;
match ^ = tokens [ j ] - > invert ;
}
2017-01-30 02:23:33 -05:00
}
2016-05-22 11:47:34 -04:00
}
return match ;
2015-07-01 03:12:22 -04:00
}
2015-01-05 15:53:50 -05:00
2015-03-27 15:28:53 -04:00
int execute_generator ( const char * cmd )
2015-01-05 15:53:50 -05:00
{
char * * args = NULL ;
int argv = 0 ;
2017-06-13 04:15:21 -04:00
helper_parse_setup ( config . run_command , & args , & argv , " {cmd} " , cmd , ( char * ) 0 ) ;
2015-01-05 15:53:50 -05:00
int fd = - 1 ;
GError * error = NULL ;
2015-09-19 06:57:48 -04:00
g_spawn_async_with_pipes ( NULL , args , NULL , G_SPAWN_SEARCH_PATH , NULL , NULL , NULL , NULL , & fd , NULL , & error ) ;
2015-01-05 15:53:50 -05:00
if ( error ! = NULL ) {
2015-09-19 06:57:48 -04:00
char * msg = g_strdup_printf ( " Failed to execute: '%s' \n Error: '%s' " , cmd , error - > message ) ;
2016-04-19 16:10:34 -04:00
rofi_view_error_dialog ( msg , FALSE ) ;
2015-01-05 15:53:50 -05:00
g_free ( msg ) ;
// print error.
g_error_free ( error ) ;
fd = - 1 ;
}
g_strfreev ( args ) ;
return fd ;
}
2015-02-03 02:00:33 -05:00
2015-07-30 02:57:09 -04:00
int create_pid_file ( const char * pidfile )
2015-02-03 02:00:33 -05:00
{
if ( pidfile = = NULL ) {
2015-07-30 02:57:09 -04:00
return - 1 ;
2015-02-03 02:00:33 -05:00
}
2015-07-30 02:57:09 -04:00
int fd = g_open ( pidfile , O_RDWR | O_CREAT , S_IRUSR | S_IWUSR ) ;
2015-02-03 02:00:33 -05:00
if ( fd < 0 ) {
2017-04-15 05:58:49 -04:00
g_warning ( " Failed to create pid file: '%s'. " , pidfile ) ;
2015-07-30 02:57:09 -04:00
return - 1 ;
2015-02-03 02:00:33 -05:00
}
// Set it to close the File Descriptor on exit.
int flags = fcntl ( fd , F_GETFD , NULL ) ;
flags = flags | FD_CLOEXEC ;
if ( fcntl ( fd , F_SETFD , flags , NULL ) < 0 ) {
2017-04-15 05:58:49 -04:00
g_warning ( " Failed to set CLOEXEC on pidfile. " ) ;
2015-07-30 02:57:09 -04:00
remove_pid_file ( fd ) ;
return - 1 ;
2015-02-03 02:00:33 -05:00
}
// Try to get exclusive write lock on FD
int retv = flock ( fd , LOCK_EX | LOCK_NB ) ;
if ( retv ! = 0 ) {
2017-04-15 05:58:49 -04:00
g_warning ( " Failed to set lock on pidfile: Rofi already running? " ) ;
g_warning ( " Got error: %d %s " , retv , g_strerror ( errno ) ) ;
2015-07-30 02:57:09 -04:00
remove_pid_file ( fd ) ;
return - 1 ;
2015-02-03 02:00:33 -05:00
}
if ( ftruncate ( fd , ( off_t ) 0 ) = = 0 ) {
// Write pid, not needed, but for completeness sake.
char buffer [ 64 ] ;
int length = snprintf ( buffer , 64 , " %i " , getpid ( ) ) ;
ssize_t l = 0 ;
while ( l < length ) {
l + = write ( fd , & buffer [ l ] , length - l ) ;
}
}
2015-07-30 02:57:09 -04:00
return fd ;
}
void remove_pid_file ( int fd )
{
if ( fd > = 0 ) {
2015-08-02 09:56:52 -04:00
if ( close ( fd ) ) {
2017-04-15 05:58:49 -04:00
g_warning ( " Failed to close pidfile: '%s' " , g_strerror ( errno ) ) ;
2015-08-02 09:56:52 -04:00
}
2015-07-30 02:57:09 -04:00
}
2015-02-03 02:00:33 -05:00
}
2015-02-03 02:21:59 -05:00
2017-02-03 03:52:56 -05:00
gboolean helper_validate_font ( PangoFontDescription * pfd , const char * font )
{
2017-02-03 14:49:16 -05:00
const char * fam = pango_font_description_get_family ( pfd ) ;
int size = pango_font_description_get_size ( pfd ) ;
if ( fam = = NULL | | size = = 0 ) {
2017-04-15 05:37:50 -04:00
g_debug ( " Pango failed to parse font: '%s' " , font ) ;
g_debug ( " Got family: <b>%s</b> at size: <b>%d</b> " , fam ? fam : " {unknown} " , size ) ;
2017-02-03 03:52:56 -05:00
return FALSE ;
}
return TRUE ;
}
2015-02-03 02:21:59 -05:00
/**
* Do some input validation , especially the first few could break things .
* It is good to catch them beforehand .
*
* This functions exits the program with 1 when it finds an invalid configuration .
*/
2016-03-01 12:11:55 -05:00
int config_sanity_check ( void )
2015-02-03 02:21:59 -05:00
{
2015-07-28 16:22:18 -04:00
int found_error = FALSE ;
2015-09-19 06:21:30 -04:00
GString * msg = g_string_new (
" <big><b>The configuration failed to validate:</b></big> \n " ) ;
2016-08-25 02:43:40 -04:00
2018-06-13 05:57:23 -04:00
if ( config . sorting_method ) {
if ( g_strcmp0 ( config . sorting_method , " normal " ) = = 0 ) {
config . sorting_method_enum = SORT_NORMAL ;
}
else if ( g_strcmp0 ( config . sorting_method , " levenshtein " ) = = 0 ) {
config . sorting_method_enum = SORT_NORMAL ;
}
else if ( g_strcmp0 ( config . sorting_method , " fzf " ) = = 0 ) {
config . sorting_method_enum = SORT_FZF ;
2018-08-08 09:55:13 -04:00
}
else {
2018-06-13 05:57:23 -04:00
g_string_append_printf ( msg , " \t <b>config.sorting_method</b>=%s is not a valid sorting strategy. \n Valid options are: normal or fzf. \n " ,
config . sorting_method ) ;
found_error = 1 ;
}
}
2016-08-25 02:43:40 -04:00
if ( config . matching ) {
if ( g_strcmp0 ( config . matching , " regex " ) = = 0 ) {
config . matching_method = MM_REGEX ;
}
else if ( g_strcmp0 ( config . matching , " glob " ) = = 0 ) {
config . matching_method = MM_GLOB ;
}
else if ( g_strcmp0 ( config . matching , " fuzzy " ) = = 0 ) {
config . matching_method = MM_FUZZY ;
}
else if ( g_strcmp0 ( config . matching , " normal " ) = = 0 ) {
config . matching_method = MM_NORMAL ; ;
}
2021-06-01 04:44:23 -04:00
else if ( g_strcmp0 ( config . matching , " prefix " ) = = 0 ) {
2021-05-29 07:39:31 -04:00
config . matching_method = MM_PREFIX ;
}
2016-08-25 02:43:40 -04:00
else {
2021-05-29 07:39:31 -04:00
g_string_append_printf ( msg , " \t <b>config.matching</b>=%s is not a valid matching strategy. \n Valid options are: glob, regex, fuzzy, prefix or normal. \n " ,
2016-08-25 02:43:40 -04:00
config . matching ) ;
found_error = 1 ;
}
}
2015-02-03 02:21:59 -05:00
if ( config . element_height < 1 ) {
2015-09-19 06:57:48 -04:00
g_string_append_printf ( msg , " \t <b>config.element_height</b>=%d is invalid. An element needs to be atleast 1 line high. \n " ,
config . element_height ) ;
2015-07-28 16:22:18 -04:00
config . element_height = 1 ;
found_error = TRUE ;
2015-02-03 02:21:59 -05:00
}
2017-07-12 06:27:45 -04:00
if ( ! ( config . location > = 0 & & config . location < = 8 ) ) {
2015-09-19 06:57:48 -04:00
g_string_append_printf ( msg , " \t <b>config.location</b>=%d is invalid. Value should be between %d and %d. \n " ,
2017-07-12 06:27:45 -04:00
config . location , 0 , 8 ) ;
2015-07-28 16:22:18 -04:00
config . location = WL_CENTER ;
found_error = 1 ;
}
2015-10-18 07:40:39 -04:00
// Check size
{
2016-08-19 04:46:38 -04:00
workarea mon ;
2016-10-14 10:46:54 -04:00
if ( ! monitor_active ( & mon ) ) {
2016-08-19 12:01:26 -04:00
const char * name = config . monitor ;
if ( name & & name [ 0 ] = = ' - ' ) {
int index = name [ 1 ] - ' 0 ' ;
if ( index < 5 & & index > 0 ) {
name = monitor_position_entries [ index - 1 ] ;
}
}
g_string_append_printf ( msg , " \t <b>config.monitor</b>=%s Could not find monitor. \n " , name ) ;
2016-08-24 04:36:39 -04:00
found_error = TRUE ;
2015-10-18 07:40:39 -04:00
}
}
2016-03-01 13:48:18 -05:00
if ( config . menu_font ) {
PangoFontDescription * pfd = pango_font_description_from_string ( config . menu_font ) ;
const char * fam = pango_font_description_get_family ( pfd ) ;
int size = pango_font_description_get_size ( pfd ) ;
if ( fam = = NULL | | size = = 0 ) {
g_string_append_printf ( msg , " Pango failed to parse font: '%s' \n " , config . menu_font ) ;
g_string_append_printf ( msg , " Got font family: <b>%s</b> at size <b>%d</b> \n " , fam ? fam : " {unknown} " , size ) ;
config . menu_font = NULL ;
found_error = TRUE ;
}
pango_font_description_free ( pfd ) ;
}
2016-08-19 04:46:38 -04:00
if ( g_strcmp0 ( config . monitor , " -3 " ) = = 0 ) {
2016-03-20 05:16:55 -04:00
// On -3, set to location 1.
2021-06-01 04:44:23 -04:00
config . location = 1 ;
2016-03-20 05:16:55 -04:00
}
2015-07-28 16:22:18 -04:00
if ( found_error ) {
g_string_append ( msg , " Please update your configuration. " ) ;
2017-01-09 12:32:26 -05:00
rofi_add_error_message ( msg ) ;
2016-02-19 13:50:48 -05:00
return TRUE ;
2015-02-03 02:21:59 -05:00
}
2015-07-28 16:22:18 -04:00
g_string_free ( msg , TRUE ) ;
2016-02-19 13:50:48 -05:00
return FALSE ;
2015-02-03 02:21:59 -05:00
}
2015-10-01 07:16:41 -04:00
2015-11-27 07:01:25 -05:00
char * rofi_expand_path ( const char * input )
{
char * * str = g_strsplit ( input , G_DIR_SEPARATOR_S , - 1 ) ;
for ( unsigned int i = 0 ; str & & str [ i ] ; i + + ) {
// Replace ~ with current user homedir.
if ( str [ i ] [ 0 ] = = ' ~ ' & & str [ i ] [ 1 ] = = ' \0 ' ) {
g_free ( str [ i ] ) ;
str [ i ] = g_strdup ( g_get_home_dir ( ) ) ;
}
// If other user, ask getpwnam.
else if ( str [ i ] [ 0 ] = = ' ~ ' ) {
struct passwd * p = getpwnam ( & ( str [ i ] [ 1 ] ) ) ;
if ( p ! = NULL ) {
g_free ( str [ i ] ) ;
str [ i ] = g_strdup ( p - > pw_dir ) ;
}
}
2015-11-28 06:59:10 -05:00
else if ( i = = 0 ) {
char * s = str [ i ] ;
if ( input [ 0 ] = = G_DIR_SEPARATOR ) {
str [ i ] = g_strdup_printf ( " %s%s " , G_DIR_SEPARATOR_S , s ) ;
g_free ( s ) ;
}
}
2015-11-27 07:01:25 -05:00
}
char * retv = g_build_filenamev ( str ) ;
g_strfreev ( str ) ;
return retv ;
}
2016-01-04 11:14:15 -05:00
2016-11-15 15:54:31 -05:00
/** Return the minimum value of a,b,c */
2016-01-04 11:14:15 -05:00
# define MIN3( a, b, c ) ( ( a ) < ( b ) ? ( ( a ) < ( c ) ? ( a ) : ( c ) ) : ( ( b ) < ( c ) ? ( b ) : ( c ) ) )
2017-01-21 06:58:52 -05:00
unsigned int levenshtein ( const char * needle , const glong needlelen , const char * haystack , const glong haystacklen )
2016-01-04 11:14:15 -05:00
{
2017-04-27 16:59:14 -04:00
if ( needlelen = = G_MAXLONG ) {
2017-04-06 13:55:47 -04:00
// String to long, we cannot handle this.
return UINT_MAX ;
}
2016-01-04 11:14:15 -05:00
unsigned int column [ needlelen + 1 ] ;
2017-05-03 11:41:14 -04:00
for ( glong y = 0 ; y < needlelen ; y + + ) {
2016-01-04 11:14:15 -05:00
column [ y ] = y ;
}
2017-05-03 11:41:14 -04:00
// Removed out of the loop, otherwise static code analyzers think it is unset.. silly but true.
// old loop: for ( glong y = 0; y <= needlelen; y++)
column [ needlelen ] = needlelen ;
2017-01-18 03:43:44 -05:00
for ( glong x = 1 ; x < = haystacklen ; x + + ) {
2016-01-04 11:14:15 -05:00
const char * needles = needle ;
column [ 0 ] = x ;
gunichar haystackc = g_utf8_get_char ( haystack ) ;
if ( ! config . case_sensitive ) {
haystackc = g_unichar_tolower ( haystackc ) ;
}
2017-01-18 03:43:44 -05:00
for ( glong y = 1 , lastdiag = x - 1 ; y < = needlelen ; y + + ) {
2016-01-04 11:14:15 -05:00
gunichar needlec = g_utf8_get_char ( needles ) ;
if ( ! config . case_sensitive ) {
needlec = g_unichar_tolower ( needlec ) ;
}
2016-10-28 17:28:49 -04:00
unsigned int olddiag = column [ y ] ;
2016-01-04 11:14:15 -05:00
column [ y ] = MIN3 ( column [ y ] + 1 , column [ y - 1 ] + 1 , lastdiag + ( needlec = = haystackc ? 0 : 1 ) ) ;
lastdiag = olddiag ;
needles = g_utf8_next_char ( needles ) ;
}
haystack = g_utf8_next_char ( haystack ) ;
}
return column [ needlelen ] ;
}
2016-04-10 06:05:34 -04:00
char * rofi_latin_to_utf8_strdup ( const char * input , gssize length )
{
gsize slength = 0 ;
return g_convert_with_fallback ( input , length , " UTF-8 " , " latin1 " , " \uFFFD " , NULL , & slength , NULL ) ;
}
2017-04-12 12:19:58 -04:00
gchar * rofi_escape_markup ( gchar * text )
{
if ( text = = NULL ) {
return NULL ;
}
gchar * ret = g_markup_escape_text ( text , - 1 ) ;
g_free ( text ) ;
return ret ;
}
2017-04-23 09:17:15 -04:00
char * rofi_force_utf8 ( const gchar * data , ssize_t length )
2016-04-10 06:05:34 -04:00
{
2017-04-23 09:17:15 -04:00
if ( data = = NULL ) {
2016-04-10 06:05:34 -04:00
return NULL ;
}
const char * end ;
GString * string ;
if ( g_utf8_validate ( data , length , & end ) ) {
2017-04-23 09:17:15 -04:00
return g_memdup ( data , length + 1 ) ;
2016-04-10 06:05:34 -04:00
}
string = g_string_sized_new ( length + 16 ) ;
do {
/* Valid part of the string */
g_string_append_len ( string , data , end - data ) ;
/* Replacement character */
g_string_append ( string , " \uFFFD " ) ;
length - = ( end - data ) + 1 ;
2016-04-10 08:30:13 -04:00
data = end + 1 ;
2016-04-10 06:05:34 -04:00
} while ( ! g_utf8_validate ( data , length , & end ) ) ;
if ( length ) {
g_string_append_len ( string , data , length ) ;
}
return g_string_free ( string , FALSE ) ;
}
2017-01-11 03:20:19 -05:00
/****
* FZF like scorer
*/
/** Max length of input to score. */
# define FUZZY_SCORER_MAX_LENGTH 256
/** minimum score */
# define MIN_SCORE ( INT_MIN / 2 )
/** Leading gap score */
# define LEADING_GAP_SCORE -4
/** gap score */
# define GAP_SCORE -5
/** start of word score */
# define WORD_START_SCORE 50
/** non-word score */
# define NON_WORD_SCORE 40
/** CamelCase score */
# define CAMEL_SCORE ( WORD_START_SCORE + GAP_SCORE - 1 )
/** Consecutive score */
# define CONSECUTIVE_SCORE ( WORD_START_SCORE + GAP_SCORE )
/** non-start multiplier */
# define PATTERN_NON_START_MULTIPLIER 1
/** start multiplier */
# define PATTERN_START_MULTIPLIER 2
/**
* Character classification .
*/
enum CharClass
{
/* Lower case */
LOWER ,
/* Upper case */
UPPER ,
/* Number */
DIGIT ,
/* non word character */
NON_WORD
} ;
/**
* @ param c The character to determine class of
*
* @ returns the class of the character c .
*/
static enum CharClass rofi_scorer_get_character_class ( gunichar c )
{
if ( g_unichar_islower ( c ) ) {
return LOWER ;
}
if ( g_unichar_isupper ( c ) ) {
return UPPER ;
}
if ( g_unichar_isdigit ( c ) ) {
return DIGIT ;
}
return NON_WORD ;
}
/**
* @ param prev The previous character .
* @ param curr The current character
*
* Scrore the transition .
*
* @ returns score of the transition .
*/
static int rofi_scorer_get_score_for ( enum CharClass prev , enum CharClass curr )
{
if ( prev = = NON_WORD & & curr ! = NON_WORD ) {
return WORD_START_SCORE ;
}
if ( ( prev = = LOWER & & curr = = UPPER ) | |
( prev ! = DIGIT & & curr = = DIGIT ) ) {
return CAMEL_SCORE ;
}
if ( curr = = NON_WORD ) {
return NON_WORD_SCORE ;
}
return 0 ;
}
int rofi_scorer_fuzzy_evaluate ( const char * pattern , glong plen , const char * str , glong slen )
{
if ( slen > FUZZY_SCORER_MAX_LENGTH ) {
return - MIN_SCORE ;
}
glong pi , si ;
// whether we are aligning the first character of pattern
gboolean pfirst = TRUE ;
// whether the start of a word in pattern
gboolean pstart = TRUE ;
// score for each position
int * score = g_malloc_n ( slen , sizeof ( int ) ) ;
// dp[i]: maximum value by aligning pattern[0..pi] to str[0..si]
int * dp = g_malloc_n ( slen , sizeof ( int ) ) ;
// uleft: value of the upper left cell; ulefts: maximum value of uleft and cells on the left. The arbitrary initial
// values suppress warnings.
int uleft = 0 , ulefts = 0 , left , lefts ;
2017-06-03 14:35:50 -04:00
const gchar * pit = pattern , * sit ;
enum CharClass prev = NON_WORD ;
2017-01-11 03:20:19 -05:00
for ( si = 0 , sit = str ; si < slen ; si + + , sit = g_utf8_next_char ( sit ) ) {
2017-04-27 16:59:14 -04:00
enum CharClass cur = rofi_scorer_get_character_class ( g_utf8_get_char ( sit ) ) ;
2017-01-11 03:20:19 -05:00
score [ si ] = rofi_scorer_get_score_for ( prev , cur ) ;
prev = cur ;
dp [ si ] = MIN_SCORE ;
}
for ( pi = 0 ; pi < plen ; pi + + , pit = g_utf8_next_char ( pit ) ) {
gunichar pc = g_utf8_get_char ( pit ) , sc ;
if ( g_unichar_isspace ( pc ) ) {
pstart = TRUE ;
continue ;
}
lefts = MIN_SCORE ;
for ( si = 0 , sit = str ; si < slen ; si + + , sit = g_utf8_next_char ( sit ) ) {
left = dp [ si ] ;
lefts = MAX ( lefts + GAP_SCORE , left ) ;
sc = g_utf8_get_char ( sit ) ;
if ( config . case_sensitive
? pc = = sc
: g_unichar_tolower ( pc ) = = g_unichar_tolower ( sc ) ) {
int t = score [ si ] * ( pstart ? PATTERN_START_MULTIPLIER : PATTERN_NON_START_MULTIPLIER ) ;
dp [ si ] = pfirst
? LEADING_GAP_SCORE * si + t
: MAX ( uleft + CONSECUTIVE_SCORE , ulefts + t ) ;
}
else {
dp [ si ] = MIN_SCORE ;
}
uleft = left ;
ulefts = lefts ;
}
pfirst = pstart = FALSE ;
}
lefts = MIN_SCORE ;
for ( si = 0 ; si < slen ; si + + ) {
lefts = MAX ( lefts + GAP_SCORE , dp [ si ] ) ;
}
g_free ( score ) ;
g_free ( dp ) ;
return - lefts ;
}
2017-01-26 13:46:46 -05:00
/**
* @ param a UTF - 8 string to compare
* @ param b UTF - 8 string to compare
* @ param n Maximum number of characters to compare
*
* Compares the ` G_NORMALIZE_ALL_COMPOSE ` forms of the two strings .
*
* @ returns less than , equal to , or greater than zero if the first ` n ` characters ( not bytes ) of ` a `
* are found , respectively , to be less than , to match , or be greater than the first ` n `
* characters ( not bytes ) of ` b ` .
*/
int utf8_strncmp ( const char * a , const char * b , size_t n )
{
char * na = g_utf8_normalize ( a , - 1 , G_NORMALIZE_ALL_COMPOSE ) ;
char * nb = g_utf8_normalize ( b , - 1 , G_NORMALIZE_ALL_COMPOSE ) ;
* g_utf8_offset_to_pointer ( na , n ) = ' \0 ' ;
* g_utf8_offset_to_pointer ( nb , n ) = ' \0 ' ;
int r = g_utf8_collate ( na , nb ) ;
g_free ( na ) ;
g_free ( nb ) ;
return r ;
}
2017-02-17 08:06:31 -05:00
2017-06-02 08:54:21 -04:00
gboolean helper_execute ( const char * wd , char * * args , const char * error_precmd , const char * error_cmd , RofiHelperExecuteContext * context )
2017-02-17 08:06:31 -05:00
{
2016-03-11 04:43:32 -05:00
gboolean retv = TRUE ;
GError * error = NULL ;
GSpawnChildSetupFunc child_setup = NULL ;
gpointer user_data = NULL ;
2017-06-02 08:54:21 -04:00
display_startup_notification ( context , & child_setup , & user_data ) ;
2016-03-11 04:43:32 -05:00
g_spawn_async ( wd , args , NULL , G_SPAWN_SEARCH_PATH , child_setup , user_data , NULL , & error ) ;
2017-02-17 08:06:31 -05:00
if ( error ! = NULL ) {
2016-03-11 04:43:32 -05:00
char * msg = g_strdup_printf ( " Failed to execute: '%s%s' \n Error: '%s' " , error_precmd , error_cmd , error - > message ) ;
2017-02-17 08:06:31 -05:00
rofi_view_error_dialog ( msg , FALSE ) ;
g_free ( msg ) ;
// print error.
g_error_free ( error ) ;
retv = FALSE ;
}
// Free the args list.
g_strfreev ( args ) ;
return retv ;
}
2017-04-17 11:46:01 -04:00
2017-06-02 08:54:21 -04:00
gboolean helper_execute_command ( const char * wd , const char * cmd , gboolean run_in_term , RofiHelperExecuteContext * context )
2016-03-11 04:43:32 -05:00
{
char * * args = NULL ;
int argc = 0 ;
if ( run_in_term ) {
2017-06-13 04:15:21 -04:00
helper_parse_setup ( config . run_shell_command , & args , & argc , " {cmd} " , cmd , ( char * ) 0 ) ;
2016-03-11 04:43:32 -05:00
}
else {
2017-06-13 04:15:21 -04:00
helper_parse_setup ( config . run_command , & args , & argc , " {cmd} " , cmd , ( char * ) 0 ) ;
2016-03-11 04:43:32 -05:00
}
2020-05-04 08:10:35 -04:00
if ( args = = NULL ) {
return FALSE ;
}
2017-06-02 08:54:21 -04:00
if ( context ! = NULL ) {
if ( context - > name = = NULL ) {
context - > name = args [ 0 ] ;
}
if ( context - > binary = = NULL ) {
context - > binary = args [ 0 ] ;
}
if ( context - > description = = NULL ) {
gsize l = strlen ( " Launching '' via rofi " ) + strlen ( cmd ) + 1 ;
gchar * description = g_newa ( gchar , l ) ;
g_snprintf ( description , l , " Launching '%s' via rofi " , cmd ) ;
context - > description = description ;
}
if ( context - > command = = NULL ) {
context - > command = cmd ;
}
}
return helper_execute ( wd , args , " " , cmd , context ) ;
2016-03-11 04:43:32 -05:00
}
2017-04-17 11:46:01 -04:00
char * helper_get_theme_path ( const char * file )
{
char * filename = rofi_expand_path ( file ) ;
2017-06-22 02:54:29 -04:00
g_debug ( " Opening theme, testing: %s \n " , filename ) ;
2017-04-17 11:46:01 -04:00
if ( g_file_test ( filename , G_FILE_TEST_EXISTS ) ) {
return filename ;
}
g_free ( filename ) ;
if ( g_str_has_suffix ( file , " .rasi " ) ) {
filename = g_strdup ( file ) ;
2017-04-27 16:59:14 -04:00
}
else {
2017-04-17 11:46:01 -04:00
filename = g_strconcat ( file , " .rasi " , NULL ) ;
}
2019-08-04 15:04:26 -04:00
// Check config's themes directory.
2017-04-17 11:46:01 -04:00
const char * cpath = g_get_user_config_dir ( ) ;
2019-08-04 15:04:26 -04:00
if ( cpath ) {
char * themep = g_build_filename ( cpath , " rofi " , " themes " , filename , NULL ) ;
g_debug ( " Opening theme, testing: %s \n " , themep ) ;
if ( themep & & g_file_test ( themep , G_FILE_TEST_EXISTS ) ) {
g_free ( filename ) ;
return themep ;
}
g_free ( themep ) ;
}
// Check config directory.
2017-04-17 11:46:01 -04:00
if ( cpath ) {
char * themep = g_build_filename ( cpath , " rofi " , filename , NULL ) ;
2017-06-22 02:54:29 -04:00
g_debug ( " Opening theme, testing: %s \n " , themep ) ;
2017-04-17 11:46:01 -04:00
if ( g_file_test ( themep , G_FILE_TEST_EXISTS ) ) {
g_free ( filename ) ;
return themep ;
}
g_free ( themep ) ;
}
2017-06-25 14:25:17 -04:00
const char * datadir = g_get_user_data_dir ( ) ;
2017-06-22 02:54:29 -04:00
if ( datadir ) {
char * theme_path = g_build_filename ( datadir , " rofi " , " themes " , filename , NULL ) ;
g_debug ( " Opening theme, testing: %s \n " , theme_path ) ;
if ( theme_path ) {
if ( g_file_test ( theme_path , G_FILE_TEST_EXISTS ) ) {
g_free ( filename ) ;
return theme_path ;
}
g_free ( theme_path ) ;
}
}
2017-04-17 11:46:01 -04:00
char * theme_path = g_build_filename ( THEME_DIR , filename , NULL ) ;
if ( theme_path ) {
2017-06-22 02:54:29 -04:00
g_debug ( " Opening theme, testing: %s \n " , theme_path ) ;
2017-04-17 11:46:01 -04:00
if ( g_file_test ( theme_path , G_FILE_TEST_EXISTS ) ) {
g_free ( filename ) ;
return theme_path ;
}
g_free ( theme_path ) ;
}
return filename ;
}
2017-06-01 07:36:11 -04:00
2021-01-17 10:09:58 -05:00
static gboolean parse_pair ( char * input , rofi_range_pair * item )
2017-09-27 14:00:33 -04:00
{
2019-07-02 15:27:46 -04:00
// Skip leading blanks.
2020-02-02 07:56:37 -05:00
while ( input ! = NULL & & isblank ( * input ) ) {
2019-07-02 15:27:46 -04:00
+ + input ;
2020-02-02 07:56:37 -05:00
}
2019-07-02 15:27:46 -04:00
2021-01-17 10:09:58 -05:00
if ( input = = NULL ) {
return FALSE ;
}
2020-02-02 07:56:37 -05:00
const char * sep [ ] = { " - " , " : " } ;
int pythonic = ( strchr ( input , ' : ' ) | | input [ 0 ] = = ' - ' ) ? 1 : 0 ;
int index = 0 ;
2019-07-02 15:27:46 -04:00
for ( char * token = strsep ( & input , sep [ pythonic ] ) ; token ! = NULL ; token = strsep ( & input , sep [ pythonic ] ) ) {
2017-09-27 14:00:33 -04:00
if ( index = = 0 ) {
2019-07-02 15:27:46 -04:00
item - > start = item - > stop = ( int ) strtol ( token , NULL , 10 ) ;
2017-09-27 14:00:33 -04:00
index + + ;
2019-07-02 15:27:46 -04:00
continue ;
2017-09-27 14:00:33 -04:00
}
2019-07-02 15:27:46 -04:00
if ( token [ 0 ] = = ' \0 ' ) {
item - > stop = - 1 ;
continue ;
}
item - > stop = ( int ) strtol ( token , NULL , 10 ) ;
if ( pythonic ) {
- - item - > stop ;
2017-09-27 14:00:33 -04:00
}
}
2021-01-17 10:09:58 -05:00
return TRUE ;
2017-09-27 14:00:33 -04:00
}
void parse_ranges ( char * input , rofi_range_pair * * list , unsigned int * length )
{
char * endp ;
if ( input = = NULL ) {
return ;
}
const char * const sep = " , " ;
for ( char * token = strtok_r ( input , sep , & endp ) ; token ! = NULL ; token = strtok_r ( NULL , sep , & endp ) ) {
// Make space.
* list = g_realloc ( ( * list ) , ( ( * length ) + 1 ) * sizeof ( struct rofi_range_pair ) ) ;
// Parse a single pair.
2021-01-17 10:09:58 -05:00
if ( parse_pair ( token , & ( ( * list ) [ * length ] ) ) ) {
( * length ) + + ;
}
2017-09-27 14:00:33 -04:00
}
}
void rofi_output_formatted_line ( const char * format , const char * string , int selected_line , const char * filter )
{
for ( int i = 0 ; format & & format [ i ] ; i + + ) {
if ( format [ i ] = = ' i ' ) {
fprintf ( stdout , " %d " , selected_line ) ;
}
else if ( format [ i ] = = ' d ' ) {
fprintf ( stdout , " %d " , ( selected_line + 1 ) ) ;
}
else if ( format [ i ] = = ' s ' ) {
fputs ( string , stdout ) ;
}
2019-05-04 04:24:59 -04:00
else if ( format [ i ] = = ' p ' ) {
char * esc = NULL ;
2020-02-02 07:56:37 -05:00
pango_parse_markup ( string , - 1 , 0 , NULL , & esc , NULL , NULL ) ;
if ( esc ) {
2019-05-04 04:24:59 -04:00
fputs ( esc , stdout ) ;
g_free ( esc ) ;
2020-02-02 07:56:37 -05:00
}
else {
fputs ( " invalid string " , stdout ) ;
2019-05-04 04:24:59 -04:00
}
}
2017-09-27 14:00:33 -04:00
else if ( format [ i ] = = ' q ' ) {
char * quote = g_shell_quote ( string ) ;
fputs ( quote , stdout ) ;
g_free ( quote ) ;
}
else if ( format [ i ] = = ' f ' ) {
if ( filter ) {
fputs ( filter , stdout ) ;
}
}
else if ( format [ i ] = = ' F ' ) {
if ( filter ) {
char * quote = g_shell_quote ( filter ) ;
fputs ( quote , stdout ) ;
g_free ( quote ) ;
}
}
else {
fputc ( format [ i ] , stdout ) ;
}
}
fputc ( ' \n ' , stdout ) ;
fflush ( stdout ) ;
}
2018-10-16 15:01:45 -04:00
static gboolean helper_eval_cb2 ( const GMatchInfo * info , GString * res , gpointer data )
{
gchar * match ;
// Get the match
2020-02-02 07:56:37 -05:00
int num_match = g_match_info_get_match_count ( info ) ;
2018-10-16 15:01:45 -04:00
// Just {text} This is inside () 5.
if ( num_match = = 5 ) {
2020-02-02 07:56:37 -05:00
match = g_match_info_fetch ( info , 4 ) ;
2018-10-16 15:01:45 -04:00
if ( match ! = NULL ) {
// Lookup the match, so we can replace it.
gchar * r = g_hash_table_lookup ( ( GHashTable * ) data , match ) ;
if ( r ! = NULL ) {
// Append the replacement to the string.
g_string_append ( res , r ) ;
}
// Free match.
g_free ( match ) ;
}
}
// {} with [] guard around it.
else if ( num_match = = 4 ) {
2020-02-02 07:56:37 -05:00
match = g_match_info_fetch ( info , 2 ) ;
2018-10-16 15:01:45 -04:00
if ( match ! = NULL ) {
// Lookup the match, so we can replace it.
gchar * r = g_hash_table_lookup ( ( GHashTable * ) data , match ) ;
if ( r ! = NULL ) {
// Add (optional) prefix
2020-02-02 07:56:37 -05:00
gchar * prefix = g_match_info_fetch ( info , 1 ) ;
2018-10-16 15:01:45 -04:00
g_string_append ( res , prefix ) ;
2020-02-02 07:56:37 -05:00
g_free ( prefix ) ;
2018-10-16 15:01:45 -04:00
// Append the replacement to the string.
g_string_append ( res , r ) ;
// Add (optional) postfix
2020-02-02 07:56:37 -05:00
gchar * post = g_match_info_fetch ( info , 3 ) ;
2018-10-16 15:01:45 -04:00
g_string_append ( res , post ) ;
2020-02-02 07:56:37 -05:00
g_free ( post ) ;
2018-10-16 15:01:45 -04:00
}
// Free match.
g_free ( match ) ;
}
}
// Else we have an invalid match.
// Continue replacement.
return FALSE ;
}
char * helper_string_replace_if_exists ( char * string , . . . )
{
GHashTable * h ;
h = g_hash_table_new ( g_str_hash , g_str_equal ) ;
2020-02-02 07:56:37 -05:00
va_list ap ;
2018-10-16 15:01:45 -04:00
va_start ( ap , string ) ;
2019-02-10 07:55:09 -05:00
// Add list from variable arguments.
2018-10-16 15:01:45 -04:00
while ( 1 ) {
char * key = va_arg ( ap , char * ) ;
if ( key = = ( char * ) 0 ) {
break ;
}
char * value = va_arg ( ap , char * ) ;
g_hash_table_insert ( h , key , value ) ;
}
2019-02-10 07:55:09 -05:00
char * retv = helper_string_replace_if_exists_v ( string , h ) ;
2018-10-16 15:01:45 -04:00
va_end ( ap ) ;
2019-02-10 07:55:09 -05:00
// Destroy key-value storage.
g_hash_table_destroy ( h ) ;
return retv ;
}
2019-02-10 08:26:06 -05:00
/**
* @ param string The string with elements to be replaced
* @ param h Hash table with set of { key } , value that will be replaced , terminated by a NULL
*
* Items { key } are replaced by the value if ' { key } ' is passed as key / value pair , otherwise removed from string .
* If the { key } is in between [ ] all the text between [ ] are removed if { key }
* is not found . Otherwise key is replaced and [ & ] removed .
*
* This allows for optional replacement , f . e . ' { ssh - client } [ - t { title } ] - e
* " {cmd} " ' the ' - t { title } ' is only there if { title } is set .
*
* @ returns a new string with the keys replaced .
*/
2019-02-10 07:55:09 -05:00
char * helper_string_replace_if_exists_v ( char * string , GHashTable * h )
{
2020-02-02 07:56:37 -05:00
GError * error = NULL ;
char * res = NULL ;
2018-10-16 15:01:45 -04:00
// Replace hits within {-\w+}.
2021-06-15 08:32:46 -04:00
GRegex * reg = g_regex_new ( " \\ [(.*)({[- \\ w]+})(.*) \\ ]|({[ \\ w-]+}) " , G_REGEX_UNGREEDY , 0 , & error ) ;
2020-02-02 07:56:37 -05:00
if ( error = = NULL ) {
2019-02-10 08:32:21 -05:00
res = g_regex_replace_eval ( reg , string , - 1 , 0 , 0 , helper_eval_cb2 , h , & error ) ;
}
2018-10-16 15:01:45 -04:00
// Free regex.
g_regex_unref ( reg ) ;
// Throw error if shell parsing fails.
2019-02-10 08:26:06 -05:00
if ( error ! = NULL ) {
2018-10-16 15:01:45 -04:00
char * msg = g_strdup_printf ( " Failed to parse: '%s' \n Error: '%s' " , string , error - > message ) ;
rofi_view_error_dialog ( msg , FALSE ) ;
g_free ( msg ) ;
// print error.
g_error_free ( error ) ;
g_free ( res ) ;
return NULL ;
}
return res ;
}