From f3b2db1090c8a4eab6865ac097ffd7c3f35a57cd Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 5 Aug 2024 17:23:46 +0100 Subject: [PATCH] tools: add animgen Add a tool to generate C function from a compiled animation script. The generated function will reproduce the animation script when called. The goal is to implement animation presets. Instead of creating strings and then compile them into scripts, by using these generated function, we can skip the parsing step and create compiled scripts directly. Placeholders are supported in the preset script, so some configurability still exists even when using presets. Signed-off-by: Yuxuan Shui --- meson.build | 1 + src/meson.build | 122 +++++++++++----- src/transition/curve.c | 36 ++++- src/transition/curve.h | 1 + src/transition/script.c | 196 ++++++++++++++------------ src/transition/script.h | 11 ++ src/transition/script_internal.h | 98 +++++++++++++ src/utils/dynarr.c | 26 ++++ src/utils/dynarr.h | 12 +- src/utils/meson.build | 12 +- src/utils/str.h | 13 ++ src/wm/win.h | 2 + tools/animgen.c | 229 +++++++++++++++++++++++++++++++ tools/meson.build | 8 ++ 14 files changed, 640 insertions(+), 127 deletions(-) create mode 100644 src/transition/script_internal.h create mode 100644 src/utils/dynarr.c create mode 100644 tools/animgen.c create mode 100644 tools/meson.build diff --git a/meson.build b/meson.build index 809a324d..9b17f0d0 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ test_h_dep = subproject('test.h').get_variable('test_h_dep') subdir('src') subdir('man') +subdir('tools') install_data('bin/picom-trans', install_dir: get_option('bindir')) install_data('picom.desktop', install_dir: 'share/applications') diff --git a/src/meson.build b/src/meson.build index ed878430..2a0419f3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,27 +2,52 @@ libev = dependency('libev', required: false) if not libev.found() libev = cc.find_library('ev') endif -base_deps = [ - cc.find_library('m'), - libev -] +base_deps = [cc.find_library('m'), libev] -srcs = [ files('picom.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', - 'diagnostic.c', 'render.c', 'log.c', - 'options.c', 'event.c', 'atom.c', - 'vblank.c','config_libconfig.c', 'inspect.c', 'api.c') ] +srcs = [ + files( + 'api.c', + 'atom.c', + 'c2.c', + 'config.c', + 'config_libconfig.c', + 'diagnostic.c', + 'event.c', + 'inspect.c', + 'log.c', + 'options.c', + 'picom.c', + 'render.c', + 'vblank.c', + 'vsync.c', + 'x.c', + ), +] picom_inc = include_directories(['.', '../include']) cflags = [] required_xcb_packages = [ - 'xcb', 'xcb-composite', 'xcb-damage', 'xcb-glx', 'xcb-present', 'xcb-randr', - 'xcb-render', 'xcb-shape', 'xcb-sync', 'xcb-xfixes' + 'xcb', + 'xcb-composite', + 'xcb-damage', + 'xcb-glx', + 'xcb-present', + 'xcb-randr', + 'xcb-render', + 'xcb-shape', + 'xcb-sync', + 'xcb-xfixes', ] # Some XCB packages are here because their versioning differs (see check below). required_packages = [ - 'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util', + 'pixman-1', + 'x11', + 'x11-xcb', + 'xcb-image', + 'xcb-renderutil', + 'xcb-util', 'threads', ] @@ -41,9 +66,11 @@ if not libconfig_dep.found() cmake = import('cmake') sub_libconfig_opts = cmake.subproject_options() - sub_libconfig_opts.add_cmake_defines({ - 'BUILD_SHARED_LIBS': false, - }) + sub_libconfig_opts.add_cmake_defines( + { + 'BUILD_SHARED_LIBS': false, + }, + ) sub_libconfig_opts.set_install(false) sub_libconfig = cmake.subproject('libconfig', options: sub_libconfig_opts) base_deps += [sub_libconfig.dependency('config')] @@ -52,7 +79,7 @@ else endif if not cc.has_header('uthash.h') - error('Dependency uthash not found') + error('Dependency uthash not found') endif deps = [] @@ -71,18 +98,18 @@ endif if get_option('opengl') cflags += ['-DCONFIG_OPENGL'] deps += [dependency('epoxy', required: true)] - srcs += [ 'opengl.c' ] + srcs += ['opengl.c'] endif if get_option('dbus') cflags += ['-DCONFIG_DBUS'] deps += [dependency('dbus-1', required: true)] - srcs += [ 'dbus.c', 'rtkit.c' ] + srcs += ['dbus.c', 'rtkit.c'] endif if get_option('xrescheck') cflags += ['-DDEBUG_XRC'] - srcs += [ 'xrescheck.c' ] + srcs += ['xrescheck.c'] endif if get_option('unittest') @@ -92,8 +119,12 @@ endif host_system = host_machine.system() if host_system == 'linux' cflags += ['-DHAS_INOTIFY'] -elif (host_system == 'freebsd' or host_system == 'netbsd' or - host_system == 'dragonfly' or host_system == 'openbsd') +elif ( + host_system == 'freebsd' + or host_system == 'netbsd' + or host_system == 'dragonfly' + or host_system == 'openbsd' +) cflags += ['-DHAS_KQUEUE'] endif @@ -105,26 +136,51 @@ subdir('utils') dl_dep = [] if not cc.has_function('dlopen') - dl_dep = [ cc.find_library('dl', required: true) ] + dl_dep = [cc.find_library('dl', required: true)] endif -picom = executable('picom', srcs, c_args: cflags, - dependencies: [ base_deps, deps, test_h_dep ] + dl_dep, - install: true, include_directories: picom_inc, - export_dynamic: true, gnu_symbol_visibility: 'hidden') +libtools = static_library( + 'libtools', + [ + 'log.c', + 'utils/dynarr.c', + 'utils/misc.c', + 'utils/str.c', + 'transition/curve.c', + 'transition/script.c', + ], + include_directories: picom_inc, + dependencies: [test_h_dep], + install: false, + build_by_default: false, +) + +picom = executable( + 'picom', + srcs, + c_args: cflags, + dependencies: [base_deps, deps, test_h_dep] + dl_dep, + install: true, + include_directories: picom_inc, + export_dynamic: true, + gnu_symbol_visibility: 'hidden', +) if get_option('unittest') - test('picom unittest', picom, args: [ '--unittest' ]) + test('picom unittest', picom, args: ['--unittest']) endif install_symlink('picom-inspect', install_dir: 'bin', pointing_to: 'picom') if cc.has_argument('-fsanitize=fuzzer') - c2_fuzz = executable('c2_fuzz', srcs + ['fuzzer/c2.c'], - c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'], - link_args: ['-fsanitize=fuzzer'], - dependencies: [ base_deps, deps, test_h_dep ], - build_by_default: false, - install: false, include_directories: picom_inc - ) + c2_fuzz = executable( + 'c2_fuzz', + srcs + ['fuzzer/c2.c'], + c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'], + link_args: ['-fsanitize=fuzzer'], + dependencies: [base_deps, deps, test_h_dep], + build_by_default: false, + install: false, + include_directories: picom_inc, + ) endif diff --git a/src/transition/curve.c b/src/transition/curve.c index 872a5d07..4b5dc2aa 100644 --- a/src/transition/curve.c +++ b/src/transition/curve.c @@ -15,6 +15,10 @@ static double curve_sample_linear(const struct curve *this attr_unused, double p return progress; } +static char *curve_linear_to_c(const struct curve * /*this*/) { + return strdup("{.type = CURVE_LINEAR},"); +} + // Cubic bezier interpolator. // // Stolen from servo: @@ -78,6 +82,15 @@ curve_sample_cubic_bezier(const struct curve_cubic_bezier *curve, double progres return cubic_bezier_sample_y(curve, t); } +static char *curve_cubic_bezier_to_c(const struct curve_cubic_bezier *curve) { + char *buf = NULL; + casprintf(&buf, + "{.type = CURVE_CUBIC_BEZIER, .bezier = { .ax = %f, .bx = %f, " + ".cx = %f, .ay = %f, .by = %f, .cy = %f }},", + curve->ax, curve->bx, curve->cx, curve->ay, curve->by, curve->cy); + return buf; +} + static double curve_sample_step(const struct curve_step *this, double progress) { double y_steps = this->steps - 1 + this->jump_end + this->jump_start, x_steps = this->steps; @@ -93,6 +106,16 @@ static double curve_sample_step(const struct curve_step *this, double progress) return quantized / y_steps; } +static char *curve_step_to_c(const struct curve_step *this) { + char *buf = NULL; + casprintf(&buf, + "{.type = CURVE_STEP, .step = { .steps = %d, .jump_start = %s, " + ".jump_end = %s }},", + this->steps, this->jump_start ? "true" : "false", + this->jump_end ? "true" : "false"); + return buf; +} + struct curve parse_linear(const char *str, const char **end, char **err) { *end = str; *err = NULL; @@ -196,6 +219,17 @@ double curve_sample(const struct curve *curve, double progress) { case CURVE_STEP: return curve_sample_step(&curve->step, progress); case CURVE_CUBIC_BEZIER: return curve_sample_cubic_bezier(&curve->bezier, progress); - case CURVE_INVALID: unreachable(); + case CURVE_INVALID: + default: unreachable(); + } +} + +char *curve_to_c(const struct curve *curve) { + switch (curve->type) { + case CURVE_LINEAR: return curve_linear_to_c(curve); + case CURVE_STEP: return curve_step_to_c(&curve->step); + case CURVE_CUBIC_BEZIER: return curve_cubic_bezier_to_c(&curve->bezier); + case CURVE_INVALID: + default: unreachable(); } } diff --git a/src/transition/curve.h b/src/transition/curve.h index cb414077..b0c58aca 100644 --- a/src/transition/curve.h +++ b/src/transition/curve.h @@ -49,3 +49,4 @@ static inline struct curve curve_new_step(int steps, bool jump_start, bool jump_ struct curve curve_parse(const char *str, const char **end, char **err); /// Calculate the value of the curve at `progress`. double curve_sample(const struct curve *curve, double progress); +char *curve_to_c(const struct curve *curve); diff --git a/src/transition/script.c b/src/transition/script.c index f5d1ce5b..24edd8a0 100644 --- a/src/transition/script.c +++ b/src/transition/script.c @@ -7,66 +7,18 @@ #include #include +#include "utils/dynarr.h" #include "utils/list.h" #include "utils/str.h" #include "utils/uthash_extra.h" #include "curve.h" #include "script.h" +#include "script_internal.h" -enum op { - OP_ADD = 0, - OP_SUB, - OP_MUL, - OP_DIV, - /// Exponent - OP_EXP, - /// Negation - OP_NEG, - OP_MAX, -}; - -enum instruction_type { - /// Push an immediate value to the top of the stack - INST_IMM = 0, - /// Pop two values from the top of the stack, apply operator, - /// and push the result to the top of the stack - INST_OP, - /// Load a memory slot and push its value to the top of the stack. - INST_LOAD, - /// Load from evaluation context and push the value to the top of the stack. - INST_LOAD_CTX, - /// Pop one value from the top of the stack, and store it into a memory slot. - INST_STORE, - /// Pop one value from the top of the stack, if the memory slot contains NaN, - /// store it into the memory slot; otherwise discard the value. - INST_STORE_OVER_NAN, - /// Pop a value from the top of the stack, clamp its value to [0, 1], then - /// evaluate a curve at that point, push the result to the top of the stack. - INST_CURVE, - /// Jump to the branch target only when the script is evaluated for the first - /// time. Used to perform initialization and such. - INST_BRANCH_ONCE, - /// Unconditional branch - INST_BRANCH, - INST_HALT, -}; - -struct instruction { - enum instruction_type type; - union { - double imm; - enum op op; - /// Memory slot for load and store - unsigned slot; - /// Context offset for load_ctx - ptrdiff_t ctx; - /// Relative PC change for branching - int rel; - /// The curve - struct curve curve; - }; -}; +#define X(x) [x] = #x, +static const char *op_names[] = {OPERATORS}; +#undef X struct fragment { struct list_node siblings; @@ -101,39 +53,6 @@ struct compilation_stack { unsigned deps[]; }; -/// Store metadata about where the result of a variable is stored -struct variable_allocation { - UT_hash_handle hh; - char *name; - unsigned index; - /// The memory slot for variable named `name` - unsigned slot; -}; - -/// When interrupting an already executing script and starting a new script, -/// we might want to inherit some of the existing values of variables as starting points, -/// i.e. we want to "resume" animation for the current state. This is configurable, and -/// can be disabled by enabling the `reset` property on a transition. This struct store -/// where the `start` variables of those "resumable" transition variables, which can be -/// overridden at the start of execution for this use case. -struct overridable_slot { - UT_hash_handle hh; - char *name; - unsigned slot; -}; - -struct script { - unsigned len; - unsigned n_slots; - /// The memory slot for storing the elapsed time. - /// The next slot after this is used for storing the total duration of the script. - unsigned elapsed_slot; - unsigned stack_size; - struct variable_allocation *vars; - struct overridable_slot *overrides; - struct instruction instrs[]; -}; - struct script_compile_context { struct script_context_info_internal *context_info; struct variable_allocation *vars; @@ -155,7 +74,6 @@ struct script_compile_context { }; static const char operators[] = "+-*/^"; -static const char *operator_names[] = {"+", "-", "*", "/", "^", "neg", "max"}; static const enum op operator_types[] = {OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_EXP}; static const int operator_pre[] = {0, 0, 1, 1, 2}; @@ -171,7 +89,7 @@ static void log_instruction_(enum log_level level, const char *func, unsigned in case INST_BRANCH_ONCE: logv("br_once %d", inst->rel); break; case INST_HALT: log_printf(tls_logger, level, func, "%u: halt", index); break; case INST_CURVE: log_printf(tls_logger, level, func, "%u: curve", index); break; - case INST_OP: logv("op %d (%s)", inst->op, operator_names[inst->op]); break; + case INST_OP: logv("op %s", op_names[inst->op]); break; case INST_LOAD: logv("load %u", inst->slot); break; case INST_STORE: logv("store %u", inst->slot); break; case INST_STORE_OVER_NAN: logv("store/nan %u", inst->slot); break; @@ -182,6 +100,41 @@ static void log_instruction_(enum log_level level, const char *func, unsigned in #define log_instruction(level, i, inst) \ log_instruction_(LOG_LEVEL_##level, __func__, i, &(inst)) +char *instruction_to_c(struct instruction i) { + char *buf = NULL; + switch (i.type) { + case INST_IMM: casprintf(&buf, "{.type = INST_IMM, .imm = %f},", i.imm); break; + case INST_BRANCH: + casprintf(&buf, "{.type = INST_BRANCH, .rel = %d},", i.rel); + break; + case INST_BRANCH_ONCE: + casprintf(&buf, "{.type = INST_BRANCH_ONCE, .rel = %d},", i.rel); + break; + case INST_HALT: casprintf(&buf, "{.type = INST_HALT},"); break; + case INST_CURVE:; + char *curve = curve_to_c(&i.curve); + casprintf(&buf, "{.type = INST_CURVE, .curve = %s},", curve); + free(curve); + break; + case INST_OP: + casprintf(&buf, "{.type = INST_OP, .op = %s},", op_names[i.op]); + break; + case INST_LOAD: + casprintf(&buf, "{.type = INST_LOAD, .slot = %u},", i.slot); + break; + case INST_STORE: + casprintf(&buf, "{.type = INST_STORE, .slot = %u},", i.slot); + break; + case INST_STORE_OVER_NAN: + casprintf(&buf, "{.type = INST_STORE_OVER_NAN, .slot = %u},", i.slot); + break; + case INST_LOAD_CTX: + casprintf(&buf, "{.type = INST_LOAD_CTX, .ctx = %ld},", i.ctx); + break; + } + return buf; +} + static char parse_op(const char *input_str, const char **end, char **err) { char *op = strchr(operators, input_str[0]); *err = NULL; @@ -1107,6 +1060,73 @@ script_compile(config_setting_t *setting, struct script_parse_config cfg, char * return script; } +char *script_to_c(const struct script *script, const struct script_output_info *outputs) { + char **buf = dynarr_new(char *, script->len * 40); + char *tmp = NULL; + casprintf(&tmp, "{\n" + " static const struct instruction instrs[] = {\n"); + dynarr_push(buf, tmp); + for (unsigned i = 0; i < script->len; i++) { + dynarr_push(buf, instruction_to_c(script->instrs[i])); + } + casprintf(&tmp, + " };\n struct script *ret = \n" + " malloc(offsetof(struct script, instrs) + sizeof(instrs));\n" + " ret->len = ARR_SIZE(instrs); ret->elapsed_slot = %u;" + " ret->n_slots = %u; ret->stack_size = %u;\n" + " ret->vars = NULL; ret->overrides = NULL;\n" + " memcpy(ret->instrs, instrs, sizeof(instrs));\n", + script->elapsed_slot, script->n_slots, script->stack_size); + dynarr_push(buf, tmp); + + struct variable_allocation *var, *next_var; + HASH_ITER(hh, script->vars, var, next_var) { + char *var_str = NULL; + casprintf(&var_str, + " {\n" + " struct variable_allocation *var = \n" + " malloc(sizeof(*var));\n" + " *var = (struct variable_allocation){\n" + " .name = strdup(\"%s\"), .slot = %u, .index = %u\n" + " };\n" + " HASH_ADD_STR(ret->vars, name, var);\n" + " }\n", + var->name, var->slot, var->index); + dynarr_push(buf, var_str); + } + + struct overridable_slot *override, *next_override; + HASH_ITER(hh, script->overrides, override, next_override) { + char *override_str = NULL; + casprintf(&override_str, + " {\n" + " struct overridable_slot *override = \n" + " malloc(sizeof(*override));\n" + " *override = (struct overridable_slot){\n" + " .name = strdup(\"%s\"), .slot = %u\n" + " };\n" + " HASH_ADD_STR(ret->overrides, name, override);\n" + " }\n", + override->name, override->slot); + dynarr_push(buf, override_str); + } + + for (size_t i = 0; outputs && outputs[i].name; i++) { + struct variable_allocation *alloc = NULL; + HASH_FIND_STR(script->vars, outputs[i].name, alloc); + if (alloc) { + casprintf(&tmp, " output_slots[%zu] = %u;\n", i, alloc->slot); + } else { + casprintf(&tmp, " output_slots[%zu] = -1;\n", i); + } + dynarr_push(buf, tmp); + } + + casprintf(&tmp, " return ret;\n}\n"); + dynarr_push(buf, tmp); + return dynarr_join(buf, ""); +} + struct script_instance *script_instance_new(const struct script *script) { // allocate no space for the variable length array is UB. unsigned memory_size = max2(1, script->n_slots + script->stack_size); diff --git a/src/transition/script.h b/src/transition/script.h index 192d9c8c..fd2bc415 100644 --- a/src/transition/script.h +++ b/src/transition/script.h @@ -43,6 +43,8 @@ typedef struct config_setting_t config_setting_t; static_assert(alignof(double) > alignof(unsigned), "double/unsigned has unexpected " "alignment"); +#define SCRIPT_CTX_PLACEHOLDER_BASE (0x40000000) + struct script * script_compile(config_setting_t *setting, struct script_parse_config cfg, char **out_err); void script_free(struct script *script); @@ -70,3 +72,12 @@ static inline bool script_instance_is_finished(const struct script_instance *ins return instance->memory[script_elapsed_slot(instance->script)] >= instance->memory[script_total_duration_slot(instance->script)]; } + +/// Generate code for a C function that will return a script identical to `script` when +/// called. The generated function will take a `int *output_slots` parameter, which it +/// will fill in, based on `outputs` passed to this function. Specifically, the generated +/// function will fill in `output_slots[i]` with the slot number of the output variable +/// named `outputs[i].name`. The generated function will return a pointer to the script. +/// This function only generates the function body, you need to provide the function +/// signature and the function name yourself. +char *script_to_c(const struct script *script, const struct script_output_info *outputs); diff --git a/src/transition/script_internal.h b/src/transition/script_internal.h new file mode 100644 index 00000000..47bf736b --- /dev/null +++ b/src/transition/script_internal.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once + +#include + +#include "curve.h" + +#define OPERATORS \ + X(OP_ADD) \ + X(OP_SUB) \ + X(OP_MUL) \ + X(OP_DIV) \ + /* Exponent */ \ + X(OP_EXP) \ + /* Negation */ \ + X(OP_NEG) \ + X(OP_MAX) + +#define X(x) x, +enum op { OPERATORS }; +#undef X + +enum instruction_type { + /// Push an immediate value to the top of the stack + INST_IMM = 0, + /// Pop two values from the top of the stack, apply operator, + /// and push the result to the top of the stack + INST_OP, + /// Load a memory slot and push its value to the top of the stack. + INST_LOAD, + /// Load from evaluation context and push the value to the top of the stack. + INST_LOAD_CTX, + /// Pop one value from the top of the stack, and store it into a memory slot. + INST_STORE, + /// Pop one value from the top of the stack, if the memory slot contains NaN, + /// store it into the memory slot; otherwise discard the value. + INST_STORE_OVER_NAN, + /// Pop a value from the top of the stack, clamp its value to [0, 1], then + /// evaluate a curve at that point, push the result to the top of the stack. + INST_CURVE, + /// Jump to the branch target only when the script is evaluated for the first + /// time. Used to perform initialization and such. + INST_BRANCH_ONCE, + /// Unconditional branch + INST_BRANCH, + INST_HALT, +}; + +/// Store metadata about where the result of a variable is stored +struct variable_allocation { + UT_hash_handle hh; + char *name; + unsigned index; + /// The memory slot for variable named `name` + unsigned slot; +}; + +struct instruction { + enum instruction_type type; + union { + double imm; + enum op op; + /// Memory slot for load and store + unsigned slot; + /// Context offset for load_ctx + ptrdiff_t ctx; + /// Relative PC change for branching + int rel; + /// The curve + struct curve curve; + }; +}; + +/// When interrupting an already executing script and starting a new script, +/// we might want to inherit some of the existing values of variables as starting points, +/// i.e. we want to "resume" animation for the current state. This is configurable, and +/// can be disabled by enabling the `reset` property on a transition. This struct store +/// where the `start` variables of those "resumable" transition variables, which can be +/// overridden at the start of execution for this use case. +struct overridable_slot { + UT_hash_handle hh; + char *name; + unsigned slot; +}; + +struct script { + unsigned len; + unsigned n_slots; + /// The memory slot for storing the elapsed time. + /// The next slot after this is used for storing the total duration of the script. + unsigned elapsed_slot; + unsigned stack_size; + struct variable_allocation *vars; + struct overridable_slot *overrides; + struct instruction instrs[]; +}; diff --git a/src/utils/dynarr.c b/src/utils/dynarr.c new file mode 100644 index 00000000..4c250496 --- /dev/null +++ b/src/utils/dynarr.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include "dynarr.h" +char *dynarr_join(char **arr, const char *sep) { + size_t total_len = 0; + dynarr_foreach(arr, i) { + total_len += strlen(*i); + } + + char *ret = malloc(total_len + strlen(sep) * (dynarr_len(arr) - 1) + 1); + size_t pos = 0; + allocchk(ret); + dynarr_foreach(arr, i) { + if (i != arr) { + strcpy(ret + pos, sep); + pos += strlen(sep); + } + strcpy(ret + pos, *i); + pos += strlen(*i); + free(*i); + } + dynarr_free_pod(arr); + ret[pos] = '\0'; + return ret; +} diff --git a/src/utils/dynarr.h b/src/utils/dynarr.h index 4c9f7005..fc56b757 100644 --- a/src/utils/dynarr.h +++ b/src/utils/dynarr.h @@ -72,7 +72,7 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { #define dynarr_free(arr, dtor) \ do { \ dynarr_clear(arr, dtor); \ - free((struct dynarr_header *)(arr)-1); \ + free((struct dynarr_header *)(arr) - 1); \ (arr) = NULL; \ } while (0) #define dynarr_free_pod(arr) dynarr_free(arr, (void (*)(typeof(arr)))NULL) @@ -86,7 +86,7 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { #define dynarr_resize(arr, newlen, init, dtor) \ do { \ BUG_ON((arr) == NULL); \ - dynarr_reserve((arr), (newlen)-dynarr_len(arr)); \ + dynarr_reserve((arr), (newlen) - dynarr_len(arr)); \ if ((init) != NULL) { \ for (size_t i = dynarr_len(arr); i < (newlen); i++) { \ (init)((arr) + i); \ @@ -121,9 +121,9 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { #define dynarr_remove_swap(arr, idx) \ dynarr_remove_swap_impl(sizeof(typeof(*(arr))), (void *)(arr), idx) /// Return the length of the array -#define dynarr_len(arr) (((struct dynarr_header *)(arr)-1)->len) +#define dynarr_len(arr) (((struct dynarr_header *)(arr) - 1)->len) /// Return the capacity of the array -#define dynarr_cap(arr) (((struct dynarr_header *)(arr)-1)->cap) +#define dynarr_cap(arr) (((struct dynarr_header *)(arr) - 1)->cap) /// Return the last element of the array #define dynarr_last(arr) ((arr)[dynarr_len(arr) - 1]) /// Return the pointer just past the last element of the array @@ -183,3 +183,7 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { } \ dynarr_find_ret; \ }) + +/// Concatenate a dynarr of strings into a single string, separated by `sep`. The array +/// will be freed by this function. +char *dynarr_join(char **arr, const char *sep); diff --git a/src/utils/meson.build b/src/utils/meson.build index a6272f82..c1bf78a6 100644 --- a/src/utils/meson.build +++ b/src/utils/meson.build @@ -1 +1,11 @@ -srcs += [ files('cache.c', 'file_watch.c', 'kernel.c', 'statistics.c', 'str.c', 'misc.c') ] +srcs += [ + files( + 'cache.c', + 'dynarr.c', + 'file_watch.c', + 'kernel.c', + 'misc.c', + 'statistics.c', + 'str.c', + ), +] diff --git a/src/utils/str.h b/src/utils/str.h index 9a284e43..546d0c95 100644 --- a/src/utils/str.h +++ b/src/utils/str.h @@ -4,6 +4,7 @@ #pragma once #include #include +#include #include #include #include @@ -93,3 +94,15 @@ static inline bool starts_with(const char *str, const char *needle, bool ignore_ /// reallocates it if it's not big enough. int asnprintf(char **strp, size_t *capacity, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + +/// Like `asprintf`, but it aborts the program if memory allocation fails. +static inline size_t __attribute__((format(printf, 2, 3))) +casprintf(char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = vasprintf(strp, fmt, ap); + va_end(ap); + + BUG_ON(ret < 0); + return (size_t)ret; +} diff --git a/src/wm/win.h b/src/wm/win.h index a82843c4..fa26e352 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -261,6 +261,8 @@ struct win_script_context { double monitor_x, monitor_y; double monitor_width, monitor_height; }; +static_assert(SCRIPT_CTX_PLACEHOLDER_BASE > sizeof(struct win_script_context), + "win_script_context too large"); static const struct script_context_info win_script_context_info[] = { {"window-x", offsetof(struct win_script_context, x)}, diff --git a/tools/animgen.c b/tools/animgen.c new file mode 100644 index 00000000..451ce358 --- /dev/null +++ b/tools/animgen.c @@ -0,0 +1,229 @@ +#include +#include +#include "compiler.h" // IWYU pragma: keep +#include "transition/script.h" +#include "transition/script_internal.h" +#include "utils/dynarr.h" +#include "wm/win.h" + +struct placeholder { + UT_hash_handle hh; + const char *name; + unsigned index; + double default_value; +}; + +// `config_setting_lookup_*` surprisingly doesn't support paths like +// `config_setting_lookup` does. e.g. indexing like `[0]` is not supported. So we define +// our own helper functions here. + +bool config_extra_lookup_int(config_setting_t *setting, const char *path, int *value) { + auto sub = config_setting_lookup(setting, path); + if (!sub) { + return false; + } + if (config_setting_type(sub) != CONFIG_TYPE_INT) { + return false; + } + *value = config_setting_get_int(sub); + return true; +} + +bool config_extra_lookup_float(config_setting_t *setting, const char *path, double *value) { + auto sub = config_setting_lookup(setting, path); + if (!sub) { + return false; + } + if (config_setting_type(sub) != CONFIG_TYPE_FLOAT) { + return false; + } + *value = config_setting_get_float(sub); + return true; +} + +void codegen(const char *name, const char *body, const struct placeholder *placeholders) { + printf("static struct script *script_template__%s(int *output_slots)\n%s\n", name, body); + printf("static bool win_script_preset__%s(struct win_script *output, " + "config_setting_t *setting) {\n", + name); + printf(" output->script = script_template__%s(output->output_indices);\n", name); + for (size_t i = 0; i < 10; i++) { + if (placeholders[i].name) { + printf(" double placeholder_%s = %f;\n", placeholders[i].name, + placeholders[i].default_value); + printf(" config_setting_lookup_float(setting, \"%s\", " + "&placeholder_%s);\n", + placeholders[i].name, placeholders[i].name); + } + } + printf(" struct script_specialization_context spec[] = {\n"); + for (size_t i = 0; i < 10; i++) { + if (placeholders[i].name) { + printf(" {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + %zu, " + ".value = placeholder_%s},\n", + i * 4, placeholders[i].name); + } + } + printf(" };\n"); + printf(" script_specialize(output->script, spec, ARR_SIZE(spec));\n"); + printf(" return true;\n"); + printf("}\n"); +} + +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); + + config_read_file(&cfg, argv[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) + 10] = { + {"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 \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 placeholder *placeholders_by_name = NULL; + struct placeholder placeholders[10] = {}; + + auto placeholders_config = config_setting_get_member(sub, "placeholders"); + if (placeholders_config) { + for (unsigned j = 0; + j < (unsigned)config_setting_length(placeholders_config); j++) { + auto placeholder_config = + config_setting_get_elem(placeholders_config, j); + const char *placeholder_name = + config_setting_name(placeholder_config); + if (!config_setting_is_list(placeholder_config) || + config_setting_length(placeholder_config) != 2) { + fprintf(stderr, + "Invalid placeholder %s in %s, line %d. " + "It must be a list of 2 numbers.\n", + placeholder_name, name, + config_setting_source_line(placeholder_config)); + continue; + } + + int index; + double default_value; + if (!config_extra_lookup_int(placeholder_config, "[0]", &index) || + !config_extra_lookup_float(placeholder_config, "[1]", + &default_value)) { + fprintf(stderr, + "Invalid placeholder %s in %s, line %d. " + "Failed to get elements.\n", + placeholder_name, name, + config_setting_source_line(placeholder_config)); + continue; + } + if (index < 0 || (size_t)index >= ARR_SIZE(placeholders)) { + fprintf( + stderr, "Invalid placeholder index %d in %s, line %d\n", + index, name, + config_setting_source_line(placeholder_config)); + continue; + } + struct placeholder *placeholder = + malloc(sizeof(*placeholder)); + placeholder->name = strdup(placeholder_name); + placeholder->index = (unsigned)index; + placeholder->default_value = default_value; + HASH_ADD_STR(placeholders_by_name, name, placeholder); + + placeholders[index] = *placeholder; + } + config_setting_remove(sub, "placeholders"); + placeholders_config = 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) { + size_t index = ((size_t)script->instrs[j].ctx - base) / 4; + BUG_ON(index >= ARR_SIZE(placeholders)); + if (!placeholders[index].name) { + 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)); + } + struct placeholder *p, *np; + HASH_ITER(hh, placeholders_by_name, p, np) { + free((void *)p->name); + HASH_DEL(placeholders_by_name, p); + free(p); + } + 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) { + printf(" {\"%s\", win_script_preset__%s},\n", *p, *p); + free(*p); + } + printf(" {NULL, NULL},\n};\n"); + dynarr_free_pod(presets); + return 0; +} diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 00000000..1595bcdc --- /dev/null +++ b/tools/meson.build @@ -0,0 +1,8 @@ +executable( + 'animgen', + 'animgen.c', + dependencies: [ base_deps, libconfig_dep, test_h_dep, cc.find_library('m')], + link_with: [libtools], + build_by_default: false, + include_directories: picom_inc, +)