mirror of
https://github.com/yshui/picom.git
synced 2024-11-18 13:55:36 -05:00
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
12 changed files with 462 additions and 118 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
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.
|
||||
*/
|
||||
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);
|
||||
|
||||
|
|
7
src/c2.h
7
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;
|
||||
}
|
||||
|
|
11
src/common.h
11
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
|
||||
|
|
210
src/config.c
210
src/config.c
|
@ -3,13 +3,21 @@
|
|||
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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 <test.h>
|
||||
|
||||
#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/<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.
|
||||
*/
|
||||
|
@ -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,
|
||||
|
|
12
src/config.h
12
src/config.h
|
@ -15,6 +15,8 @@
|
|||
#include <xcb/xcb.h>
|
||||
#include <xcb/xfixes.h>
|
||||
|
||||
#include "uthash_extra.h"
|
||||
|
||||
#ifdef CONFIG_LIBCONFIG
|
||||
#include <libconfig.h>
|
||||
#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:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2012-2014 Richard Grenville <pyxlcy@gmail.com>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -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 "
|
||||
|
|
|
@ -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);
|
||||
|
|
116
src/picom.c
116
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) {
|
||||
|
|
|
@ -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'"
|
||||
]
|
||||
|
|
1
tests/configs/shader.frag
Normal file
1
tests/configs/shader.frag
Normal file
|
@ -0,0 +1 @@
|
|||
vec4 window_shader() {}
|
Loading…
Reference in a new issue