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>
|
|
|
|
* Copyright © 2013-2017 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
|
|
|
|
|
|
|
#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-02-27 14:45:47 -05:00
|
|
|
#include <xcb/xcb.h>
|
2016-03-01 13:48:18 -05:00
|
|
|
#include <pango/pango.h>
|
|
|
|
#include <pango/pango-fontmap.h>
|
|
|
|
#include <pango/pangocairo.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-10-18 07:40:39 -04:00
|
|
|
#include "x11-helper.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
|
|
|
|
|
|
|
void cmd_set_arguments ( int argc, char **argv )
|
|
|
|
{
|
|
|
|
stored_argc = argc;
|
|
|
|
stored_argv = argv;
|
|
|
|
}
|
|
|
|
|
2014-09-03 07:07:26 -04:00
|
|
|
/**
|
2015-03-05 14:26:52 -05:00
|
|
|
* @param info To Match information on.
|
2014-11-25 06:57:34 -05:00
|
|
|
* @param res The string being generated.
|
|
|
|
* @param data User data
|
|
|
|
*
|
|
|
|
* Replace the entries. This function gets called by g_regex_replace_eval.
|
|
|
|
*
|
|
|
|
* @returns TRUE to stop replacement, FALSE to continue
|
2014-09-03 07:07:26 -04:00
|
|
|
*/
|
2015-09-19 06:57:48 -04:00
|
|
|
static gboolean helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data )
|
2014-09-03 07:07:26 -04:00
|
|
|
{
|
2015-01-15 11:59:59 -05:00
|
|
|
gchar *match;
|
2014-11-25 06:57:34 -05:00
|
|
|
// Get the match
|
2014-09-03 07:07:26 -04:00
|
|
|
match = g_match_info_fetch ( info, 0 );
|
2014-11-25 06:57:34 -05:00
|
|
|
if ( match != NULL ) {
|
|
|
|
// Lookup the match, so we can replace it.
|
2015-01-15 11:59:59 -05:00
|
|
|
gchar *r = g_hash_table_lookup ( (GHashTable *) data, match );
|
2014-11-25 06:57:34 -05:00
|
|
|
if ( r != NULL ) {
|
|
|
|
// Append the replacement to the string.
|
|
|
|
g_string_append ( res, r );
|
|
|
|
}
|
|
|
|
// Free match.
|
2014-09-03 07:07:26 -04:00
|
|
|
g_free ( match );
|
|
|
|
}
|
2014-11-25 06:57:34 -05:00
|
|
|
// Continue replacement.
|
2014-09-03 07:07:26 -04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 * );
|
|
|
|
if ( key == NULL ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
char *value = va_arg ( ap, char * );
|
|
|
|
if ( value == NULL ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
g_hash_table_insert ( h, key, value );
|
|
|
|
}
|
|
|
|
va_end ( ap );
|
|
|
|
|
|
|
|
// Replace hits within {-\w+}.
|
|
|
|
GRegex *reg = g_regex_new ( "{[-\\w]+}", 0, 0, NULL );
|
2015-09-19 06:57:48 -04:00
|
|
|
char *res = g_regex_replace_eval ( reg, string, -1, 0, 0, helper_eval_cb, h, NULL );
|
2014-11-25 06:57:34 -05:00
|
|
|
// Free regex.
|
2014-09-03 07:07:26 -04:00
|
|
|
g_regex_unref ( reg );
|
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'\nError: '%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
|
|
|
|
2016-05-22 11:47:34 -04:00
|
|
|
void tokenize_free ( GRegex ** 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++ ) {
|
|
|
|
g_regex_unref ( (GRegex *) 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 {
|
|
|
|
g_string_append ( str, ".*(" );
|
|
|
|
}
|
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
|
|
|
|
|
|
|
// Macro for quickly generating regex for matching.
|
|
|
|
static inline GRegex * R ( const char *s, int case_sensitive )
|
|
|
|
{
|
|
|
|
return g_regex_new ( s, G_REGEX_OPTIMIZE | ( ( case_sensitive ) ? 0 : G_REGEX_CASELESS ), 0, NULL );
|
|
|
|
}
|
|
|
|
|
2016-05-21 04:37:23 -04:00
|
|
|
static GRegex * create_regex ( const char *input, int case_sensitive )
|
2015-11-17 10:10:14 -05:00
|
|
|
{
|
2016-06-19 12:02:49 -04:00
|
|
|
GRegex * retv = NULL;
|
2016-11-14 10:25:04 -05:00
|
|
|
gchar *r;
|
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;
|
|
|
|
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
|
|
|
}
|
2016-05-21 04:37:23 -04:00
|
|
|
return retv;
|
2015-11-17 10:10:14 -05:00
|
|
|
}
|
2016-05-22 11:47:34 -04:00
|
|
|
GRegex **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;
|
|
|
|
}
|
|
|
|
|
2016-05-26 02:39:33 -04:00
|
|
|
char *saveptr = NULL, *token;
|
2017-04-02 06:32:11 -04:00
|
|
|
GRegex **retv = NULL;
|
2015-10-04 10:37:07 -04:00
|
|
|
if ( !config.tokenize ) {
|
2016-05-22 11:47:34 -04:00
|
|
|
retv = g_malloc0 ( sizeof ( GRegex* ) * 2 );
|
|
|
|
retv[0] = (GRegex *) 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 ) ) {
|
2016-05-22 11:47:34 -04:00
|
|
|
retv = g_realloc ( retv, sizeof ( GRegex* ) * ( num_tokens + 2 ) );
|
2016-05-26 02:39:33 -04:00
|
|
|
retv[num_tokens] = (GRegex *) 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-01-08 15:36:06 -05:00
|
|
|
if ( strcasecmp ( stored_argv[i], key ) == 0 && i < ( stored_argc - 1 ) ) {
|
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-01-08 15:36:06 -05:00
|
|
|
if ( strcasecmp ( stored_argv[i], key ) == 0 && i < ( stored_argc - 1 ) ) {
|
|
|
|
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-02-17 08:06:31 -05:00
|
|
|
PangoAttrList *helper_token_match_get_pango_attr ( ThemeHighlight th, GRegex **tokens, const char *input, PangoAttrList *retv )
|
2016-05-10 12:02:23 -04:00
|
|
|
{
|
|
|
|
// Do a tokenized match.
|
|
|
|
if ( tokens ) {
|
|
|
|
for ( int j = 0; tokens[j]; j++ ) {
|
|
|
|
GMatchInfo *gmi = NULL;
|
|
|
|
g_regex_match ( (GRegex *) tokens[j], input, G_REGEX_MATCH_PARTIAL, &gmi );
|
|
|
|
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-01-08 18:09:02 -05:00
|
|
|
if ( th.style & HL_BOLD ) {
|
|
|
|
PangoAttribute *pa = pango_attr_weight_new ( PANGO_WEIGHT_BOLD );
|
|
|
|
pa->start_index = start;
|
|
|
|
pa->end_index = end;
|
|
|
|
pango_attr_list_insert ( retv, pa );
|
|
|
|
}
|
|
|
|
if ( th.style & HL_UNDERLINE ) {
|
|
|
|
PangoAttribute *pa = pango_attr_underline_new ( PANGO_UNDERLINE_SINGLE );
|
|
|
|
pa->start_index = start;
|
|
|
|
pa->end_index = end;
|
|
|
|
pango_attr_list_insert ( retv, pa );
|
|
|
|
}
|
|
|
|
if ( th.style & HL_ITALIC ) {
|
|
|
|
PangoAttribute *pa = pango_attr_style_new ( PANGO_STYLE_ITALIC );
|
|
|
|
pa->start_index = start;
|
|
|
|
pa->end_index = end;
|
|
|
|
pango_attr_list_insert ( retv, pa );
|
|
|
|
}
|
|
|
|
if ( th.style & HL_COLOR ) {
|
|
|
|
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 );
|
|
|
|
}
|
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-02-17 03:58:47 -05:00
|
|
|
int helper_token_match ( GRegex * 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 ) {
|
|
|
|
for ( int j = 0; match && tokens[j]; j++ ) {
|
|
|
|
match = g_regex_match ( (const GRegex *) tokens[j], input, 0, NULL );
|
|
|
|
}
|
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;
|
|
|
|
helper_parse_setup ( config.run_command, &args, &argv, "{cmd}", cmd, NULL );
|
|
|
|
|
|
|
|
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'\nError: '%s'", cmd, error->message );
|
2016-04-19 16:10:34 -04:00
|
|
|
fputs ( msg, stderr );
|
|
|
|
fputs ( "\n", stderr );
|
|
|
|
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
|
|
|
|
|
|
|
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;;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
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",
|
|
|
|
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
|
|
|
}
|
|
|
|
if ( config.menu_columns == 0 ) {
|
2015-09-19 06:57:48 -04:00
|
|
|
g_string_append_printf ( msg, "\t<b>config.menu_columns</b>=%d is invalid. You need at least one visible column.\n",
|
|
|
|
config.menu_columns );
|
2015-07-28 16:22:18 -04:00
|
|
|
config.menu_columns = 1;
|
|
|
|
found_error = TRUE;
|
2015-02-03 02:21:59 -05:00
|
|
|
}
|
|
|
|
if ( config.menu_width == 0 ) {
|
2015-10-18 07:40:39 -04:00
|
|
|
g_string_append_printf ( msg, "<b>config.menu_width</b>=0 is invalid. You cannot have a window with no width." );
|
2015-07-28 16:22:18 -04:00
|
|
|
config.menu_columns = 50;
|
|
|
|
found_error = TRUE;
|
2015-02-03 02:21:59 -05:00
|
|
|
}
|
|
|
|
if ( !( config.location >= WL_CENTER && config.location <= WL_WEST ) ) {
|
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",
|
|
|
|
config.location, WL_CENTER, WL_WEST );
|
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.
|
|
|
|
config.location = 1;
|
|
|
|
config.fullscreen = 0;
|
|
|
|
}
|
|
|
|
|
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-06 13:55:47 -04:00
|
|
|
if ( needlelen == G_MAXLONG ){
|
|
|
|
// String to long, we cannot handle this.
|
|
|
|
return UINT_MAX;
|
|
|
|
}
|
2016-01-04 11:14:15 -05:00
|
|
|
unsigned int column[needlelen + 1];
|
2017-01-18 03:43:44 -05:00
|
|
|
for ( glong y = 0; y <= needlelen; y++ ) {
|
2016-01-04 11:14:15 -05:00
|
|
|
column[y] = y;
|
|
|
|
}
|
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 );
|
|
|
|
}
|
|
|
|
|
2016-06-21 16:40:42 -04:00
|
|
|
char * rofi_force_utf8 ( gchar *start, ssize_t length )
|
2016-04-10 06:05:34 -04:00
|
|
|
{
|
|
|
|
if ( start == NULL ) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
const char *data = start;
|
|
|
|
const char *end;
|
|
|
|
GString *string;
|
|
|
|
|
|
|
|
if ( g_utf8_validate ( data, length, &end ) ) {
|
2016-06-21 16:40:42 -04:00
|
|
|
return g_memdup ( start, 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param pattern The user input to match against.
|
|
|
|
* @param plen Pattern length.
|
|
|
|
* @param str The input to match against pattern.
|
|
|
|
* @param slen Lenght of str.
|
|
|
|
*
|
|
|
|
* rofi_scorer_fuzzy_evaluate implements a global sequence alignment algorithm to find the maximum accumulated score by
|
|
|
|
* aligning `pattern` to `str`. It applies when `pattern` is a subsequence of `str`.
|
|
|
|
*
|
|
|
|
* Scoring criteria
|
|
|
|
* - Prefer matches at the start of a word, or the start of subwords in CamelCase/camelCase/camel123 words. See WORD_START_SCORE/CAMEL_SCORE.
|
|
|
|
* - Non-word characters matter. See NON_WORD_SCORE.
|
|
|
|
* - The first characters of words of `pattern` receive bonus because they usually have more significance than the rest.
|
|
|
|
* See PATTERN_START_MULTIPLIER/PATTERN_NON_START_MULTIPLIER.
|
|
|
|
* - Superfluous characters in `str` will reduce the score (gap penalty). See GAP_SCORE.
|
|
|
|
* - Prefer early occurrence of the first character. See LEADING_GAP_SCORE/GAP_SCORE.
|
|
|
|
*
|
|
|
|
* The recurrence of the dynamic programming:
|
|
|
|
* dp[i][j]: maximum accumulated score by aligning pattern[0..i] to str[0..j]
|
|
|
|
* dp[0][j] = leading_gap_penalty(0, j) + score[j]
|
|
|
|
* dp[i][j] = max(dp[i-1][j-1] + CONSECUTIVE_SCORE, max(dp[i-1][k] + gap_penalty(k+1, j) + score[j] : k < j))
|
|
|
|
*
|
|
|
|
* The first dimension can be suppressed since we do not need a matching scheme, which reduces the space complexity from
|
|
|
|
* O(N*M) to O(M)
|
|
|
|
*
|
|
|
|
* @returns the sorting weight.
|
|
|
|
*/
|
|
|
|
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-04-02 06:32:11 -04:00
|
|
|
const gchar *pit = pattern, *sit;
|
|
|
|
enum CharClass prev = NON_WORD, cur;
|
2017-01-11 03:20:19 -05:00
|
|
|
for ( si = 0, sit = str; si < slen; si++, sit = g_utf8_next_char ( sit ) ) {
|
|
|
|
cur = rofi_scorer_get_character_class ( g_utf8_get_char ( sit ) );
|
|
|
|
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
|
|
|
|
|
|
|
int helper_execute_command ( const char *wd, const char *cmd, int run_in_term )
|
|
|
|
{
|
|
|
|
int retv = TRUE;
|
|
|
|
char **args = NULL;
|
|
|
|
int argc = 0;
|
|
|
|
if ( run_in_term ) {
|
|
|
|
helper_parse_setup ( config.run_shell_command, &args, &argc, "{cmd}", cmd, NULL );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
helper_parse_setup ( config.run_command, &args, &argc, "{cmd}", cmd, NULL );
|
|
|
|
}
|
|
|
|
GError *error = NULL;
|
|
|
|
g_spawn_async ( wd, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error );
|
|
|
|
if ( error != NULL ) {
|
|
|
|
char *msg = g_strdup_printf ( "Failed to execute: '%s'\nError: '%s'", cmd, error->message );
|
|
|
|
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;
|
|
|
|
}
|