clients: add BEMENU_OPTS env var support

It's possible to pass any CLI argument through BEMENU_OPTS env variable
instead.
This commit is contained in:
Jari Vetoniemi 2020-02-07 23:11:42 +02:00
parent bc584cc5f0
commit dac1ffde7e
4 changed files with 127 additions and 98 deletions

View File

@ -64,6 +64,7 @@ All dependencies are searched with `pkg-config`
| Variable | Description | Value |
|------------------|-----------------------------------------|----------------------|
| BEMENU_OPTS | Options for bemenu, bemenu-run from env | Any cli argument |
| BEMENU_BACKEND | Forces a renderer by name | x11, wayland, curses |
| BEMENU_RENDERER | Forces a renderer by loading a .so file | Path to the .so file |
| BEMENU_RENDERERS | Override renderer load directory | Path to a directory |

View File

@ -17,17 +17,10 @@ struct paths {
char *paths;
};
static char*
c_strdup2(const char *str, size_t size)
{
char *cpy = calloc(1, size + 1);
return (cpy ? memcpy(cpy, str, size) : NULL);
}
static char*
c_strdup(const char *str)
{
return c_strdup2(str, strlen(str));
return cstrcopy(str, strlen(str));
}
static char*
@ -134,73 +127,6 @@ read_items_to_menu_from_path(struct bm_menu *menu)
read_items_to_menu_from_dir(menu, path);
}
#define WHITESPACE " \t\n\r"
static const char*
tokenize(const char *cstr, size_t *out_len, const char *separator, bool skip_whitespace, const char **state)
{
assert(out_len && separator && state);
const char *current = (state && *state ? *state : cstr);
if (!current || !*current || !cstr || !*cstr)
return NULL;
current += strspn(current, separator);
if (skip_whitespace)
current += strspn(current, WHITESPACE);
*out_len = strcspn(current, separator);
*state = current + *out_len;
if (skip_whitespace) {
const size_t ws = strcspn(current, WHITESPACE);
*out_len -= (ws < *out_len ? *out_len - ws : 0);
}
return current;
}
static const char*
tokenize_quoted(const char *cstr, size_t *out_len, const char *separator, const char *quotes, const char **state)
{
assert(out_len && separator && quotes && state);
const char *e, *current = tokenize(cstr, out_len, separator, true, state);
if (!current)
return NULL;
for (const char *q = quotes; *q; ++q) {
if (*current != *q)
continue;
bool escaped = false;
for (e = ++current; *e; ++e) {
if (escaped)
escaped = false;
else if (*e == '\\')
escaped = true;
else if (*e == *q)
break;
}
*out_len = e - current;
e = (!*e ? e : e + 1);
if (*e) {
size_t tmp;
const char *state2 = NULL;
*state = tokenize(e, &tmp, separator, true, &state2);
} else {
*state = e;
}
break;
}
return current;
}
static inline void ignore_ret(int useless, ...) { (void)useless; }
static void
@ -214,27 +140,10 @@ launch(const char *bin)
ignore_ret(0, freopen("/dev/null", "w", stdout));
ignore_ret(0, freopen("/dev/null", "w", stderr));
size_t count = 0;
{
size_t len;
const char *state = NULL;
while (tokenize_quoted(bin, &len, " ", "\"'", &state))
++count;
}
char **tokens;
if (!count || !(tokens = calloc(count + 1, sizeof(char*))))
if (!(tokens = tokenize_quoted_to_argv(bin, NULL, NULL)))
_exit(EXIT_FAILURE);
{
size_t i = 0, len;
const char *t, *state = NULL;
while (i < count && (t = tokenize_quoted(bin, &len, " ", "\"'", &state))) {
if (!(tokens[i++] = c_strdup2(t, len)))
_exit(EXIT_FAILURE);
}
}
execvp(tokens[0], tokens);
_exit(EXIT_SUCCESS);
}

View File

