mirror of
https://github.com/yshui/picom.git
synced 2024-11-25 14:06:08 -05:00
commit
3dfc422edf
21 changed files with 845 additions and 208 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. Shader is searched first relative to the directory the configuration file is in, then in the usual places for a configuration file. See section *SHADER INTERFACE* below for more details on the interface.
|
||||
|
||||
*--window-shader-fg-rule* 'SHADER':'CONDITION'::
|
||||
Specify GLSL fragment shader path for rendering window contents using patterns. Similar to *--opacity-rule*, arguments should be in the format of 'SHADER:CONDITION', e.g. "shader.frag:name = \'window\'". Leading and trailing whitespaces in 'SHADER' will be trimmed. If 'SHADER' is "default", then the default shader will be used for the 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.
|
||||
|
@ -346,6 +352,61 @@ This is the old condition format we once used. Support of this format might be r
|
|||
|
||||
'PATTERN' is the actual pattern string.
|
||||
|
||||
SHADER INTERFACE
|
||||
----------------
|
||||
|
||||
This secion describes the interface of a custom shader, how it is used by picom, and what parameters are passed by picom to the shader. This only applies to the new, experimental backends.
|
||||
|
||||
A custom shader is a GLSL fragment shader program, which can be used to override the default way of how a window is rendered. If a custom shader is used, the default picom effects (e.g. dimming, color inversion, etc.) will no longer be automatically applied. It would be the custom shader's responsibility to apply these effects.
|
||||
|
||||
The interface between picom and a custom shader is dependent on which backend is being used. The xrender backend doesn't support shader at all. Here we descibes the interface provided by the glx backend.
|
||||
|
||||
The shader must defined a function, 'vec4 window_shader()', which would be the entry point of the shader. The returned 'vec4' will be used to set 'gl_FragColor'. A function, 'vec4 default_post_processing(vec4 c)', is provided from applying the default picom effects to input color 'c'.
|
||||
|
||||
The following uniform/input variables are made available to the shader:
|
||||
|
||||
[source,glsl]
|
||||
----
|
||||
in vec2 texcoord; // texture coordinate of the fragment
|
||||
|
||||
uniform float opacity; // opacity of the window (0.0 - 1.0)
|
||||
uniform float dim; // dimming factor of the window (0.0 - 1.0, higher means more dim)
|
||||
uniform float corner_radius; // corner radius of the window (pixels)
|
||||
uniform float border_width; // estimated border width of the window (pixels)
|
||||
uniform bool invert_color; // whether to invert the color of the window
|
||||
uniform sampler2D tex; // texture of the window
|
||||
uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture
|
||||
uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0)
|
||||
uniform float time; // time in milliseconds, counting from an unspecified starting point
|
||||
----
|
||||
|
||||
The default behavior of picom window rendering can be replicated by the following shader:
|
||||
|
||||
[source,glsl]
|
||||
----
|
||||
#version 330
|
||||
in vec2 texcoord; // texture coordinate of the fragment
|
||||
|
||||
uniform sampler2D tex; // texture of the window
|
||||
|
||||
// Default window post-processing:
|
||||
// 1) invert color
|
||||
// 2) opacity / transparency
|
||||
// 3) max-brightness clamping
|
||||
// 4) rounded corners
|
||||
vec4 default_post_processing(vec4 c);
|
||||
|
||||
// Default window shader:
|
||||
// 1) fetch the specified pixel
|
||||
// 2) apply default post-processing
|
||||
vec4 window_shader() {
|
||||
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
|
||||
return default_post_processing(c);
|
||||
}
|
||||
----
|
||||
|
||||
The interface is expected to be mostly stable.
|
||||
|
||||
CONFIGURATION FILES
|
||||
-------------------
|
||||
picom could read from a configuration file if libconfig support is compiled in. If *--config* is not used, picom will seek for a configuration file in `$XDG_CONFIG_HOME/picom.conf` (`~/.config/picom.conf`, usually), then `$XDG_CONFIG_HOME/picom/picom.conf`, then `$XDG_CONFIG_DIRS/picom.conf` (often `/etc/xdg/picom.conf`), then `$XDG_CONFIG_DIRS/picom/picom.conf`.
|
||||
|
|
|
@ -321,11 +321,17 @@ use-damage = true;
|
|||
#
|
||||
# xrender-sync-fence = false
|
||||
|
||||
# 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. Read the man page for a detailed explanation of the interface.
|
||||
#
|
||||
# glx-fshader-win = ""
|
||||
# window-shader-fg = "default"
|
||||
|
||||
# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar
|
||||
# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg.
|
||||
#
|
||||
# window-shader-fg-rule = [
|
||||
# "my_shader.frag:window_type != 'dock'"
|
||||
# ]
|
||||
|
||||
# Force all windows to be painted with blending. Useful if you
|
||||
# have a glx-fshader-win that could turn opaque pixels transparent.
|
||||
|
|
|
@ -383,6 +383,10 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
|
||||
w->win_image, &border_width);
|
||||
}
|
||||
|
||||
ps->backend_data->ops->set_image_property(
|
||||
ps->backend_data, IMAGE_PROPERTY_CUSTOM_SHADER, w->win_image,
|
||||
w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL);
|
||||
}
|
||||
|
||||
if (w->opacity * MAX_ALPHA < 1) {
|
||||
|
|
|
@ -70,6 +70,9 @@ enum image_properties {
|
|||
// Border width
|
||||
// 1 int, default: 0
|
||||
IMAGE_PROPERTY_BORDER_WIDTH,
|
||||
// Custom shader for this window.
|
||||
// 1 pointer to shader struct, default: NULL
|
||||
IMAGE_PROPERTY_CUSTOM_SHADER,
|
||||
};
|
||||
|
||||
enum image_operations {
|
||||
|
@ -77,6 +80,12 @@ enum image_operations {
|
|||
IMAGE_OP_APPLY_ALPHA,
|
||||
};
|
||||
|
||||
enum shader_attributes {
|
||||
// Whether the shader needs to be render regardless of whether the window is
|
||||
// updated.
|
||||
SHADER_ATTRIBUTE_ANIMATED = 1,
|
||||
};
|
||||
|
||||
struct gaussian_blur_args {
|
||||
int size;
|
||||
double deviation;
|
||||
|
@ -199,8 +208,24 @@ struct backend_operations {
|
|||
/// Free resources associated with an image data structure
|
||||
void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
|
||||
|
||||
/// Create a shader object from a shader source.
|
||||
///
|
||||
/// Optional
|
||||
void *(*create_shader)(backend_t *backend_data, const char *source)attr_nonnull(1, 2);
|
||||
|
||||
/// Free a shader object.
|
||||
///
|
||||
/// Required if create_shader is present.
|
||||
void (*destroy_shader)(backend_t *backend_data, void *shader) attr_nonnull(1, 2);
|
||||
|
||||
// =========== Query ===========
|
||||
|
||||
/// Get the attributes of a shader.
|
||||
///
|
||||
/// Optional, Returns a bitmask of attributes, see `shader_attributes`.
|
||||
uint64_t (*get_shader_attributes)(backend_t *backend_data, void *shader)
|
||||
attr_nonnull(1, 2);
|
||||
|
||||
/// Return if image is not completely opaque.
|
||||
///
|
||||
/// This function is needed because some backend might change the content of the
|
||||
|
|
|
@ -452,6 +452,7 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti
|
|||
case IMAGE_PROPERTY_CORNER_RADIUS: tex->corner_radius = dargs[0]; break;
|
||||
case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break;
|
||||
case IMAGE_PROPERTY_BORDER_WIDTH: tex->border_width = *(int *)arg; break;
|
||||
case IMAGE_PROPERTY_CUSTOM_SHADER: break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
|
||||
|
||||
#include "backend/backend.h"
|
||||
|
@ -73,7 +74,7 @@ GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
|
|||
{
|
||||
GLint status = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (GL_FALSE == status) {
|
||||
if (status == GL_FALSE) {
|
||||
GLint log_len = 0;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
|
||||
if (log_len) {
|
||||
|
@ -93,6 +94,7 @@ end:
|
|||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
gl_check_err();
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
@ -105,15 +107,16 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
|
|||
goto end;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nshaders; ++i)
|
||||
for (int i = 0; i < nshaders; ++i) {
|
||||
glAttachShader(program, shaders[i]);
|
||||
}
|
||||
glLinkProgram(program);
|
||||
|
||||
// Get program status
|
||||
{
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (GL_FALSE == status) {
|
||||
if (status == GL_FALSE) {
|
||||
GLint log_len = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
|
||||
if (log_len) {
|
||||
|
@ -128,59 +131,83 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
|
|||
|
||||
end:
|
||||
if (program) {
|
||||
for (int i = 0; i < nshaders; ++i)
|
||||
for (int i = 0; i < nshaders; ++i) {
|
||||
glDetachShader(program, shaders[i]);
|
||||
}
|
||||
}
|
||||
if (program && !success) {
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
gl_check_err();
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a program from NULL-terminated arrays of vertex and fragment shader
|
||||
* strings.
|
||||
*/
|
||||
GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders) {
|
||||
int vert_count, frag_count;
|
||||
for (vert_count = 0; vert_shaders && vert_shaders[vert_count]; ++vert_count) {
|
||||
}
|
||||
for (frag_count = 0; frag_shaders && frag_shaders[frag_count]; ++frag_count) {
|
||||
}
|
||||
|
||||
GLuint prog = 0;
|
||||
auto shaders = (GLuint *)ccalloc(vert_count + frag_count, GLuint);
|
||||
for (int i = 0; i < vert_count; ++i) {
|
||||
shaders[i] = gl_create_shader(GL_VERTEX_SHADER, vert_shaders[i]);
|
||||
if (shaders[i] == 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < frag_count; ++i) {
|
||||
shaders[vert_count + i] =
|
||||
gl_create_shader(GL_FRAGMENT_SHADER, frag_shaders[i]);
|
||||
if (shaders[vert_count + i] == 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
prog = gl_create_program(shaders, vert_count + frag_count);
|
||||
|
||||
out:
|
||||
for (int i = 0; i < vert_count + frag_count; ++i) {
|
||||
if (shaders[i] != 0) {
|
||||
glDeleteShader(shaders[i]);
|
||||
}
|
||||
}
|
||||
free(shaders);
|
||||
gl_check_err();
|
||||
|
||||
return prog;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a program from vertex and fragment shader strings.
|
||||
*/
|
||||
GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) {
|
||||
GLuint vert_shader = 0;
|
||||
GLuint frag_shader = 0;
|
||||
GLuint prog = 0;
|
||||
const char *vert_shaders[2] = {vert_shader_str, NULL};
|
||||
const char *frag_shaders[2] = {frag_shader_str, NULL};
|
||||
|
||||
if (vert_shader_str)
|
||||
vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str);
|
||||
if (frag_shader_str)
|
||||
frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str);
|
||||
|
||||
{
|
||||
GLuint shaders[2];
|
||||
int count = 0;
|
||||
if (vert_shader) {
|
||||
shaders[count++] = vert_shader;
|
||||
}
|
||||
if (frag_shader) {
|
||||
shaders[count++] = frag_shader;
|
||||
}
|
||||
if (count) {
|
||||
prog = gl_create_program(shaders, count);
|
||||
}
|
||||
}
|
||||
|
||||
if (vert_shader)
|
||||
glDeleteShader(vert_shader);
|
||||
if (frag_shader)
|
||||
glDeleteShader(frag_shader);
|
||||
|
||||
return prog;
|
||||
return gl_create_program_from_strv(vert_shaders, frag_shaders);
|
||||
}
|
||||
|
||||
static void gl_free_prog_main(gl_win_shader_t *pprogram) {
|
||||
if (!pprogram)
|
||||
void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) {
|
||||
if (!shader) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pprogram = (gl_win_shader_t *)shader;
|
||||
if (pprogram->prog) {
|
||||
glDeleteProgram(pprogram->prog);
|
||||
pprogram->prog = 0;
|
||||
}
|
||||
gl_check_err();
|
||||
|
||||
free(shader);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -371,35 +398,47 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
|
|||
brightness = gl_average_texture_color(base, img);
|
||||
}
|
||||
|
||||
assert(gd->win_shader.prog);
|
||||
glUseProgram(gd->win_shader.prog);
|
||||
if (gd->win_shader.uniform_opacity >= 0) {
|
||||
glUniform1f(gd->win_shader.uniform_opacity, (float)img->opacity);
|
||||
auto win_shader = inner->shader;
|
||||
if (!win_shader) {
|
||||
win_shader = gd->default_shader;
|
||||
}
|
||||
if (gd->win_shader.uniform_invert_color >= 0) {
|
||||
glUniform1i(gd->win_shader.uniform_invert_color, img->color_inverted);
|
||||
|
||||
assert(win_shader);
|
||||
assert(win_shader->prog);
|
||||
glUseProgram(win_shader->prog);
|
||||
if (win_shader->uniform_opacity >= 0) {
|
||||
glUniform1f(win_shader->uniform_opacity, (float)img->opacity);
|
||||
}
|
||||
if (gd->win_shader.uniform_tex >= 0) {
|
||||
glUniform1i(gd->win_shader.uniform_tex, 0);
|
||||
if (win_shader->uniform_invert_color >= 0) {
|
||||
glUniform1i(win_shader->uniform_invert_color, img->color_inverted);
|
||||
}
|
||||
if (gd->win_shader.uniform_dim >= 0) {
|
||||
glUniform1f(gd->win_shader.uniform_dim, (float)img->dim);
|
||||
if (win_shader->uniform_tex >= 0) {
|
||||
glUniform1i(win_shader->uniform_tex, 0);
|
||||
}
|
||||
if (gd->win_shader.uniform_brightness >= 0) {
|
||||
glUniform1i(gd->win_shader.uniform_brightness, 1);
|
||||
if (win_shader->uniform_dim >= 0) {
|
||||
glUniform1f(win_shader->uniform_dim, (float)img->dim);
|
||||
}
|
||||
if (gd->win_shader.uniform_max_brightness >= 0) {
|
||||
glUniform1f(gd->win_shader.uniform_max_brightness, (float)img->max_brightness);
|
||||
if (win_shader->uniform_brightness >= 0) {
|
||||
glUniform1i(win_shader->uniform_brightness, 1);
|
||||
}
|
||||
if (gd->win_shader.uniform_corner_radius >= 0) {
|
||||
glUniform1f(gd->win_shader.uniform_corner_radius, (float)img->corner_radius);
|
||||
if (win_shader->uniform_max_brightness >= 0) {
|
||||
glUniform1f(win_shader->uniform_max_brightness, (float)img->max_brightness);
|
||||
}
|
||||
if (gd->win_shader.uniform_border_width >= 0) {
|
||||
if (win_shader->uniform_corner_radius >= 0) {
|
||||
glUniform1f(win_shader->uniform_corner_radius, (float)img->corner_radius);
|
||||
}
|
||||
if (win_shader->uniform_border_width >= 0) {
|
||||
auto border_width = img->border_width;
|
||||
if (border_width > img->corner_radius) {
|
||||
border_width = 0;
|
||||
}
|
||||
glUniform1f(gd->win_shader.uniform_border_width, (float)border_width);
|
||||
glUniform1f(win_shader->uniform_border_width, (float)border_width);
|
||||
}
|
||||
if (win_shader->uniform_time >= 0) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
glUniform1f(win_shader->uniform_time,
|
||||
(float)ts.tv_sec * 1000.0f + (float)ts.tv_nsec / 1.0e6f);
|
||||
}
|
||||
|
||||
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
|
||||
|
@ -883,13 +922,14 @@ const char *vertex_shader = GLSL(330,
|
|||
/**
|
||||
* Load a GLSL main program from shader strings.
|
||||
*/
|
||||
static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str,
|
||||
gl_win_shader_t *ret) {
|
||||
static bool gl_win_shader_from_stringv(const char **vshader_strv,
|
||||
const char **fshader_strv, gl_win_shader_t *ret) {
|
||||
// Build program
|
||||
ret->prog = gl_create_program_from_str(vshader_str, fshader_str);
|
||||
ret->prog = gl_create_program_from_strv(vshader_strv, fshader_strv);
|
||||
if (!ret->prog) {
|
||||
log_error("Failed to create GLSL program.");
|
||||
return -1;
|
||||
gl_check_err();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get uniform addresses
|
||||
|
@ -901,6 +941,7 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade
|
|||
bind_uniform(ret, max_brightness);
|
||||
bind_uniform(ret, corner_radius);
|
||||
bind_uniform(ret, border_width);
|
||||
bind_uniform(ret, time);
|
||||
|
||||
gl_check_err();
|
||||
|
||||
|
@ -1545,8 +1586,7 @@ const char *win_shader_glsl = GLSL(330,
|
|||
return length(max(d, 0.0));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
|
||||
vec4 default_post_processing(vec4 c) {
|
||||
vec4 border_color = texture(tex, vec2(0.0, 0.5));
|
||||
if (invert_color) {
|
||||
c = vec4(c.aaa - c.rgb, c.a);
|
||||
|
@ -1581,7 +1621,23 @@ const char *win_shader_glsl = GLSL(330,
|
|||
c = (1.0f - factor) * c + factor * border_color;
|
||||
}
|
||||
|
||||
gl_FragColor = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
vec4 window_shader();
|
||||
|
||||
void main() {
|
||||
gl_FragColor = window_shader();
|
||||
}
|
||||
);
|
||||
|
||||
const char *win_shader_default = GLSL(330,
|
||||
in vec2 texcoord;
|
||||
uniform sampler2D tex;
|
||||
vec4 default_post_processing(vec4 c);
|
||||
vec4 window_shader() {
|
||||
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
|
||||
return default_post_processing(c);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1596,6 +1652,45 @@ const char *present_vertex_shader = GLSL(330,
|
|||
);
|
||||
// clang-format on
|
||||
|
||||
void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) {
|
||||
auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t);
|
||||
|
||||
const char *vert_shaders[2] = {vertex_shader, NULL};
|
||||
const char *frag_shaders[3] = {win_shader_glsl, source, NULL};
|
||||
|
||||
if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) {
|
||||
free(win_shader);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GLint viewport_dimensions[2];
|
||||
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
|
||||
|
||||
// Set projection matrix to gl viewport dimensions so we can use screen
|
||||
// coordinates for all vertices
|
||||
// Note: OpenGL matrices are column major
|
||||
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
|
||||
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
{-1, -1, 0, 1}};
|
||||
|
||||
int pml = glGetUniformLocationChecked(win_shader->prog, "projection");
|
||||
glUseProgram(win_shader->prog);
|
||||
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||
glUseProgram(0);
|
||||
|
||||
return win_shader;
|
||||
}
|
||||
|
||||
uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *shader) {
|
||||
auto win_shader = (gl_win_shader_t *)shader;
|
||||
uint64_t ret = 0;
|
||||
if (glGetUniformLocation(win_shader->prog, "time") >= 0) {
|
||||
ret |= SHADER_ATTRIBUTE_ANIMATED;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool gl_init(struct gl_data *gd, session_t *ps) {
|
||||
// Initialize GLX data structure
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
@ -1637,24 +1732,24 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
// Initialize shaders
|
||||
gd->default_shader = gl_create_window_shader(NULL, win_shader_default);
|
||||
if (!gd->default_shader) {
|
||||
log_error("Failed to create window shaders");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set projection matrix to gl viewport dimensions so we can use screen
|
||||
// coordinates for all vertices
|
||||
// Note: OpenGL matrices are column major
|
||||
GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0},
|
||||
{0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0},
|
||||
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
|
||||
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
{-1, -1, 0, 1}};
|
||||
|
||||
// Initialize shaders
|
||||
gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader);
|
||||
int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection");
|
||||
glUseProgram(gd->win_shader.prog);
|
||||
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||
glUseProgram(0);
|
||||
|
||||
gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag);
|
||||
gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color");
|
||||
pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
|
||||
int pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
|
||||
glUseProgram(gd->fill_shader.prog);
|
||||
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||
glUseProgram(0);
|
||||
|
@ -1713,13 +1808,16 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
|
|||
}
|
||||
|
||||
void gl_deinit(struct gl_data *gd) {
|
||||
gl_free_prog_main(&gd->win_shader);
|
||||
|
||||
if (gd->logger) {
|
||||
log_remove_target_tls(gd->logger);
|
||||
gd->logger = NULL;
|
||||
}
|
||||
|
||||
if (gd->default_shader) {
|
||||
gl_destroy_window_shader(&gd->base, gd->default_shader);
|
||||
gd->default_shader = NULL;
|
||||
}
|
||||
|
||||
gl_check_err();
|
||||
}
|
||||
|
||||
|
@ -1923,6 +2021,18 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
|
||||
void *image_data, void *args) {
|
||||
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
|
||||
return default_set_image_property(backend_data, prop, image_data, args);
|
||||
}
|
||||
|
||||
struct backend_image *img = image_data;
|
||||
auto inner = (struct gl_texture *)img->inner;
|
||||
inner->shader = args;
|
||||
return true;
|
||||
}
|
||||
|
||||
enum device_status gl_device_status(backend_t *base) {
|
||||
auto gd = (struct gl_data *)base;
|
||||
if (!gd->has_robustness) {
|
||||
|
|
|
@ -28,6 +28,8 @@ static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
|
|||
|
||||
// Program and uniforms for window shader
|
||||
typedef struct {
|
||||
UT_hash_handle hh;
|
||||
uint32_t id;
|
||||
GLuint prog;
|
||||
GLint uniform_opacity;
|
||||
GLint uniform_invert_color;
|
||||
|
@ -37,6 +39,7 @@ typedef struct {
|
|||
GLint uniform_max_brightness;
|
||||
GLint uniform_corner_radius;
|
||||
GLint uniform_border_width;
|
||||
GLint uniform_time;
|
||||
} gl_win_shader_t;
|
||||
|
||||
// Program and uniforms for brightness shader
|
||||
|
@ -68,6 +71,7 @@ struct gl_texture {
|
|||
|
||||
// Textures for auxiliary uses.
|
||||
GLuint auxiliary_texture[2];
|
||||
gl_win_shader_t *shader;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
|
@ -79,7 +83,8 @@ struct gl_data {
|
|||
bool has_robustness;
|
||||
// Height and width of the root window
|
||||
int height, width;
|
||||
gl_win_shader_t win_shader;
|
||||
// Hash-table of window shaders
|
||||
gl_win_shader_t *default_shader;
|
||||
gl_brightness_shader_t brightness_shader;
|
||||
gl_fill_shader_t fill_shader;
|
||||
GLuint back_texture, back_fbo;
|
||||
|
@ -103,12 +108,17 @@ typedef struct session session_t;
|
|||
GLuint gl_create_shader(GLenum shader_type, const char *shader_str);
|
||||
GLuint gl_create_program(const GLuint *const shaders, int nshaders);
|
||||
GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
|
||||
void *gl_create_window_shader(backend_t *backend_data, const char *source);
|
||||
void gl_destroy_window_shader(backend_t *backend_data, void *shader);
|
||||
uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
|
||||
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
|
||||
void *image_data, void *args);
|
||||
|
||||
/**
|
||||
* @brief Render a region with texture data.
|
||||
*/
|
||||
void gl_compose(backend_t *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt,
|
||||
const region_t *reg_visible);
|
||||
void gl_compose(backend_t *, void *image_data, int dst_x, int dst_y,
|
||||
const region_t *reg_tgt, const region_t *reg_visible);
|
||||
|
||||
void gl_resize(struct gl_data *, int width, int height);
|
||||
|
||||
|
|
|
@ -534,7 +534,7 @@ struct backend_operations glx_ops = {
|
|||
.release_image = gl_release_image,
|
||||
.compose = gl_compose,
|
||||
.image_op = gl_image_op,
|
||||
.set_image_property = default_set_image_property,
|
||||
.set_image_property = gl_set_image_property,
|
||||
.clone_image = default_clone_image,
|
||||
.blur = gl_blur,
|
||||
.is_image_transparent = default_is_image_transparent,
|
||||
|
@ -547,6 +547,9 @@ struct backend_operations glx_ops = {
|
|||
.get_blur_size = gl_get_blur_size,
|
||||
.diagnostics = glx_diagnostics,
|
||||
.device_status = gl_device_status,
|
||||
.create_shader = gl_create_window_shader,
|
||||
.destroy_shader = gl_destroy_window_shader,
|
||||
.get_shader_attributes = gl_get_shader_attributes,
|
||||
.max_buffer_age = 5, // Why?
|
||||
};
|
||||
|
||||
|
|
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;
|
||||
}
|
||||
|
|
12
src/common.h
12
src/common.h
|
@ -130,6 +130,14 @@ typedef struct _latom {
|
|||
struct _latom *next;
|
||||
} latom_t;
|
||||
|
||||
struct shader_info {
|
||||
char *key;
|
||||
char *source;
|
||||
void *backend_shader;
|
||||
uint64_t attributes;
|
||||
UT_hash_handle hh;
|
||||
};
|
||||
|
||||
/// Structure containing all necessary data for a session.
|
||||
typedef struct session {
|
||||
// === Event handlers ===
|
||||
|
@ -150,6 +158,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 +170,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,18 @@ 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`. `SHADER_PATH` can be \"default\", in which\n"
|
||||
" case the default shader will be used. Only works when\n"
|
||||
" `--experimental-backends` is enabled.\n";
|
||||
FILE *f = (ret ? stderr : stdout);
|
||||
fprintf(f, usage_text, argv0);
|
||||
#undef WARNING_DISABLED
|
||||
|
@ -442,6 +453,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 +805,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 +834,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 +876,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 +923,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);
|
||||
|
|
172
src/picom.c
172
src/picom.c
|
@ -431,6 +431,14 @@ static void destroy_backend(session_t *ps) {
|
|||
free_paint(ps, &w->paint);
|
||||
}
|
||||
|
||||
HASH_ITER2(ps->shaders, shader) {
|
||||
if (shader->backend_shader != NULL) {
|
||||
ps->backend_data->ops->destroy_shader(ps->backend_data,
|
||||
shader->backend_shader);
|
||||
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;
|
||||
|
@ -499,10 +507,29 @@ static bool initialize_backend(session_t *ps) {
|
|||
|
||||
if (!initialize_blur(ps)) {
|
||||
log_fatal("Failed to prepare for background blur, aborting...");
|
||||
ps->backend_data->ops->deinit(ps->backend_data);
|
||||
ps->backend_data = NULL;
|
||||
quit(ps);
|
||||
return false;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Create shaders
|
||||
HASH_ITER2(ps->shaders, shader) {
|
||||
assert(shader->backend_shader == NULL);
|
||||
shader->backend_shader = ps->backend_data->ops->create_shader(
|
||||
ps->backend_data, shader->source);
|
||||
if (shader->backend_shader == NULL) {
|
||||
log_warn("Failed to create shader for shader file %s, "
|
||||
"this shader will not be used",
|
||||
shader->key);
|
||||
} else {
|
||||
if (ps->backend_data->ops->get_shader_attributes) {
|
||||
shader->attributes =
|
||||
ps->backend_data->ops->get_shader_attributes(
|
||||
ps->backend_data, shader->backend_shader);
|
||||
} else {
|
||||
shader->attributes = 0;
|
||||
}
|
||||
log_debug("Shader %s has attributes %ld", shader->key,
|
||||
shader->attributes);
|
||||
}
|
||||
}
|
||||
|
||||
// window_stack shouldn't include window that's
|
||||
|
@ -525,6 +552,11 @@ static bool initialize_backend(session_t *ps) {
|
|||
|
||||
// The old backends binds pixmap lazily, nothing to do here
|
||||
return true;
|
||||
err:
|
||||
ps->backend_data->ops->deinit(ps->backend_data);
|
||||
ps->backend_data = NULL;
|
||||
quit(ps);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Handle configure event of the root window
|
||||
|
@ -618,11 +650,13 @@ static void handle_root_flags(session_t *ps) {
|
|||
}
|
||||
}
|
||||
|
||||
static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) {
|
||||
static struct managed_win *
|
||||
paint_preprocess(session_t *ps, bool *fade_running, bool *animation) {
|
||||
// XXX need better, more general name for `fade_running`. It really
|
||||
// means if fade is still ongoing after the current frame is rendered
|
||||
struct managed_win *bottom = NULL;
|
||||
*fade_running = false;
|
||||
*animation = false;
|
||||
|
||||
// Fading step calculation
|
||||
long long steps = 0L;
|
||||
|
@ -637,7 +671,9 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) {
|
|||
}
|
||||
ps->fade_time += steps * ps->o.fade_delta;
|
||||
|
||||
// First, let's process fading
|
||||
// First, let's process fading, and animated shaders
|
||||
// TODO(yshui) check if a window is fully obscured, and if we don't need to
|
||||
// process fading or animation for it.
|
||||
win_stack_foreach_managed_safe(w, &ps->window_stack) {
|
||||
const winmode_t mode_old = w->mode;
|
||||
const bool was_painted = w->to_paint;
|
||||
|
@ -648,6 +684,11 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) {
|
|||
add_damage_from_win(ps, w);
|
||||
}
|
||||
|
||||
if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) {
|
||||
add_damage_from_win(ps, w);
|
||||
*animation = true;
|
||||
}
|
||||
|
||||
// Run fading
|
||||
if (run_fade(ps, &w, steps)) {
|
||||
*fade_running = true;
|
||||
|
@ -1432,8 +1473,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
|
|||
* screen is not redirected. its sole purpose should be to decide whether the
|
||||
* screen should be redirected. */
|
||||
bool fade_running = false;
|
||||
bool animation = false;
|
||||
bool was_redirected = ps->redirected;
|
||||
auto bottom = paint_preprocess(ps, &fade_running);
|
||||
auto bottom = paint_preprocess(ps, &fade_running, &animation);
|
||||
ps->tmout_unredir_hit = false;
|
||||
|
||||
if (!was_redirected && ps->redirected) {
|
||||
|
@ -1482,7 +1524,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
|
|||
// TODO(yshui) Investigate how big the X critical section needs to be. There are
|
||||
// suggestions that rendering should be in the critical section as well.
|
||||
|
||||
ps->redraw_needed = false;
|
||||
ps->redraw_needed = animation;
|
||||
}
|
||||
|
||||
static void draw_callback(EV_P_ ev_idle *w, int revents) {
|
||||
|
@ -1490,8 +1532,9 @@ static void draw_callback(EV_P_ ev_idle *w, int revents) {
|
|||
|
||||
draw_callback_impl(EV_A_ ps, revents);
|
||||
|
||||
// Don't do painting non-stop unless we are in benchmark mode
|
||||
if (!ps->o.benchmark) {
|
||||
// Don't do painting non-stop unless we are in benchmark mode, or if
|
||||
// draw_callback_impl thinks we should continue painting.
|
||||
if (!ps->o.benchmark && !ps->redraw_needed) {
|
||||
ev_idle_stop(EV_A_ & ps->draw_idle);
|
||||
}
|
||||
}
|
||||
|
@ -1526,6 +1569,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 +1852,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 +1905,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 +1913,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 +2284,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 +2345,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) {
|
||||
|
|
37
src/win.c
37
src/win.c
|
@ -1052,6 +1052,19 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou
|
|||
add_damage_from_win(ps, w);
|
||||
}
|
||||
|
||||
static void
|
||||
win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shader_new) {
|
||||
if (w->fg_shader == shader_new) {
|
||||
return;
|
||||
}
|
||||
|
||||
w->fg_shader = shader_new;
|
||||
|
||||
// A different shader might change how the window is drawn, these changes should
|
||||
// be rare however, so this should be fine.
|
||||
add_damage_from_win(ps, w);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a window should have background blurred.
|
||||
*/
|
||||
|
@ -1098,6 +1111,28 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine custom window shader to use for a window.
|
||||
*/
|
||||
static void win_determine_fg_shader(session_t *ps, struct managed_win *w) {
|
||||
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto shader_new = ps->o.window_shader_fg;
|
||||
void *val = NULL;
|
||||
if (c2_match(ps, w, ps->o.window_shader_fg_rules, &val)) {
|
||||
shader_new = val;
|
||||
}
|
||||
|
||||
struct shader_info *shader = NULL;
|
||||
if (shader_new) {
|
||||
HASH_FIND_STR(ps->shaders, shader_new, shader);
|
||||
}
|
||||
|
||||
win_set_fg_shader(ps, w, shader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update window opacity according to opacity rules.
|
||||
*/
|
||||
|
@ -1134,6 +1169,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
|
|||
win_determine_invert_color(ps, w);
|
||||
win_determine_blur_background(ps, w);
|
||||
win_determine_rounded_corners(ps, w);
|
||||
win_determine_fg_shader(ps, w);
|
||||
w->mode = win_calc_mode(w);
|
||||
log_debug("Window mode changed to %d", w->mode);
|
||||
win_update_opacity_rule(ps, w);
|
||||
|
@ -1463,6 +1499,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||
.prev_trans = NULL,
|
||||
.shadow = false,
|
||||
.clip_shadow_above = false,
|
||||
.fg_shader = NULL,
|
||||
.xinerama_scr = -1,
|
||||
.mode = WMODE_TRANS,
|
||||
.ever_damaged = false,
|
||||
|
|
|
@ -209,7 +209,8 @@ struct managed_win {
|
|||
/// Previous window opacity.
|
||||
double opacity_target_old;
|
||||
/// true if window (or client window, for broken window managers
|
||||
/// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity prop
|
||||
/// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity
|
||||
/// prop
|
||||
bool has_opacity_prop;
|
||||
/// Cached value of opacity window attribute.
|
||||
opacity_t opacity_prop;
|
||||
|
@ -270,6 +271,9 @@ struct managed_win {
|
|||
/// Whether to blur window background.
|
||||
bool blur_background;
|
||||
|
||||
/// The custom window shader to use when rendering.
|
||||
struct shader_info *fg_shader;
|
||||
|
||||
#ifdef CONFIG_OPENGL
|
||||
/// Textures and FBO background blur use.
|
||||
glx_blur_cache_t glx_blur_cache;
|
||||
|
|
|
@ -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