diff --git a/Changelog b/Changelog index 4689b2f6..78bf9737 100644 --- a/Changelog +++ b/Changelog @@ -14,6 +14,8 @@ - On broken UTF-8 show everything up to the broken character. (#121) Others: - Significant code refactoring. + - Rewriting of config parser, Commandline Parser uses structure from Xresource parser. + Avoiding duplication and making it easier to maintain. Performance improvements: - Fix blocking on grabbing keyboard. diff --git a/Makefile.am b/Makefile.am index d4695f4b..25cc0073 100644 --- a/Makefile.am +++ b/Makefile.am @@ -128,6 +128,8 @@ helper_test_SOURCES=\ include/rofi.h\ source/helper.c\ include/helper.h\ + source/xrmoptions.h\ + source/xrmoptions.c\ test/helper-test.c .PHONY: test diff --git a/include/helper.h b/include/helper.h index 909ecb3f..b985c2fe 100644 --- a/include/helper.h +++ b/include/helper.h @@ -146,19 +146,12 @@ int execute_generator ( char * cmd ) __attribute__( ( nonnull ) ); */ void create_pid_file ( const char *pidfile ); -/** - * @param argc Number of arguments. - * @param argv Array of arguments. - * - * Parse commandline options. - */ -void config_parse_cmd_options ( int argc, char ** argv ); - /** * 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. */ -void config_sanity_check ( void ); +void config_sanity_check ( int argc, char **argv ); +char helper_parse_char ( const char *arg ); #endif // __HELPER_H__ diff --git a/include/xrmoptions.h b/include/xrmoptions.h index b7efae8e..35428692 100644 --- a/include/xrmoptions.h +++ b/include/xrmoptions.h @@ -8,7 +8,8 @@ typedef enum xrm_String = 0, xrm_Number = 1, xrm_SNumber = 2, - xrm_Boolean = 3 + xrm_Boolean = 3, + xrm_Char = 4 } XrmOptionType; /** @@ -19,6 +20,21 @@ typedef enum */ void config_parse_xresource_options ( Display *display ); + +/** + * @param argc Number of arguments. + * @param argv Array of arguments. + * + * Parse commandline options. + */ +void config_parse_cmd_options ( int argc, char ** argv ); +/** + * @param argc Number of arguments. + * @param argv Array of arguments. + * + * Parse dynamic commandline options. + */ +void config_parse_cmd_options_dynamic ( int argc, char ** argv ); /** * @param display Handler of the display to fetch the settings from. * @@ -38,5 +54,8 @@ void config_xresource_free ( void ); */ void xresource_dump ( void ); +/** + * Add option to the dynamic option parser. + */ void config_parser_add_option ( XrmOptionType type, const char *key, void **value ); #endif diff --git a/source/helper.c b/source/helper.c index d81d537e..f439529e 100644 --- a/source/helper.c +++ b/source/helper.c @@ -252,60 +252,65 @@ int find_arg_uint ( const int argc, char * const argv[], const char * const key, return FALSE; } +char helper_parse_char ( const char *arg ) +{ + char retv = -1; + int len = strlen ( arg ); + // If the length is 1, it is not escaped. + if ( len == 1 ) { + retv = arg[0]; + } + // If the length is 2 and the first character is '\', we unescape it. + else if ( len == 2 && arg[0] == '\\' ) { + // New line + if ( arg[1] == 'n' ) { + retv = '\n'; + } + // Bell + else if ( arg[1] == 'a' ) { + retv = '\a'; + } + // Backspace + else if ( arg[1] == 'b' ) { + retv = '\b'; + } + // Tab + else if ( arg[1] == 't' ) { + retv = '\t'; + } + // Vertical tab + else if ( arg[1] == 'v' ) { + retv = '\v'; + } + // Form feed + else if ( arg[1] == 'f' ) { + retv = '\f'; + } + // Carriage return + else if ( arg[1] == 'r' ) { + retv = '\r'; + } + // Forward slash + else if ( arg[1] == '\\' ) { + retv = '\\'; + } + } + else if ( len > 2 && arg[0] == '\\' && arg[1] == 'x' ) { + retv = (char) strtol ( &arg[2], NULL, 16 ); + } + if ( retv < 0 ) { + fprintf ( stderr, "Failed to parse character string: \"%s\"\n", arg ); + exit ( 1 ); + } + return retv; +} + int find_arg_char ( const int argc, char * const argv[], const char * const key, char *val ) { int i = find_arg ( argc, argv, key ); if ( val != NULL && i > 0 && i < ( argc - 1 ) ) { - int len = strlen ( argv[i + 1] ); - // If the length is 1, it is not escaped. - if ( len == 1 ) { - *val = argv[i + 1][0]; - } - // If the length is 2 and the first character is '\', we unescape it. - else if ( len == 2 && argv[i + 1][0] == '\\' ) { - // New line - if ( argv[i + 1][1] == 'n' ) { - *val = '\n'; - } - // Bell - else if ( argv[i + 1][1] == 'a' ) { - *val = '\a'; - } - // Backspace - else if ( argv[i + 1][1] == 'b' ) { - *val = '\b'; - } - // Tab - else if ( argv[i + 1][1] == 't' ) { - *val = '\t'; - } - // Vertical tab - else if ( argv[i + 1][1] == 'v' ) { - *val = '\v'; - } - // Form feed - else if ( argv[i + 1][1] == 'f' ) { - *val = '\f'; - } - // Carriage return - else if ( argv[i + 1][1] == 'r' ) { - *val = '\r'; - } - // Forward slash - else if ( argv[i + 1][1] == '\\' ) { - *val = '\\'; - } - // Otherwise it is not valid and throw error - else { - fprintf ( stderr, "Failed to parse command-line argument." ); - exit ( 1 ); - } - } - else{ - fprintf ( stderr, "Failed to parse command-line argument." ); - exit ( 1 ); - } + *val = helper_parse_char ( argv[i + 1] ); return TRUE; } return FALSE; @@ -405,84 +410,25 @@ void create_pid_file ( const char *pidfile ) } } -void config_parse_cmd_options ( int argc, char ** argv ) -{ - if ( find_arg ( argc, argv, "-rnow") >= 0 || - find_arg ( argc, argv, "-snow") >= 0 || - find_arg ( argc, argv, "-now") >= 0 || - find_arg ( argc, argv, "-key") >= 0 || - find_arg ( argc, argv, "-skey") >= 0 || - find_arg ( argc, argv, "-rkey") >= 0 ) { - fprintf(stderr, "The -snow, -now, -rnow, -key, -rkey, -skey are deprecated " - "and have been removed.\n" - "Please see the manpage: %s -help for the correct syntax.", argv[0]); - exit(EXIT_FAILURE); - } - - find_arg_str ( argc, argv, "-switchers", &( config.switchers ) ); - // Parse commandline arguments about the looks. - find_arg_uint ( argc, argv, "-opacity", &( config.window_opacity ) ); - - find_arg_int ( argc, argv, "-width", &( config.menu_width ) ); - - find_arg_uint ( argc, argv, "-lines", &( config.menu_lines ) ); - find_arg_uint ( argc, argv, "-columns", &( config.menu_columns ) ); - - find_arg_str ( argc, argv, "-font", &( config.menu_font ) ); - find_arg_str ( argc, argv, "-fg", &( config.menu_fg ) ); - find_arg_str ( argc, argv, "-bg", &( config.menu_bg ) ); - find_arg_str ( argc, argv, "-bgalt", &( config.menu_bg_alt ) ); - find_arg_str ( argc, argv, "-hlfg", &( config.menu_hlfg ) ); - find_arg_str ( argc, argv, "-hlbg", &( config.menu_hlbg ) ); - find_arg_str ( argc, argv, "-bc", &( config.menu_bc ) ); - find_arg_uint ( argc, argv, "-bw", &( config.menu_bw ) ); - - // Parse commandline arguments about size and position - find_arg_uint ( argc, argv, "-location", &( config.location ) ); - find_arg_uint ( argc, argv, "-padding", &( config.padding ) ); - find_arg_int ( argc, argv, "-xoffset", &( config.x_offset ) ); - find_arg_int ( argc, argv, "-yoffset", &( config.y_offset ) ); - if ( find_arg ( argc, argv, "-fixed-num-lines" ) >= 0 ) { - config.fixed_num_lines = 1; - } - if ( find_arg ( argc, argv, "-disable-history" ) >= 0 ) { - config.disable_history = TRUE; - } - if ( find_arg ( argc, argv, "-levenshtein-sort" ) >= 0 ) { - config.levenshtein_sort = TRUE; - } - if ( find_arg ( argc, argv, "-case-sensitive" ) >= 0 ) { - config.case_sensitive = TRUE; - } - - // Parse commandline arguments about behavior - find_arg_str ( argc, argv, "-terminal", &( config.terminal_emulator ) ); - - find_arg_str ( argc, argv, "-ssh-client", &( config.ssh_client ) ); - find_arg_str ( argc, argv, "-ssh-command", &( config.ssh_command ) ); - find_arg_str ( argc, argv, "-run-command", &( config.run_command ) ); - find_arg_str ( argc, argv, "-run-list-command", &( config.run_list_command ) ); - find_arg_str ( argc, argv, "-run-shell-command", &( config.run_shell_command ) ); - - find_arg_char ( argc, argv, "-sep", &( config.separator ) ); - - find_arg_int ( argc, argv, "-eh", &( config.element_height ) ); - - find_arg_uint ( argc, argv, "-lazy-filter-limit", &( config.lazy_filter_limit ) ); - - if ( find_arg ( argc, argv, "-sidebar-mode" ) >= 0 ) { - config.sidebar_mode = TRUE; - } -} - /** * 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. */ -void config_sanity_check ( void ) +void config_sanity_check ( int argc, char **argv ) { + if ( find_arg ( argc, argv, "-rnow" ) >= 0 || + find_arg ( argc, argv, "-snow" ) >= 0 || + find_arg ( argc, argv, "-now" ) >= 0 || + find_arg ( argc, argv, "-key" ) >= 0 || + find_arg ( argc, argv, "-skey" ) >= 0 || + find_arg ( argc, argv, "-rkey" ) >= 0 ) { + fprintf ( stderr, "The -snow, -now, -rnow, -key, -rkey, -skey are deprecated " + "and have been removed.\n" + "Please see the manpage: %s -help for the correct syntax.", argv[0] ); + exit ( EXIT_FAILURE ); + } if ( config.element_height < 1 ) { fprintf ( stderr, "config.element_height is invalid. It needs to be atleast 1 line high.\n" ); exit ( 1 ); diff --git a/source/rofi.c b/source/rofi.c index 106802c0..76c8f366 100644 --- a/source/rofi.c +++ b/source/rofi.c @@ -553,9 +553,10 @@ inline static void menu_nav_down ( MenuState *state ) * specified by key and modstate. Returns -1 if none was found */ extern unsigned int NumlockMask; -static int locate_switcher( KeySym key, unsigned int modstate ) { +static int locate_switcher ( KeySym key, unsigned int modstate ) +{ // ignore annoying modifiers - unsigned int modstate_filtered = modstate & ~(LockMask | NumlockMask); + unsigned int modstate_filtered = modstate & ~( LockMask | NumlockMask ); for ( unsigned int i = 0; i < num_switchers; i++ ) { if ( switchers[i].keystr != NULL ) { if ( ( modstate_filtered == switchers[i].modmask ) && @@ -577,7 +578,7 @@ static void menu_keyboard_navigation ( MenuState *state, KeySym key, unsigned in { // pressing one of the global key bindings closes the switcher. this allows fast closing of the // menu if an item is not selected - if ( locate_switcher( key, modstate ) != -1 || key == XK_Escape ) { + if ( locate_switcher ( key, modstate ) != -1 || key == XK_Escape ) { state->retv = MENU_CANCEL; state->quit = TRUE; } @@ -1438,9 +1439,9 @@ static void run_switcher ( int do_fork, SwitcherMode mode ) static void handle_keypress ( XEvent *ev ) { int index; - KeySym key = XkbKeycodeToKeysym ( display, ev->xkey.keycode, 0, 0 ); - index = locate_switcher( key, ev->xkey.state ); - if( index >= 0 ) { + KeySym key = XkbKeycodeToKeysym ( display, ev->xkey.keycode, 0, 0 ); + index = locate_switcher ( key, ev->xkey.state ); + if ( index >= 0 ) { run_switcher ( TRUE, index ); } else { @@ -1476,6 +1477,10 @@ static void cleanup () XCloseDisplay ( display ); } } + // Cleanup pid file. + if ( pidfile ) { + unlink ( pidfile ); + } // Cleaning up memory allocated by the Xresources file. config_xresource_free (); @@ -1484,22 +1489,13 @@ static void cleanup () if ( switchers[i].cb_data_free != NULL ) { switchers[i].cb_data_free ( switchers[i].cb_data ); } - if ( switchers[i].keystr != NULL ) { - g_free ( switchers[i].keystr ); - switchers[i].keystr = NULL; - } + // Switcher keystr is free'ed when needed by config system. if ( switchers[i].keycfg != NULL ) { g_free ( switchers[i].keycfg ); switchers[i].keycfg = NULL; } } g_free ( switchers ); - - // Cleanup pid file. - if ( pidfile ) { - unlink ( pidfile ); - g_free ( pidfile ); - } } /** @@ -1570,8 +1566,7 @@ static void setup_switchers ( void ) // We cannot do this in main loop, as we create pointer to string, // and re-alloc moves that pointer. for ( unsigned int i = 0; i < num_switchers; i++ ) { - switchers[i].keycfg = g_strdup_printf ( "key-%s", - switchers[i].name ); + switchers[i].keycfg = g_strdup_printf ( "key-%s", switchers[i].name ); config_parser_add_option ( xrm_String, switchers[i].keycfg, (void * *) &( switchers[i].keystr ) ); @@ -1601,16 +1596,10 @@ static inline void load_configuration_dynamic ( Display *display ) { // Load in config from X resources. config_parse_xresource_options_dynamic ( display ); + config_parse_cmd_options_dynamic ( stored_argc, stored_argv ); - for ( unsigned int i = 0; i < num_switchers; i++ ) { - if ( switchers[i].keycfg != NULL ) { - char *flag = g_strdup_printf ( "-%s", switchers[i].keycfg ); - find_arg_str_alloc ( stored_argc, stored_argv, flag, &( switchers[i].keystr ) ); - g_free ( flag ); - } - } // Sanity check - config_sanity_check (); + config_sanity_check ( stored_argc, stored_argv ); } @@ -1655,6 +1644,7 @@ static void show_error_message ( const char *msg ) int main ( int argc, char *argv[] ) { + int dmenu_mode = FALSE; stored_argc = argc; stored_argv = argv; @@ -1669,16 +1659,30 @@ int main ( int argc, char *argv[] ) exit ( EXIT_SUCCESS ); } + // Detect if we are in dmenu mode. + // This has two possible causes. + // 1 the user specifies it on the command-line. + if ( find_arg ( argc, argv, "-dmenu" ) >= 0 ) { + dmenu_mode = TRUE; + } + // 2 the binary that executed is called dmenu (e.g. symlink to rofi) + else{ + // Get the base name of the executable called. + char *base_name = g_path_get_basename ( argv[0] ); + dmenu_mode = ( strcmp ( base_name, "dmenu" ) == 0 ); + // Free the basename for dmenu detection. + g_free ( base_name ); + } + // Get the path to the cache dir. cache_dir = g_get_user_cache_dir (); // Create pid file path. - if ( find_arg_str_alloc ( argc, argv, "-pid", &( pidfile ) ) == FALSE ) { - const char *path = g_get_user_runtime_dir (); - if ( path ) { - pidfile = g_build_filename ( path, "rofi.pid", NULL ); - } + const char *path = g_get_user_runtime_dir (); + if ( path ) { + pidfile = g_build_filename ( path, "rofi.pid", NULL ); } + config_parser_add_option ( xrm_String, "pid", (void * *) &pidfile ); // Register cleanup function. atexit ( cleanup ); @@ -1692,10 +1696,15 @@ int main ( int argc, char *argv[] ) return EXIT_FAILURE; } - load_configuration ( display ); - // setup_switchers - setup_switchers (); + if ( !dmenu_mode ) { + // setup_switchers + setup_switchers (); + } + else { + // Add dmenu options. + config_parser_add_option ( xrm_Char, "sep", (void * *) &( config.separator ) ); + } // Reload for dynamic part. load_configuration_dynamic ( display ); @@ -1716,21 +1725,6 @@ int main ( int argc, char *argv[] ) exit ( EXIT_SUCCESS ); } - // Detect if we are in dmenu mode. - // This has two possible causes. - int dmenu_mode = FALSE; - // 1 the user specifies it on the command-line. - if ( find_arg ( argc, argv, "-dmenu" ) >= 0 ) { - dmenu_mode = TRUE; - } - // 2 the binary that executed is called dmenu (e.g. symlink to rofi) - else{ - // Get the base name of the executable called. - char *base_name = g_path_get_basename ( argv[0] ); - dmenu_mode = ( strcmp ( base_name, "dmenu" ) == 0 ); - // Free the basename for dmenu detection. - g_free ( base_name ); - } // Dmenu mode. if ( dmenu_mode == TRUE ) { diff --git a/source/xrmoptions.c b/source/xrmoptions.c index 3dea144f..fd19bc2a 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -32,6 +32,7 @@ #include #include "rofi.h" #include "xrmoptions.h" +#include "helper.h" typedef struct { @@ -43,12 +44,13 @@ typedef struct int * snum; char ** str; void *pointer; + char * charc; }; char *mem; } XrmOption; /** - * Map X resource settings to internal options - * Currently supports string and number. + * Map X resource and commandline options to internal options + * Currently supports string, boolean and number (signed and unsigned). */ static XrmOption xrmOptions[] = { { xrm_String, "switchers", { .str = &config.switchers }, NULL }, @@ -98,9 +100,9 @@ static XrmOption xrmOptions[] = { { xrm_Boolean, "disable-history", { .num = &config.disable_history }, NULL }, { xrm_Boolean, "levenshtein-sort", { .num = &config.levenshtein_sort }, NULL }, { xrm_Boolean, "case-sensitive", { .num = &config.case_sensitive }, NULL }, - /* Key bindings */ { xrm_Boolean, "sidebar-mode", { .num = &config.sidebar_mode }, NULL }, - { xrm_Number, "lazy-filter-limit", { .num = &config.lazy_filter_limit }, NULL } + { xrm_Number, "lazy-filter-limit", { .num = &config.lazy_filter_limit }, NULL }, + { xrm_SNumber, "eh", { .snum = &config.element_height }, NULL } }; // Dynamic options. @@ -150,6 +152,9 @@ static void config_parser_set ( XrmOption *option, XrmValue *xrmValue ) *( option->num ) = FALSE; } } + else if ( option->type == xrm_Char ) { + *( option->charc ) = helper_parse_char ( xrmValue->addr ); + } } void config_parse_xresource_options ( Display *display ) @@ -185,6 +190,59 @@ void config_parse_xresource_options ( Display *display ) XrmDestroyDatabase ( xDB ); } +/** + * Parse an option from the commandline vector. + */ +static void config_parse_cmd_option ( XrmOption *option, int argc, char **argv ) +{ + // Prepend a - to the option name. + char *key = g_strdup_printf ( "-%s", option->name ); + switch ( option->type ) + { + case xrm_Number: + find_arg_uint ( argc, argv, key, option->num ); + break; + case xrm_SNumber: + find_arg_int ( argc, argv, key, option->snum ); + break; + case xrm_String: + if ( find_arg_str ( argc, argv, key, option->str ) == TRUE ) { + if ( option->mem != NULL ) { + g_free ( option->mem ); + option->mem = NULL; + } + } + break; + case xrm_Boolean: + if ( find_arg ( argc, argv, key ) >= 0 ) { + *( option->num ) = TRUE; + } + break; + case xrm_Char: + find_arg_char ( argc, argv, key, option->charc ); + break; + default: + break; + } + g_free ( key ); +} + +void config_parse_cmd_options ( int argc, char ** argv ) +{ + for ( unsigned int i = 0; i < sizeof ( xrmOptions ) / sizeof ( XrmOption ); ++i ) { + XrmOption *op = &( xrmOptions[i] ); + config_parse_cmd_option ( op, argc, argv ); + } +} + +void config_parse_cmd_options_dynamic ( int argc, char ** argv ) +{ + for ( unsigned int i = 0; i < num_extra_options; ++i ) { + XrmOption *op = &( extra_options[i] ); + config_parse_cmd_option ( op, argc, argv ); + } +} + void config_parse_xresource_options_dynamic ( Display *display ) { char *xRMS; @@ -256,6 +314,14 @@ void xresource_dump_entry ( const char *namePrefix, XrmOption *option ) case xrm_Boolean: printf ( "%s", ( *( option->num ) == TRUE ) ? "true" : "false" ); break; + case xrm_Char: + if ( *( option->charc ) > 32 && *( option->charc ) < 127 ) { + printf ( "%c", *( option->charc ) ); + } + else { + printf ( "\\x%02X", *( option->charc ) ); + } + break; default: break; }