init commit
This commit is contained in:
98
wofi/src/config.c
Normal file
98
wofi/src/config.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2024 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <map.h>
|
||||
|
||||
void config_put(struct map* map, char* line) {
|
||||
size_t line_size = strlen(line);
|
||||
char* new_line = calloc(1, line_size + 1);
|
||||
size_t new_line_count = 0;
|
||||
for(size_t count = 0; count < line_size; ++count) {
|
||||
if(line[count] == '\\') {
|
||||
new_line[new_line_count++] = line[++count];
|
||||
} else if(line[count] == '#') {
|
||||
break;
|
||||
} else {
|
||||
new_line[new_line_count++] = line[count];
|
||||
}
|
||||
}
|
||||
line = new_line;
|
||||
char* equals = strchr(line, '=');
|
||||
if(equals == NULL) {
|
||||
free(line);
|
||||
return;
|
||||
}
|
||||
*equals = 0;
|
||||
char* key = equals - 1;
|
||||
while(*key == ' ') {
|
||||
--key;
|
||||
}
|
||||
*(key + 1) = 0;
|
||||
char* value = equals + 1;
|
||||
while(*value == ' ') {
|
||||
++value;
|
||||
}
|
||||
size_t len = strlen(value);
|
||||
char* end = value + len - 1;
|
||||
if(*end == '\n' || *end == ' ') {
|
||||
*end = 0;
|
||||
}
|
||||
map_put(map, line, value);
|
||||
free(line);
|
||||
}
|
||||
|
||||
void config_load(struct map* map, const char* config) {
|
||||
FILE* file = fopen(config, "r");
|
||||
char* line = NULL;
|
||||
size_t size = 0;
|
||||
while(getline(&line, &size, file) != -1) {
|
||||
config_put(map, line);
|
||||
}
|
||||
free(line);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
char* config_get(struct map* config, const char* key, char* def_opt) {
|
||||
char* opt = map_get(config, key);
|
||||
if(opt == NULL) {
|
||||
opt = def_opt;
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
|
||||
int config_get_mnemonic(struct map* config, const char* key, char* def_opt, int num_choices, ...) {
|
||||
char* opt = config_get(config, key, def_opt);
|
||||
va_list ap;
|
||||
va_start(ap, num_choices);
|
||||
int result = 0;
|
||||
for(int i = 0; i < num_choices; i++) {
|
||||
char* cmp_str = va_arg(ap, char*);
|
||||
if(strcmp(opt, cmp_str) == 0) {
|
||||
result = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
798
wofi/src/main.c
Normal file
798
wofi/src/main.c
Normal file
@@ -0,0 +1,798 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <map.h>
|
||||
#include <wofi.h>
|
||||
#include <utils.h>
|
||||
#include <config.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static const char* nyan_colors[] = {"#FF0000", "#FFA500", "#FFFF00", "#00FF00", "#0000FF", "#FF00FF"};
|
||||
static size_t nyan_color_l = sizeof(nyan_colors) / sizeof(char*);
|
||||
|
||||
static char* CONFIG_LOCATION;
|
||||
static char* COLORS_LOCATION;
|
||||
static struct map* config;
|
||||
static char* config_path;
|
||||
static char* stylesheet;
|
||||
static char* color_path;
|
||||
static uint8_t nyan_shift = 0;
|
||||
|
||||
struct option_node {
|
||||
char* option;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
static char* get_exec_name(char* path) {
|
||||
char* slash = strrchr(path, '/');
|
||||
uint64_t offset;
|
||||
if(slash == NULL) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = (slash - path) + 1;
|
||||
}
|
||||
return path + offset;
|
||||
}
|
||||
|
||||
static void print_usage(char** argv) {
|
||||
printf("%s [options]\n", get_exec_name(argv[0]));
|
||||
printf("Options:\n");
|
||||
printf("--help\t\t\t-h\tDisplays this help message\n");
|
||||
printf("--fork\t\t\t-f\tForks the menu so you can close the terminal\n");
|
||||
printf("--conf\t\t\t-c\tSelects a config file to use\n");
|
||||
printf("--style\t\t\t-s\tSelects a stylesheet to use\n");
|
||||
printf("--color\t\t\t-C\tSelects a colors file to use\n");
|
||||
printf("--dmenu\t\t\t-d\tRuns in dmenu mode\n");
|
||||
printf("--show\t\t\t-S\tSpecifies the mode to run in. A list can be found in wofi(7)\n");
|
||||
printf("--width\t\t\t-W\tSpecifies the surface width\n");
|
||||
printf("--height\t\t-H\tSpecifies the surface height\n");
|
||||
printf("--prompt\t\t-p\tPrompt to display\n");
|
||||
printf("--xoffset\t\t-x\tThe x offset\n");
|
||||
printf("--yoffset\t\t-y\tThe y offset\n");
|
||||
printf("--normal-window\t\t-n\tRender to a normal window\n");
|
||||
printf("--allow-images\t\t-I\tAllows images to be rendered\n");
|
||||
printf("--allow-markup\t\t-m\tAllows pango markup\n");
|
||||
printf("--cache-file\t\t-k\tSets the cache file to use\n");
|
||||
printf("--term\t\t\t-t\tSpecifies the terminal to use when running in a term\n");
|
||||
printf("--password\t\t-P\tRuns in password mode\n");
|
||||
printf("--exec-search\t\t-e\tMakes enter always use the search contents not the first result\n");
|
||||
printf("--hide-scroll\t\t-b\tHides the scroll bars\n");
|
||||
printf("--matching\t\t-M\tSets the matching method, default is contains\n");
|
||||
printf("--insensitive\t\t-i\tAllows case insensitive searching\n");
|
||||
printf("--parse-search\t\t-q\tParses the search text removing image escapes and pango\n");
|
||||
printf("--version\t\t-v\tPrints the version and then exits\n");
|
||||
printf("--location\t\t-l\tSets the location\n");
|
||||
printf("--no-actions\t\t-a\tDisables multiple actions for modes that support it\n");
|
||||
printf("--define\t\t-D\tSets a config option\n");
|
||||
printf("--lines\t\t\t-L\tSets the height in number of lines\n");
|
||||
printf("--columns\t\t-w\tSets the number of columns to display\n");
|
||||
printf("--sort-order\t\t-O\tSets the sort order\n");
|
||||
printf("--gtk-dark\t\t-G\tUses the dark variant of the current GTK theme\n");
|
||||
printf("--search\t\t-Q\tSearch for something immediately on open\n");
|
||||
printf("--monitor\t\t-o\tSets the monitor to open on\n");
|
||||
printf("--pre-display-cmd\t-r\tRuns command for the displayed entries, without changing the output. %%s for the real string\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void wofi_load_css(bool nyan) {
|
||||
if(access(stylesheet, R_OK) == 0) {
|
||||
FILE* file = fopen(stylesheet, "r");
|
||||
fseek(file, 0, SEEK_END);
|
||||
ssize_t size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
char* data = malloc(size + 1);
|
||||
if (fread(data, 1, size, file) == 0) {
|
||||
fprintf(stderr, "failed to read stylesheet data from file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
data[size] = 0;
|
||||
struct wl_list lines;
|
||||
struct node {
|
||||
char* line;
|
||||
struct wl_list link;
|
||||
};
|
||||
wl_list_init(&lines);
|
||||
if(nyan) {
|
||||
for(ssize_t count = nyan_shift; count < 100 + nyan_shift; ++count) {
|
||||
size_t i = count % nyan_color_l;
|
||||
struct node* entry = malloc(sizeof(struct node));
|
||||
entry->line = strdup(nyan_colors[i]);
|
||||
wl_list_insert(&lines, &entry->link);
|
||||
}
|
||||
nyan_shift = (nyan_shift + 1) % nyan_color_l;
|
||||
} else {
|
||||
if(access(color_path, R_OK) == 0) {
|
||||
file = fopen(color_path, "r");
|
||||
char* line = NULL;
|
||||
size_t line_size = 0;
|
||||
ssize_t line_l = 0;
|
||||
while((line_l = getline(&line, &line_size, file)) != -1) {
|
||||
struct node* entry = malloc(sizeof(struct node));
|
||||
line[line_l - 1] = 0;
|
||||
entry->line = malloc(line_l + 1);
|
||||
strcpy(entry->line, line);
|
||||
wl_list_insert(&lines, &entry->link);
|
||||
}
|
||||
fclose(file);
|
||||
free(line);
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t count = wl_list_length(&lines) - 1;
|
||||
if(count > 99) {
|
||||
fprintf(stderr, "Woah there that's a lot of colors. Try having no more than 100, thanks\n");
|
||||
exit(1);
|
||||
}
|
||||
struct node* node;
|
||||
wl_list_for_each(node, &lines, link) {
|
||||
//Do --wofi-color replace
|
||||
const char* color = node->line;
|
||||
const char* wofi_color = "--wofi-color";
|
||||
char count_str[3];
|
||||
snprintf(count_str, 3, "%zu", count--);
|
||||
char* needle = utils_concat(2, wofi_color, count_str);
|
||||
size_t color_len = strlen(color);
|
||||
size_t needle_len = strlen(needle);
|
||||
if(color_len > needle_len) {
|
||||
free(needle);
|
||||
fprintf(stderr, "What color format is this, try #FFFFFF, kthxbi\n");
|
||||
continue;
|
||||
}
|
||||
char* replace = strstr(data, needle);
|
||||
while(replace != NULL) {
|
||||
memcpy(replace, color, color_len);
|
||||
memset(replace + color_len, ' ', needle_len - color_len);
|
||||
replace = strstr(data, needle);
|
||||
}
|
||||
free(needle);
|
||||
|
||||
|
||||
//Do --wofi-rgb-color replace
|
||||
if(color_len < 7) {
|
||||
fprintf(stderr, "What color format is this, try #FFFFFF, kthxbi\n");
|
||||
continue;
|
||||
}
|
||||
const char* wofi_rgb_color = "--wofi-rgb-color";
|
||||
needle = utils_concat(2, wofi_rgb_color, count_str);
|
||||
needle_len = strlen(needle);
|
||||
replace = strstr(data, needle);
|
||||
while(replace != NULL) {
|
||||
char r[3];
|
||||
char g[3];
|
||||
char b[3];
|
||||
memcpy(r, color + 1, 2);
|
||||
memcpy(g, color + 3, 2);
|
||||
memcpy(b, color + 5, 2);
|
||||
r[2] = 0;
|
||||
g[2] = 0;
|
||||
b[2] = 0;
|
||||
char rgb[14];
|
||||
snprintf(rgb, 14, "%ld, %ld, %ld", strtol(r, NULL, 16), strtol(g, NULL, 16), strtol(b, NULL, 16));
|
||||
size_t rgb_len = strlen(rgb);
|
||||
memcpy(replace, rgb, rgb_len);
|
||||
memset(replace + rgb_len, ' ', needle_len - rgb_len);
|
||||
replace = strstr(data, needle);
|
||||
}
|
||||
free(needle);
|
||||
}
|
||||
GtkCssProvider* css = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_data(css, data, strlen(data), NULL);
|
||||
free(data);
|
||||
struct node* tmp;
|
||||
wl_list_for_each_safe(node, tmp, &lines, link) {
|
||||
free(node->line);
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
}
|
||||
|
||||
static void sig(int32_t signum) {
|
||||
switch(signum) {
|
||||
case SIGTERM:
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
const struct option opts[] = {
|
||||
{
|
||||
.name = "help",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'h'
|
||||
},
|
||||
{
|
||||
.name = "fork",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'f'
|
||||
},
|
||||
{
|
||||
.name = "conf",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'c'
|
||||
},
|
||||
{
|
||||
.name = "style",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 's'
|
||||
},
|
||||
{
|
||||
.name = "color",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'C'
|
||||
},
|
||||
{
|
||||
.name = "dmenu",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'd'
|
||||
},
|
||||
{
|
||||
.name = "show",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'S'
|
||||
},
|
||||
{
|
||||
.name = "width",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'W'
|
||||
},
|
||||
{
|
||||
.name = "height",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'H'
|
||||
},
|
||||
{
|
||||
.name = "prompt",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'p'
|
||||
},
|
||||
{
|
||||
.name = "xoffset",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'x'
|
||||
},
|
||||
{
|
||||
.name = "yoffset",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'y'
|
||||
},
|
||||
{
|
||||
.name = "normal-window",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'n'
|
||||
},
|
||||
{
|
||||
.name = "allow-images",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'I'
|
||||
},
|
||||
{
|
||||
.name = "allow-markup",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'm'
|
||||
},
|
||||
{
|
||||
.name = "cache-file",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'k'
|
||||
},
|
||||
{
|
||||
.name = "term",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 't'
|
||||
},
|
||||
{
|
||||
.name = "password",
|
||||
.has_arg = optional_argument,
|
||||
.flag = NULL,
|
||||
.val = 'P'
|
||||
},
|
||||
{
|
||||
.name = "exec-search",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'e'
|
||||
},
|
||||
{
|
||||
.name = "hide-scroll",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'b'
|
||||
},
|
||||
{
|
||||
.name = "matching",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'M'
|
||||
},
|
||||
{
|
||||
.name = "insensitive",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'i'
|
||||
},
|
||||
{
|
||||
.name = "parse-search",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'q'
|
||||
},
|
||||
{
|
||||
.name = "version",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'v'
|
||||
},
|
||||
{
|
||||
.name = "location",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'l'
|
||||
},
|
||||
{
|
||||
.name = "no-actions",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'a'
|
||||
},
|
||||
{
|
||||
.name = "define",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'D'
|
||||
},
|
||||
{
|
||||
.name = "lines",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'L'
|
||||
},
|
||||
{
|
||||
.name = "columns",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'w'
|
||||
},
|
||||
{
|
||||
.name = "sort-order",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'O'
|
||||
},
|
||||
{
|
||||
.name = "gtk-dark",
|
||||
.has_arg = no_argument,
|
||||
.flag = NULL,
|
||||
.val = 'G'
|
||||
},
|
||||
{
|
||||
.name = "search",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'Q'
|
||||
},
|
||||
{
|
||||
.name = "monitor",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'o'
|
||||
},
|
||||
{
|
||||
.name = "pre-display-cmd",
|
||||
.has_arg = required_argument,
|
||||
.flag = NULL,
|
||||
.val = 'r'
|
||||
},
|
||||
{
|
||||
.name = NULL,
|
||||
.has_arg = 0,
|
||||
.flag = NULL,
|
||||
.val = 0
|
||||
}
|
||||
};
|
||||
|
||||
const char* config_str = NULL;
|
||||
char* style_str = NULL;
|
||||
char* color_str = NULL;
|
||||
char* mode = NULL;
|
||||
char* prompt = NULL;
|
||||
char* width = NULL;
|
||||
char* height = NULL;
|
||||
char* x = NULL;
|
||||
char* y = NULL;
|
||||
char* normal_window = NULL;
|
||||
char* allow_images = NULL;
|
||||
char* allow_markup = NULL;
|
||||
char* cache_file = NULL;
|
||||
char* terminal = NULL;
|
||||
char* password_char = "false";
|
||||
char* exec_search = NULL;
|
||||
char* hide_scroll = NULL;
|
||||
char* matching = NULL;
|
||||
char* insensitive = NULL;
|
||||
char* parse_search = NULL;
|
||||
char* location = NULL;
|
||||
char* no_actions = NULL;
|
||||
char* lines = NULL;
|
||||
char* columns = NULL;
|
||||
char* sort_order = NULL;
|
||||
char* gtk_dark = NULL;
|
||||
char* search = NULL;
|
||||
char* monitor = NULL;
|
||||
char* pre_display_cmd = NULL;
|
||||
|
||||
struct wl_list options;
|
||||
wl_list_init(&options);
|
||||
struct option_node* node;
|
||||
|
||||
int opt;
|
||||
while((opt = getopt_long(argc, argv, "hfc:s:C:dS:W:H:p:x:y:nImk:t:P::ebM:iqvl:aD:L:w:O:GQ:o:r:", opts, NULL)) != -1) {
|
||||
switch(opt) {
|
||||
case 'h':
|
||||
print_usage(argv);
|
||||
break;
|
||||
case 'f':
|
||||
if(fork() > 0) {
|
||||
exit(0);
|
||||
}
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
fclose(stdin);
|
||||
break;
|
||||
case 'c':
|
||||
config_str = optarg;
|
||||
break;
|
||||
case 's':
|
||||
style_str = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
color_str = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
mode = "dmenu";
|
||||
break;
|
||||
case 'S':
|
||||
mode = optarg;
|
||||
break;
|
||||
case 'W':
|
||||
width = optarg;
|
||||
break;
|
||||
case 'H':
|
||||
height = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
prompt = optarg;
|
||||
break;
|
||||
case 'x':
|
||||
x = optarg;
|
||||
break;
|
||||
case 'y':
|
||||
y = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
normal_window = "true";
|
||||
break;
|
||||
case 'I':
|
||||
allow_images = "true";
|
||||
break;
|
||||
case 'm':
|
||||
allow_markup = "true";
|
||||
break;
|
||||
case 'k':
|
||||
cache_file = optarg;
|
||||
break;
|
||||
case 't':
|
||||
terminal = optarg;
|
||||
break;
|
||||
case 'P':
|
||||
password_char = optarg;
|
||||
break;
|
||||
case 'e':
|
||||
exec_search = "true";
|
||||
break;
|
||||
case 'b':
|
||||
hide_scroll = "true";
|
||||
break;
|
||||
case 'M':
|
||||
matching = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
insensitive = "true";
|
||||
break;
|
||||
case 'q':
|
||||
parse_search = "true";
|
||||
break;
|
||||
case 'v':
|
||||
printf(VERSION"\n");
|
||||
exit(0);
|
||||
break;
|
||||
case 'l':
|
||||
location = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
no_actions = "true";
|
||||
break;
|
||||
case 'D':
|
||||
node = malloc(sizeof(struct option_node));
|
||||
node->option = optarg;
|
||||
wl_list_insert(&options, &node->link);
|
||||
break;
|
||||
case 'L':
|
||||
lines = optarg;
|
||||
break;
|
||||
case 'w':
|
||||
columns = optarg;
|
||||
break;
|
||||
case 'O':
|
||||
sort_order = optarg;
|
||||
break;
|
||||
case 'G':
|
||||
gtk_dark = "true";
|
||||
break;
|
||||
case 'Q':
|
||||
search = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
monitor = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
pre_display_cmd = optarg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const char* home_dir = getenv("HOME");
|
||||
const char* xdg_conf = getenv("XDG_CONFIG_HOME");
|
||||
if(xdg_conf == NULL) {
|
||||
CONFIG_LOCATION = utils_concat(2, home_dir, "/.config/wofi");
|
||||
} else {
|
||||
CONFIG_LOCATION = utils_concat(2, xdg_conf, "/wofi");
|
||||
}
|
||||
|
||||
const char* xdg_cache = getenv("XDG_CACHE_HOME");
|
||||
if(xdg_cache == NULL) {
|
||||
COLORS_LOCATION = utils_concat(2, home_dir, "/.cache/wal/colors");
|
||||
} else {
|
||||
COLORS_LOCATION = utils_concat(2, xdg_cache, "/wal/colors");
|
||||
}
|
||||
|
||||
config = map_init();
|
||||
|
||||
//Check if --conf was specified
|
||||
if(config_str == NULL) {
|
||||
const char* config_f = "/config";
|
||||
config_path = utils_concat(2, CONFIG_LOCATION, config_f);
|
||||
} else {
|
||||
config_path = strdup(config_str);
|
||||
}
|
||||
if(access(config_path, R_OK) == 0) {
|
||||
config_load(config, config_path);
|
||||
}
|
||||
free(config_path);
|
||||
|
||||
if(style_str == NULL) {
|
||||
style_str = map_get(config, "style");
|
||||
}
|
||||
|
||||
//Check if --style was specified
|
||||
if(style_str == NULL) {
|
||||
style_str = map_get(config, "stylesheet");
|
||||
if(style_str == NULL) {
|
||||
const char* style_f = "/style.css";
|
||||
stylesheet = utils_concat(2, CONFIG_LOCATION, style_f);
|
||||
} else {
|
||||
if(style_str[0] == '/') {
|
||||
stylesheet = strdup(style_str);
|
||||
} else {
|
||||
stylesheet = utils_concat(3, CONFIG_LOCATION, "/", style_str);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stylesheet = strdup(style_str);
|
||||
}
|
||||
|
||||
if(color_str == NULL) {
|
||||
color_str = map_get(config, "color");
|
||||
}
|
||||
|
||||
//Check if --color was specified
|
||||
if(color_str == NULL) {
|
||||
color_str = map_get(config, "colors");
|
||||
if(color_str == NULL) {
|
||||
color_path = strdup(COLORS_LOCATION);
|
||||
} else {
|
||||
if(color_str[0] == '/') {
|
||||
color_path = strdup(color_str);
|
||||
} else {
|
||||
color_path = utils_concat(3, CONFIG_LOCATION, "/", color_str);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
color_path = strdup(color_str);
|
||||
}
|
||||
|
||||
//Check if --gtk-dark was specified
|
||||
if(gtk_dark == NULL) {
|
||||
gtk_dark = map_get(config, "gtk_dark");
|
||||
}
|
||||
|
||||
free(COLORS_LOCATION);
|
||||
|
||||
struct option_node* tmp;
|
||||
wl_list_for_each_safe(node, tmp, &options, link) {
|
||||
config_put(config, node->option);
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
|
||||
if(map_get(config, "show") != NULL) {
|
||||
map_put(config, "mode", map_get(config, "show"));
|
||||
}
|
||||
|
||||
if(strcmp(get_exec_name(argv[0]), "dmenu") == 0) {
|
||||
map_put(config, "mode", "dmenu");
|
||||
cache_file = "/dev/null";
|
||||
} else if(strcmp(get_exec_name(argv[0]), "wofi-askpass") == 0) {
|
||||
map_put(config, "mode", "dmenu");
|
||||
cache_file = "/dev/null";
|
||||
password_char = "*";
|
||||
prompt = "Password";
|
||||
} else if(mode != NULL) {
|
||||
map_put(config, "mode", mode);
|
||||
} else if(map_get(config, "mode") == NULL) {
|
||||
fprintf(stderr, "I need a mode, please give me a mode, that's what --show is for\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
map_put(config, "config_dir", CONFIG_LOCATION);
|
||||
|
||||
if(width != NULL) {
|
||||
map_put(config, "width", width);
|
||||
}
|
||||
if(height != NULL) {
|
||||
map_put(config, "height", height);
|
||||
}
|
||||
if(prompt != NULL) {
|
||||
map_put(config, "prompt", prompt);
|
||||
}
|
||||
if(map_get(config, "xoffset") != NULL) {
|
||||
map_put(config, "x", map_get(config, "xoffset"));
|
||||
}
|
||||
if(x != NULL) {
|
||||
map_put(config, "x", x);
|
||||
}
|
||||
if(map_get(config, "yoffset") != NULL) {
|
||||
map_put(config, "y", map_get(config, "yoffset"));
|
||||
}
|
||||
if(y != NULL) {
|
||||
map_put(config, "y", y);
|
||||
}
|
||||
if(normal_window != NULL) {
|
||||
map_put(config, "normal_window", normal_window);
|
||||
}
|
||||
if(allow_images != NULL) {
|
||||
map_put(config, "allow_images", allow_images);
|
||||
}
|
||||
if(allow_markup != NULL) {
|
||||
map_put(config, "allow_markup", allow_markup);
|
||||
}
|
||||
if(cache_file != NULL) {
|
||||
map_put(config, "cache_file", cache_file);
|
||||
}
|
||||
if(terminal != NULL) {
|
||||
map_put(config, "term", terminal);
|
||||
}
|
||||
if(map_get(config, "password") != NULL) {
|
||||
map_put(config, "password_char", map_get(config, "password"));
|
||||
}
|
||||
if(password_char == NULL || (password_char != NULL && strcmp(password_char, "false") != 0)) {
|
||||
if(password_char == NULL) {
|
||||
password_char = "*";
|
||||
}
|
||||
map_put(config, "password_char", password_char);
|
||||
}
|
||||
if(exec_search != NULL) {
|
||||
map_put(config, "exec_search", exec_search);
|
||||
}
|
||||
if(hide_scroll != NULL) {
|
||||
map_put(config, "hide_scroll", hide_scroll);
|
||||
}
|
||||
if(matching != NULL) {
|
||||
map_put(config, "matching", matching);
|
||||
}
|
||||
if(insensitive != NULL) {
|
||||
map_put(config, "insensitive", insensitive);
|
||||
}
|
||||
if(parse_search != NULL) {
|
||||
map_put(config, "parse_search", parse_search);
|
||||
}
|
||||
if(location != NULL) {
|
||||
map_put(config, "location", location);
|
||||
}
|
||||
if(no_actions != NULL) {
|
||||
map_put(config, "no_actions", no_actions);
|
||||
}
|
||||
if(lines != NULL) {
|
||||
map_put(config, "lines", lines);
|
||||
}
|
||||
if(columns != NULL) {
|
||||
map_put(config, "columns", columns);
|
||||
}
|
||||
if(sort_order != NULL) {
|
||||
map_put(config, "sort_order", sort_order);
|
||||
}
|
||||
if(search != NULL) {
|
||||
map_put(config, "search", search);
|
||||
}
|
||||
if(monitor != NULL) {
|
||||
map_put(config, "monitor", monitor);
|
||||
}
|
||||
if(pre_display_cmd != NULL) {
|
||||
map_put(config, "pre_display_cmd", pre_display_cmd);
|
||||
}
|
||||
|
||||
struct sigaction sigact = {0};
|
||||
sigact.sa_handler = sig;
|
||||
sigaction(SIGTERM, &sigact, NULL);
|
||||
|
||||
|
||||
gtk_init(&argc, &argv);
|
||||
|
||||
if(gtk_dark != NULL && strcmp(gtk_dark, "true") == 0) {
|
||||
g_object_set(gtk_settings_get_default(),
|
||||
"gtk-application-prefer-dark-theme", TRUE, NULL);
|
||||
}
|
||||
wofi_load_css(false);
|
||||
|
||||
wofi_init(config);
|
||||
gtk_main();
|
||||
return 0;
|
||||
}
|
96
wofi/src/map.c
Normal file
96
wofi/src/map.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <map.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <gmodule.h>
|
||||
|
||||
struct map {
|
||||
GTree* tree;
|
||||
bool mman;
|
||||
};
|
||||
|
||||
static gint compare(gconstpointer p1, gconstpointer p2, gpointer data) {
|
||||
(void) data;
|
||||
const char* str1 = p1;
|
||||
const char* str2 = p2;
|
||||
return strcmp(str1, str2);
|
||||
}
|
||||
|
||||
struct map* map_init(void) {
|
||||
struct map* map = malloc(sizeof(struct map));
|
||||
map->tree = g_tree_new_full(compare, NULL, free, free);
|
||||
map->mman = true;
|
||||
return map;
|
||||
}
|
||||
|
||||
struct map* map_init_void(void) {
|
||||
struct map* map = malloc(sizeof(struct map));
|
||||
map->tree = g_tree_new_full(compare, NULL, free, NULL);
|
||||
map->mman = false;
|
||||
return map;
|
||||
}
|
||||
|
||||
void map_free(struct map* map) {
|
||||
g_tree_destroy(map->tree);
|
||||
free(map);
|
||||
}
|
||||
|
||||
static void put(struct map* map, const char* key, void* value) {
|
||||
char* k = strdup(key);
|
||||
char* v = value;
|
||||
if(map->mman && value != NULL) {
|
||||
v = strdup(value);
|
||||
}
|
||||
g_tree_insert(map->tree, k, v);
|
||||
}
|
||||
|
||||
bool map_put(struct map* map, const char* key, char* value) {
|
||||
if(map->mman) {
|
||||
put(map, key, value);
|
||||
return true;
|
||||
} else {
|
||||
fprintf(stderr, "This is an unmanaged map please use map_put_void\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool map_put_void(struct map* map, const char* key, void* value) {
|
||||
if(map->mman) {
|
||||
fprintf(stderr, "This is an managed map please use map_put\n");
|
||||
return false;
|
||||
} else {
|
||||
put(map, key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void* map_get(struct map* map, const char* key) {
|
||||
return g_tree_lookup(map->tree, key);
|
||||
}
|
||||
|
||||
bool map_contains(struct map* map, const char* key) {
|
||||
return map_get(map, key) != NULL;
|
||||
}
|
||||
|
||||
size_t map_size(struct map* map) {
|
||||
return g_tree_nnodes(map->tree);
|
||||
}
|
412
wofi/src/match.c
Normal file
412
wofi/src/match.c
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2022 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <match.h>
|
||||
#include <string.h>
|
||||
|
||||
// leading gap
|
||||
#define SCORE_GAP_LEADING -0.005
|
||||
// trailing gap
|
||||
#define SCORE_GAP_TRAILING -0.005
|
||||
// gap in the middle
|
||||
#define SCORE_GAP_INNER -0.01
|
||||
// we matched the characters consecutively
|
||||
#define SCORE_MATCH_CONSECUTIVE 1.0
|
||||
// we got a consecutive match, but insensitive is on
|
||||
// and we didn't match the case.
|
||||
#define SCORE_MATCH_NOT_MATCH_CASE 0.9
|
||||
// we are matching after a slash
|
||||
#define SCORE_MATCH_SLASH 0.9
|
||||
// we are matching after a space dash or hyphen
|
||||
#define SCORE_MATCH_WORD 0.8
|
||||
// we are matching a camel case letter
|
||||
#define SCORE_MATCH_CAPITAL 0.7
|
||||
// we are matching after a dot
|
||||
#define SCORE_MATCH_DOT 0.6
|
||||
|
||||
#define SWAP(x, y, T) \
|
||||
do { \
|
||||
T SWAP = x; \
|
||||
x = y; \
|
||||
y = SWAP; \
|
||||
} while(0)
|
||||
|
||||
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
// matching
|
||||
static bool contains_match(const char* filter, const char* text, bool insensitive) {
|
||||
if(filter == NULL || strcmp(filter, "") == 0) {
|
||||
return true;
|
||||
}
|
||||
if(text == NULL) {
|
||||
return false;
|
||||
}
|
||||
if(insensitive) {
|
||||
return strcasestr(text, filter) != NULL;
|
||||
} else {
|
||||
return strstr(text, filter) != NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char* strcasechr(const char* s,char c, bool insensitive) {
|
||||
if(insensitive) {
|
||||
const char accept[3] = {tolower(c), toupper(c), 0};
|
||||
return strpbrk(s, accept);
|
||||
} else {
|
||||
return strchr(s, c);
|
||||
}
|
||||
}
|
||||
|
||||
static bool fuzzy_match(const char* filter, const char* text, bool insensitive) {
|
||||
if(filter == NULL || strcmp(filter, "") == 0) {
|
||||
return true;
|
||||
}
|
||||
if(text == NULL) {
|
||||
return false;
|
||||
}
|
||||
// we just check that all the characters (ignoring case) are in the
|
||||
// search text possibly case insensitively in the correct order
|
||||
while(*filter != 0) {
|
||||
char nch = *filter++;
|
||||
|
||||
if(!(text = strcasechr(text, nch, insensitive))) {
|
||||
return false;
|
||||
}
|
||||
text++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool multi_contains_match(const char* filter, const char* text, bool insensitive) {
|
||||
if(filter == NULL || strcmp(filter, "") == 0) {
|
||||
return true;
|
||||
}
|
||||
if(text == NULL) {
|
||||
return false;
|
||||
}
|
||||
char new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE];
|
||||
strncpy(new_filter, filter, sizeof(new_filter));
|
||||
new_filter[sizeof(new_filter) - 1] = '\0';
|
||||
char* token;
|
||||
char* rest = new_filter;
|
||||
while((token = strtok_r(rest, " ", &rest))) {
|
||||
if(contains_match(token, text, insensitive) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match_for_matching_mode(const char* filter, const char* text,
|
||||
enum matching_mode matching, bool insensitive) {
|
||||
bool retval;
|
||||
switch(matching) {
|
||||
case MATCHING_MODE_MULTI_CONTAINS:
|
||||
retval = multi_contains_match(filter, text, insensitive);
|
||||
break;
|
||||
case MATCHING_MODE_CONTAINS:
|
||||
retval = contains_match(filter, text, insensitive);
|
||||
break;
|
||||
case MATCHING_MODE_FUZZY:
|
||||
retval = fuzzy_match(filter, text, insensitive);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
// end matching
|
||||
|
||||
// fuzzy matching
|
||||
static void precompute_bonus(const char* haystack, score_t* match_bonus) {
|
||||
/* Which positions are beginning of words */
|
||||
int m = strlen(haystack);
|
||||
char last_ch = '\0';
|
||||
for(int i = 0; i < m; i++) {
|
||||
char ch = haystack[i];
|
||||
|
||||
score_t score = 0;
|
||||
if(isalnum(ch)) {
|
||||
if(!last_ch || last_ch == '/') {
|
||||
score = SCORE_MATCH_SLASH;
|
||||
} else if(last_ch == '-' || last_ch == '_' ||
|
||||
last_ch == ' ') {
|
||||
score = SCORE_MATCH_WORD;
|
||||
} else if(last_ch >= 'a' && last_ch <= 'z' &&
|
||||
ch >= 'A' && ch <= 'Z') {
|
||||
/* CamelCase */
|
||||
score = SCORE_MATCH_CAPITAL;
|
||||
} else if(last_ch == '.') {
|
||||
score = SCORE_MATCH_DOT;
|
||||
}
|
||||
}
|
||||
|
||||
match_bonus[i] = score;
|
||||
last_ch = ch;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool match_with_case(char a, char b, bool insensitive) {
|
||||
if(insensitive) {
|
||||
return tolower(a) == tolower(b);
|
||||
} else {
|
||||
return a == b;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void match_row(int row, score_t* curr_D, score_t* curr_M,
|
||||
const score_t* last_D, const score_t* last_M,
|
||||
const char* needle, const char* haystack, int n, int m, score_t* match_bonus, bool insensitive) {
|
||||
int i = row;
|
||||
|
||||
score_t prev_score = SCORE_MIN;
|
||||
score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER;
|
||||
|
||||
for(int j = 0; j < m; j++) {
|
||||
if(match_with_case(needle[i], haystack[j], insensitive)) {
|
||||
score_t score = SCORE_MIN;
|
||||
if(!i) {
|
||||
// first line we fill in a row for non-matching
|
||||
score = (j * SCORE_GAP_LEADING) + match_bonus[j];
|
||||
} else if(j) { /* i > 0 && j > 0*/
|
||||
// we definitely match case insensitively already so if
|
||||
// our character isn't the same then we have a different case
|
||||
score_t consecutive_bonus = needle[i] == haystack[j] ? SCORE_MATCH_CONSECUTIVE : SCORE_MATCH_NOT_MATCH_CASE;
|
||||
|
||||
score = max(last_M[j - 1] + match_bonus[j],
|
||||
/* consecutive match, doesn't stack
|
||||
with match_bonus */
|
||||
last_D[j - 1] + consecutive_bonus);
|
||||
}
|
||||
curr_D[j] = score;
|
||||
curr_M[j] = prev_score = max(score, prev_score + gap_score);
|
||||
} else {
|
||||
curr_D[j] = SCORE_MIN;
|
||||
curr_M[j] = prev_score = prev_score + gap_score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzzy matching scoring. Adapted from
|
||||
// https://github.com/jhawthorn/fzy/blob/master/src/match.c and
|
||||
// https://github.com/jhawthorn/fzy/blob/master/ALGORITHM.md
|
||||
// For a fuzzy match string needle being searched for in haystack we provide a
|
||||
// number score for how well we match.
|
||||
// We create two matrices of size needle_len (n) by haystack_len (m).
|
||||
// The first matrix is the score matrix. Each position (i,j) within this matrix
|
||||
// consists of the score that corresponds to the score that would be generated
|
||||
// by matching the first i characters of the needle with the first j
|
||||
// characters of the haystack. Gaps have a fixed penalty for having a gap along
|
||||
// with a linear penalty for gap size (c.f. gotoh's algorithm).
|
||||
// matches give a positive score, with a slight weight given to matches after
|
||||
// certain special characters (i.e. the first character after a `/` will be
|
||||
// "almost" consecutive but lower than an actual consecutive match).
|
||||
// Our second matrix is our diagonal matrix where we store the best match
|
||||
// that ends at a match. This allows us to calculate our gap penalties alongside
|
||||
// our consecutive match scores.
|
||||
// In addition, since we only rely on the current, and previous row of the
|
||||
// matrices and we only want to compute the score, we only store those scores
|
||||
// and reuse the previous rows (rather than storing the entire (n*m) matrix).
|
||||
// In addition we've simplified some of the algorithm compared to fzy to
|
||||
// improve legibility. (Can reimplement lookup tables later if wanted.)
|
||||
// Also, the reference algorithm does not take into account case sensitivity
|
||||
// which has been implemented here.
|
||||
|
||||
static score_t fuzzy_score(const char* haystack, const char* needle, bool insensitive) {
|
||||
if(*needle == 0)
|
||||
return SCORE_MIN;
|
||||
|
||||
int n = strlen(needle);
|
||||
int m = strlen(haystack);
|
||||
score_t match_bonus[m];
|
||||
precompute_bonus(haystack, match_bonus);
|
||||
|
||||
if(m > MATCH_FUZZY_MAX_LEN || n > m) {
|
||||
/*
|
||||
* Unreasonably large candidate: return no score
|
||||
* If it is a valid match it will still be returned, it will
|
||||
* just be ranked below any reasonably sized candidates
|
||||
*/
|
||||
return SCORE_MIN;
|
||||
} else if(n == m) {
|
||||
/* Since this method can only be called with a haystack which
|
||||
* matches needle. If the lengths of the strings are equal the
|
||||
* strings themselves must also be equal (ignoring case).
|
||||
*/
|
||||
return SCORE_MAX;
|
||||
}
|
||||
|
||||
/*
|
||||
* D[][] Stores the best score for this position ending with a match.
|
||||
* M[][] Stores the best possible score at this position.
|
||||
*/
|
||||
score_t D[2][MATCH_FUZZY_MAX_LEN], M[2][MATCH_FUZZY_MAX_LEN];
|
||||
|
||||
score_t* last_D, *last_M;
|
||||
score_t* curr_D, *curr_M;
|
||||
|
||||
last_D = D[0];
|
||||
last_M = M[0];
|
||||
curr_D = D[1];
|
||||
curr_M = M[1];
|
||||
|
||||
for(int i = 0; i < n; i++) {
|
||||
match_row(i, curr_D, curr_M, last_D, last_M, needle, haystack, n, m, match_bonus, insensitive);
|
||||
|
||||
SWAP(curr_D, last_D, score_t *);
|
||||
SWAP(curr_M, last_M, score_t *);
|
||||
}
|
||||
|
||||
return last_M[m - 1];
|
||||
}
|
||||
// end fuzzy matching
|
||||
|
||||
// sorting
|
||||
static int fuzzy_sort(const char* text1, const char* text2, const char* filter, bool insensitive) {
|
||||
bool match1 = fuzzy_match(filter, text1, insensitive);
|
||||
bool match2 = fuzzy_match(filter, text2, insensitive);
|
||||
// both filters match do fuzzy scoring
|
||||
if(match1 && match2) {
|
||||
score_t dist1 = fuzzy_score(text1, filter, insensitive);
|
||||
score_t dist2 = fuzzy_score(text2, filter, insensitive);
|
||||
if(dist1 == dist2) {
|
||||
// same same
|
||||
return 0;
|
||||
} else if(dist1 > dist2) { // highest score wins.
|
||||
// text1 goes first
|
||||
return -1;
|
||||
} else {
|
||||
// text2 goes first
|
||||
return 1;
|
||||
}
|
||||
} else if(match1) {
|
||||
// text1 goes first
|
||||
return -1;
|
||||
} else if(match2) {
|
||||
// text2 goes first
|
||||
return 1;
|
||||
} else {
|
||||
// same same.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// we sort based on how early in the string all the matches are.
|
||||
// if there are matches for each.
|
||||
static int multi_contains_sort(const char* text1, const char* text2, const char* filter, bool insensitive) {
|
||||
// sum of string positions of each match
|
||||
int t1_count = 0;
|
||||
int t2_count = 0;
|
||||
// does this string match with mult-contains
|
||||
bool t1_match = true;
|
||||
bool t2_match = true;
|
||||
|
||||
char new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE];
|
||||
strncpy(new_filter, filter, sizeof(new_filter));
|
||||
new_filter[sizeof(new_filter) - 1] = '\0';
|
||||
|
||||
char* token;
|
||||
char* rest = new_filter;
|
||||
while((token = strtok_r(rest, " ", &rest))) {
|
||||
char* str1, *str2;
|
||||
if(insensitive) {
|
||||
str1 = strcasestr(text1, token);
|
||||
str2 = strcasestr(text2, token);
|
||||
} else {
|
||||
str1 = strstr(text1, token);
|
||||
str2 = strstr(text2, token);
|
||||
}
|
||||
t1_match = t1_match && str1 != NULL;
|
||||
t2_match = t2_match && str2 != NULL;
|
||||
if(str1 != NULL) {
|
||||
int pos1 = str1 - text1;
|
||||
t1_count += pos1;
|
||||
}
|
||||
if(str2 != NULL) {
|
||||
int pos2 = str2 - text2;
|
||||
t2_count += pos2;
|
||||
}
|
||||
}
|
||||
if(t1_match && t2_match) {
|
||||
// both match
|
||||
// return the one with the smallest count.
|
||||
return t1_count - t2_count;
|
||||
} else if(t1_match) {
|
||||
return -1;
|
||||
} else if(t2_match) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static int contains_sort(const char* text1, const char* text2, const char* filter, bool insensitive) {
|
||||
char* str1, *str2;
|
||||
|
||||
if(insensitive) {
|
||||
str1 = strcasestr(text1, filter);
|
||||
str2 = strcasestr(text2, filter);
|
||||
} else {
|
||||
str1 = strstr(text1, filter);
|
||||
str2 = strstr(text2, filter);
|
||||
}
|
||||
bool tx1 = str1 == text1;
|
||||
bool tx2 = str2 == text2;
|
||||
bool txc1 = str1 != NULL;
|
||||
bool txc2 = str2 != NULL;
|
||||
|
||||
if(tx1 && tx2) {
|
||||
return 0;
|
||||
} else if(tx1) {
|
||||
return -1;
|
||||
} else if(tx2) {
|
||||
return 1;
|
||||
} else if(txc1 && txc2) {
|
||||
return 0;
|
||||
} else if(txc1) {
|
||||
return -1;
|
||||
} else if(txc2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int sort_for_matching_mode(const char* text1, const char* text2, int fallback,
|
||||
enum matching_mode match_type, const char* filter, bool insensitive) {
|
||||
int primary = 0;
|
||||
switch(match_type) {
|
||||
case MATCHING_MODE_MULTI_CONTAINS:
|
||||
primary = multi_contains_sort(text1, text2, filter, insensitive);
|
||||
break;
|
||||
case MATCHING_MODE_CONTAINS:
|
||||
primary = contains_sort(text1, text2, filter, insensitive);
|
||||
break;
|
||||
case MATCHING_MODE_FUZZY:
|
||||
primary = fuzzy_sort(text1, text2, filter, insensitive);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
if(primary == 0) {
|
||||
return fallback;
|
||||
}
|
||||
return primary;
|
||||
}
|
||||
// end sorting
|
||||
|
60
wofi/src/property_box.c
Normal file
60
wofi/src/property_box.c
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <property_box.h>
|
||||
|
||||
#include <map.h>
|
||||
|
||||
struct _WofiPropertyBox {
|
||||
GtkBox super;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct map* properties;
|
||||
} WofiPropertyBoxPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE(WofiPropertyBox, wofi_property_box, GTK_TYPE_BOX)
|
||||
|
||||
static void wofi_property_box_init(WofiPropertyBox* box) {
|
||||
WofiPropertyBoxPrivate* this = wofi_property_box_get_instance_private(box);
|
||||
this->properties = map_init();
|
||||
}
|
||||
|
||||
static void finalize(GObject* obj) {
|
||||
WofiPropertyBoxPrivate* this = wofi_property_box_get_instance_private(WOFI_PROPERTY_BOX(obj));
|
||||
map_free(this->properties);
|
||||
G_OBJECT_CLASS(wofi_property_box_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static void wofi_property_box_class_init(WofiPropertyBoxClass* class) {
|
||||
GObjectClass* g_class = G_OBJECT_CLASS(class);
|
||||
g_class->finalize = finalize;
|
||||
}
|
||||
|
||||
GtkWidget* wofi_property_box_new(GtkOrientation orientation, gint spacing) {
|
||||
return g_object_new(WOFI_TYPE_PROPERTY_BOX, "orientation", orientation, "spacing", spacing, NULL);
|
||||
}
|
||||
|
||||
void wofi_property_box_add_property(WofiPropertyBox* box, const gchar* key, gchar* value) {
|
||||
WofiPropertyBoxPrivate* this = wofi_property_box_get_instance_private(box);
|
||||
map_put(this->properties, key, value);
|
||||
}
|
||||
|
||||
const gchar* wofi_property_box_get_property(WofiPropertyBox* box, const gchar* key) {
|
||||
WofiPropertyBoxPrivate* this = wofi_property_box_get_instance_private(box);
|
||||
return map_get(this->properties, key);
|
||||
}
|
119
wofi/src/utils.c
Normal file
119
wofi/src/utils.c
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <utils.h>
|
||||
|
||||
#include <libgen.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
time_t utils_get_time_millis(void) {
|
||||
struct timeval time;
|
||||
gettimeofday(&time, NULL);
|
||||
return (time.tv_sec * 1000) + (time.tv_usec / 1000);
|
||||
}
|
||||
|
||||
void utils_sleep_millis(time_t millis) {
|
||||
struct timespec time;
|
||||
time.tv_sec = millis / 1000;
|
||||
time.tv_nsec = (millis % 1000) * pow(1000, 2);
|
||||
nanosleep(&time, NULL);
|
||||
}
|
||||
|
||||
char* utils_concat(size_t arg_count, ...) {
|
||||
va_list args;
|
||||
va_start(args, arg_count);
|
||||
size_t buf_s = 1;
|
||||
for(size_t count = 0; count < arg_count; ++count) {
|
||||
buf_s += strlen(va_arg(args, char*));
|
||||
}
|
||||
va_end(args);
|
||||
va_start(args, arg_count);
|
||||
char* buffer = malloc(buf_s);
|
||||
strcpy(buffer, va_arg(args, char*));
|
||||
for(size_t count = 0; count < arg_count - 1; ++count) {
|
||||
strcat(buffer, va_arg(args, char*));
|
||||
}
|
||||
va_end(args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
size_t utils_min(size_t n1, size_t n2) {
|
||||
return n1 < n2 ? n1 : n2;
|
||||
}
|
||||
|
||||
size_t utils_max(size_t n1, size_t n2) {
|
||||
return n1 > n2 ? n1 : n2;
|
||||
}
|
||||
|
||||
size_t utils_min3(size_t n1, size_t n2, size_t n3) {
|
||||
if(n1 < n2 && n1 < n3) {
|
||||
return n1;
|
||||
} else if(n2 < n1 && n2 < n3) {
|
||||
return n2;
|
||||
} else {
|
||||
return n3;
|
||||
}
|
||||
}
|
||||
|
||||
size_t utils_distance(const char* haystack, const char* needle) {
|
||||
size_t str1_len = strlen(haystack);
|
||||
size_t str2_len = strlen(needle);
|
||||
|
||||
size_t arr[str1_len + 1][str2_len + 1];
|
||||
arr[0][0] = 0;
|
||||
for(size_t count = 1; count <= str1_len; ++count) {
|
||||
arr[count][0] = count;
|
||||
}
|
||||
for(size_t count = 1; count <= str2_len; ++count) {
|
||||
arr[0][count] = count;
|
||||
}
|
||||
|
||||
uint8_t cost;
|
||||
for(size_t c1 = 1; c1 <= str1_len; ++c1) {
|
||||
for(size_t c2 = 1; c2 <= str2_len; ++c2) {
|
||||
if(haystack[c1 - 1] == needle[c2 - 1]) {
|
||||
cost = 0;
|
||||
} else {
|
||||
cost = 1;
|
||||
}
|
||||
arr[c1][c2] = utils_min3(arr[c1 - 1][c2] + 1, arr[c1][c2 - 1] + 1, arr[c1 - 1][c2 - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
if(strstr(haystack, needle) != NULL) {
|
||||
arr[str1_len][str2_len] -= str2_len;
|
||||
}
|
||||
|
||||
return arr[str1_len][str2_len];
|
||||
}
|
||||
|
||||
void utils_mkdir(char* path, mode_t mode) {
|
||||
if(access(path, F_OK) != 0) {
|
||||
char* tmp = strdup(path);
|
||||
utils_mkdir(dirname(tmp), mode);
|
||||
mkdir(path, mode);
|
||||
free(tmp);
|
||||
}
|
||||
}
|
80
wofi/src/utils_g.c
Normal file
80
wofi/src/utils_g.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <utils_g.h>
|
||||
|
||||
#include <wofi_api.h>
|
||||
|
||||
GdkPixbuf* utils_g_resize_pixbuf(GdkPixbuf* pixbuf, uint64_t image_size, GdkInterpType interp) {
|
||||
int width = gdk_pixbuf_get_width(pixbuf);
|
||||
int height = gdk_pixbuf_get_height(pixbuf);
|
||||
|
||||
if(height > width) {
|
||||
float percent = (float) image_size / height;
|
||||
GdkPixbuf* tmp = gdk_pixbuf_scale_simple(pixbuf, width * percent, image_size, interp);
|
||||
g_object_unref(pixbuf);
|
||||
return tmp;
|
||||
} else {
|
||||
float percent = (float) image_size / width;
|
||||
GdkPixbuf* tmp = gdk_pixbuf_scale_simple(pixbuf, image_size, height * percent, interp);
|
||||
g_object_unref(pixbuf);
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
GdkPixbuf* utils_g_pixbuf_from_base64(char* base64) {
|
||||
char* str = strdup(base64);
|
||||
char* original_str = str;
|
||||
|
||||
if(strncmp(str, "data:", sizeof("data:") - 1) == 0) {
|
||||
str += sizeof("data:") - 1;
|
||||
}
|
||||
|
||||
GError* err = NULL;
|
||||
GdkPixbufLoader* loader;
|
||||
if(strncmp(str, "image/", sizeof("image/") - 1) == 0) {
|
||||
char* mime = strchr(str, ';');
|
||||
*mime = 0;
|
||||
loader = gdk_pixbuf_loader_new_with_mime_type(str, &err);
|
||||
if(err != NULL) {
|
||||
goto fail;
|
||||
}
|
||||
str = mime + 1;
|
||||
str = strchr(str, ',') + 1;
|
||||
} else {
|
||||
loader = gdk_pixbuf_loader_new();
|
||||
}
|
||||
|
||||
gsize data_l;
|
||||
guchar* data = g_base64_decode(str, &data_l);
|
||||
|
||||
gdk_pixbuf_loader_write(loader, data, data_l, &err);
|
||||
if(err != NULL) {
|
||||
g_free(data);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_free(data);
|
||||
|
||||
free(original_str);
|
||||
return gdk_pixbuf_loader_get_pixbuf(loader);
|
||||
|
||||
fail:
|
||||
free(str);
|
||||
fprintf(stderr, "Error loading base64 %s\n", err->message);
|
||||
return NULL;
|
||||
}
|
148
wofi/src/widget_builder.c
Normal file
148
wofi/src/widget_builder.c
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2022 Scoopta
|
||||
* This file is part of Wofi
|
||||
* Wofi is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Wofi is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Wofi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <widget_builder.h>
|
||||
|
||||
#include <wofi.h>
|
||||
#include <utils.h>
|
||||
|
||||
struct widget_builder* wofi_widget_builder_init(struct mode* mode, size_t actions) {
|
||||
struct widget_builder* builder = calloc(actions, sizeof(struct widget_builder));
|
||||
|
||||
for(size_t count = 0; count < actions; ++count) {
|
||||
builder[count].mode = mode;
|
||||
builder[count].box = WOFI_PROPERTY_BOX(wofi_property_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
|
||||
|
||||
if(count == 0) {
|
||||
builder->actions = actions;
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
void wofi_widget_builder_set_search_text(struct widget_builder* builder, char* search_text) {
|
||||
wofi_property_box_add_property(builder->box, "filter", search_text);
|
||||
}
|
||||
|
||||
void wofi_widget_builder_set_action(struct widget_builder* builder, char* action) {
|
||||
wofi_property_box_add_property(builder->box, "action", action);
|
||||
}
|
||||
|
||||
static void va_to_list(struct wl_list* classes, va_list args) {
|
||||
char* arg;
|
||||
while((arg = va_arg(args, char*)) != NULL) {
|
||||
struct css_class* class = malloc(sizeof(struct css_class));
|
||||
class->class = arg;
|
||||
wl_list_insert(classes, &class->link);
|
||||
}
|
||||
}
|
||||
|
||||
void wofi_widget_builder_insert_text(struct widget_builder* builder, const char* text, ...) {
|
||||
struct wl_list classes;
|
||||
wl_list_init(&classes);
|
||||
|
||||
va_list args;
|
||||
va_start(args, text);
|
||||
va_to_list(&classes, args);
|
||||
va_end(args);
|
||||
|
||||
wofi_widget_builder_insert_text_with_list(builder, text, &classes);
|
||||
|
||||
struct css_class* node, *tmp;
|
||||
wl_list_for_each_safe(node, tmp, &classes, link) {
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
void wofi_widget_builder_insert_text_with_list(struct widget_builder* builder, const char* text, struct wl_list* classes) {
|
||||
GtkWidget* label = gtk_label_new(text);
|
||||
gtk_container_add(GTK_CONTAINER(builder->box), label);
|
||||
gtk_widget_set_name(label, "text");
|
||||
|
||||
GtkStyleContext* ctx = gtk_widget_get_style_context(label);
|
||||
|
||||
struct css_class* node;
|
||||
wl_list_for_each(node, classes, link) {
|
||||
char* tmp = utils_concat(3, builder->mode->name, "-", node->class);
|
||||
gtk_style_context_add_class(ctx, tmp);
|
||||
free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void wofi_widget_builder_insert_image(struct widget_builder* builder, GdkPixbuf* pixbuf, ...) {
|
||||
struct wl_list classes;
|
||||
wl_list_init(&classes);
|
||||
|
||||
va_list args;
|
||||
va_start(args, pixbuf);
|
||||
va_to_list(&classes, args);
|
||||
va_end(args);
|
||||
|
||||
wofi_widget_builder_insert_image_with_list(builder, pixbuf, &classes);
|
||||
|
||||
struct css_class* node, *tmp;
|
||||
wl_list_for_each_safe(node, tmp, &classes, link) {
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
void wofi_widget_builder_insert_image_with_list(struct widget_builder* builder, GdkPixbuf* pixbuf, struct wl_list* classes) {
|
||||
GtkWidget* img = gtk_image_new();
|
||||
cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(pixbuf, wofi_get_window_scale(), gtk_widget_get_window(img));
|
||||
gtk_image_set_from_surface(GTK_IMAGE(img), surface);
|
||||
cairo_surface_destroy(surface);
|
||||
gtk_container_add(GTK_CONTAINER(builder->box), img);
|
||||
gtk_widget_set_name(img, "img");
|
||||
|
||||
GtkStyleContext* ctx = gtk_widget_get_style_context(img);
|
||||
|
||||
struct css_class* node;
|
||||
wl_list_for_each(node, classes, link) {
|
||||
char* tmp = utils_concat(3, builder->mode->name, "-", node->class);
|
||||
gtk_style_context_add_class(ctx, tmp);
|
||||
free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
struct widget_builder* wofi_widget_builder_get_idx(struct widget_builder* builder, size_t idx) {
|
||||
return builder + idx;
|
||||
}
|
||||
|
||||
struct widget* wofi_widget_builder_get_widget(struct widget_builder* builder) {
|
||||
if(builder->actions == 0) {
|
||||
fprintf(stderr, "%s: This is not the 0 index of the widget_builder array\n", builder->mode->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(builder->widget == NULL) {
|
||||
builder->widget = malloc(sizeof(struct widget));
|
||||
builder->widget->builder = builder;
|
||||
builder->widget->action_count = builder->actions;
|
||||
}
|
||||
|
||||
for(size_t count = 0; count < builder->actions; ++count) {
|
||||
}
|
||||
|
||||
return builder->widget;
|
||||
}
|
||||
|
||||
void wofi_widget_builder_free(struct widget_builder* builder) {
|
||||
if(builder->widget != NULL) {
|
||||
free(builder->widget);
|
||||
}
|
||||
free(builder);
|
||||
}
|
2093
wofi/src/wofi.c
Normal file
2093
wofi/src/wofi.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user