From 654772b8cf956b645d3202b3671485714dd18f38 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 17 Jul 2022 17:49:35 +0100 Subject: [PATCH] config: add options `window-shader-fg` and `window-shader-fg-rules` Used for setting custom window shaders and rules for choosing custom window shaders. Added a "c2_userdata_free" parameter to c2_list_free, so allocated userdata stored in nodes can be freed. Signed-off-by: Bernd Busse Signed-off-by: Yuxuan Shui --- .circleci/config.yml | 2 +- man/picom.1.asciidoc | 8 +- src/c2.c | 9 +- src/c2.h | 7 +- src/common.h | 11 ++ src/config.c | 210 ++++++++++++++++++++++++++++++++ src/config.h | 12 ++ src/config_libconfig.c | 133 +++++++------------- src/options.c | 64 ++++++++-- src/picom.c | 116 ++++++++++++++++-- tests/configs/parsing_test.conf | 7 ++ tests/configs/shader.frag | 1 + 12 files changed, 462 insertions(+), 118 deletions(-) create mode 100644 tests/configs/shader.frag diff --git a/.circleci/config.yml b/.circleci/config.yml index bed676bf..90812c82 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,7 +54,7 @@ jobs: command: ninja -vC build test - run: name: test config file parsing - command: xvfb-run -s "-screen 0 640x480x24" build/src/picom --config tests/configs/parsing_test.conf --no-vsync --diagnostics + command: xvfb-run -s "-screen 0 640x480x24" build/src/picom --experimental-backends --config tests/configs/parsing_test.conf --no-vsync --diagnostics - run: name: run testsuite command: tests/run_tests.sh build/src/picom diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index ee379c3a..42fe1342 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -236,7 +236,7 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box Use X Sync fence to sync clients' draw calls, to make sure all draw calls are finished before picom starts drawing. Needed on nvidia-drivers with GLX backend for some users. *--glx-fshader-win* 'SHADER':: - GLX backend: Use specified GLSL fragment shader for rendering window contents. See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl` in the source tree for examples. + GLX backend: Use specified GLSL fragment shader for rendering window contents. See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl` in the source tree for examples. Doesn't work with `--experimental-backends` enabled. *--force-win-blend*:: Force all windows to be painted with blending. Useful if you have a *--glx-fshader-win* that could turn opaque pixels transparent. @@ -259,6 +259,12 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box *--transparent-clipping*:: Make transparent windows clip other windows like non-transparent windows do, instead of blending on top of them. +*--window-shader-fg* 'SHADER':: + Specify GLSL fragment shader path for rendering window contents. Only works when `--experimental-backends` is enabled. + +*--window-shader-fg-rule* 'SHADER':'CONDITION':: + Specify GLSL fragment shader path for rendering window contents using patterns. Pattern should be in the format of `SHADER:PATTERN`, similar to `--opacity-rule`. Leading and trailing whitespaces in `SHADER` will be trimmed. If `SHADER` is "default", then the default shader will be used for matching windows. (This also unfortunately means you can't use a shader file named "default"). Only works when `--experimental-backends` is enabled. + FORMAT OF CONDITIONS -------------------- Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators. diff --git a/src/c2.c b/src/c2.c index e557f299..80ecb24c 100644 --- a/src/c2.c +++ b/src/c2.c @@ -1151,11 +1151,16 @@ static void c2_free(c2_ptr_t p) { /** * Free a condition tree in c2_lptr_t. */ -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) { - if (!lp) +c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) { + if (!lp) { return NULL; + } c2_lptr_t *pnext = lp->next; + if (f) { + f(lp->data); + } + lp->data = NULL; c2_free(lp->ptr); free(lp); diff --git a/src/c2.h b/src/c2.h index 1f3390b5..a7ddd392 100644 --- a/src/c2.h +++ b/src/c2.h @@ -18,9 +18,10 @@ typedef struct _c2_lptr c2_lptr_t; typedef struct session session_t; struct managed_win; +typedef void (*c2_userdata_free)(void *); c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp); +c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f); bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata); @@ -34,8 +35,8 @@ void *c2_list_get_data(const c2_lptr_t *condlist); /** * Destroy a condition list. */ -static inline void c2_list_free(c2_lptr_t **pcondlst) { - while ((*pcondlst = c2_free_lptr(*pcondlst))) { +static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) { + while ((*pcondlst = c2_free_lptr(*pcondlst, f))) { } *pcondlst = NULL; } diff --git a/src/common.h b/src/common.h index f6353b0d..6154e5f5 100644 --- a/src/common.h +++ b/src/common.h @@ -130,6 +130,13 @@ typedef struct _latom { struct _latom *next; } latom_t; +struct shader_info { + char *key; + char *source; + void *backend_shader; + UT_hash_handle hh; +}; + /// Structure containing all necessary data for a session. typedef struct session { // === Event handlers === @@ -150,6 +157,8 @@ typedef struct session { ev_signal usr1_signal; /// Signal handler for SIGINT ev_signal int_signal; + + // === Backend related === /// backend data backend_t *backend_data; /// backend blur context @@ -160,6 +169,8 @@ typedef struct session { void *file_watch_handle; /// libev mainloop struct ev_loop *loop; + /// Shaders + struct shader_info *shaders; // === Display related === /// Whether the X server is grabbed by us diff --git a/src/config.c b/src/config.c index 90324778..045fa74c 100644 --- a/src/config.c +++ b/src/config.c @@ -3,13 +3,21 @@ // Copyright (c) 2013 Richard Grenville #include +#include +#include #include #include #include +#include #include #include +#include +#include +#include #include // for xcb_render_fixed_t, XXX +#include + #include "c2.h" #include "common.h" #include "compiler.h" @@ -23,6 +31,98 @@ #include "config.h" +const char *xdg_config_home(void) { + char *xdgh = getenv("XDG_CONFIG_HOME"); + char *home = getenv("HOME"); + const char *default_dir = "/.config"; + + if (!xdgh) { + if (!home) { + return NULL; + } + + xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1); + + strcpy(xdgh, home); + strcat(xdgh, default_dir); + } else { + xdgh = strdup(xdgh); + } + + return xdgh; +} + +char **xdg_config_dirs(void) { + char *xdgd = getenv("XDG_CONFIG_DIRS"); + size_t count = 0; + + if (!xdgd) { + xdgd = "/etc/xdg"; + } + + for (int i = 0; xdgd[i]; i++) { + if (xdgd[i] == ':') { + count++; + } + } + + // Store the string and the result pointers together so they can be + // freed together + char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1); + auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd); + auto path = dirs; + + for (size_t i = 0; i < count; i++) { + dir_list[i] = path; + path = strchr(path, ':'); + *path = '\0'; + path++; + } + dir_list[count] = path; + + size_t fill = 0; + for (size_t i = 0; i <= count; i++) { + if (dir_list[i][0] == '/') { + dir_list[fill] = dir_list[i]; + fill++; + } + } + + dir_list[fill] = NULL; + + return dir_list; +} + +TEST_CASE(xdg_config_dirs) { + auto old_var = getenv("XDG_CONFIG_DIRS"); + if (old_var) { + old_var = strdup(old_var); + } + unsetenv("XDG_CONFIG_DIRS"); + + auto result = xdg_config_dirs(); + TEST_STREQUAL(result[0], "/etc/xdg"); + TEST_EQUAL(result[1], NULL); + free(result); + + setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1); + result = xdg_config_dirs(); + TEST_STREQUAL(result[0], "/etc/xdg"); + TEST_STREQUAL(result[1], "/"); + TEST_EQUAL(result[2], NULL); + free(result); + + setenv("XDG_CONFIG_DIRS", ":", 1); + result = xdg_config_dirs(); + TEST_EQUAL(result[0], NULL); + free(result); + + if (old_var) { + setenv("XDG_CONFIG_DIRS", old_var, 1); + free(old_var); + } +} + /** * Parse a long number. */ @@ -438,6 +538,114 @@ bool parse_rule_opacity(c2_lptr_t **res, const char *src) { return c2_parse(res, endptr, (void *)val); } +/// Search for auxiliary file under a base directory +static char *locate_auxiliary_file_at(const char *base, const char *scope, const char *file) { + scoped_charp path = mstrjoin(base, scope); + mstrextend(&path, "/"); + mstrextend(&path, file); + if (access(path, O_RDONLY) == 0) { + // Canonicalize path to avoid duplicates + char *abspath = realpath(path, NULL); + return abspath; + } + return NULL; +} + +/** + * Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary + * file. + * + * Follows the XDG specification to search for the shader file in configuration locations. + * + * The search order is: + * 1) If an absolute path is given, use it directly. + * 2) Search for the file directly under `include_dir`. + * 3) Search for the file in the XDG configuration directories, under path + * /picom// + */ +char *locate_auxiliary_file(const char *scope, const char *path, const char *include_dir) { + if (!path || strlen(path) == 0) { + return NULL; + } + + // Filename is absolute path, so try to load from there + if (path[0] == '/') { + if (access(path, O_RDONLY) == 0) { + return realpath(path, NULL); + } + } + + // First try to load file from the include directory (i.e. relative to the + // config file) + if (include_dir && strlen(include_dir)) { + char *ret = locate_auxiliary_file_at(include_dir, "", path); + if (ret) { + return ret; + } + } + + // Fall back to searching in user config directory + scoped_charp picom_scope = mstrjoin("/picom/", scope); + scoped_charp config_home = (char *)xdg_config_home(); + char *ret = locate_auxiliary_file_at(config_home, picom_scope, path); + if (ret) { + return ret; + } + + // Fall back to searching in system config directory + auto config_dirs = xdg_config_dirs(); + for (int i = 0; config_dirs[i]; i++) { + ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path); + if (ret) { + free(config_dirs); + return ret; + } + } + free(config_dirs); + + return ret; +} + +/** + * Parse a list of window shader rules. + */ +bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *include_dir) { + if (!src) { + return false; + } + + // Find custom shader terminator + const char *endptr = strchr(src, ':'); + if (!endptr) { + log_error("Custom shader terminator not found: %s", src); + return false; + } + + // Parse and create custom shader + scoped_charp untrimed_shader_source = strdup(src); + if (!untrimed_shader_source) { + return false; + } + auto source_end = strchr(untrimed_shader_source, ':'); + *source_end = '\0'; + + size_t length; + char *tmp = (char *)trim_both(untrimed_shader_source, &length); + tmp[length] = '\0'; + char *shader_source = NULL; + + if (strcasecmp(tmp, "default") != 0) { + shader_source = locate_auxiliary_file("shaders", tmp, include_dir); + if (!shader_source) { + log_error("Custom shader file \"%s\" not found for rule: %s", tmp, src); + free(shader_source); + return false; + } + } + + return c2_parse(res, ++endptr, (void *)shader_source); +} + /** * Add a pattern to a condition linked list. */ @@ -565,6 +773,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .blur_background_blacklist = NULL, .blur_kerns = NULL, .blur_kernel_count = 0, + .window_shader_fg = NULL, + .window_shader_fg_rules = NULL, .inactive_dim = 0.0, .inactive_dim_fixed = false, .invert_color_list = NULL, diff --git a/src/config.h b/src/config.h index 03ef74b3..51f4f593 100644 --- a/src/config.h +++ b/src/config.h @@ -15,6 +15,8 @@ #include #include +#include "uthash_extra.h" + #ifdef CONFIG_LIBCONFIG #include #endif @@ -206,6 +208,10 @@ typedef struct options { struct conv **blur_kerns; /// Number of convolution kernels int blur_kernel_count; + /// Custom fragment shader for painting windows + char *window_shader_fg; + /// Rules to change custom fragment shader for painting windows. + c2_lptr_t *window_shader_fg_rules; /// How much to dim an inactive window. 0.0 - 1.0, 0 to disable. double inactive_dim; /// Whether to use fixed inactive dim opacity, instead of deciding @@ -255,6 +261,9 @@ bool must_use parse_int(const char *, int *); struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count); bool must_use parse_geometry(session_t *, const char *, region_t *); bool must_use parse_rule_opacity(c2_lptr_t **, const char *); +bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *); +char *must_use locate_auxiliary_file(const char *scope, const char *path, + const char *include_dir); enum blur_method must_use parse_blur_method(const char *src); /** @@ -263,6 +272,9 @@ enum blur_method must_use parse_blur_method(const char *src); bool condlst_add(c2_lptr_t **, const char *); #ifdef CONFIG_LIBCONFIG +const char *xdg_config_home(void); +char **xdg_config_dirs(void); + /// Parse a configuration file /// Returns the actually config_file name used, allocated on heap /// Outputs: diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 0e64869c..e3c70ad5 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2012-2014 Richard Grenville +#include #include #include #include @@ -37,98 +38,6 @@ static inline int lcfg_lookup_bool(const config_t *config, const char *path, boo return ret; } -const char *xdg_config_home(void) { - char *xdgh = getenv("XDG_CONFIG_HOME"); - char *home = getenv("HOME"); - const char *default_dir = "/.config"; - - if (!xdgh) { - if (!home) { - return NULL; - } - - xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1); - - strcpy(xdgh, home); - strcat(xdgh, default_dir); - } else { - xdgh = strdup(xdgh); - } - - return xdgh; -} - -char **xdg_config_dirs(void) { - char *xdgd = getenv("XDG_CONFIG_DIRS"); - size_t count = 0; - - if (!xdgd) { - xdgd = "/etc/xdg"; - } - - for (int i = 0; xdgd[i]; i++) { - if (xdgd[i] == ':') { - count++; - } - } - - // Store the string and the result pointers together so they can be - // freed together - char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1); - auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd); - auto path = dirs; - - for (size_t i = 0; i < count; i++) { - dir_list[i] = path; - path = strchr(path, ':'); - *path = '\0'; - path++; - } - dir_list[count] = path; - - size_t fill = 0; - for (size_t i = 0; i <= count; i++) { - if (dir_list[i][0] == '/') { - dir_list[fill] = dir_list[i]; - fill++; - } - } - - dir_list[fill] = NULL; - - return dir_list; -} - -TEST_CASE(xdg_config_dirs) { - auto old_var = getenv("XDG_CONFIG_DIRS"); - if (old_var) { - old_var = strdup(old_var); - } - unsetenv("XDG_CONFIG_DIRS"); - - auto result = xdg_config_dirs(); - TEST_STREQUAL(result[0], "/etc/xdg"); - TEST_EQUAL(result[1], NULL); - free(result); - - setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1); - result = xdg_config_dirs(); - TEST_STREQUAL(result[0], "/etc/xdg"); - TEST_STREQUAL(result[1], "/"); - TEST_EQUAL(result[2], NULL); - free(result); - - setenv("XDG_CONFIG_DIRS", ":", 1); - result = xdg_config_dirs(); - TEST_EQUAL(result[0], NULL); - free(result); - - if (old_var) { - setenv("XDG_CONFIG_DIRS", old_var, 1); - free(old_var); - } -} - /// Search for config file under a base directory FILE *open_config_file_at(const char *base, char **out_path) { static const char *config_paths[] = {"/picom.conf", "/picom/picom.conf", @@ -251,6 +160,36 @@ parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) { } } +/** + * Parse a window shader rule list in configuration file. + */ +static inline void parse_cfg_condlst_shader(options_t *opt, const config_t *pcfg, + const char *name, const char *include_dir) { + config_setting_t *setting = config_lookup(pcfg, name); + if (setting) { + // Parse an array of options + if (config_setting_is_array(setting)) { + int i = config_setting_length(setting); + while (i--) { + if (!parse_rule_window_shader( + &opt->window_shader_fg_rules, + config_setting_get_string_elem(setting, i), + include_dir)) { + exit(1); + } + } + } + // Treat it as a single pattern if it's a string + else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { + if (!parse_rule_window_shader(&opt->window_shader_fg_rules, + config_setting_get_string(setting), + include_dir)) { + exit(1); + } + } + } +} + static inline void parse_wintype_config(const config_t *cfg, const char *member_name, win_option_t *o, win_option_mask_t *mask) { char *str = mstrjoin("wintypes.", member_name); @@ -602,6 +541,16 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad opt->max_brightness = 1.0; } + // --window-shader-fg + if (config_lookup_string(&cfg, "window-shader-fg", &sval)) { + opt->window_shader_fg = + locate_auxiliary_file("shaders", sval, config_get_include_dir(&cfg)); + } + + // --window-shader-fg-rule + parse_cfg_condlst_shader(opt, &cfg, "window-shader-fg-rule", + config_get_include_dir(&cfg)); + // --glx-use-gpushader4 if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival)) { log_error("glx-use-gpushader4 has been removed, please remove it " diff --git a/src/options.c b/src/options.c index e0a17136..22deafb1 100644 --- a/src/options.c +++ b/src/options.c @@ -346,7 +346,17 @@ static void usage(const char *argv0, int ret) { "\n" "--transparent-clipping\n" " Make transparent windows clip other windows like non-transparent windows\n" - " do, instead of blending on top of them\n"; + " do, instead of blending on top of them\n" + "\n" + "--window-shader-fg shader\n" + " Specify GLSL fragment shader path for rendering window contents. Only\n" + " works when `--experimental-backends` is enabled.\n" + "\n" + "--window-shader-fg-rule shader:condition\n" + " Specify GLSL fragment shader path for rendering window contents using\n" + " patterns. Pattern should be in the format of `SHADER PATH:PATTERN`,\n" + " similar to `--opacity-rule`. Only works when `--experimental-backends`\n" + " is enabled.\n"; FILE *f = (ret ? stderr : stdout); fprintf(f, usage_text, argv0); #undef WARNING_DISABLED @@ -442,6 +452,8 @@ static const struct option longopts[] = { {"corner-radius", required_argument, NULL, 333}, {"rounded-corners-exclude", required_argument, NULL, 334}, {"clip-shadow-above", required_argument, NULL, 335}, + {"window-shader-fg", required_argument, NULL, 336}, + {"window-shader-fg-rule", required_argument, NULL, 337}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -792,6 +804,24 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 317: opt->glx_fshader_win_str = strdup(optarg); break; + case 336: { + // --window-shader-fg + scoped_charp cwd = getcwd(NULL, 0); + opt->window_shader_fg = + locate_auxiliary_file("shaders", optarg, cwd); + if (!opt->window_shader_fg) { + exit(1); + } + break; + } + case 337: { + // --window-shader-fg-rule + scoped_charp cwd = getcwd(NULL, 0); + if (!parse_rule_window_shader(&opt->window_shader_fg_rules, optarg, cwd)) { + exit(1); + } + break; + } case 321: { enum log_level tmp_level = string_to_log_level(optarg); if (tmp_level == LOG_LEVEL_INVALID) { @@ -803,13 +833,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } P_CASEBOOL(319, no_x_selection); P_CASEBOOL(323, use_damage); - case 324: - opt->use_damage = false; - break; - case 325: - opt->vsync = false; - break; - + case 324: opt->use_damage = false; break; + case 325: opt->vsync = false; break; case 326: opt->max_brightness = atof(optarg); break; @@ -850,7 +875,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); - case 801: opt->print_diagnostics = true; break; + case 801: + opt->print_diagnostics = true; + break; P_CASEBOOL(802, debug_mode); P_CASEBOOL(803, no_ewmh_fullscreen); default: usage(argv[0], 1); break; @@ -895,6 +922,25 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, return false; } + if (opt->glx_fshader_win_str && opt->experimental_backends) { + log_warn("--glx-fshader-win has been replaced by " + "\"--window-shader-fg\" for the experimental backends."); + } + + if (opt->window_shader_fg || opt->window_shader_fg_rules) { + if (!opt->experimental_backends || opt->backend != BKEND_GLX) { + log_warn("The new window shader interface only works with the " + "experimental glx backend.%s", + (opt->backend == BKEND_GLX) ? " You may want to use " + "\"--glx-fshader-win\" " + "instead on the legacy " + "glx backend." + : ""); + opt->window_shader_fg = NULL; + c2_list_free(&opt->window_shader_fg_rules, free); + } + } + // Range checking and option assignments opt->fade_delta = max2(opt->fade_delta, 1); opt->shadow_radius = max2(opt->shadow_radius, 0); diff --git a/src/picom.c b/src/picom.c index 7f03286a..5fc04121 100644 --- a/src/picom.c +++ b/src/picom.c @@ -431,6 +431,13 @@ static void destroy_backend(session_t *ps) { free_paint(ps, &w->paint); } + HASH_ITER2(ps->shaders, shader) { + if (shader->backend_shader != NULL) { + // Free the shader here. + shader->backend_shader = NULL; + } + } + if (ps->backend_data && ps->root_image) { ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); ps->root_image = NULL; @@ -1526,6 +1533,62 @@ static void config_file_change_cb(void *_ps) { reset_enable(ps->loop, NULL, 0); } +static bool load_shader_source(session_t *ps, const char *path) { + if (!path) { + // Using the default shader. + return false; + } + + log_info("Loading shader source from %s", path); + + struct shader_info *shader = NULL; + HASH_FIND_STR(ps->shaders, path, shader); + if (shader) { + log_debug("Shader already loaded, reusing"); + return false; + } + + shader = ccalloc(1, struct shader_info); + shader->key = strdup(path); + HASH_ADD_KEYPTR(hh, ps->shaders, shader->key, strlen(shader->key), shader); + + FILE *f = fopen(path, "r"); + if (!f) { + log_error("Failed to open custom shader file: %s", path); + goto err; + } + struct stat statbuf; + if (fstat(fileno(f), &statbuf) < 0) { + log_error("Failed to access custom shader file: %s", path); + goto err; + } + + auto num_bytes = (size_t)statbuf.st_size; + shader->source = ccalloc(num_bytes + 1, char); + auto read_bytes = fread(shader->source, sizeof(char), num_bytes, f); + if (read_bytes < num_bytes || ferror(f)) { + // This is a difficult to hit error case, review thoroughly. + log_error("Failed to read custom shader at %s. (read %lu bytes, expected " + "%lu bytes)", + path, read_bytes, num_bytes); + goto err; + } + return false; +err: + HASH_DEL(ps->shaders, shader); + if (f) { + fclose(f); + } + free(shader->source); + free(shader->key); + free(shader); + return true; +} + +static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) { + return load_shader_source(data, c2_list_get_data(cond)); +} + /** * Initialize a session. * @@ -1753,6 +1816,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, return NULL; } + if (ps->o.window_shader_fg) { + log_debug("Default window shader: \"%s\"", ps->o.window_shader_fg); + } + if (ps->o.logpath) { auto l = file_logger_new(ps->o.logpath); if (l) { @@ -1802,6 +1869,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.fade_blacklist) && c2_list_postprocess(ps, ps->o.blur_background_blacklist) && c2_list_postprocess(ps, ps->o.invert_color_list) && + c2_list_postprocess(ps, ps->o.window_shader_fg_rules) && c2_list_postprocess(ps, ps->o.opacity_rules) && c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && c2_list_postprocess(ps, ps->o.focus_blacklist))) { @@ -1809,6 +1877,22 @@ static session_t *session_init(int argc, char **argv, Display *dpy, "might not work"); } + // Load shader source file specified in the shader rules + if (c2_list_foreach(ps->o.window_shader_fg_rules, load_shader_source_for_condition, ps)) { + log_error("Failed to load shader source file for some of the window " + "shader rules"); + } + if (load_shader_source(ps, ps->o.window_shader_fg)) { + log_error("Failed to load window shader source file"); + } + + if (log_get_level_tls() <= LOG_LEVEL_DEBUG) { + HASH_ITER2(ps->shaders, shader) { + log_debug("Shader %s:", shader->key); + log_debug("%s", shader->source); + } + } + ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); sum_kernel_preprocess(ps->gaussian_map); @@ -2164,16 +2248,17 @@ static void session_destroy(session_t *ps) { list_init_head(&ps->window_stack); // Free blacklists - c2_list_free(&ps->o.shadow_blacklist); - c2_list_free(&ps->o.shadow_clip_list); - c2_list_free(&ps->o.fade_blacklist); - c2_list_free(&ps->o.focus_blacklist); - c2_list_free(&ps->o.invert_color_list); - c2_list_free(&ps->o.blur_background_blacklist); - c2_list_free(&ps->o.opacity_rules); - c2_list_free(&ps->o.paint_blacklist); - c2_list_free(&ps->o.unredir_if_possible_blacklist); - c2_list_free(&ps->o.rounded_corners_blacklist); + c2_list_free(&ps->o.shadow_blacklist, NULL); + c2_list_free(&ps->o.shadow_clip_list, NULL); + c2_list_free(&ps->o.fade_blacklist, NULL); + c2_list_free(&ps->o.focus_blacklist, NULL); + c2_list_free(&ps->o.invert_color_list, NULL); + c2_list_free(&ps->o.blur_background_blacklist, NULL); + c2_list_free(&ps->o.opacity_rules, NULL); + c2_list_free(&ps->o.paint_blacklist, NULL); + c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL); + c2_list_free(&ps->o.rounded_corners_blacklist, NULL); + c2_list_free(&ps->o.window_shader_fg_rules, free); // Free tracked atom list { @@ -2224,6 +2309,17 @@ static void session_destroy(session_t *ps) { free(ps->o.glx_fshader_win_str); free_xinerama_info(ps); + // Release custom window shaders + free(ps->o.window_shader_fg); + struct shader_info *shader, *tmp; + HASH_ITER(hh, ps->shaders, shader, tmp) { + HASH_DEL(ps->shaders, shader); + assert(shader->backend_shader == NULL); + free(shader->source); + free(shader->key); + free(shader); + } + #ifdef CONFIG_VSYNC_DRM // Close file opened for DRM VSync if (ps->drm_fd >= 0) { diff --git a/tests/configs/parsing_test.conf b/tests/configs/parsing_test.conf index adbf6cbf..5269e6e8 100644 --- a/tests/configs/parsing_test.conf +++ b/tests/configs/parsing_test.conf @@ -417,3 +417,10 @@ wintypes: popup_menu = { opacity = 0.8; } dropdown_menu = { opacity = 0.8; } }; + +window-shader-fg-rule = +[ + "shader.frag:name = 'test'", + " shader.frag :name = 'a'", + "default:name = 'b'" +] diff --git a/tests/configs/shader.frag b/tests/configs/shader.frag new file mode 100644 index 00000000..a25e2945 --- /dev/null +++ b/tests/configs/shader.frag @@ -0,0 +1 @@ +vec4 window_shader() {}