#include "pistachio.h"
#define CONFIG_FILE "~/.config/pistachio/configuration"
#define SEARCH_SIZE 16.0
#define RESULTS_SIZE 12.5
#define ERROR_SIZE 14.0
#define FOREGROUND 0xfff8f8f8
#define BACKGROUND 0xe8303030
#define CARET 0xffe0e0e0
#define SELECTED 0xf0608040
#define ERROR_COLOR 0xf0ff8080
#define WINDOW_WIDTH 0.4
#define WINDOW_HEIGHT 0.3
#define TERMINAL_PROGRAM "xfce4-terminal"
#define FOLDER_PROGRAM "thunar"
#define DEFAULT_PROGRAM "xed"
#define POOL_SIZE 16 * 1024
static Arena arena = {0};
Settings config = {
.window_w = WINDOW_WIDTH,
.window_h = WINDOW_HEIGHT,
.font_path = FONT_PATH,
.search_font = {
.size = SEARCH_SIZE,
.color = FOREGROUND,
.oblique = false,
.bold = false
},
.results_font = {
.size = RESULTS_SIZE,
.color = FOREGROUND,
.oblique = false,
.bold = false
},
.error_font = {
.size = ERROR_SIZE,
.color = ERROR_COLOR,
.oblique = true,
.bold = false
},
.back_color = BACKGROUND,
.caret_color = CARET,
.selected_color = SELECTED,
.terminal_program = {
FOLDER_PROGRAM,
NULL,
0,
false,
NULL
},
.folder_program = {
FOLDER_PROGRAM,
NULL,
0,
false,
NULL
},
.default_program = {
DEFAULT_PROGRAM,
NULL,
0,
false,
NULL
}
};
char *command_list[] = {
"font-path",
"search-font",
"results-font",
"error-font",
"back-color",
"caret-color",
"hl-color",
"window-width",
"window-height",
"terminal-command",
"folder-command",
"default-command",
"program",
"command",
"nodaemon"
};
char *allocate_string(char *src, int len) {
char *str = allocate(&arena, len + 1);
memcpy(str, src, len);
str[len] = 0;
return str;
}
int set_color(char *params, int params_len, int offset, u32 *out_color) {
char *p = ¶ms[offset];
int idx = 0;
u32 color = 0;
int i;
for (i = offset; i < params_len && idx < 8; i++, p++) {
if (*p == ' ' || *p == '\t')
break;
if (*p >= '0' && *p <= '9')
color = (color << 4) | (*p - '0');
else if (*p >= 'A' && *p <= 'F')
color = (color << 4) | (*p - 'A' + 0xa);
else if (*p >= 'a' && *p <= 'f')
color = (color << 4) | (*p - 'a' + 0xa);
else
idx--;
idx++;
}
if (idx == 8 || color > 0xFFFFFF)
*out_color = color;
return i - offset;
}
void set_font_attrs(char *params, int params_len, Font_Attrs *font) {
if (!params_len)
return;
// find the end of the first word
char *p = params;
char *word = p;
while ((p-params) < params_len && *p != ' ' && *p != '\t') p++;
char *size_str = allocate_string(word, p-word);
double size = atof(size_str);
if (size > 0.0)
font->size = (float)size;
// skip to the next word
while ((p-params) < params_len && (*p == ' ' || *p == '\t')) p++;
p += set_color(params, params_len, p-params, &font->color);
while ((p-params) < params_len && (*p == ' ' || *p == '\t')) p++;
while ((p-params) < params_len) {
if (!strncmp(p, "oblique", 7)) {
font->oblique = true;
p += 7;
}
else if (!strncmp(p, "bold", 4)) {
font->bold = true;
p += 4;
}
p++;
}
}
// If size >= 1.0, then size is a percentage of a screen dimension.
// Else size is in pixels.
void set_size(char *params, int params_len, float *size) {
if (params_len < 2)
return;
char *str = allocate_string(params, params_len);
if (params[params_len-1] == '%')
*size = (float)(atof(str) / 100.0);
else
*size = (float)atof(str);
}
void add_program(char *command, char *extensions, int n_extensions, bool daemonize) {
Program **head = &config.programs;
while (*head)
head = &(*head)->next;
Program *prog = allocate(&arena, sizeof(Program));
*prog = (Program) {
.command = command,
.extensions = extensions,
.n_extensions = n_extensions,
.daemonize = daemonize,
.next = NULL
};
*head = prog;
}
void find_first_two_words(char *params, int params_len, int *first_len, int *second) {
// find the end of the first word
char *p = params;
while ((p-params) < params_len && *p != ' ' && *p != '\t') p++;
*first_len = p-params;
// skip to the next word
while ((p-params) < params_len && (*p == ' ' || *p == '\t')) p++;
*second = p-params;
}
void set_program(char *params, int params_len, bool daemonize) {
if (!params_len)
return;
int cmd_len, exts_offset;
find_first_two_words(params, params_len, &cmd_len, &exts_offset);
if (exts_offset >= params_len)
return;
char *cmd = allocate_string(params, cmd_len);
int exts_size = params_len - exts_offset + 1;
char *exts = allocate(&arena, exts_size);
memset(exts, 0, exts_size);
int n_exts = 1;
bool was_blank = false;
char *src = ¶ms[exts_offset];
char *dst = exts;
for (int i = exts_offset; i < params_len; i++) {
if (*src != ' ' && *src != '\t') {
if (was_blank) {
n_exts++;
dst++;
}
*dst++ = *src++;
was_blank = false;
}
else {
src++;
was_blank = true;
}
}
add_program(cmd, exts, n_exts, daemonize);
}
void set_command(char *params, int params_len, bool daemonize) {
if (!params_len)
return;
int ext_len, cmd_offset;
find_first_two_words(params, params_len, &ext_len, &cmd_offset);
if (cmd_offset >= params_len)
return;
char *ext = allocate_string(params, ext_len);
char *cmd = allocate_string(¶ms[cmd_offset], params_len - cmd_offset);
add_program(cmd, ext, 1, daemonize);
}
void parse_config_line(char *line, int len, bool *daemonize) {
int idx = -1;
int cmd_len = 0;
for (int i = 0; i < sizeof(command_list) / sizeof(char*); i++) {
cmd_len = strlen(command_list[i]);
if (!strncmp(command_list[i], line, cmd_len)) {
idx = i;
break;
}
}
if (idx < 0)
return;
// constrict the line length to before the trailing whitespace (if there is any)
char *p = &line[len-1];
while (*p && (*p == ' ' || *p == '\t')) p--;
len = p - line + 1;
// find the beginning of the parameters
char *params = &line[cmd_len];
int params_len = len - cmd_len;
for (
int i = cmd_len;
i < len && (*params == ' ' || *params == '\t');
i++, params++, params_len--
);
switch (idx) {
case 0: // font-name
config.font_path = allocate_string(params, params_len);
break;
case 1: // search-font
set_font_attrs(params, params_len, &config.search_font);
break;
case 2: // results-font
set_font_attrs(params, params_len, &config.results_font);
break;
case 3: // error-font
set_font_attrs(params, params_len, &config.error_font);
break;
case 4: // back-color
set_color(params, params_len, 0, &config.back_color);
break;
case 5: // caret-color
set_color(params, params_len, 0, &config.caret_color);
break;
case 6: // hl-color
set_color(params, params_len, 0, &config.selected_color);
break;
case 7: // window-width
set_size(params, params_len, &config.window_w);
break;
case 8: // window-height
set_size(params, params_len, &config.window_h);
break;
case 9: // terminal-command
config.terminal_program.command = allocate_string(params, params_len);
config.terminal_program.daemonize = daemonize != NULL ? *daemonize : true;
break;
case 10: // folder-command
config.folder_program.command = allocate_string(params, params_len);
config.folder_program.daemonize = daemonize != NULL ? *daemonize : true;
break;
case 11: // default-command
config.default_program.command = allocate_string(params, params_len);
config.default_program.daemonize = daemonize != NULL ? *daemonize : true;
break;
case 12: // program
set_program(params, params_len, daemonize != NULL ? *daemonize : true);
break;
case 13: // command
set_command(params, params_len, daemonize != NULL ? *daemonize : true);
break;
case 14: // nodaemon
{
bool d = false;
parse_config_line(params, params_len, &d);
break;
}
}
}
Settings *load_config() {
if (!arena.initialized)
make_arena(POOL_SIZE, &arena);
char *path = get_desugared_path(CONFIG_FILE, strlen(CONFIG_FILE));
FILE *f = fopen(path, "rb");
if (!f) {
save_config(path);
return &config;
}
fseek(f, 0, SEEK_END);
int sz = ftell(f);
rewind(f);
char *file = allocate(&arena, sz + 1);
fread(file, 1, sz, f);
file[sz] = 0;
fclose(f);
char *p = file;
while (*p) {
// skip leading whitespace
while (*p && (*p == ' ' || *p == '\t')) p++;
bool comment = *p == '#';
// find the last viable character
char *line = p;
while (*p && *p != '#' && *p != '\n' && *p != '\r') p++;
int len = p - line;
// skip to the end of the line if we found a comment
if (*p == '#')
while (*p && *p != '\n') p++;
// skip to the next viable line
while (*p && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t')) p++;
if (comment)
continue;
parse_config_line(line, len, NULL);
}
if (config.font_path[0] == '~')
config.font_path = get_desugared_path(config.font_path, strlen(config.font_path));
return &config;
}
void save_config(char *cfg_path) {
int len = strlen(cfg_path);
char *path = allocate(&arena, len + 1);
memset(path, 0, len + 1);
char *src = cfg_path, *dst = path;
for (int i = 0; i < len; i++) {
if (i > 0 && *src == '/')
mkdir(path, 0777);
*dst++ = *src++;
}
FILE *f = fopen(path, "w");
if (!f) {
fprintf(stderr, "Could not write new configuration file \"%s\"\n", path);
return;
}
u32 colors[] = {
config.search_font.color,
config.results_font.color,
config.error_font.color,
config.back_color,
config.caret_color,
config.selected_color
};
char color_strs[54] = {0};
char *p = color_strs;
for (int i = 0; i < 6; i++) {
u32 c = colors[i];
for (int j = 0; j < 8; j++) {
u32 d = (c >> 28) & 0xf;
*p++ = d < 10 ? '0' + d : 'a' + (d-10);
c <<= 4;
}
p++;
}
fprintf(f,
"font-path %s\n"
"search-font %g %s%s\n"
"results-font %g %s%s\n"
"error-font %g %s%s\n"
"back-color %s\n"
"caret-color %s\n"
"hl-color %s\n"
"window-width %g%%\n"
"window-height %g%%\n"
"terminal-program %s\n"
"folder-program %s\n"
"default-program %s\n",
config.font_path,
config.search_font.size, &color_strs[0 * 9], config.search_font.oblique ? " oblique" : "",
config.results_font.size, &color_strs[1 * 9], config.results_font.oblique ? " oblique" : "",
config.error_font.size, &color_strs[2 * 9], config.error_font.oblique ? " oblique" : "",
&color_strs[3 * 9],
&color_strs[4 * 9],
&color_strs[5 * 9],
config.window_w * 100.0,
config.window_h * 100.0,
config.terminal_program.command,
config.folder_program.command,
config.default_program.command
);
fclose(f);
}