mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-07-05 00:06:20 -04:00
Merge 8b4dd043ed into 78135af753
This commit is contained in:
commit
f07f994678
9 changed files with 970 additions and 8 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"title": "Visuals",
|
"title": "Visuals",
|
||||||
"pages": ["theming", "status-bar", "effects", "animations"]
|
"pages": ["theming", "status-bar", "effects", "animations", "shaders"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
docs/visuals/shaders.md
Normal file
121
docs/visuals/shaders.md
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
---
|
||||||
|
title: Shader Transitions
|
||||||
|
description: Write custom GLSL fragment shaders for window open/close animations.
|
||||||
|
---
|
||||||
|
|
||||||
|
mangowm can drive a window's open and close animation with a custom GLSL fragment shader instead of (or alongside) the built-in parametric fades and zooms. Each window is snapshotted to a texture once, then your shader runs every animation tick against that texture to produce the visible frame.
|
||||||
|
|
||||||
|
This is a small, focused feature. It only covers the open and close transitions (not move/tag/focus animations), and shader files are only discovered at startup and after a GPU reset. There is no hot-reload while the compositor is running.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Drop a `.frag` file in `~/.config/mango/shaders/`, e.g. `dissolve.frag`.
|
||||||
|
2. Reference it by filename (without the `.frag` extension) in your config:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
effect_open=dissolve
|
||||||
|
effect_close=burn
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Restart mangowm. Shader files are scanned once at startup and again after a GPU reset. Adding or editing a `.frag` file while the compositor is already running has no effect until the next restart.
|
||||||
|
|
||||||
|
## Writing a shader
|
||||||
|
|
||||||
|
A shader file is just the body of a GLSL ES 3.00 fragment shader, typically a single `void main() { ... }` function. The loader wraps your body with a fixed header before compiling it, equivalent to:
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_progress;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform vec2 u_size;
|
||||||
|
in vec2 v_texcoord;
|
||||||
|
out vec4 fragColor;
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not redeclare any of these names in your file: `u_texture`, `u_progress`, `u_time`, `u_size`, `v_texcoord`, or `fragColor`. The header already declares them. Redeclaring any of them is a GLSL compile error and your shader will fail to load (see [Fail-safe behavior](#fail-safe-behavior)).
|
||||||
|
|
||||||
|
| Name | Type | Meaning |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `u_texture` | `sampler2D` | A snapshot of the window's contents, sampled with `texture(u_texture, v_texcoord)`. |
|
||||||
|
| `u_progress` | `float` | Animation progress, `0.0` to `1.0`. See [Progress convention](#progress-convention) below. This is the one thing every shader must get right. |
|
||||||
|
| `u_time` | `float` | Seconds elapsed since the animation started. Useful for time-based effects (e.g. a glitch shimmer) independent of progress. |
|
||||||
|
| `u_size` | `vec2` | Output size in pixels (the window width and height being rendered into). |
|
||||||
|
| `v_texcoord` | `vec2` | Interpolated texture coordinate, `0.0` to `1.0` across the window. |
|
||||||
|
| `fragColor` | `out vec4` | Your shader's output color for the pixel. Write to this instead of `gl_FragColor`. |
|
||||||
|
|
||||||
|
Your shader writes its output to `fragColor`, which the header declares as `out vec4`. Since the engine composites the result with premultiplied alpha, the RGB channels you write must already be multiplied by the alpha you write. That means `fragColor = vec4(color.rgb * alpha, alpha)`, not `vec4(color.rgb, alpha)`. Forgetting this produces a white or light halo around fading-out windows.
|
||||||
|
|
||||||
|
### GLSL ES 3.00
|
||||||
|
|
||||||
|
The shader is compiled as GLSL ES 3.00. The loader prepends the `#version 300 es` line and the header above, so you write only the body:
|
||||||
|
|
||||||
|
- Sample textures with `texture(sampler, uv)`, not `texture2D(...)`.
|
||||||
|
- Write the output to `fragColor`, not `gl_FragColor`. The `out` variable is already declared in the header, so do not declare your own.
|
||||||
|
- Read the interpolated coordinate from `v_texcoord` (declared `in` by the header). Inputs from the vertex stage use `in`, not `varying`.
|
||||||
|
- No redeclaring `u_texture` / `u_progress` / `u_time` / `u_size` / `v_texcoord` / `fragColor` (see above).
|
||||||
|
|
||||||
|
### Precision: highp is guaranteed
|
||||||
|
|
||||||
|
GLSL ES 3.00 fragment shaders are guaranteed `highp` (fp32) floats. The classic noise hash works with no workaround:
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
float hash12(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You do not need the multi-round fp16-safe hashes found in older GLES2 shader code, and you do not need `GL_FRAGMENT_PRECISION_HIGH` guards. `mediump` is still available if you want it for a hot loop, but the default `highp` is fine everywhere.
|
||||||
|
|
||||||
|
## Progress convention
|
||||||
|
|
||||||
|
Write your shader so `u_progress == 0.0` means the window is fully present and `u_progress == 1.0` means the window is fully gone. The compositor runs progress in the right direction for both open and close, so a single shader body works for both:
|
||||||
|
|
||||||
|
- **Close**: progress is driven directly from the close animation's elapsed fraction (`progress = min(animation_passed, 1.0)`), so it runs `0 -> 1` as the window closes: present, then gone.
|
||||||
|
- **Open**: progress is driven from `1.0 - min(animation_passed, 1.0)`, so it runs `1 -> 0` as the window opens: gone, then present.
|
||||||
|
|
||||||
|
The engine inverts progress for you on open. You never need two different shaders (or an `if` on direction) for the open vs. close case. One body covering `progress: 0 = present, 1 = gone` dissolves a window away on close and dissolves it in on open.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Setting | Scope | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `effect_open` | global | Name (no `.frag`) of the shader to use for window open animations. |
|
||||||
|
| `effect_close` | global | Name (no `.frag`) of the shader to use for window close animations. |
|
||||||
|
|
||||||
|
```ini
|
||||||
|
effect_open=dissolve
|
||||||
|
effect_close=burn
|
||||||
|
```
|
||||||
|
|
||||||
|
Both can also be set per-window via a `windowrule`, which takes precedence over the global setting for matching windows:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
windowrule=appid:firefox,effect_close:glitch
|
||||||
|
```
|
||||||
|
|
||||||
|
If `effect_open`/`effect_close` is left unset (globally and per-window), or the window doesn't match a rule, the window uses the existing parametric animation (`animation_type_open`/`animation_type_close`, fade, zoom, etc.) exactly as before this feature existed. Shaders are strictly opt-in.
|
||||||
|
|
||||||
|
## Fail-safe behavior
|
||||||
|
|
||||||
|
Shader loading and selection are designed to never break your session:
|
||||||
|
|
||||||
|
- **Compile/link failure**: if a `.frag` file fails to compile or link, the loader logs an error (visible with `WLR_ERROR` logging) and skips that file. It is never registered. Other shaders in the directory still load normally.
|
||||||
|
- **Unknown shader name**: if `effect_open`/`effect_close` (global or per-window) names a shader that was never successfully loaded (typo, compile failure, or you just haven't created the file), that window silently falls back to the normal parametric open/close animation. No crash, no missing window content. It just behaves as if the shader setting weren't there.
|
||||||
|
- **Missing shader directory**: if `~/.config/mango/shaders/` doesn't exist, it's logged at `INFO` level and skipped. Having no custom shaders at all is a normal, supported configuration.
|
||||||
|
|
||||||
|
## Restart required for new files
|
||||||
|
|
||||||
|
`~/.config/mango/shaders/` is scanned for `*.frag` files once at startup, and again automatically after a GPU reset (which also clears all previously compiled shader programs). There is no file-watching or hot-reload. If you add a new `.frag` file or rename one while mangowm is running, you need to restart the compositor before `effect_open` / `effect_close` can reference it. Editing the contents of an already-loaded shader also requires a restart to take effect.
|
||||||
|
|
||||||
|
## Example shaders
|
||||||
|
|
||||||
|
Example shaders live in a separate repo, [ernestoCruz05/shader_examples](https://github.com/ernestoCruz05/shader_examples). Clone it and copy whichever `.frag` files you want into your shader directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/ernestoCruz05/shader_examples.git
|
||||||
|
cp shader_examples/*.frag ~/.config/mango/shaders/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart mangowm and reference them by basename (without the `.frag` extension) in your config (see [Quick start](#quick-start)).
|
||||||
|
|
@ -39,6 +39,8 @@ libscenefx_dep = dependency('scenefx-0.5',version: '>=0.5.0')
|
||||||
pixman_dep = dependency('pixman-1')
|
pixman_dep = dependency('pixman-1')
|
||||||
cjson_dep = dependency('libcjson')
|
cjson_dep = dependency('libcjson')
|
||||||
pangocairo_dep = dependency('pangocairo')
|
pangocairo_dep = dependency('pangocairo')
|
||||||
|
egl_dep = dependency('egl')
|
||||||
|
glesv2_dep = dependency('glesv2')
|
||||||
|
|
||||||
# version info
|
# version info
|
||||||
git = find_program('git', required : false)
|
git = find_program('git', required : false)
|
||||||
|
|
@ -97,7 +99,7 @@ executable('mango',
|
||||||
'src/mango.c',
|
'src/mango.c',
|
||||||
'src/common/util.c',
|
'src/common/util.c',
|
||||||
'src/draw/text-node.c',
|
'src/draw/text-node.c',
|
||||||
|
'src/draw/effect_pass.c',
|
||||||
wayland_sources,
|
wayland_sources,
|
||||||
dependencies : [
|
dependencies : [
|
||||||
libm,
|
libm,
|
||||||
|
|
@ -114,6 +116,8 @@ executable('mango',
|
||||||
pixman_dep,
|
pixman_dep,
|
||||||
cjson_dep,
|
cjson_dep,
|
||||||
pangocairo_dep,
|
pangocairo_dep,
|
||||||
|
egl_dep,
|
||||||
|
glesv2_dep,
|
||||||
],
|
],
|
||||||
install : true,
|
install : true,
|
||||||
c_args : c_args,
|
c_args : c_args,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ void set_rect_size(struct wlr_scene_rect *rect, int32_t width, int32_t height) {
|
||||||
wlr_scene_rect_set_size(rect, GEZERO(width), GEZERO(height));
|
wlr_scene_rect_set_size(rect, GEZERO(width), GEZERO(height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void client_surface_set_enabled(Client *c, bool enabled) {
|
||||||
|
if (!c->fx.active && c->scene_surface)
|
||||||
|
wlr_scene_node_set_enabled(&c->scene_surface->node, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
struct fx_corner_radii set_client_corner_location(Client *c) {
|
struct fx_corner_radii set_client_corner_location(Client *c) {
|
||||||
struct fx_corner_radii current_corner_location =
|
struct fx_corner_radii current_corner_location =
|
||||||
corner_radii_all(config.border_radius);
|
corner_radii_all(config.border_radius);
|
||||||
|
|
@ -1053,6 +1058,7 @@ void client_apply_clip(Client *c, float factor) {
|
||||||
set_client_corner_location(c);
|
set_client_corner_location(c);
|
||||||
|
|
||||||
if (!config.animations && !c->overview_scene_surface) {
|
if (!config.animations && !c->overview_scene_surface) {
|
||||||
|
client_fx_settle(c);
|
||||||
c->animation.running = false;
|
c->animation.running = false;
|
||||||
c->need_output_flush = false;
|
c->need_output_flush = false;
|
||||||
c->animainit_geom = c->current = c->pending = c->animation.current =
|
c->animainit_geom = c->current = c->pending = c->animation.current =
|
||||||
|
|
@ -1123,10 +1129,10 @@ void client_apply_clip(Client *c, float factor) {
|
||||||
// 如果窗口剪切区域已经剪切到0,则不渲染窗口表面
|
// 如果窗口剪切区域已经剪切到0,则不渲染窗口表面
|
||||||
if (clip_box.width <= 0 || clip_box.height <= 0) {
|
if (clip_box.width <= 0 || clip_box.height <= 0) {
|
||||||
should_render_client_surface = false;
|
should_render_client_surface = false;
|
||||||
wlr_scene_node_set_enabled(&c->scene_surface->node, false);
|
client_surface_set_enabled(c, false);
|
||||||
} else {
|
} else {
|
||||||
should_render_client_surface = true;
|
should_render_client_surface = true;
|
||||||
wlr_scene_node_set_enabled(&c->scene_surface->node, true);
|
client_surface_set_enabled(c, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不用在执行下面的窗口表面剪切和缩放等效果操作
|
// 不用在执行下面的窗口表面剪切和缩放等效果操作
|
||||||
|
|
@ -1227,7 +1233,10 @@ void fadeout_client_animation_next_tick(Client *c) {
|
||||||
&c->scene->node, snap_scene_buffer_apply_effect, &buffer_data);
|
&c->scene->node, snap_scene_buffer_apply_effect, &buffer_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_fx_tick(c, MANGO_MIN(animation_passed, 1.0), passed_time);
|
||||||
|
|
||||||
if (animation_passed >= 1.0) {
|
if (animation_passed >= 1.0) {
|
||||||
|
client_fx_settle(c);
|
||||||
wl_list_remove(&c->fadeout_link);
|
wl_list_remove(&c->fadeout_link);
|
||||||
wlr_scene_node_destroy(&c->scene->node);
|
wlr_scene_node_destroy(&c->scene->node);
|
||||||
free(c);
|
free(c);
|
||||||
|
|
@ -1235,6 +1244,99 @@ void fadeout_client_animation_next_tick(Client *c) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void client_fx_settle(Client *c) {
|
||||||
|
if (!c || !c->fx.active)
|
||||||
|
return;
|
||||||
|
if (c->fx.scene_buffer)
|
||||||
|
wlr_scene_node_destroy(&c->fx.scene_buffer->node);
|
||||||
|
if (c->fx.src)
|
||||||
|
wlr_texture_destroy(c->fx.src);
|
||||||
|
if (c->fx.swap)
|
||||||
|
wlr_swapchain_destroy(c->fx.swap);
|
||||||
|
c->fx.scene_buffer = NULL;
|
||||||
|
c->fx.src = NULL;
|
||||||
|
c->fx.swap = NULL;
|
||||||
|
c->fx.shader = NULL;
|
||||||
|
c->fx.active = false;
|
||||||
|
if (c->fx.fadeout) {
|
||||||
|
if (c->scene)
|
||||||
|
wlr_scene_node_set_enabled(&c->scene->node, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (c->scene_surface && !c->is_clip_to_hide && !c->is_logic_hide)
|
||||||
|
wlr_scene_node_set_enabled(&c->scene_surface->node, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool client_fx_begin(Client *c, struct effect_shader *shader,
|
||||||
|
struct wlr_scene_node *flatten_root,
|
||||||
|
struct wlr_scene_tree *parent, int width,
|
||||||
|
int height, bool include_rects) {
|
||||||
|
struct wlr_buffer *flat =
|
||||||
|
effect_pass_flatten(flatten_root, width, height, include_rects);
|
||||||
|
if (!flat) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: fx setup failed (effect_pass_flatten), "
|
||||||
|
"falling back to parametric animation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct wlr_texture *src = wlr_texture_from_buffer(drw, flat);
|
||||||
|
if (src && !effect_pass_texture_usable(src)) {
|
||||||
|
wlr_texture_destroy(src);
|
||||||
|
wlr_buffer_drop(flat);
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: snapshot texture is not shader-compatible "
|
||||||
|
"(external-only import), falling back to parametric animation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct wlr_swapchain *swap =
|
||||||
|
src ? effect_pass_create_swapchain(width, height) : NULL;
|
||||||
|
struct wlr_scene_buffer *scene_buffer =
|
||||||
|
swap ? wlr_scene_buffer_create(parent, NULL) : NULL;
|
||||||
|
wlr_buffer_drop(flat);
|
||||||
|
if (!scene_buffer) {
|
||||||
|
if (src)
|
||||||
|
wlr_texture_destroy(src);
|
||||||
|
if (swap)
|
||||||
|
wlr_swapchain_destroy(swap);
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: fx setup failed (%s), falling back to "
|
||||||
|
"parametric animation",
|
||||||
|
!src ? "wlr_texture_from_buffer"
|
||||||
|
: !swap ? "effect_pass_create_swapchain"
|
||||||
|
: "wlr_scene_buffer_create");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
c->fx.active = true;
|
||||||
|
c->fx.shader = shader;
|
||||||
|
c->fx.src = src;
|
||||||
|
c->fx.swap = swap;
|
||||||
|
c->fx.scene_buffer = scene_buffer;
|
||||||
|
c->fx.width = width;
|
||||||
|
c->fx.height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_fx_tick(Client *c, float progress, int32_t passed_time) {
|
||||||
|
if (!c->fx.active || !c->fx.swap)
|
||||||
|
return;
|
||||||
|
struct wlr_buffer *dst = wlr_swapchain_acquire(c->fx.swap);
|
||||||
|
if (!dst)
|
||||||
|
return;
|
||||||
|
struct effect_uniforms u = {
|
||||||
|
.progress = progress,
|
||||||
|
.time = (float)passed_time / 1000.0f,
|
||||||
|
};
|
||||||
|
bool ok = effect_pass_run(c->fx.shader, c->fx.src, dst, u);
|
||||||
|
if (ok)
|
||||||
|
wlr_scene_buffer_set_buffer(c->fx.scene_buffer, dst);
|
||||||
|
wlr_buffer_unlock(dst);
|
||||||
|
if (!ok) {
|
||||||
|
wlr_log(WLR_ERROR, "effect_pass: shader pass failed, falling back to "
|
||||||
|
"parametric animation");
|
||||||
|
client_fx_settle(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void client_animation_next_tick(Client *c) {
|
void client_animation_next_tick(Client *c) {
|
||||||
struct timespec now;
|
struct timespec now;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
|
@ -1272,9 +1374,37 @@ void client_animation_next_tick(Client *c) {
|
||||||
|
|
||||||
c->is_pending_open_animation = false;
|
c->is_pending_open_animation = false;
|
||||||
|
|
||||||
|
if (c->animation.action == OPEN && c->animation.running &&
|
||||||
|
!c->fx.open_setup_done && !c->fx.active && c->scene &&
|
||||||
|
c->scene->node.enabled && c->scene_surface) {
|
||||||
|
struct wlr_box fx_sgeom;
|
||||||
|
client_get_geometry(c, &fx_sgeom);
|
||||||
|
bool fx_size_ready = fx_sgeom.width >= c->geom.width - 2 * (int)c->bw &&
|
||||||
|
fx_sgeom.height >= c->geom.height - 2 * (int)c->bw;
|
||||||
|
if (fx_size_ready || animation_passed > 0.33)
|
||||||
|
c->fx.open_setup_done = true;
|
||||||
|
struct effect_shader *fx_shader =
|
||||||
|
fx_size_ready ? pick_effect(c->effect_open, config.effect_open)
|
||||||
|
: NULL;
|
||||||
|
if (fx_shader) {
|
||||||
|
struct wlr_box fx_clip;
|
||||||
|
client_get_clip(c, &fx_clip);
|
||||||
|
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node,
|
||||||
|
&fx_clip);
|
||||||
|
if (client_fx_begin(c, fx_shader, &c->scene->node, c->scene,
|
||||||
|
c->geom.width, c->geom.height, false)) {
|
||||||
|
wlr_scene_node_set_enabled(&c->scene_surface->node, false);
|
||||||
|
wlr_log(WLR_DEBUG,
|
||||||
|
"effect_pass: open-fade shader active for client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client_apply_clip(c, factor);
|
client_apply_clip(c, factor);
|
||||||
|
client_fx_tick(c, 1.0 - MANGO_MIN(animation_passed, 1.0), passed_time);
|
||||||
|
|
||||||
if (animation_passed >= 1.0) {
|
if (animation_passed >= 1.0) {
|
||||||
|
client_fx_settle(c);
|
||||||
|
|
||||||
// clear the open action state
|
// clear the open action state
|
||||||
// To prevent him from being mistaken that
|
// To prevent him from being mistaken that
|
||||||
|
|
@ -1308,10 +1438,9 @@ void client_animation_next_tick(Client *c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_fadeout_client(Client *c) {
|
void init_fadeout_client(Client *c) {
|
||||||
|
client_fx_settle(c);
|
||||||
if (!c->mon || client_is_unmanaged(c))
|
if (!c->mon || client_is_unmanaged(c))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!c->scene) {
|
if (!c->scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1344,6 +1473,19 @@ void init_fadeout_client(Client *c) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct effect_shader *fx_shader =
|
||||||
|
pick_effect(c->effect_close, config.effect_close);
|
||||||
|
fadeout_client->fx.fadeout = true;
|
||||||
|
if (fx_shader &&
|
||||||
|
client_fx_begin(fadeout_client, fx_shader, &fadeout_client->scene->node,
|
||||||
|
layers[LyrFadeOut], c->geom.width, c->geom.height,
|
||||||
|
true)) {
|
||||||
|
wlr_scene_node_set_enabled(&fadeout_client->scene->node, false);
|
||||||
|
wlr_scene_node_set_position(&fadeout_client->fx.scene_buffer->node,
|
||||||
|
c->geom.x, c->geom.y);
|
||||||
|
wlr_log(WLR_DEBUG, "effect_pass: close-fade shader active for client");
|
||||||
|
}
|
||||||
|
|
||||||
fadeout_client->animation.duration = config.animation_duration_close;
|
fadeout_client->animation.duration = config.animation_duration_close;
|
||||||
fadeout_client->geom = fadeout_client->current =
|
fadeout_client->geom = fadeout_client->current =
|
||||||
fadeout_client->animainit_geom = fadeout_client->animation.initial =
|
fadeout_client->animainit_geom = fadeout_client->animation.initial =
|
||||||
|
|
@ -1394,7 +1536,8 @@ void init_fadeout_client(Client *c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fadeout_client->animation.time_started = get_now_in_ms();
|
fadeout_client->animation.time_started = get_now_in_ms();
|
||||||
wlr_scene_node_set_enabled(&fadeout_client->scene->node, true);
|
if (!fadeout_client->fx.active)
|
||||||
|
wlr_scene_node_set_enabled(&fadeout_client->scene->node, true);
|
||||||
wl_list_insert(&fadeout_clients, &fadeout_client->fadeout_link);
|
wl_list_insert(&fadeout_clients, &fadeout_client->fadeout_link);
|
||||||
|
|
||||||
// 请求刷新屏幕
|
// 请求刷新屏幕
|
||||||
|
|
@ -1478,6 +1621,10 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
|
||||||
if (!c->mon)
|
if (!c->mon)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (c->fx.active &&
|
||||||
|
(geo.width != c->fx.width || geo.height != c->fx.height))
|
||||||
|
client_fx_settle(c);
|
||||||
|
|
||||||
c->need_output_flush = true;
|
c->need_output_flush = true;
|
||||||
c->dirty = true;
|
c->dirty = true;
|
||||||
|
|
||||||
|
|
@ -1557,6 +1704,8 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c == grabc) {
|
if (c == grabc) {
|
||||||
|
client_fx_settle(c);
|
||||||
|
|
||||||
c->animation.running = false;
|
c->animation.running = false;
|
||||||
c->need_output_flush = false;
|
c->need_output_flush = false;
|
||||||
|
|
||||||
|
|
@ -1771,6 +1920,9 @@ bool client_draw_frame(Client *c) {
|
||||||
if (!c || !client_surface(c)->mapped)
|
if (!c || !client_surface(c)->mapped)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (c->fx.active && !c->animation.running)
|
||||||
|
client_fx_settle(c);
|
||||||
|
|
||||||
if (!c->need_output_flush) {
|
if (!c->need_output_flush) {
|
||||||
return client_apply_focus_opacity(c);
|
return client_apply_focus_opacity(c);
|
||||||
}
|
}
|
||||||
|
|
@ -1778,6 +1930,7 @@ bool client_draw_frame(Client *c) {
|
||||||
if (config.animations && c->animation.running) {
|
if (config.animations && c->animation.running) {
|
||||||
client_animation_next_tick(c);
|
client_animation_next_tick(c);
|
||||||
} else {
|
} else {
|
||||||
|
client_fx_settle(c);
|
||||||
wlr_scene_node_set_position(&c->scene->node, c->pending.x,
|
wlr_scene_node_set_position(&c->scene->node, c->pending.x,
|
||||||
c->pending.y);
|
c->pending.y);
|
||||||
c->animation.current = c->animainit_geom = c->animation.initial =
|
c->animation.current = c->animainit_geom = c->animation.initial =
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) {
|
||||||
c->is_clip_to_hide = false;
|
c->is_clip_to_hide = false;
|
||||||
c->is_logic_hide = false;
|
c->is_logic_hide = false;
|
||||||
wlr_scene_node_set_enabled(&c->scene->node, true);
|
wlr_scene_node_set_enabled(&c->scene->node, true);
|
||||||
wlr_scene_node_set_enabled(&c->scene_surface->node, true);
|
client_surface_set_enabled(c, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->animation.tag_from_rule && want_animation &&
|
if (!c->animation.tag_from_rule && want_animation &&
|
||||||
|
|
@ -118,6 +118,7 @@ void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) {
|
||||||
c->animation.tagining = false;
|
c->animation.tagining = false;
|
||||||
set_tagout_animation(m, c);
|
set_tagout_animation(m, c);
|
||||||
} else {
|
} else {
|
||||||
|
client_fx_settle(c);
|
||||||
c->animation.running = false;
|
c->animation.running = false;
|
||||||
wlr_scene_node_set_enabled(&c->scene->node, false);
|
wlr_scene_node_set_enabled(&c->scene->node, false);
|
||||||
c->animainit_geom = c->current = c->pending = c->animation.current =
|
c->animainit_geom = c->current = c->pending = c->animation.current =
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ typedef struct {
|
||||||
float scroller_proportion;
|
float scroller_proportion;
|
||||||
const char *animation_type_open;
|
const char *animation_type_open;
|
||||||
const char *animation_type_close;
|
const char *animation_type_close;
|
||||||
|
const char *effect_open;
|
||||||
|
const char *effect_close;
|
||||||
const char *layer_animation_type_open;
|
const char *layer_animation_type_open;
|
||||||
const char *layer_animation_type_close;
|
const char *layer_animation_type_close;
|
||||||
int32_t isnoborder;
|
int32_t isnoborder;
|
||||||
|
|
@ -201,6 +203,8 @@ typedef struct {
|
||||||
char animation_type_close[10];
|
char animation_type_close[10];
|
||||||
char layer_animation_type_open[10];
|
char layer_animation_type_open[10];
|
||||||
char layer_animation_type_close[10];
|
char layer_animation_type_close[10];
|
||||||
|
char effect_open[64];
|
||||||
|
char effect_close[64];
|
||||||
int32_t animation_fade_in;
|
int32_t animation_fade_in;
|
||||||
int32_t animation_fade_out;
|
int32_t animation_fade_out;
|
||||||
int32_t tag_animation_direction;
|
int32_t tag_animation_direction;
|
||||||
|
|
@ -1362,6 +1366,12 @@ bool parse_option(Config *config, char *key, char *value) {
|
||||||
snprintf(config->animation_type_close,
|
snprintf(config->animation_type_close,
|
||||||
sizeof(config->animation_type_close), "%.9s",
|
sizeof(config->animation_type_close), "%.9s",
|
||||||
value); // string limit to 9 char
|
value); // string limit to 9 char
|
||||||
|
} else if (strcmp(key, "effect_open") == 0) {
|
||||||
|
snprintf(config->effect_open, sizeof(config->effect_open), "%.63s",
|
||||||
|
value);
|
||||||
|
} else if (strcmp(key, "effect_close") == 0) {
|
||||||
|
snprintf(config->effect_close, sizeof(config->effect_close), "%.63s",
|
||||||
|
value);
|
||||||
} else if (strcmp(key, "layer_animation_type_open") == 0) {
|
} else if (strcmp(key, "layer_animation_type_open") == 0) {
|
||||||
snprintf(config->layer_animation_type_open,
|
snprintf(config->layer_animation_type_open,
|
||||||
sizeof(config->layer_animation_type_open), "%.9s",
|
sizeof(config->layer_animation_type_open), "%.9s",
|
||||||
|
|
@ -2411,6 +2421,8 @@ bool parse_option(Config *config, char *key, char *value) {
|
||||||
// string rule value, relay to a client property
|
// string rule value, relay to a client property
|
||||||
rule->animation_type_open = NULL;
|
rule->animation_type_open = NULL;
|
||||||
rule->animation_type_close = NULL;
|
rule->animation_type_close = NULL;
|
||||||
|
rule->effect_open = NULL;
|
||||||
|
rule->effect_close = NULL;
|
||||||
|
|
||||||
// float rule value, relay to a client property
|
// float rule value, relay to a client property
|
||||||
rule->focused_opacity = 0;
|
rule->focused_opacity = 0;
|
||||||
|
|
@ -2452,6 +2464,10 @@ bool parse_option(Config *config, char *key, char *value) {
|
||||||
rule->animation_type_open = strdup(val);
|
rule->animation_type_open = strdup(val);
|
||||||
} else if (strcmp(key, "animation_type_close") == 0) {
|
} else if (strcmp(key, "animation_type_close") == 0) {
|
||||||
rule->animation_type_close = strdup(val);
|
rule->animation_type_close = strdup(val);
|
||||||
|
} else if (strcmp(key, "effect_open") == 0) {
|
||||||
|
rule->effect_open = strdup(val);
|
||||||
|
} else if (strcmp(key, "effect_close") == 0) {
|
||||||
|
rule->effect_close = strdup(val);
|
||||||
} else if (strcmp(key, "tags") == 0) {
|
} else if (strcmp(key, "tags") == 0) {
|
||||||
rule->tags = 1 << (atoi(val) - 1);
|
rule->tags = 1 << (atoi(val) - 1);
|
||||||
} else if (strcmp(key, "monitor") == 0) {
|
} else if (strcmp(key, "monitor") == 0) {
|
||||||
|
|
@ -3221,12 +3237,18 @@ void free_config(void) {
|
||||||
free((void *)rule->animation_type_open);
|
free((void *)rule->animation_type_open);
|
||||||
if (rule->animation_type_close)
|
if (rule->animation_type_close)
|
||||||
free((void *)rule->animation_type_close);
|
free((void *)rule->animation_type_close);
|
||||||
|
if (rule->effect_open)
|
||||||
|
free((void *)rule->effect_open);
|
||||||
|
if (rule->effect_close)
|
||||||
|
free((void *)rule->effect_close);
|
||||||
if (rule->monitor)
|
if (rule->monitor)
|
||||||
free((void *)rule->monitor);
|
free((void *)rule->monitor);
|
||||||
rule->id = NULL;
|
rule->id = NULL;
|
||||||
rule->title = NULL;
|
rule->title = NULL;
|
||||||
rule->animation_type_open = NULL;
|
rule->animation_type_open = NULL;
|
||||||
rule->animation_type_close = NULL;
|
rule->animation_type_close = NULL;
|
||||||
|
rule->effect_open = NULL;
|
||||||
|
rule->effect_close = NULL;
|
||||||
rule->monitor = NULL;
|
rule->monitor = NULL;
|
||||||
// 释放 globalkeybinding 的 arg.v(如果动态分配)
|
// 释放 globalkeybinding 的 arg.v(如果动态分配)
|
||||||
if (rule->globalkeybinding.arg.v) {
|
if (rule->globalkeybinding.arg.v) {
|
||||||
|
|
|
||||||
562
src/draw/effect_pass.c
Normal file
562
src/draw/effect_pass.c
Normal file
|
|
@ -0,0 +1,562 @@
|
||||||
|
#include "effect_pass.h"
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
#include <GLES2/gl2.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <drm_fourcc.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <scenefx/render/fx_renderer/fx_renderer.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <wlr/render/drm_format_set.h>
|
||||||
|
#include <wlr/render/egl.h>
|
||||||
|
#include <wlr/render/pass.h>
|
||||||
|
#include <wlr/render/wlr_texture.h>
|
||||||
|
#include <wlr/types/wlr_buffer.h>
|
||||||
|
#include <wlr/util/log.h>
|
||||||
|
|
||||||
|
bool wlr_texture_is_fx(struct wlr_texture *texture);
|
||||||
|
bool wlr_renderer_is_fx(struct wlr_renderer *renderer);
|
||||||
|
|
||||||
|
static struct wlr_buffer *effect_alloc_render_buffer(int w, int h);
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
bool ready;
|
||||||
|
struct wlr_renderer *renderer;
|
||||||
|
struct wlr_allocator *alloc;
|
||||||
|
EGLDisplay egl_display;
|
||||||
|
EGLContext egl_context;
|
||||||
|
} state;
|
||||||
|
|
||||||
|
bool effect_pass_init(struct wlr_renderer *renderer,
|
||||||
|
struct wlr_allocator *alloc) {
|
||||||
|
if (!wlr_renderer_is_fx(renderer)) {
|
||||||
|
wlr_log(WLR_ERROR, "effect_pass: renderer is not a SceneFX renderer, "
|
||||||
|
"shaders disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.renderer = renderer;
|
||||||
|
state.alloc = alloc;
|
||||||
|
state.egl_display = EGL_NO_DISPLAY;
|
||||||
|
state.egl_context = EGL_NO_CONTEXT;
|
||||||
|
state.ready = true;
|
||||||
|
|
||||||
|
struct wlr_buffer *probe = effect_alloc_render_buffer(1, 1);
|
||||||
|
if (probe) {
|
||||||
|
struct wlr_render_pass *pass =
|
||||||
|
wlr_renderer_begin_buffer_pass(renderer, probe, NULL);
|
||||||
|
if (pass) {
|
||||||
|
state.egl_display = eglGetCurrentDisplay();
|
||||||
|
state.egl_context = eglGetCurrentContext();
|
||||||
|
wlr_render_pass_submit(pass);
|
||||||
|
}
|
||||||
|
wlr_buffer_drop(probe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.egl_display == EGL_NO_DISPLAY) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: could not acquire EGL context, shaders disabled");
|
||||||
|
state.ready = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct effect_shader {
|
||||||
|
GLuint program;
|
||||||
|
GLint loc_tex, loc_progress, loc_time, loc_size, loc_pos, loc_uv;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *VERT_SRC =
|
||||||
|
"#version 300 es\n"
|
||||||
|
"in vec2 a_pos;\n"
|
||||||
|
"in vec2 a_uv;\n"
|
||||||
|
"out vec2 v_texcoord;\n"
|
||||||
|
"void main() { v_texcoord = a_uv; gl_Position = vec4(a_pos, 0.0, 1.0); }\n";
|
||||||
|
|
||||||
|
static const char *INJECT_HEADER = "#version 300 es\n"
|
||||||
|
"precision highp float;\n"
|
||||||
|
"uniform sampler2D u_texture;\n"
|
||||||
|
"uniform float u_progress;\n"
|
||||||
|
"uniform float u_time;\n"
|
||||||
|
"uniform vec2 u_size;\n"
|
||||||
|
"in vec2 v_texcoord;\n"
|
||||||
|
"out vec4 fragColor;\n";
|
||||||
|
|
||||||
|
static GLuint effect_compile_shader(GLenum type, const char *src1,
|
||||||
|
const char *src2) {
|
||||||
|
GLuint s = glCreateShader(type);
|
||||||
|
const char *srcs[2] = {src1, src2 ? src2 : ""};
|
||||||
|
glShaderSource(s, src2 ? 2 : 1, srcs, NULL);
|
||||||
|
glCompileShader(s);
|
||||||
|
GLint ok = 0;
|
||||||
|
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
char log[1024];
|
||||||
|
glGetShaderInfoLog(s, sizeof(log), NULL, log);
|
||||||
|
wlr_log(WLR_ERROR, "effect_pass: shader compile failed: %s", log);
|
||||||
|
glDeleteShader(s);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct effect_shader *make_shader(const char *frag_body) {
|
||||||
|
GLuint vs = effect_compile_shader(GL_VERTEX_SHADER, VERT_SRC, NULL);
|
||||||
|
GLuint fs =
|
||||||
|
effect_compile_shader(GL_FRAGMENT_SHADER, INJECT_HEADER, frag_body);
|
||||||
|
if (!vs || !fs) {
|
||||||
|
if (vs)
|
||||||
|
glDeleteShader(vs);
|
||||||
|
if (fs)
|
||||||
|
glDeleteShader(fs);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
GLuint prog = glCreateProgram();
|
||||||
|
glAttachShader(prog, vs);
|
||||||
|
glAttachShader(prog, fs);
|
||||||
|
glLinkProgram(prog);
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
GLint ok = 0;
|
||||||
|
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
glDeleteProgram(prog);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
struct effect_shader *sh = calloc(1, sizeof(*sh));
|
||||||
|
sh->program = prog;
|
||||||
|
sh->loc_tex = glGetUniformLocation(prog, "u_texture");
|
||||||
|
sh->loc_progress = glGetUniformLocation(prog, "u_progress");
|
||||||
|
sh->loc_time = glGetUniformLocation(prog, "u_time");
|
||||||
|
sh->loc_size = glGetUniformLocation(prog, "u_size");
|
||||||
|
sh->loc_pos = glGetAttribLocation(prog, "a_pos");
|
||||||
|
sh->loc_uv = glGetAttribLocation(prog, "a_uv");
|
||||||
|
return sh;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct registry_entry {
|
||||||
|
char name[64];
|
||||||
|
struct effect_shader *shader;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct registry_entry *registry;
|
||||||
|
static size_t registry_count;
|
||||||
|
static size_t registry_cap;
|
||||||
|
|
||||||
|
static void registry_put(const char *name, struct effect_shader *shader) {
|
||||||
|
for (size_t i = 0; i < registry_count; i++) {
|
||||||
|
if (strcmp(registry[i].name, name) == 0) {
|
||||||
|
wlr_log(WLR_INFO,
|
||||||
|
"effect_pass: shader '%s' redefined, replacing previous",
|
||||||
|
name);
|
||||||
|
glDeleteProgram(registry[i].shader->program);
|
||||||
|
free(registry[i].shader);
|
||||||
|
registry[i].shader = shader;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (registry_count == registry_cap) {
|
||||||
|
size_t new_cap = registry_cap ? registry_cap * 2 : 8;
|
||||||
|
struct registry_entry *grown =
|
||||||
|
realloc(registry, new_cap * sizeof(*grown));
|
||||||
|
if (!grown) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: out of memory growing shader registry, "
|
||||||
|
"dropping '%s'",
|
||||||
|
name);
|
||||||
|
glDeleteProgram(shader->program);
|
||||||
|
free(shader);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registry = grown;
|
||||||
|
registry_cap = new_cap;
|
||||||
|
}
|
||||||
|
snprintf(registry[registry_count].name,
|
||||||
|
sizeof(registry[registry_count].name), "%s", name);
|
||||||
|
registry[registry_count].shader = shader;
|
||||||
|
registry_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void effect_pass_load_dir(const char *dir) {
|
||||||
|
if (!state.ready || !dir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DIR *d = opendir(dir);
|
||||||
|
if (!d) {
|
||||||
|
wlr_log(
|
||||||
|
WLR_INFO,
|
||||||
|
"effect_pass: shader directory '%s' not available (%s), skipping",
|
||||||
|
dir, strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLContext prev_ctx = eglGetCurrentContext();
|
||||||
|
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
|
||||||
|
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
|
||||||
|
EGLDisplay prev_dpy = eglGetCurrentDisplay();
|
||||||
|
eglMakeCurrent(state.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||||
|
state.egl_context);
|
||||||
|
|
||||||
|
static const char ext[] = ".frag";
|
||||||
|
const size_t ext_len = sizeof(ext) - 1;
|
||||||
|
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(d)) != NULL) {
|
||||||
|
const char *fname = ent->d_name;
|
||||||
|
size_t len = strlen(fname);
|
||||||
|
if (len <= ext_len || strcmp(fname + len - ext_len, ext) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t name_len = len - ext_len;
|
||||||
|
if (name_len >= sizeof(registry[0].name)) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: shader filename '%s' too long, skipping",
|
||||||
|
fname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[PATH_MAX];
|
||||||
|
int n = snprintf(path, sizeof(path), "%s/%s", dir, fname);
|
||||||
|
if (n < 0 || (size_t)n >= sizeof(path)) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: shader path '%s/%s' too long, skipping", dir,
|
||||||
|
fname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: failed to open '%s' (%s), skipping", path,
|
||||||
|
strerror(errno));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fseek(f, 0, SEEK_END) != 0) {
|
||||||
|
wlr_log(WLR_ERROR, "effect_pass: failed to read '%s', skipping",
|
||||||
|
path);
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long size = ftell(f);
|
||||||
|
if (size < 0 || fseek(f, 0, SEEK_SET) != 0) {
|
||||||
|
wlr_log(WLR_ERROR, "effect_pass: failed to read '%s', skipping",
|
||||||
|
path);
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char *buf = malloc((size_t)size + 1);
|
||||||
|
if (!buf) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: out of memory reading '%s', skipping", path);
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t got = fread(buf, 1, (size_t)size, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[got] = '\0';
|
||||||
|
|
||||||
|
char name[sizeof(registry[0].name)];
|
||||||
|
snprintf(name, sizeof(name), "%.*s", (int)name_len, fname);
|
||||||
|
|
||||||
|
struct effect_shader *sh = make_shader(buf);
|
||||||
|
free(buf);
|
||||||
|
if (!sh) {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: shader '%s' failed to compile, skipping",
|
||||||
|
name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
registry_put(name, sh);
|
||||||
|
wlr_log(WLR_INFO, "effect_pass: registered shader '%s'", name);
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
wlr_log(WLR_INFO, "effect_pass: %zu shader(s) registered from '%s'",
|
||||||
|
registry_count, dir);
|
||||||
|
|
||||||
|
eglMakeCurrent(prev_dpy == EGL_NO_DISPLAY ? state.egl_display : prev_dpy,
|
||||||
|
prev_draw, prev_read, prev_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct effect_shader *effect_pass_get(const char *name) {
|
||||||
|
if (!name) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < registry_count; i++) {
|
||||||
|
if (strcmp(registry[i].name, name) == 0) {
|
||||||
|
return registry[i].shader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool effect_pass_texture_usable(struct wlr_texture *src) {
|
||||||
|
if (!src || !wlr_texture_is_fx(src))
|
||||||
|
return false;
|
||||||
|
struct fx_texture_attribs sa;
|
||||||
|
fx_texture_get_attribs(src, &sa);
|
||||||
|
return sa.target == GL_TEXTURE_2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool effect_pass_run(struct effect_shader *shader, struct wlr_texture *src,
|
||||||
|
struct wlr_buffer *dst, struct effect_uniforms u) {
|
||||||
|
if (!state.ready || !shader || !src || !dst)
|
||||||
|
return false;
|
||||||
|
struct fx_texture_attribs sa;
|
||||||
|
if (!effect_pass_texture_usable(src))
|
||||||
|
return false;
|
||||||
|
fx_texture_get_attribs(src, &sa);
|
||||||
|
GLuint fbo = fx_renderer_get_buffer_fbo(state.renderer, dst);
|
||||||
|
if (!fbo)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EGLContext prev_ctx = eglGetCurrentContext();
|
||||||
|
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
|
||||||
|
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
|
||||||
|
EGLDisplay prev_dpy = eglGetCurrentDisplay();
|
||||||
|
eglMakeCurrent(state.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||||
|
state.egl_context);
|
||||||
|
|
||||||
|
while (glGetError() != GL_NO_ERROR)
|
||||||
|
;
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glViewport(0, 0, dst->width, dst->height);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
glDisable(GL_STENCIL_TEST);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
glUseProgram(shader->program);
|
||||||
|
|
||||||
|
static const GLfloat pos[] = {-1, -1, 1, -1, -1, 1, 1, 1};
|
||||||
|
static const GLfloat uv[] = {0, 0, 1, 0, 0, 1, 1, 1};
|
||||||
|
if (shader->loc_pos >= 0) {
|
||||||
|
glVertexAttribPointer(shader->loc_pos, 2, GL_FLOAT, GL_FALSE, 0, pos);
|
||||||
|
glEnableVertexAttribArray(shader->loc_pos);
|
||||||
|
}
|
||||||
|
if (shader->loc_uv >= 0) {
|
||||||
|
glVertexAttribPointer(shader->loc_uv, 2, GL_FLOAT, GL_FALSE, 0, uv);
|
||||||
|
glEnableVertexAttribArray(shader->loc_uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(sa.target, sa.tex);
|
||||||
|
glTexParameteri(sa.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(sa.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glUniform1i(shader->loc_tex, 0);
|
||||||
|
glUniform1f(shader->loc_progress, u.progress);
|
||||||
|
glUniform1f(shader->loc_time, u.time);
|
||||||
|
glUniform2f(shader->loc_size, (float)dst->width, (float)dst->height);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
if (shader->loc_pos >= 0)
|
||||||
|
glDisableVertexAttribArray(shader->loc_pos);
|
||||||
|
if (shader->loc_uv >= 0)
|
||||||
|
glDisableVertexAttribArray(shader->loc_uv);
|
||||||
|
glBindTexture(sa.target, 0);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glUseProgram(0);
|
||||||
|
glFlush();
|
||||||
|
GLenum err = glGetError();
|
||||||
|
|
||||||
|
eglMakeCurrent(prev_dpy == EGL_NO_DISPLAY ? state.egl_display : prev_dpy,
|
||||||
|
prev_draw, prev_read, prev_ctx);
|
||||||
|
return err == GL_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wlr_drm_format *effect_render_format(void) {
|
||||||
|
const struct wlr_drm_format_set *texture_formats =
|
||||||
|
wlr_renderer_get_texture_formats(state.renderer, WLR_BUFFER_CAP_DMABUF);
|
||||||
|
if (!texture_formats) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return wlr_drm_format_set_get(texture_formats, DRM_FORMAT_ABGR8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct wlr_buffer *effect_alloc_render_buffer(int w, int h) {
|
||||||
|
const struct wlr_drm_format *fmt = effect_render_format();
|
||||||
|
if (!fmt) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return wlr_allocator_create_buffer(state.alloc, w, h, fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_swapchain *effect_pass_create_swapchain(int width, int height) {
|
||||||
|
if (!state.ready || width <= 0 || height <= 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
const struct wlr_drm_format *fmt = effect_render_format();
|
||||||
|
if (!fmt) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return wlr_swapchain_create(state.alloc, width, height, fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void effect_flatten_bounds(struct wlr_scene_node *node, int *min_x,
|
||||||
|
int *min_y, bool *found) {
|
||||||
|
if (!node->enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node->type == WLR_SCENE_NODE_BUFFER ||
|
||||||
|
node->type == WLR_SCENE_NODE_RECT) {
|
||||||
|
int ax = 0, ay = 0;
|
||||||
|
wlr_scene_node_coords(node, &ax, &ay);
|
||||||
|
if (!*found) {
|
||||||
|
*min_x = ax;
|
||||||
|
*min_y = ay;
|
||||||
|
*found = true;
|
||||||
|
} else {
|
||||||
|
if (ax < *min_x)
|
||||||
|
*min_x = ax;
|
||||||
|
if (ay < *min_y)
|
||||||
|
*min_y = ay;
|
||||||
|
}
|
||||||
|
} else if (node->type == WLR_SCENE_NODE_TREE) {
|
||||||
|
struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
|
||||||
|
struct wlr_scene_node *child;
|
||||||
|
wl_list_for_each(child, &tree->children, link) {
|
||||||
|
effect_flatten_bounds(child, min_x, min_y, found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void effect_flatten_paint(struct wlr_scene_node *node,
|
||||||
|
struct wlr_render_pass *pass, int ox, int oy,
|
||||||
|
bool include_rects) {
|
||||||
|
if (!node->enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int abs_x = 0, abs_y = 0;
|
||||||
|
wlr_scene_node_coords(node, &abs_x, &abs_y);
|
||||||
|
int nx = abs_x - ox, ny = abs_y - oy;
|
||||||
|
switch (node->type) {
|
||||||
|
case WLR_SCENE_NODE_BUFFER: {
|
||||||
|
struct wlr_scene_buffer *sb = wlr_scene_buffer_from_node(node);
|
||||||
|
if (sb->buffer) {
|
||||||
|
struct wlr_texture *t =
|
||||||
|
wlr_texture_from_buffer(state.renderer, sb->buffer);
|
||||||
|
if (t) {
|
||||||
|
int dw = sb->dst_width > 0 ? sb->dst_width : t->width;
|
||||||
|
int dh = sb->dst_height > 0 ? sb->dst_height : t->height;
|
||||||
|
wlr_render_pass_add_texture(
|
||||||
|
pass, &(struct wlr_render_texture_options){
|
||||||
|
.texture = t,
|
||||||
|
.dst_box =
|
||||||
|
{
|
||||||
|
.x = nx,
|
||||||
|
.y = ny,
|
||||||
|
.width = dw,
|
||||||
|
.height = dh,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wlr_texture_destroy(t);
|
||||||
|
} else {
|
||||||
|
wlr_log(WLR_ERROR,
|
||||||
|
"effect_pass: flatten failed to create texture from "
|
||||||
|
"buffer, snapshot will be incomplete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WLR_SCENE_NODE_RECT: {
|
||||||
|
if (!include_rects)
|
||||||
|
break;
|
||||||
|
struct wlr_scene_rect *r = wlr_scene_rect_from_node(node);
|
||||||
|
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
|
||||||
|
.box = {.x = nx,
|
||||||
|
.y = ny,
|
||||||
|
.width = r->width,
|
||||||
|
.height = r->height},
|
||||||
|
.color = {r->color[0], r->color[1],
|
||||||
|
r->color[2], r->color[3]},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WLR_SCENE_NODE_TREE: {
|
||||||
|
struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
|
||||||
|
struct wlr_scene_node *child;
|
||||||
|
wl_list_for_each(child, &tree->children, link) {
|
||||||
|
effect_flatten_paint(child, pass, ox, oy, include_rects);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WLR_SCENE_NODE_SHADOW:
|
||||||
|
case WLR_SCENE_NODE_OPTIMIZED_BLUR:
|
||||||
|
case WLR_SCENE_NODE_BLUR:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_buffer *effect_pass_flatten(struct wlr_scene_node *node, int width,
|
||||||
|
int height, bool include_rects) {
|
||||||
|
if (!state.ready || !node || width <= 0 || height <= 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_buffer *dst = effect_alloc_render_buffer(width, height);
|
||||||
|
if (!dst) {
|
||||||
|
wlr_log(WLR_ERROR, "effect_pass: failed to allocate flatten buffer");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_render_pass *pass =
|
||||||
|
wlr_renderer_begin_buffer_pass(state.renderer, dst, NULL);
|
||||||
|
if (!pass) {
|
||||||
|
wlr_buffer_drop(dst);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_render_pass_add_rect(
|
||||||
|
pass, &(struct wlr_render_rect_options){
|
||||||
|
.box = {.x = 0, .y = 0, .width = width, .height = height},
|
||||||
|
.color = {0, 0, 0, 0},
|
||||||
|
.blend_mode = WLR_RENDER_BLEND_MODE_NONE,
|
||||||
|
});
|
||||||
|
|
||||||
|
int ox = 0, oy = 0;
|
||||||
|
bool found = false;
|
||||||
|
effect_flatten_bounds(node, &ox, &oy, &found);
|
||||||
|
if (!found) {
|
||||||
|
wlr_scene_node_coords(node, &ox, &oy);
|
||||||
|
}
|
||||||
|
effect_flatten_paint(node, pass, ox, oy, include_rects);
|
||||||
|
|
||||||
|
if (!wlr_render_pass_submit(pass)) {
|
||||||
|
wlr_buffer_drop(dst);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
void effect_pass_finish(void) {
|
||||||
|
if (registry_count > 0) {
|
||||||
|
EGLContext prev_ctx = eglGetCurrentContext();
|
||||||
|
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
|
||||||
|
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
|
||||||
|
EGLDisplay prev_dpy = eglGetCurrentDisplay();
|
||||||
|
eglMakeCurrent(state.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||||
|
state.egl_context);
|
||||||
|
for (size_t i = 0; i < registry_count; i++) {
|
||||||
|
if (registry[i].shader) {
|
||||||
|
glDeleteProgram(registry[i].shader->program);
|
||||||
|
free(registry[i].shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eglMakeCurrent(prev_dpy == EGL_NO_DISPLAY ? state.egl_display
|
||||||
|
: prev_dpy,
|
||||||
|
prev_draw, prev_read, prev_ctx);
|
||||||
|
}
|
||||||
|
free(registry);
|
||||||
|
registry = NULL;
|
||||||
|
registry_count = 0;
|
||||||
|
registry_cap = 0;
|
||||||
|
|
||||||
|
state.ready = false;
|
||||||
|
}
|
||||||
29
src/draw/effect_pass.h
Normal file
29
src/draw/effect_pass.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef MANGO_EFFECT_PASS_H
|
||||||
|
#define MANGO_EFFECT_PASS_H
|
||||||
|
#include <scenefx/types/wlr_scene.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <wlr/render/allocator.h>
|
||||||
|
#include <wlr/render/swapchain.h>
|
||||||
|
#include <wlr/render/wlr_renderer.h>
|
||||||
|
#include <wlr/render/wlr_texture.h>
|
||||||
|
|
||||||
|
bool effect_pass_init(struct wlr_renderer *renderer,
|
||||||
|
struct wlr_allocator *alloc);
|
||||||
|
|
||||||
|
void effect_pass_finish(void);
|
||||||
|
|
||||||
|
struct effect_uniforms {
|
||||||
|
float progress;
|
||||||
|
float time;
|
||||||
|
};
|
||||||
|
struct effect_shader;
|
||||||
|
void effect_pass_load_dir(const char *dir);
|
||||||
|
struct effect_shader *effect_pass_get(const char *name);
|
||||||
|
bool effect_pass_run(struct effect_shader *shader, struct wlr_texture *src,
|
||||||
|
struct wlr_buffer *dst, struct effect_uniforms u);
|
||||||
|
bool effect_pass_texture_usable(struct wlr_texture *src);
|
||||||
|
struct wlr_buffer *effect_pass_flatten(struct wlr_scene_node *node, int width,
|
||||||
|
int height, bool include_rects);
|
||||||
|
struct wlr_swapchain *effect_pass_create_swapchain(int width, int height);
|
||||||
|
|
||||||
|
#endif
|
||||||
70
src/mango.c
70
src/mango.c
|
|
@ -99,7 +99,9 @@
|
||||||
#include <xcb/xcb_icccm.h>
|
#include <xcb/xcb_icccm.h>
|
||||||
#endif
|
#endif
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
#include "draw/effect_pass.h"
|
||||||
#include "draw/text-node.h"
|
#include "draw/text-node.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
/* macros */
|
/* macros */
|
||||||
#define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B))
|
#define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B))
|
||||||
|
|
@ -357,6 +359,16 @@ struct Client {
|
||||||
struct wl_list link;
|
struct wl_list link;
|
||||||
struct wl_list flink;
|
struct wl_list flink;
|
||||||
struct wl_list fadeout_link;
|
struct wl_list fadeout_link;
|
||||||
|
struct {
|
||||||
|
bool active;
|
||||||
|
bool open_setup_done;
|
||||||
|
bool fadeout;
|
||||||
|
struct effect_shader *shader;
|
||||||
|
struct wlr_scene_buffer *scene_buffer;
|
||||||
|
struct wlr_swapchain *swap;
|
||||||
|
struct wlr_texture *src;
|
||||||
|
int width, height;
|
||||||
|
} fx;
|
||||||
union {
|
union {
|
||||||
struct wlr_xdg_surface *xdg;
|
struct wlr_xdg_surface *xdg;
|
||||||
struct wlr_xwayland_surface *xwayland;
|
struct wlr_xwayland_surface *xwayland;
|
||||||
|
|
@ -406,6 +418,8 @@ struct Client {
|
||||||
|
|
||||||
const char *animation_type_open;
|
const char *animation_type_open;
|
||||||
const char *animation_type_close;
|
const char *animation_type_close;
|
||||||
|
char effect_open[64];
|
||||||
|
char effect_close[64];
|
||||||
int32_t is_in_scratchpad;
|
int32_t is_in_scratchpad;
|
||||||
int32_t iscustomsize;
|
int32_t iscustomsize;
|
||||||
int32_t iscustompos;
|
int32_t iscustompos;
|
||||||
|
|
@ -966,6 +980,36 @@ static bool mango_scene_output_commit(struct wlr_scene_output *scene_output,
|
||||||
static bool mango_output_commit(Monitor *m);
|
static bool mango_output_commit(Monitor *m);
|
||||||
static bool check_tearing_frame_allow(Monitor *m);
|
static bool check_tearing_frame_allow(Monitor *m);
|
||||||
static void client_set_group_config(Client *c);
|
static void client_set_group_config(Client *c);
|
||||||
|
static void client_fx_settle(Client *c);
|
||||||
|
static void client_surface_set_enabled(Client *c, bool enabled);
|
||||||
|
static bool client_fx_begin(Client *c, struct effect_shader *shader,
|
||||||
|
struct wlr_scene_node *flatten_root,
|
||||||
|
struct wlr_scene_tree *parent, int width,
|
||||||
|
int height, bool include_rects);
|
||||||
|
static void client_fx_tick(Client *c, float progress, int32_t passed_time);
|
||||||
|
static struct effect_shader *pick_effect(const char *per_client,
|
||||||
|
const char *global) {
|
||||||
|
const char *name = (per_client && per_client[0]) ? per_client
|
||||||
|
: (global && global[0]) ? global
|
||||||
|
: NULL;
|
||||||
|
struct effect_shader *sh = name ? effect_pass_get(name) : NULL;
|
||||||
|
if (name && !sh)
|
||||||
|
wlr_log(WLR_DEBUG,
|
||||||
|
"effect_pass: shader '%s' not found, using "
|
||||||
|
"parametric animation",
|
||||||
|
name);
|
||||||
|
return sh;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_user_shader_dir(void) {
|
||||||
|
const char *home = getenv("HOME");
|
||||||
|
if (!home) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char shader_dir[512];
|
||||||
|
snprintf(shader_dir, sizeof(shader_dir), "%s/.config/mango/shaders", home);
|
||||||
|
effect_pass_load_dir(shader_dir);
|
||||||
|
}
|
||||||
|
|
||||||
#include "data/static_keymap.h"
|
#include "data/static_keymap.h"
|
||||||
#include "dispatch/bind_declare.h"
|
#include "dispatch/bind_declare.h"
|
||||||
|
|
@ -1346,6 +1390,8 @@ void client_replace(Client *c, Client *w, bool is_group_change_member,
|
||||||
|
|
||||||
wl_list_safe_reinsert_next(&w->link, &c->link);
|
wl_list_safe_reinsert_next(&w->link, &c->link);
|
||||||
wl_list_safe_reinsert_prev(&w->flink, &c->flink);
|
wl_list_safe_reinsert_prev(&w->flink, &c->flink);
|
||||||
|
client_fx_settle(w);
|
||||||
|
client_fx_settle(c);
|
||||||
wlr_scene_node_set_enabled(&w->scene->node, false);
|
wlr_scene_node_set_enabled(&w->scene->node, false);
|
||||||
|
|
||||||
if (!c->is_logic_hide) {
|
if (!c->is_logic_hide) {
|
||||||
|
|
@ -1490,6 +1536,8 @@ void gpureset(struct wl_listener *listener, void *data) {
|
||||||
struct wlr_renderer *old_drw = drw;
|
struct wlr_renderer *old_drw = drw;
|
||||||
struct wlr_allocator *old_alloc = alloc;
|
struct wlr_allocator *old_alloc = alloc;
|
||||||
struct Monitor *m = NULL;
|
struct Monitor *m = NULL;
|
||||||
|
Client *fxc = NULL, *fxtmp = NULL;
|
||||||
|
Client *oc = NULL, *octmp = NULL;
|
||||||
|
|
||||||
wlr_log(WLR_DEBUG, "gpu reset");
|
wlr_log(WLR_DEBUG, "gpu reset");
|
||||||
|
|
||||||
|
|
@ -1499,6 +1547,15 @@ void gpureset(struct wl_listener *listener, void *data) {
|
||||||
if (!(alloc = wlr_allocator_autocreate(backend, drw)))
|
if (!(alloc = wlr_allocator_autocreate(backend, drw)))
|
||||||
die("couldn't recreate allocator");
|
die("couldn't recreate allocator");
|
||||||
|
|
||||||
|
wl_list_for_each_safe(fxc, fxtmp, &fadeout_clients, fadeout_link)
|
||||||
|
client_fx_settle(fxc);
|
||||||
|
|
||||||
|
wl_list_for_each_safe(oc, octmp, &clients, link) client_fx_settle(oc);
|
||||||
|
|
||||||
|
effect_pass_finish();
|
||||||
|
effect_pass_init(drw, alloc);
|
||||||
|
load_user_shader_dir();
|
||||||
|
|
||||||
wl_list_remove(&gpu_reset.link);
|
wl_list_remove(&gpu_reset.link);
|
||||||
wl_signal_add(&drw->events.lost, &gpu_reset);
|
wl_signal_add(&drw->events.lost, &gpu_reset);
|
||||||
|
|
||||||
|
|
@ -1630,6 +1687,11 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) {
|
||||||
|
|
||||||
APPLY_STRING_PROP(c, r, animation_type_open);
|
APPLY_STRING_PROP(c, r, animation_type_open);
|
||||||
APPLY_STRING_PROP(c, r, animation_type_close);
|
APPLY_STRING_PROP(c, r, animation_type_close);
|
||||||
|
if (r->effect_open)
|
||||||
|
snprintf(c->effect_open, sizeof(c->effect_open), "%s", r->effect_open);
|
||||||
|
if (r->effect_close)
|
||||||
|
snprintf(c->effect_close, sizeof(c->effect_close), "%s",
|
||||||
|
r->effect_close);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_float_malposition(Client *tc) {
|
void set_float_malposition(Client *tc) {
|
||||||
|
|
@ -2648,6 +2710,8 @@ void cleanup(void) {
|
||||||
|
|
||||||
mango_im_relay_finish(mango_input_method_relay);
|
mango_im_relay_finish(mango_input_method_relay);
|
||||||
|
|
||||||
|
effect_pass_finish();
|
||||||
|
|
||||||
/* If it's not destroyed manually it will cause a use-after-free of
|
/* If it's not destroyed manually it will cause a use-after-free of
|
||||||
* wlr_seat. Destroy it until it's fixed in the wlroots side */
|
* wlr_seat. Destroy it until it's fixed in the wlroots side */
|
||||||
wlr_backend_destroy(backend);
|
wlr_backend_destroy(backend);
|
||||||
|
|
@ -6232,6 +6296,9 @@ void setup(void) {
|
||||||
if (!(alloc = wlr_allocator_autocreate(backend, drw)))
|
if (!(alloc = wlr_allocator_autocreate(backend, drw)))
|
||||||
die("couldn't create allocator");
|
die("couldn't create allocator");
|
||||||
|
|
||||||
|
effect_pass_init(drw, alloc);
|
||||||
|
load_user_shader_dir();
|
||||||
|
|
||||||
/* This creates some hands-off wlroots interfaces. The compositor is
|
/* This creates some hands-off wlroots interfaces. The compositor is
|
||||||
* necessary for clients to allocate surfaces and the data device
|
* necessary for clients to allocate surfaces and the data device
|
||||||
* manager handles the clipboard. Each of these wlroots interfaces has
|
* manager handles the clipboard. Each of these wlroots interfaces has
|
||||||
|
|
@ -6546,6 +6613,8 @@ void overview_backup_surface(Client *c) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_fx_settle(c);
|
||||||
|
|
||||||
struct wlr_box geometry;
|
struct wlr_box geometry;
|
||||||
client_get_geometry(c, &geometry);
|
client_get_geometry(c, &geometry);
|
||||||
struct wlr_box clip_box = (struct wlr_box){
|
struct wlr_box clip_box = (struct wlr_box){
|
||||||
|
|
@ -6770,6 +6839,7 @@ void unmapnotify(struct wl_listener *listener, void *data) {
|
||||||
Client *c = wl_container_of(listener, c, unmap);
|
Client *c = wl_container_of(listener, c, unmap);
|
||||||
Monitor *m = NULL;
|
Monitor *m = NULL;
|
||||||
Client *nextfocus = NULL;
|
Client *nextfocus = NULL;
|
||||||
|
client_fx_settle(c);
|
||||||
c->iskilling = 1;
|
c->iskilling = 1;
|
||||||
struct ScrollerStackNode *target_node =
|
struct ScrollerStackNode *target_node =
|
||||||
c->mon ? find_scroller_node(
|
c->mon ? find_scroller_node(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue