mirror of https://github.com/yshui/picom.git
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 <bernd@busse-net.de> Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
parent
896acabab1
commit
654772b8cf
|
@ -54,7 +54,7 @@ jobs:
|
||||||
command: ninja -vC build test
|
command: ninja -vC build test
|
||||||
- run:
|
- run:
|
||||||
name: test config file parsing
|
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:
|
- run:
|
||||||
name: run testsuite
|
name: run testsuite
|
||||||
command: tests/run_tests.sh build/src/picom
|
command: tests/run_tests.sh build/src/picom
|
||||||
|
|
|
@ -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.
|
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-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-win-blend*::
|
||||||
Force all windows to be painted with blending. Useful if you have a *--glx-fshader-win* that could turn opaque pixels transparent.
|
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*::
|
*--transparent-clipping*::
|
||||||
Make transparent windows clip other windows like non-transparent windows do, instead of blending on top of them.
|
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
|
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.
|
Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators.
|
||||||
|
|
9
src/c2.c
9
src/c2.c
|
@ -1151,11 +1151,16 @@ static void c2_free(c2_ptr_t p) {
|
||||||
/**
|
/**
|
||||||
* Free a condition tree in c2_lptr_t.
|
* Free a condition tree in c2_lptr_t.
|
||||||
*/
|
*/
|
||||||
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) {
|
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) {
|
||||||
if (!lp)
|
if (!lp) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
c2_lptr_t *pnext = lp->next;
|
c2_lptr_t *pnext = lp->next;
|
||||||
|
if (f) {
|
||||||
|
f(lp->data);
|
||||||
|
}
|
||||||
|
lp->data = NULL;
|
||||||
c2_free(lp->ptr);
|
c2_free(lp->ptr);
|
||||||
free(lp);
|
free(lp);
|
||||||
|
|
||||||
|
|
7
src/c2.h
7
src/c2.h
|
@ -18,9 +18,10 @@ typedef struct _c2_lptr c2_lptr_t;
|
||||||
typedef struct session session_t;
|
typedef struct session session_t;
|
||||||
struct managed_win;
|
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_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,
|
bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
|
||||||
void **pdata);
|
void **pdata);
|
||||||
|
@ -34,8 +35,8 @@ void *c2_list_get_data(const c2_lptr_t *condlist);
|
||||||
/**
|
/**
|
||||||
* Destroy a condition list.
|
* Destroy a condition list.
|
||||||
*/
|
*/
|
||||||
static inline void c2_list_free(c2_lptr_t **pcondlst) {
|
static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) {
|
||||||
while ((*pcondlst = c2_free_lptr(*pcondlst))) {
|
while ((*pcondlst = c2_free_lptr(*pcondlst, f))) {
|
||||||
}
|
}
|
||||||
*pcondlst = NULL;
|
*pcondlst = NULL;
|
||||||
}
|
}
|
||||||
|
|
11
src/common.h
11
src/common.h
|
@ -130,6 +130,13 @@ typedef struct _latom {
|
||||||
struct _latom *next;
|
struct _latom *next;
|
||||||
} latom_t;
|
} latom_t;
|
||||||
|
|
||||||
|
struct shader_info {
|
||||||
|
char *key;
|
||||||
|
char *source;
|
||||||
|
void *backend_shader;
|
||||||
|
UT_hash_handle hh;
|
||||||
|
};
|
||||||
|
|
||||||
/// Structure containing all necessary data for a session.
|
/// Structure containing all necessary data for a session.
|
||||||
typedef struct session {
|
typedef struct session {
|
||||||
// === Event handlers ===
|
// === Event handlers ===
|
||||||
|
@ -150,6 +157,8 @@ typedef struct session {
|
||||||
ev_signal usr1_signal;
|
ev_signal usr1_signal;
|
||||||
/// Signal handler for SIGINT
|
/// Signal handler for SIGINT
|
||||||
ev_signal int_signal;
|
ev_signal int_signal;
|
||||||
|
|
||||||
|
// === Backend related ===
|
||||||
/// backend data
|
/// backend data
|
||||||
backend_t *backend_data;
|
backend_t *backend_data;
|
||||||
/// backend blur context
|
/// backend blur context
|
||||||
|
@ -160,6 +169,8 @@ typedef struct session {
|
||||||
void *file_watch_handle;
|
void *file_watch_handle;
|
||||||
/// libev mainloop
|
/// libev mainloop
|
||||||
struct ev_loop *loop;
|
struct ev_loop *loop;
|
||||||
|
/// Shaders
|
||||||
|
struct shader_info *shaders;
|
||||||
|
|
||||||
// === Display related ===
|
// === Display related ===
|
||||||
/// Whether the X server is grabbed by us
|
/// Whether the X server is grabbed by us
|
||||||
|
|
210
src/config.c
210
src/config.c
|
@ -3,13 +3,21 @@
|
||||||
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
|
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
|
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
|
||||||
|
|
||||||
|
#include <test.h>
|
||||||
|
|
||||||
#include "c2.h"
|
#include "c2.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
|
@ -23,6 +31,98 @@
|
||||||
|
|
||||||
#include "config.h"
|
#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.
|
* 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);
|
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/<scope>/
|
||||||
|
*/
|
||||||
|
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.
|
* 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_background_blacklist = NULL,
|
||||||
.blur_kerns = NULL,
|
.blur_kerns = NULL,
|
||||||
.blur_kernel_count = 0,
|
.blur_kernel_count = 0,
|
||||||
|
.window_shader_fg = NULL,
|
||||||
|
.window_shader_fg_rules = NULL,
|
||||||
.inactive_dim = 0.0,
|
.inactive_dim = 0.0,
|
||||||
.inactive_dim_fixed = false,
|
.inactive_dim_fixed = false,
|
||||||
.invert_color_list = NULL,
|
.invert_color_list = NULL,
|
||||||
|
|
12
src/config.h
12
src/config.h
|
@ -15,6 +15,8 @@
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/xfixes.h>
|
#include <xcb/xfixes.h>
|
||||||
|
|
||||||
|
#include "uthash_extra.h"
|
||||||
|
|
||||||
#ifdef CONFIG_LIBCONFIG
|
#ifdef CONFIG_LIBCONFIG
|
||||||
#include <libconfig.h>
|
#include <libconfig.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -206,6 +208,10 @@ typedef struct options {
|
||||||
struct conv **blur_kerns;
|
struct conv **blur_kerns;
|
||||||
/// Number of convolution kernels
|
/// Number of convolution kernels
|
||||||
int blur_kernel_count;
|
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.
|
/// How much to dim an inactive window. 0.0 - 1.0, 0 to disable.
|
||||||
double inactive_dim;
|
double inactive_dim;
|
||||||
/// Whether to use fixed inactive dim opacity, instead of deciding
|
/// 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);
|
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_geometry(session_t *, const char *, region_t *);
|
||||||
bool must_use parse_rule_opacity(c2_lptr_t **, const char *);
|
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);
|
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 *);
|
bool condlst_add(c2_lptr_t **, const char *);
|
||||||
|
|
||||||
#ifdef CONFIG_LIBCONFIG
|
#ifdef CONFIG_LIBCONFIG
|
||||||
|
const char *xdg_config_home(void);
|
||||||
|
char **xdg_config_dirs(void);
|
||||||
|
|
||||||
/// Parse a configuration file
|
/// Parse a configuration file
|
||||||
/// Returns the actually config_file name used, allocated on heap
|
/// Returns the actually config_file name used, allocated on heap
|
||||||
/// Outputs:
|
/// Outputs:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2012-2014 Richard Grenville <pyxlcy@gmail.com>
|
// Copyright (c) 2012-2014 Richard Grenville <pyxlcy@gmail.com>
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -37,98 +38,6 @@ static inline int lcfg_lookup_bool(const config_t *config, const char *path, boo
|
||||||
return ret;
|
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
|
/// Search for config file under a base directory
|
||||||
FILE *open_config_file_at(const char *base, char **out_path) {
|
FILE *open_config_file_at(const char *base, char **out_path) {
|
||||||
static const char *config_paths[] = {"/picom.conf", "/picom/picom.conf",
|
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,
|
static inline void parse_wintype_config(const config_t *cfg, const char *member_name,
|
||||||
win_option_t *o, win_option_mask_t *mask) {
|
win_option_t *o, win_option_mask_t *mask) {
|
||||||
char *str = mstrjoin("wintypes.", member_name);
|
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;
|
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
|
// --glx-use-gpushader4
|
||||||
if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival)) {
|
if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival)) {
|
||||||
log_error("glx-use-gpushader4 has been removed, please remove it "
|
log_error("glx-use-gpushader4 has been removed, please remove it "
|
||||||
|
|
|
@ -346,7 +346,17 @@ static void usage(const char *argv0, int ret) {
|
||||||
"\n"
|
"\n"
|
||||||
"--transparent-clipping\n"
|
"--transparent-clipping\n"
|
||||||
" Make transparent windows clip other windows like non-transparent windows\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);
|
FILE *f = (ret ? stderr : stdout);
|
||||||
fprintf(f, usage_text, argv0);
|
fprintf(f, usage_text, argv0);
|
||||||
#undef WARNING_DISABLED
|
#undef WARNING_DISABLED
|
||||||
|
@ -442,6 +452,8 @@ static const struct option longopts[] = {
|
||||||
{"corner-radius", required_argument, NULL, 333},
|
{"corner-radius", required_argument, NULL, 333},
|
||||||
{"rounded-corners-exclude", required_argument, NULL, 334},
|
{"rounded-corners-exclude", required_argument, NULL, 334},
|
||||||
{"clip-shadow-above", required_argument, NULL, 335},
|
{"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},
|
{"experimental-backends", no_argument, NULL, 733},
|
||||||
{"monitor-repaint", no_argument, NULL, 800},
|
{"monitor-repaint", no_argument, NULL, 800},
|
||||||
{"diagnostics", no_argument, NULL, 801},
|
{"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:
|
case 317:
|
||||||
opt->glx_fshader_win_str = strdup(optarg);
|
opt->glx_fshader_win_str = strdup(optarg);
|
||||||
break;
|
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: {
|
case 321: {
|
||||||
enum log_level tmp_level = string_to_log_level(optarg);
|
enum log_level tmp_level = string_to_log_level(optarg);
|
||||||
if (tmp_level == LOG_LEVEL_INVALID) {
|
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(319, no_x_selection);
|
||||||
P_CASEBOOL(323, use_damage);
|
P_CASEBOOL(323, use_damage);
|
||||||
case 324:
|
case 324: opt->use_damage = false; break;
|
||||||
opt->use_damage = false;
|
case 325: opt->vsync = false; break;
|
||||||
break;
|
|
||||||
case 325:
|
|
||||||
opt->vsync = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 326:
|
case 326:
|
||||||
opt->max_brightness = atof(optarg);
|
opt->max_brightness = atof(optarg);
|
||||||
break;
|
break;
|
||||||
|
@ -850,7 +875,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
||||||
break;
|
break;
|
||||||
P_CASEBOOL(733, experimental_backends);
|
P_CASEBOOL(733, experimental_backends);
|
||||||
P_CASEBOOL(800, monitor_repaint);
|
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(802, debug_mode);
|
||||||
P_CASEBOOL(803, no_ewmh_fullscreen);
|
P_CASEBOOL(803, no_ewmh_fullscreen);
|
||||||
default: usage(argv[0], 1); break;
|
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;
|
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
|
// Range checking and option assignments
|
||||||
opt->fade_delta = max2(opt->fade_delta, 1);
|
opt->fade_delta = max2(opt->fade_delta, 1);
|
||||||
opt->shadow_radius = max2(opt->shadow_radius, 0);
|
opt->shadow_radius = max2(opt->shadow_radius, 0);
|
||||||
|
|
116
src/picom.c
116
src/picom.c
|
@ -431,6 +431,13 @@ static void destroy_backend(session_t *ps) {
|
||||||
free_paint(ps, &w->paint);
|
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) {
|
if (ps->backend_data && ps->root_image) {
|
||||||
ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
|
ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
|
||||||
ps->root_image = NULL;
|
ps->root_image = NULL;
|
||||||
|
@ -1526,6 +1533,62 @@ static void config_file_change_cb(void *_ps) {
|
||||||
reset_enable(ps->loop, NULL, 0);
|
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.
|
* Initialize a session.
|
||||||
*
|
*
|
||||||
|
@ -1753,6 +1816,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps->o.window_shader_fg) {
|
||||||
|
log_debug("Default window shader: \"%s\"", ps->o.window_shader_fg);
|
||||||
|
}
|
||||||
|
|
||||||
if (ps->o.logpath) {
|
if (ps->o.logpath) {
|
||||||
auto l = file_logger_new(ps->o.logpath);
|
auto l = file_logger_new(ps->o.logpath);
|
||||||
if (l) {
|
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.fade_blacklist) &&
|
||||||
c2_list_postprocess(ps, ps->o.blur_background_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.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.opacity_rules) &&
|
||||||
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
|
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
|
||||||
c2_list_postprocess(ps, ps->o.focus_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");
|
"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);
|
ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
|
||||||
sum_kernel_preprocess(ps->gaussian_map);
|
sum_kernel_preprocess(ps->gaussian_map);
|
||||||
|
|
||||||
|
@ -2164,16 +2248,17 @@ static void session_destroy(session_t *ps) {
|
||||||
list_init_head(&ps->window_stack);
|
list_init_head(&ps->window_stack);
|
||||||
|
|
||||||
// Free blacklists
|
// Free blacklists
|
||||||
c2_list_free(&ps->o.shadow_blacklist);
|
c2_list_free(&ps->o.shadow_blacklist, NULL);
|
||||||
c2_list_free(&ps->o.shadow_clip_list);
|
c2_list_free(&ps->o.shadow_clip_list, NULL);
|
||||||
c2_list_free(&ps->o.fade_blacklist);
|
c2_list_free(&ps->o.fade_blacklist, NULL);
|
||||||
c2_list_free(&ps->o.focus_blacklist);
|
c2_list_free(&ps->o.focus_blacklist, NULL);
|
||||||
c2_list_free(&ps->o.invert_color_list);
|
c2_list_free(&ps->o.invert_color_list, NULL);
|
||||||
c2_list_free(&ps->o.blur_background_blacklist);
|
c2_list_free(&ps->o.blur_background_blacklist, NULL);
|
||||||
c2_list_free(&ps->o.opacity_rules);
|
c2_list_free(&ps->o.opacity_rules, NULL);
|
||||||
c2_list_free(&ps->o.paint_blacklist);
|
c2_list_free(&ps->o.paint_blacklist, NULL);
|
||||||
c2_list_free(&ps->o.unredir_if_possible_blacklist);
|
c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL);
|
||||||
c2_list_free(&ps->o.rounded_corners_blacklist);
|
c2_list_free(&ps->o.rounded_corners_blacklist, NULL);
|
||||||
|
c2_list_free(&ps->o.window_shader_fg_rules, free);
|
||||||
|
|
||||||
// Free tracked atom list
|
// Free tracked atom list
|
||||||
{
|
{
|
||||||
|
@ -2224,6 +2309,17 @@ static void session_destroy(session_t *ps) {
|
||||||
free(ps->o.glx_fshader_win_str);
|
free(ps->o.glx_fshader_win_str);
|
||||||
free_xinerama_info(ps);
|
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
|
#ifdef CONFIG_VSYNC_DRM
|
||||||
// Close file opened for DRM VSync
|
// Close file opened for DRM VSync
|
||||||
if (ps->drm_fd >= 0) {
|
if (ps->drm_fd >= 0) {
|
||||||
|
|
|
@ -417,3 +417,10 @@ wintypes:
|
||||||
popup_menu = { opacity = 0.8; }
|
popup_menu = { opacity = 0.8; }
|
||||||
dropdown_menu = { opacity = 0.8; }
|
dropdown_menu = { opacity = 0.8; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window-shader-fg-rule =
|
||||||
|
[
|
||||||
|
"shader.frag:name = 'test'",
|
||||||
|
" shader.frag :name = 'a'",
|
||||||
|
"default:name = 'b'"
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
vec4 window_shader() {}
|
Loading…
Reference in New Issue