@ -43,6 +43,111 @@ disco(void)
exit(EXIT_SUCCESS);
}
#define WHITESPACE " \t\n\r"
static const char*
tokenize(const char *cstr, size_t *out_len, const char *separator, bool skip_whitespace, const char **state)
{
assert(out_len && separator && state);
const char *current = (state && *state ? *state : cstr);
if (!current || !*current || !cstr || !*cstr)
return NULL;
current += strspn(current, separator);
if (skip_whitespace)
current += strspn(current, WHITESPACE);
*out_len = strcspn(current, separator);
*state = current + *out_len;
if (skip_whitespace) {
const size_t ws = strcspn(current, WHITESPACE);
*out_len -= (ws < *out_len ? *out_len - ws : 0);
}
return current;
}
static const char*
tokenize_quoted(const char *cstr, size_t *out_len, const char *separator, const char *quotes, const char **state)
{
assert(out_len && separator && quotes && state);
const char *e, *current = tokenize(cstr, out_len, separator, true, state);
if (!current)
return NULL;
for (const char *q = quotes; *q; ++q) {
if (*current != *q)
continue;
bool escaped = false;
for (e = ++current; *e; ++e) {
if (escaped)
escaped = false;
else if (*e == '\\')
escaped = true;
else if (*e == *q)
break;
}
*out_len = e - current;
e = (!*e ? e : e + 1);
if (*e) {
size_t tmp;
const char *state2 = NULL;
*state = tokenize(e, &tmp, separator, true, &state2);
} else {
*state = e;
}
break;
}
return current;
}
char*
cstrcopy(const char *str, size_t size)
{
char *cpy = calloc(1, size + 1);
return (cpy ? memcpy(cpy, str, size) : NULL);
}
char**
tokenize_quoted_to_argv(const char *str, char *argv0, int *out_argc)
{
if (out_argc) *out_argc = 0;
size_t count = !!argv0;
{
size_t len;
const char *state = NULL;
while (tokenize_quoted(str, &len, " ", "\"'", &state))
++count;
}
char **tokens;
if (!count || !(tokens = calloc(count + 1, sizeof(char*))))
return NULL;
{
tokens[0] = argv0;
size_t i = !!argv0, len;
const char *t, *state = NULL;
while (i < count && (t = tokenize_quoted(str, &len, " ", "\"'", &state))) {
if (!(tokens[i++] = cstrcopy(t, len)))
return NULL;
}
}
if (out_argc) *out_argc = count;
return tokens;
}
static void
version(const char *name)
{
@ -104,8 +209,8 @@ usage(FILE *out, const char *name)
exit((out == stderr ? EXIT_FAILURE : EXIT_SUCCESS));
}
void
parse_args(struct client *client, int *argc, char **argv[])
static void
do_getopt(struct client *client, int *argc, char **argv[])
{
assert(client && argc && argv);
@ -149,9 +254,9 @@ parse_args(struct client *client, int *argc, char **argv[])
* Either break the interface and make them --sf, --sb (like they are now),
* or parse them before running getopt.. */
for (;;) {
int32_t opt = getopt_long(*argc, *argv, "hviwl:I:p:P:I:bfm:H:n", opts, NULL);
if (opt < 0)
for (optind = 0;;) {
int32_t opt;
if ((opt = getopt_long(*argc, *argv, "hviwl:I:p:P:I:bfm:H:n", opts, NULL)) < 0)
break;
switch (opt) {
@ -259,6 +364,17 @@ parse_args(struct client *client, int *argc, char **argv[])
*argv += optind;
}
void
parse_args(struct client *client, int *argc, char **argv[])
{
int num_opts;
char **opts;
const char *env;
if ((env = getenv("BEMENU_OPTS")) && (opts = tokenize_quoted_to_argv(env, (*argv)[0], &num_opts)))
do_getopt(client, &num_opts, &opts);
do_getopt(client, argc, argv);
}
struct bm_menu*
menu_with_options(const struct client *client)
{

View File

@ -2,6 +2,7 @@
#define _BM_COMMON_H_
#include <bemenu.h>
#include <stddef.h>
struct client {
enum bm_filter_mode filter_mode;
@ -21,6 +22,8 @@ struct client {
bool no_overlap;
};
char* cstrcopy(const char *str, size_t size);
char** tokenize_quoted_to_argv(const char *str, char *argv0, int *out_argc);
void parse_args(struct client *client, int *argc, char **argv[]);
struct bm_menu* menu_with_options(const struct client *client);
enum bm_run_result run_menu(const struct client *client, struct bm_menu *menu, void (*item_cb)(struct bm_item *item, const char *text));