mirror of
https://github.com/yshui/picom.git
synced 2025-02-24 16:06:42 -05:00

Support multiple choice parameters in additional to plain old numbers. With this we are now able to change the direction of sliding, yippee! Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
540 lines
17 KiB
C
540 lines
17 KiB
C
#include <libconfig.h>
|
|
#include <uthash.h>
|
|
#include "compiler.h" // IWYU pragma: keep
|
|
#include "transition/script.h"
|
|
#include "transition/script_internal.h"
|
|
#include "utils/dynarr.h"
|
|
#include "wm/win.h"
|
|
|
|
enum knob_type {
|
|
KNOB_NUMBER,
|
|
KNOB_CHOICE,
|
|
};
|
|
|
|
struct knob {
|
|
UT_hash_handle hh;
|
|
const char *name;
|
|
enum knob_type type;
|
|
|
|
union {
|
|
struct {
|
|
double default_value;
|
|
} number;
|
|
struct {
|
|
char **choices;
|
|
unsigned n_choices;
|
|
unsigned default_choice;
|
|
};
|
|
};
|
|
bool emitted;
|
|
};
|
|
|
|
struct placeholder {
|
|
struct knob *source;
|
|
double *value_for_choices;
|
|
};
|
|
|
|
bool config_extra_get_float(config_setting_t *setting, double *value) {
|
|
if (config_setting_type(setting) != CONFIG_TYPE_FLOAT &&
|
|
config_setting_type(setting) != CONFIG_TYPE_INT &&
|
|
config_setting_type(setting) != CONFIG_TYPE_INT64) {
|
|
return false;
|
|
}
|
|
*value = config_setting_get_float(setting);
|
|
return true;
|
|
}
|
|
|
|
bool config_extra_get_int(config_setting_t *setting, int *value) {
|
|
if (config_setting_type(setting) != CONFIG_TYPE_INT &&
|
|
config_setting_type(setting) != CONFIG_TYPE_INT64) {
|
|
return false;
|
|
}
|
|
*value = config_setting_get_int(setting);
|
|
return true;
|
|
}
|
|
|
|
char *sanitized_name(const char *name) {
|
|
char *ret = strdup(name);
|
|
for (char *p = ret; *p; p++) {
|
|
if (*p == '-') {
|
|
*p = '_';
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void free_charp(void *p) {
|
|
free(*(char **)p);
|
|
}
|
|
|
|
#define scopedp(type) cleanup(free_##type##p) type *
|
|
|
|
#define MAX_PLACEHOLDERS 10
|
|
|
|
void codegen(const char *name, const char *body, const struct placeholder *placeholders) {
|
|
auto ident = sanitized_name(name);
|
|
printf("static struct script *script_template__%s(int *output_slots)\n%s\n",
|
|
ident, body);
|
|
printf("static bool win_script_preset__%s(struct win_script *output, "
|
|
"config_setting_t *setting) {\n",
|
|
ident);
|
|
printf(" output->script = script_template__%s(output->output_indices);\n", ident);
|
|
for (size_t i = 0; i < MAX_PLACEHOLDERS; i++) {
|
|
if (placeholders[i].source == NULL || placeholders[i].source->emitted) {
|
|
continue;
|
|
}
|
|
|
|
auto knob = placeholders[i].source;
|
|
scopedp(char) knob_ident = sanitized_name(knob->name);
|
|
knob->emitted = true;
|
|
if (knob->type == KNOB_NUMBER) {
|
|
printf(" double knob_%s = %a;\n", knob_ident,
|
|
knob->number.default_value);
|
|
printf(" config_setting_lookup_float(setting, \"%s\", "
|
|
"&knob_%s);\n",
|
|
knob->name, knob_ident);
|
|
continue;
|
|
}
|
|
printf(" const char *knob_%s = \"%s\";\n", knob_ident,
|
|
knob->choices[knob->default_choice]);
|
|
printf(" config_setting_lookup_string(setting, \"%s\", &knob_%s);\n",
|
|
knob->name, knob_ident);
|
|
for (unsigned j = 0; j < MAX_PLACEHOLDERS; j++) {
|
|
if (placeholders[j].source != knob) {
|
|
continue;
|
|
}
|
|
printf(" double placeholder%u_%s;\n", j, knob_ident);
|
|
}
|
|
for (unsigned j = 0; j < knob->n_choices; j++) {
|
|
printf(" if (strcmp(knob_%s, \"%s\") == 0) {\n", knob_ident,
|
|
knob->choices[j]);
|
|
for (unsigned k = 0; k < MAX_PLACEHOLDERS; k++) {
|
|
if (placeholders[k].source != knob) {
|
|
continue;
|
|
}
|
|
printf(" placeholder%u_%s = %a;\n", k, knob_ident,
|
|
placeholders[k].value_for_choices[j]);
|
|
}
|
|
printf(" } else ");
|
|
}
|
|
printf("{\n");
|
|
printf(" log_error(\"Invalid choice \\\"%%s\\\" for "
|
|
"option \\\"%s\\\". Line %%d.\", knob_%s, "
|
|
"config_setting_source_line(config_setting_get_member(setting, "
|
|
"\"%s\")));\n",
|
|
knob->name, knob_ident, knob->name);
|
|
printf(" log_error(\" Valid ones are: ");
|
|
for (unsigned j = 0; j < knob->n_choices; j++) {
|
|
printf("%s\\\"%s\\\"", j ? ", " : "", knob->choices[j]);
|
|
}
|
|
printf("\");\n");
|
|
printf(" script_free(output->script);\n");
|
|
printf(" output->script = NULL;\n");
|
|
printf(" return false;\n");
|
|
printf(" }\n");
|
|
}
|
|
printf(" struct script_specialization_context spec[] = {\n");
|
|
for (size_t i = 0; i < 10; i++) {
|
|
if (placeholders[i].source == NULL) {
|
|
continue;
|
|
}
|
|
auto knob = placeholders[i].source;
|
|
auto knob_ident = sanitized_name(knob->name);
|
|
if (knob->type == KNOB_NUMBER) {
|
|
printf(" {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + %zu, "
|
|
".value = knob_%s},\n",
|
|
i * 4, knob_ident);
|
|
} else {
|
|
printf(" {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + %zu, "
|
|
".value = placeholder%zu_%s},\n",
|
|
i * 4, i, knob_ident);
|
|
}
|
|
free(knob_ident);
|
|
}
|
|
printf(" };\n");
|
|
printf(" script_specialize(output->script, spec, ARR_SIZE(spec));\n");
|
|
printf(" return true;\n");
|
|
printf("}\n");
|
|
free(ident);
|
|
}
|
|
|
|
/// Syntax for defining knobs and placeholders:
|
|
///
|
|
/// {
|
|
/// # other settings...
|
|
/// # ....
|
|
///
|
|
/// __knobs = {
|
|
/// knob1 = 0.5; # knob1 is a number, default value 0.5
|
|
///
|
|
/// # knob2 is a choice, default choice is "default_choice" (index 2)
|
|
/// # ┌----- index of the default choice
|
|
/// # v
|
|
/// knob2 = (2, ["choice1", "choice2", "default_choice"]);
|
|
/// };
|
|
/// __placeholders = (
|
|
/// #┌----- index of the placeholder
|
|
/// #v
|
|
/// (1, "knob1"), # placeholder1 takes value from knob1
|
|
///
|
|
/// # placeholder2 takes value from knob2. Because knob2 is a choice,
|
|
/// # we need to provide a mapping from choice to value.
|
|
/// (2, "knob2", [1, 2, 0]);
|
|
/// );
|
|
/// }
|
|
|
|
static bool parse_knobs(const char *preset_name, config_setting_t *knob_settings,
|
|
config_setting_t *placeholder_settings, struct knob *knobs,
|
|
struct placeholder *placeholders) {
|
|
struct knob *knobs_by_name = NULL;
|
|
if (config_setting_length(knob_settings) > MAX_PLACEHOLDERS) {
|
|
fprintf(stderr, "Too many knobs in %s, max %d allowed\n", preset_name,
|
|
MAX_PLACEHOLDERS);
|
|
return false;
|
|
}
|
|
if (config_setting_length(placeholder_settings) > MAX_PLACEHOLDERS) {
|
|
fprintf(stderr, "Too many placeholders in %s, max %d allowed\n",
|
|
preset_name, MAX_PLACEHOLDERS);
|
|
return false;
|
|
}
|
|
unsigned n_knobs = 0;
|
|
for (unsigned i = 0; i < (unsigned)config_setting_length(knob_settings); i++) {
|
|
auto config = config_setting_get_elem(knob_settings, i);
|
|
const char *name = config_setting_name(config);
|
|
double default_value;
|
|
auto knob = &knobs[n_knobs++];
|
|
knob->name = strdup(name);
|
|
if (config_extra_get_float(config, &default_value)) {
|
|
knob->type = KNOB_NUMBER;
|
|
knob->number.default_value = default_value;
|
|
HASH_ADD_STR(knobs_by_name, name, knob);
|
|
n_knobs++;
|
|
continue;
|
|
}
|
|
if (!config_setting_is_list(config) || config_setting_length(config) != 2) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder %s in %s, line %d. It must be a "
|
|
"list of length 2.\n",
|
|
name, preset_name, config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
|
|
int default_choice;
|
|
config_setting_t *choices = config_setting_get_elem(config, 1);
|
|
if (!config_extra_get_int(config_setting_get_elem(config, 0), &default_choice) ||
|
|
choices == NULL || !config_setting_is_array(choices)) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder %s in %s, line %d. Failed to get "
|
|
"elements.\n",
|
|
name, preset_name, config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
|
|
auto n_choices = (unsigned)config_setting_length(choices);
|
|
if (default_choice < 0 || (unsigned)default_choice >= n_choices) {
|
|
fprintf(stderr,
|
|
"Invalid knob choice in %s, knob %s line %d. Default "
|
|
"choice out of range.\n",
|
|
preset_name, name, config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
knob->type = KNOB_CHOICE;
|
|
knob->n_choices = 0;
|
|
knob->choices = malloc(n_choices * sizeof(char *));
|
|
knob->default_choice = (unsigned)default_choice;
|
|
|
|
bool has_error = false;
|
|
for (unsigned j = 0; j < n_choices; j++) {
|
|
auto choice =
|
|
config_setting_get_string(config_setting_get_elem(choices, j));
|
|
if (choice == NULL) {
|
|
fprintf(stderr,
|
|
"Invalid knob choice in %s, knob %s line %d. "
|
|
"Failed to get choice.\n",
|
|
preset_name, name,
|
|
config_setting_source_line(config));
|
|
has_error = true;
|
|
break;
|
|
}
|
|
for (unsigned k = 0; k < j; k++) {
|
|
if (strcmp(knob->choices[k], choice) == 0) {
|
|
fprintf(stderr,
|
|
"Invalid knob choice in %s, knob %s line "
|
|
"%d. Duplicate choice %s.\n",
|
|
preset_name, name,
|
|
config_setting_source_line(config), choice);
|
|
has_error = true;
|
|
break;
|
|
}
|
|
}
|
|
if (has_error) {
|
|
break;
|
|
}
|
|
knob->choices[knob->n_choices++] = strdup(choice);
|
|
}
|
|
if (has_error) {
|
|
for (unsigned j = 0; j < knob->n_choices; j++) {
|
|
free(knob->choices[j]);
|
|
}
|
|
free(knob->choices);
|
|
free((void *)knob->name);
|
|
knob->choices = NULL;
|
|
knob->name = NULL;
|
|
continue;
|
|
}
|
|
HASH_ADD_STR(knobs_by_name, name, knob);
|
|
n_knobs++;
|
|
}
|
|
|
|
for (unsigned i = 0; i < (unsigned)config_setting_length(placeholder_settings); i++) {
|
|
auto config = config_setting_get_elem(placeholder_settings, i);
|
|
if (!config_setting_is_list(config) || config_setting_length(config) < 2) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder in preset %s, line %d. Must be a "
|
|
"non-empty list.\n",
|
|
preset_name, config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
|
|
int index;
|
|
if (!config_extra_get_int(config_setting_get_elem(config, 0), &index)) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder in preset %s, line %d. Index must "
|
|
"be an integer.\n",
|
|
preset_name, config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
|
|
auto placeholder = &placeholders[index];
|
|
if (placeholder->source) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder in preset %s, line %d. Placeholder "
|
|
"with index %d already defined.\n",
|
|
preset_name, config_setting_source_line(config), index);
|
|
continue;
|
|
}
|
|
BUG_ON(placeholder->value_for_choices != NULL);
|
|
const char *source =
|
|
config_setting_get_string(config_setting_get_elem(config, 1));
|
|
struct knob *knob;
|
|
HASH_FIND_STR(knobs_by_name, source, knob);
|
|
if (!knob) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, line "
|
|
"%d. Source knob %s not found.\n",
|
|
index, preset_name, config_setting_source_line(config),
|
|
source);
|
|
continue;
|
|
}
|
|
|
|
if (config_setting_length(config) == 2) {
|
|
if (knob->type != KNOB_NUMBER) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, line "
|
|
"%d. Source knob %s is not a number.\n",
|
|
index, preset_name,
|
|
config_setting_source_line(config), source);
|
|
continue;
|
|
}
|
|
placeholder->source = knob;
|
|
} else if (config_setting_length(config) == 3) {
|
|
config_setting_t *value_for_choices =
|
|
config_setting_get_elem(config, 2);
|
|
if (value_for_choices == NULL ||
|
|
!config_setting_is_array(value_for_choices)) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, line "
|
|
"%d. Failed to get elements.\n",
|
|
index, preset_name,
|
|
config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
if (knob->type != KNOB_CHOICE) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, line "
|
|
"%d. Source knob %s is not a choice.\n",
|
|
index, preset_name,
|
|
config_setting_source_line(config), source);
|
|
continue;
|
|
}
|
|
if (knob->n_choices !=
|
|
(unsigned)config_setting_length(value_for_choices)) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, line "
|
|
"%d. Number of choices doesn't match.\n",
|
|
index, preset_name,
|
|
config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
placeholder->value_for_choices =
|
|
malloc(sizeof(double) * knob->n_choices);
|
|
for (unsigned j = 0; j < knob->n_choices; j++) {
|
|
double value;
|
|
if (!config_extra_get_float(
|
|
config_setting_get_elem(value_for_choices, j), &value)) {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, "
|
|
"line %d. Failed to get value.\n",
|
|
index, preset_name,
|
|
config_setting_source_line(config));
|
|
free(placeholder->value_for_choices);
|
|
placeholder->value_for_choices = NULL;
|
|
break;
|
|
}
|
|
placeholder->value_for_choices[j] = value;
|
|
}
|
|
if (placeholder->value_for_choices == NULL) {
|
|
continue;
|
|
}
|
|
placeholder->source = knob;
|
|
} else {
|
|
fprintf(stderr,
|
|
"Invalid placeholder%d definition in %s, line %d. "
|
|
"Excessive elements.\n",
|
|
index, preset_name, config_setting_source_line(config));
|
|
continue;
|
|
}
|
|
}
|
|
struct knob *k, *nk;
|
|
HASH_ITER(hh, knobs_by_name, k, nk) {
|
|
HASH_DEL(knobs_by_name, k);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, const char **argv) {
|
|
if (argc != 2) {
|
|
return 1;
|
|
}
|
|
|
|
log_init_tls();
|
|
|
|
char **presets = dynarr_new(char *, 10);
|
|
|
|
config_t cfg;
|
|
config_init(&cfg);
|
|
config_set_auto_convert(&cfg, 1);
|
|
|
|
if (!config_read_file(&cfg, argv[1])) {
|
|
fprintf(stderr, "Failed to read config file %s: %s\n", argv[1],
|
|
config_error_text(&cfg));
|
|
config_destroy(&cfg);
|
|
return 1;
|
|
}
|
|
|
|
auto settings = config_root_setting(&cfg);
|
|
|
|
// win_script_context_info and 10 extra placeholder contexts, for
|
|
// script_specialize()
|
|
static const ptrdiff_t base = SCRIPT_CTX_PLACEHOLDER_BASE;
|
|
struct script_context_info context_info[ARR_SIZE(win_script_context_info) + MAX_PLACEHOLDERS] = {
|
|
{"placeholder0", base}, {"placeholder1", base + 4},
|
|
{"placeholder2", base + 8}, {"placeholder3", base + 12},
|
|
{"placeholder4", base + 16}, {"placeholder5", base + 20},
|
|
{"placeholder6", base + 24}, {"placeholder7", base + 28},
|
|
{"placeholder8", base + 32}, {"placeholder9", base + 36},
|
|
};
|
|
memcpy(context_info + 10, win_script_context_info, sizeof(win_script_context_info));
|
|
|
|
struct script_output_info outputs[ARR_SIZE(win_script_outputs)];
|
|
memcpy(outputs, win_script_outputs, sizeof(win_script_outputs));
|
|
|
|
struct script_parse_config parse_config = {
|
|
.context_info = context_info,
|
|
.output_info = NULL,
|
|
};
|
|
printf("// This file is generated by tools/animgen.c from %s\n", argv[1]);
|
|
printf("// This file is included in git repository for "
|
|
"convenience only.\n");
|
|
printf("// DO NOT EDIT THIS FILE!\n\n");
|
|
|
|
printf("#include <libconfig.h>\n");
|
|
printf("#include \"../script.h\"\n");
|
|
printf("#include \"../curve.h\"\n");
|
|
printf("#include \"../script_internal.h\"\n");
|
|
printf("#include \"utils/misc.h\"\n");
|
|
printf("#include \"config.h\"\n");
|
|
for (unsigned i = 0; i < (unsigned)config_setting_length(settings); i++) {
|
|
auto sub = config_setting_get_elem(settings, i);
|
|
auto name = config_setting_name(sub);
|
|
struct knob knobs[MAX_PLACEHOLDERS] = {};
|
|
struct placeholder placeholders[MAX_PLACEHOLDERS] = {};
|
|
|
|
auto knob_settings = config_setting_get_member(sub, "*knobs");
|
|
if (knob_settings) {
|
|
auto placeholder_settings =
|
|
config_setting_get_member(sub, "*placeholders");
|
|
BUG_ON(!placeholder_settings);
|
|
parse_knobs(name, knob_settings, placeholder_settings, knobs,
|
|
placeholders);
|
|
config_setting_remove(sub, "*knobs");
|
|
config_setting_remove(sub, "*placeholders");
|
|
knob_settings = NULL;
|
|
}
|
|
|
|
char *err = NULL;
|
|
auto script = script_compile(sub, parse_config, &err);
|
|
if (!script) {
|
|
fprintf(stderr, "Failed to compile script %s: %s\n", name, err);
|
|
free(err);
|
|
continue;
|
|
}
|
|
bool has_err = false;
|
|
for (size_t j = 0; j < script->len; j++) {
|
|
if (script->instrs[j].type != INST_LOAD_CTX) {
|
|
continue;
|
|
}
|
|
if (script->instrs[j].ctx < base) {
|
|
continue;
|
|
}
|
|
size_t index = (size_t)(script->instrs[j].ctx - base) / 4;
|
|
BUG_ON(index >= ARR_SIZE(knobs));
|
|
if (placeholders[index].source == NULL) {
|
|
fprintf(stderr, "Placeholder %zu used, but not defined\n",
|
|
index);
|
|
has_err = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!has_err) {
|
|
char *code = script_to_c(script, outputs);
|
|
codegen(name, code, placeholders);
|
|
free(code);
|
|
|
|
dynarr_push(presets, strdup(name));
|
|
}
|
|
for (size_t j = 0; j < MAX_PLACEHOLDERS; j++) {
|
|
if (placeholders[j].value_for_choices) {
|
|
free(placeholders[j].value_for_choices);
|
|
}
|
|
}
|
|
for (size_t j = 0; j < MAX_PLACEHOLDERS; j++) {
|
|
if (knobs[j].type == KNOB_CHOICE) {
|
|
for (unsigned k = 0; k < knobs[j].n_choices; k++) {
|
|
free(knobs[j].choices[k]);
|
|
}
|
|
free(knobs[j].choices);
|
|
}
|
|
free((void *)knobs[j].name);
|
|
}
|
|
script_free(script);
|
|
}
|
|
|
|
config_destroy(&cfg);
|
|
|
|
printf("struct {\n"
|
|
" const char *name;\n"
|
|
" bool (*func)(struct win_script *output, "
|
|
"config_setting_t *setting);\n"
|
|
"} win_script_presets[] = {\n");
|
|
dynarr_foreach(presets, p) {
|
|
auto ident = sanitized_name(*p);
|
|
printf(" {\"%s\", win_script_preset__%s},\n", *p, ident);
|
|
free(*p);
|
|
free(ident);
|
|
}
|
|
printf(" {NULL, NULL},\n};\n");
|
|
dynarr_free_pod(presets);
|
|
return 0;
|
|
}
|