├── .gitignore ├── LICENSE ├── README.md ├── meson.build ├── node.cpp ├── node.hpp ├── renderer.cpp ├── renderer.hpp ├── shaders.glsl.cpp ├── testconfig ├── bluelight.ini ├── circular.ini ├── decoborder.ini ├── gaussian.ini ├── glow.ini ├── minimal.ini ├── overscale.ini └── square.ini ├── winshadows.cpp └── winshadows.xml /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .cache 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Tim Göttlicher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # winshadows 2 | 3 | ### Window Shadows Plugin for Wayfire 4 | 5 | This is a plugin for wayfire that adds window shadows. The code was initially a 6 | fork of but only a small part of 7 | that remains in the code. 8 | 9 | ## Screenshots 10 | 11 | By default, the plugin will add fast and nice shadows around windows that use server side decorations. Additionally there is an option to make the focused window glow. 12 | 13 | ![image](https://github.com/timgott/wayfire-shadows/assets/18331942/94d27159-573c-4613-8bf6-4527502539f5) 14 | 15 |
16 | Config used in screenshot 17 | 18 | ```ini 19 | [decoration] 20 | active_color = \#7A98BA76 21 | border_size = 4 22 | inactive_color = \#20201838 23 | title_height = 0 24 | 25 | [winshadows] 26 | glow_enabled = true 27 | shadow_radius = 50 28 | vertical_offset = 10 29 | horizontal_offset = 5 30 | # remaining values are default 31 | 32 | ``` 33 |
34 | 35 | ## Install 36 | 37 | 1. Get the sources 38 | ```bash 39 | git clone https://github.com/timgott/wayfire-shadows.git 40 | cd wayfire-shadows 41 | ``` 42 | 2. ⚠️ Switch to the backport0.7 branch if you use wayfire version 0.7 (last stable release) 43 | ```bash 44 | git checkout backport0.7 # (if necessary) 45 | ``` 46 | 3. Configure with meson and build & install with ninja. 47 | ```bash 48 | meson build --buildtype=release 49 | cd build 50 | # meson configure --prefix=... # if wayfire is not installed in /usr/local 51 | ninja 52 | sudo ninja install 53 | ``` 54 | 55 | ## Install with [wfplug](https://github.com/timgott/wfplug) 56 | 57 | As above, you have to change the branch on wayfire 0.7. 58 | 59 | ```bash 60 | # enable wfplug 61 | source ~/wfplug/activate # edit path if necessary 62 | 63 | # get plugin 64 | wfplug-goto-plugins 65 | git clone https://github.com/timgott/wayfire-shadows.git winshadows 66 | 67 | # build and install to wfplug 68 | wfplug-build winshadows 69 | ``` 70 | 71 | Try a testconfig with 72 | 73 | ```bash 74 | wfplug-test winshadows bluelight 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'wayfire-plugin-winshadows', 3 | 'c', 4 | 'cpp', 5 | version: '0.1', 6 | license: 'MIT', 7 | meson_version: '>=0.51.0', 8 | default_options: [ 9 | 'cpp_std=c++17', 10 | 'c_std=c11', 11 | 'warning_level=2', 12 | 'werror=false', 13 | ], 14 | ) 15 | 16 | wayfire = dependency('wayfire') 17 | wfconfig = dependency('wf-config') 18 | 19 | add_project_arguments(['-DWLR_USE_UNSTABLE'], language: ['cpp', 'c']) 20 | add_project_arguments(['-DWAYFIRE_PLUGIN'], language: ['cpp', 'c']) 21 | add_project_link_arguments(['-rdynamic'], language:'cpp') 22 | 23 | shadows = shared_module( 24 | 'winshadows', [ 25 | 'winshadows.cpp', 26 | 'node.cpp', 27 | 'renderer.cpp', 28 | 'shaders.glsl.cpp', 29 | ], 30 | 31 | dependencies: [ 32 | wfconfig, 33 | wayfire 34 | ], 35 | 36 | install: true, 37 | install_dir: join_paths( get_option( 'libdir' ), 'wayfire' ) 38 | ) 39 | 40 | install_data( 'winshadows.xml', install_dir: wayfire.get_variable( pkgconfig: 'metadatadir' ) ) 41 | 42 | summary = [ 43 | '', 44 | '----------------', 45 | 'wayfire-plugin-winshadows @0@'.format( meson.project_version() ), 46 | '----------------', 47 | '' 48 | ] 49 | message('\n'.join(summary)) 50 | -------------------------------------------------------------------------------- /node.cpp: -------------------------------------------------------------------------------- 1 | #include "node.hpp" 2 | 3 | namespace winshadows { 4 | 5 | shadow_node_t::shadow_node_t( wayfire_toplevel_view view ): wf::scene::node_t(false) { 6 | this->view = view; 7 | on_geometry_changed.set_callback([this] (auto) { 8 | update_geometry(); 9 | }); 10 | on_activated_changed.set_callback([this] (auto) { 11 | this->view->damage(); 12 | }); 13 | view->connect(&on_geometry_changed); 14 | view->connect(&on_activated_changed); 15 | update_geometry(); 16 | } 17 | 18 | shadow_node_t::~shadow_node_t() { 19 | view->disconnect(&on_geometry_changed); 20 | } 21 | 22 | wf::geometry_t shadow_node_t::get_bounding_box() { 23 | return geometry; 24 | } 25 | 26 | void shadow_node_t::gen_render_instances(std::vector &instances, wf::scene::damage_callback push_damage, wf::output_t *output) { 27 | // define renderer 28 | class shadow_render_instance_t : public wf::scene::simple_render_instance_t { 29 | public: 30 | using simple_render_instance_t::simple_render_instance_t; 31 | void render(const wf::render_target_t& target, const wf::region_t& region) override 32 | { 33 | // coordinates relative to view origin (not bounding box origin) 34 | wf::point_t frame_origin = self->frame_offset; 35 | wf::region_t paint_region = self->shadow_region + frame_origin; 36 | paint_region &= region; 37 | 38 | for (const auto& box : paint_region) 39 | { 40 | self->shadow.render(target, frame_origin, wlr_box_from_pixman_box(box), self->view->activated); 41 | } 42 | self->_was_activated = self->view->activated; 43 | } 44 | }; 45 | 46 | instances.push_back(std::make_unique(this, push_damage, output)); 47 | } 48 | 49 | void shadow_node_t::update_geometry() { 50 | wf::geometry_t frame_geometry = view->get_geometry(); 51 | shadow.resize(frame_geometry.width, frame_geometry.height); 52 | 53 | // TODO: Check whether this can be done in a nicer/easier way 54 | wf::pointf_t view_origin_f = view->get_surface_root_node()->to_global({0, 0}); 55 | wf::point_t view_origin {(int)view_origin_f.x, (int)view_origin_f.y}; 56 | 57 | // Offset between view origin and frame top left corner 58 | frame_offset = wf::origin(frame_geometry) - view_origin; 59 | 60 | // Shadow geometry is relative to the top left corner of the frame (not the view) 61 | wf::geometry_t shadow_geometry = shadow.get_geometry(); 62 | 63 | // move to view-relative coordinates 64 | geometry = shadow_geometry + frame_offset; 65 | 66 | this->shadow_region = shadow.calculate_region(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /node.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "renderer.hpp" 9 | 10 | namespace winshadows { 11 | 12 | class shadow_node_t : public wf::scene::node_t { 13 | private: 14 | int _was_activated = 1; // used to check whether redrawing on focus is necessary 15 | 16 | // geometry of the node relative to the view origin 17 | wf::geometry_t geometry; 18 | 19 | // offset between the node origin and the frame origin (i.e. top-left borders) 20 | wf::point_t frame_offset; 21 | 22 | wayfire_toplevel_view view; 23 | 24 | int width = 100, height = 100; 25 | wf::region_t shadow_region; 26 | shadow_renderer_t shadow; 27 | 28 | wf::signal::connection_t on_geometry_changed; 29 | wf::signal::connection_t on_activated_changed; 30 | 31 | void update_geometry(); 32 | 33 | public: 34 | shadow_node_t(wayfire_toplevel_view view); 35 | 36 | virtual ~shadow_node_t(); 37 | 38 | void gen_render_instances(std::vector &instances, wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override; 39 | 40 | wf::geometry_t get_bounding_box() override; 41 | 42 | }; 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /renderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "renderer.hpp" 5 | 6 | namespace winshadows { 7 | 8 | shadow_renderer_t::shadow_renderer_t() { 9 | OpenGL::render_begin(); 10 | generate_dither_texture(); 11 | recompile_shaders(); 12 | OpenGL::render_end(); 13 | 14 | light_type_option.set_callback([this] () { 15 | recompile_shaders(); 16 | }); 17 | } 18 | 19 | void shadow_renderer_t::recompile_shaders() { 20 | OpenGL::render_begin(); 21 | shadow_program.free_resources(); 22 | shadow_glow_program.free_resources(); 23 | 24 | shadow_program.set_simple( 25 | OpenGL::compile_program(shadow_vert_shader, frag_shader(light_type_option, /*no glow*/ false)) 26 | ); 27 | shadow_glow_program.set_simple( 28 | OpenGL::compile_program(shadow_vert_shader, frag_shader(light_type_option, /*glow*/ true)) 29 | ); 30 | 31 | OpenGL::render_end(); 32 | } 33 | 34 | void shadow_renderer_t::generate_dither_texture() { 35 | const int size = 32; 36 | GLuint data[size*size]; 37 | 38 | std::mt19937_64 gen{std::random_device{}()}; 39 | std::uniform_int_distribution distrib; 40 | 41 | for (int i = 0; i < size*size; i++) { 42 | data[i] = distrib(gen); 43 | } 44 | 45 | GL_CALL(glGenTextures(1, &dither_texture)); 46 | GL_CALL(glBindTexture(GL_TEXTURE_2D, dither_texture)); 47 | GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)); 48 | GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); 49 | GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); 50 | GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)); 51 | GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)); 52 | } 53 | 54 | shadow_renderer_t::~shadow_renderer_t() { 55 | OpenGL::render_begin(); 56 | shadow_program.free_resources(); 57 | shadow_glow_program.free_resources(); 58 | 59 | GL_CALL(glDeleteTextures(1, &dither_texture)); 60 | 61 | OpenGL::render_end(); 62 | } 63 | 64 | void shadow_renderer_t::render(const wf::render_target_t& fb, wf::point_t window_origin, const wf::geometry_t& scissor, const bool glow) { 65 | float radius = shadow_radius_option; 66 | 67 | wf::color_t color = shadow_color_option; 68 | 69 | // Premultiply alpha for shader 70 | glm::vec4 premultiplied = { 71 | color.r * color.a, 72 | color.g * color.a, 73 | color.b * color.a, 74 | color.a 75 | }; 76 | 77 | // Glow color, alpha=0 => additive blending (exploiting premultiplied alpha) 78 | wf::color_t glow_color = glow_color_option; 79 | glm::vec4 glow_premultiplied = { 80 | glow_color.r * glow_color.a, 81 | glow_color.g * glow_color.a, 82 | glow_color.b * glow_color.a, 83 | glow_color.a * (1.0 - glow_emissivity_option) 84 | }; 85 | 86 | // Enable glow shader only when glow radius > 0 and view is focused 87 | bool use_glow = (glow && is_glow_enabled()); 88 | OpenGL::program_t &program = 89 | use_glow ? shadow_glow_program : shadow_program; 90 | 91 | OpenGL::render_begin(fb); 92 | fb.logic_scissor(scissor); 93 | 94 | program.use(wf::TEXTURE_TYPE_RGBA); 95 | 96 | // Compute vertex rectangle geometry 97 | wf::geometry_t bounds = outer_geometry + window_origin; 98 | float left = bounds.x; 99 | float right = bounds.x + bounds.width; 100 | float top = bounds.y; 101 | float bottom = bounds.y + bounds.height; 102 | 103 | GLfloat vertexData[] = { 104 | left, bottom, 105 | right, bottom, 106 | right, top, 107 | left, top 108 | }; 109 | 110 | glm::mat4 matrix = fb.get_orthographic_projection(); 111 | 112 | // vertex parameters 113 | program.attrib_pointer("position", 2, 0, vertexData); 114 | program.uniformMatrix4f("MVP", matrix); 115 | 116 | // fragment parameters 117 | program.uniform1f("radius", radius); 118 | program.uniform4f("color", premultiplied); 119 | 120 | const auto inner = window_geometry + window_origin; 121 | const auto shadow_inner = shadow_projection_geometry + window_origin; 122 | program.uniform2f("lower", shadow_inner.x, shadow_inner.y); 123 | program.uniform2f("upper", shadow_inner.x + shadow_inner.width, shadow_inner.y + shadow_inner.height); 124 | 125 | if (use_glow) { 126 | program.uniform2f("glow_lower", inner.x, inner.y); 127 | program.uniform2f("glow_upper", inner.x + inner.width, inner.y + inner.height); 128 | 129 | program.uniform1f("glow_spread", glow_spread_option); 130 | program.uniform4f("glow_color", glow_premultiplied); 131 | program.uniform1f("glow_intensity", glow_intensity_option); 132 | program.uniform1f("glow_threshold", glow_threshold_option); 133 | } 134 | 135 | // dither texture 136 | program.uniform1i("dither_texture", 0); 137 | GL_CALL(glActiveTexture(GL_TEXTURE0)); 138 | GL_CALL(glBindTexture(GL_TEXTURE_2D, dither_texture)); 139 | 140 | GL_CALL(glEnable(GL_BLEND)); 141 | GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); 142 | GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); 143 | 144 | program.deactivate(); 145 | OpenGL::render_end(); 146 | } 147 | 148 | wf::region_t shadow_renderer_t::calculate_region() const { 149 | // TODO: geometry and region depending on whether glow is active or not 150 | wf::region_t region = wf::region_t(shadow_geometry) | wf::region_t(glow_geometry); 151 | 152 | if (clip_shadow_inside) { 153 | region ^= window_geometry; 154 | } 155 | 156 | return region; 157 | } 158 | 159 | wf::geometry_t shadow_renderer_t::get_geometry() const { 160 | return outer_geometry; 161 | } 162 | 163 | wf::geometry_t expand_geometry(const wf::geometry_t& geometry, const int marginX, const int marginY) { 164 | return { 165 | geometry.x - marginX, 166 | geometry.y - marginY, 167 | geometry.width + marginX * 2, 168 | geometry.height + marginY * 2 169 | }; 170 | } 171 | 172 | wf::geometry_t expand_geometry(const wf::geometry_t& geometry, const int margin) { 173 | return expand_geometry(geometry, margin, margin); 174 | } 175 | 176 | wf::geometry_t inflate_geometry(const wf::geometry_t& geometry, const float inflation) { 177 | int expandX = geometry.width * inflation * 0.5; 178 | int expandY = geometry.height * inflation * 0.5; 179 | return expand_geometry(geometry, expandX, expandY); 180 | } 181 | 182 | void shadow_renderer_t::resize(const int window_width, const int window_height) { 183 | window_geometry = { 184 | 0, 185 | 0, 186 | window_width, 187 | window_height 188 | }; 189 | 190 | float overscale = overscale_option / 100.0; 191 | const wf::point_t offset { horizontal_offset, vertical_offset }; 192 | shadow_projection_geometry = 193 | inflate_geometry(window_geometry, overscale) + offset; 194 | 195 | shadow_geometry = expand_geometry(shadow_projection_geometry, shadow_radius_option); 196 | 197 | int glow_radius = is_glow_enabled() ? glow_radius_limit_option : 0; 198 | glow_geometry = expand_geometry(shadow_projection_geometry, glow_radius); 199 | 200 | int left = std::min(shadow_geometry.x, glow_geometry.x); 201 | int top = std::min(shadow_geometry.y, glow_geometry.y); 202 | int right = std::max(shadow_geometry.x + shadow_geometry.width, glow_geometry.x + glow_geometry.width); 203 | int bottom = std::max(shadow_geometry.y + shadow_geometry.height, glow_geometry.y + glow_geometry.height); 204 | outer_geometry = { 205 | left, 206 | top, 207 | right - left, 208 | bottom - top 209 | }; 210 | } 211 | 212 | bool shadow_renderer_t::is_glow_enabled() const { 213 | return glow_enabled_option && (glow_radius_limit_option > 0) && (glow_intensity_option > 0); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace winshadows { 7 | /** 8 | * A class that can render shadows. 9 | * It manages the shader and calculates the necessary padding. 10 | */ 11 | class shadow_renderer_t { 12 | public: 13 | shadow_renderer_t(); 14 | ~shadow_renderer_t(); 15 | 16 | void recompile_shaders(); 17 | void render(const wf::render_target_t& fb, wf::point_t origin, const wf::geometry_t& scissor, const bool glow); 18 | void resize(const int width, const int height); 19 | wf::region_t calculate_region() const; 20 | wf::geometry_t get_geometry() const; 21 | bool is_glow_enabled() const; 22 | 23 | private: 24 | OpenGL::program_t shadow_program; 25 | OpenGL::program_t shadow_glow_program; 26 | GLuint dither_texture; 27 | void generate_dither_texture(); 28 | 29 | wf::geometry_t glow_geometry; 30 | wf::geometry_t shadow_geometry; 31 | wf::geometry_t shadow_projection_geometry; // projected window geometry 32 | wf::geometry_t outer_geometry; 33 | wf::geometry_t window_geometry; 34 | wlr_box calculate_padding(const wf::geometry_t window_geometry) const; 35 | 36 | wf::option_wrapper_t shadow_color_option { "winshadows/shadow_color" }; 37 | wf::option_wrapper_t shadow_radius_option { "winshadows/shadow_radius" }; 38 | wf::option_wrapper_t clip_shadow_inside { "winshadows/clip_shadow_inside" }; 39 | wf::option_wrapper_t vertical_offset { "winshadows/vertical_offset" }; 40 | wf::option_wrapper_t horizontal_offset { "winshadows/horizontal_offset" }; 41 | wf::option_wrapper_t light_type_option { "winshadows/light_type" }; 42 | wf::option_wrapper_t overscale_option { "winshadows/overscale" }; 43 | 44 | wf::option_wrapper_t glow_enabled_option { "winshadows/glow_enabled" }; 45 | wf::option_wrapper_t glow_color_option { "winshadows/glow_color" }; 46 | wf::option_wrapper_t glow_emissivity_option { "winshadows/glow_emissivity" }; 47 | wf::option_wrapper_t glow_spread_option { "winshadows/glow_spread" }; 48 | wf::option_wrapper_t glow_intensity_option { "winshadows/glow_intensity" }; 49 | wf::option_wrapper_t glow_threshold_option { "winshadows/glow_threshold" }; 50 | wf::option_wrapper_t glow_radius_limit_option { "winshadows/glow_radius_limit" }; 51 | 52 | static const std::string shadow_vert_shader; 53 | const std::string frag_shader(const std::string &light_type, const bool glow); 54 | }; 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /shaders.glsl.cpp: -------------------------------------------------------------------------------- 1 | // GLSL as cpp string constant (.glsl extension for syntax highlighting) 2 | #include "renderer.hpp" 3 | 4 | 5 | /* Vertex shader */ 6 | 7 | const std::string winshadows::shadow_renderer_t::shadow_vert_shader = 8 | R"( 9 | #version 300 es 10 | 11 | in mediump vec2 position; 12 | out mediump vec2 uvpos; 13 | 14 | uniform mat4 MVP; 15 | 16 | void main() { 17 | gl_Position = MVP * vec4(position.xy, 0.0, 1.0); 18 | uvpos = position.xy; 19 | })"; 20 | 21 | 22 | 23 | /* Base fragment shader definitions */ 24 | 25 | const std::string flag_define(const std::string& name, const bool value) { 26 | return "#define " + name + " " + (value? "1" : "0") + "\n"; 27 | } 28 | 29 | const std::string frag_header(const std::string& light_type, const bool glow) { 30 | return 31 | "#version 300 es\n" + 32 | flag_define("CIRCULAR_SHADOW", light_type == "circular") + 33 | flag_define("GAUSSIAN_SHADOW", light_type == "gaussian") + 34 | flag_define("SQUARE_SHADOW", light_type == "square") + 35 | flag_define("GLOW", glow); 36 | } 37 | 38 | // All definitions are inserted in the shader, the shader compiler will remove unused ones 39 | const std::string frag_body = 40 | R"( 41 | precision highp float; 42 | in vec2 uvpos; 43 | out vec4 fragColor; 44 | uniform vec2 lower; 45 | uniform vec2 upper; 46 | uniform vec4 color; 47 | 48 | uniform float radius; 49 | 50 | uniform sampler2D dither_texture; 51 | 52 | /* Gaussian shadow */ 53 | 54 | // Adapted from http://madebyevan.com/shaders/fast-rounded-rectangle-shadows/ 55 | // License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/) 56 | // This approximates the error function, needed for the gaussian integral 57 | vec4 erf(vec4 x) { 58 | vec4 s = sign(x), a = abs(x); 59 | x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; 60 | x *= x; 61 | return s - s / (x * x); 62 | } 63 | 64 | // Computes a gaussian convolution of a box from lower to upper 65 | float boxGaussian(vec2 lower, vec2 upper, vec2 point, float sigma) { 66 | vec4 query = vec4(lower - point, upper - point); 67 | vec4 integral = 0.5 + 0.5 * erf(query * (sqrt(0.5) / sigma)); 68 | return (integral.z - integral.x) * (integral.w - integral.y); 69 | } 70 | 71 | 72 | /* Circular shadow */ 73 | 74 | // Antiderivative of sqrt(1-x^2) 75 | float circleIntegral(float x) { 76 | return (sqrt(1.0-x*x)*x+asin(x)) / 2.0; 77 | } 78 | 79 | #define M_PI 3.14159265358 80 | 81 | float circleSegment(float dist) { 82 | return sqrt(1.0-dist*dist); 83 | } 84 | 85 | // assuming fullArea is the area of two parts of a circle cut by a horizontal stripe 86 | // (top and bottom are the cut lines) 87 | // compute the remaining area when cut vertically at right 88 | float circleMinusWall(float top, float bottom, float right, float fullArea) { 89 | if (right <= -1.0) { 90 | return fullArea; // entire circle 91 | } else if (right >= 1.0) { 92 | return 0.0; // nothing 93 | } else { 94 | // compute circle segment half width 95 | float w = circleSegment(right); 96 | // circle segment area 97 | float segmentTop = max(top, -w); 98 | float segmentBottom = min(bottom, w); 99 | float area = circleIntegral(segmentBottom) - circleIntegral(segmentTop) - (segmentBottom - segmentTop) * abs(right); 100 | if (right < 0.0) { 101 | return fullArea - area; 102 | } else { 103 | return area; 104 | } 105 | } 106 | } 107 | 108 | // Circle-rectangle overlap 109 | // circle is at (0,0) radius 1 110 | // only one top-left corner of rectangle is considered (assume rectangle >> circle) 111 | float circleOverlap(vec2 lower, vec2 upper) { 112 | // left/right half integral with vertical bounds 113 | float top = max(lower.y, -1.0); 114 | float bottom = min(upper.y, 1.0); 115 | float inner = 2.0 * (circleIntegral(bottom) - circleIntegral(top)); 116 | // left/right outer integrals for horizontal bounds 117 | float outerLeft = circleMinusWall(top, bottom, -lower.x, inner); 118 | float outerRight = circleMinusWall(top, bottom, upper.x, inner); 119 | return (inner - outerLeft - outerRight) / M_PI; 120 | } 121 | 122 | // Shadow of rectangle under circular area light 123 | float circularLightShadow(vec2 lower, vec2 upper, vec2 point, float radius) { 124 | vec4 query = vec4(lower - point, upper - point) / radius; 125 | return max(circleOverlap(query.st, query.pq), 0.0); 126 | } 127 | 128 | // Shadow of rectangle under square area light 129 | float squareShadow(vec2 lower, vec2 upper, vec2 point, float radius) { 130 | vec2 squareLower = point - radius; 131 | vec2 squareUpper = point + radius; 132 | vec2 overlapLower = max(lower, squareLower); 133 | vec2 overlapUpper = min(upper, squareUpper); 134 | vec2 overlap = max(overlapUpper - overlapLower, 0.0); 135 | float maxArea = radius * radius * 4.0; 136 | return overlap.x * overlap.y / maxArea; // area 137 | } 138 | 139 | 140 | /* Glow */ 141 | 142 | uniform vec4 glow_color; 143 | uniform float glow_spread; 144 | uniform vec2 glow_lower; 145 | uniform vec2 glow_upper; 146 | 147 | uniform float glow_intensity; 148 | uniform float glow_threshold; 149 | 150 | /* Inverse quartic falloff */ 151 | 152 | vec2 invQrtIntegralPartial(float xmin, float xmax, vec2 y, float z) { 153 | // Rectangle integral over 1/(x^2+y^2+1)^2 154 | // Computed using FriCAS: 155 | // formatExpression(integrate(integrate(1/((x^2+y^2+z^2)^2), x=a..b, "noPole"), y))$Format1D 156 | // Then some rewriting to make it valid glsl and simplified a bit 157 | 158 | float a = xmin; 159 | float b = xmax; 160 | 161 | float zsqr = z*z; 162 | float s1 = zsqr+a*a; 163 | float s2 = zsqr+b*b; 164 | vec2 s3 = zsqr+y*y; 165 | float t1 = sqrt(s1); 166 | float t2 = sqrt(s2); 167 | vec2 t3 = sqrt(s3); 168 | 169 | return (y*t1*t2*t3*(atan((b*t3)/(s3))-atan((a*t3)/(s3)))+(zsqr+y*y)*(b*t1*atan((y*t2)/(s2))+(-a)*t2*atan((y*t1)/(s1))))/((2.0*zsqr*(1.0+y*y*zsqr))*t1*t2); 170 | } 171 | 172 | float boxInvQrtFalloff(vec2 lower, vec2 upper, vec2 point, float scale) { 173 | vec4 query = vec4(lower - point, upper - point) / scale; 174 | vec2 integralBounds = invQrtIntegralPartial(query.x, query.z, query.yw, 1.0); 175 | return (integralBounds.y - integralBounds.x); 176 | } 177 | 178 | 179 | /* Inverse square falloff, but only vertically and horizontally */ 180 | 181 | float orthoInvSqrFalloff(vec2 lower, vec2 upper, vec2 point, float scale) { 182 | // f = (x^2+1)^(-1) 183 | // F = arctan(x) 184 | vec4 query = vec4(lower - point, upper - point) / scale; 185 | vec4 integral = atan(query); 186 | return (integral.z - integral.x) * (integral.w - integral.y); 187 | } 188 | 189 | 190 | /* Inverse square falloff, but only 1d based on distance to window edge */ 191 | 192 | float distInvSqrFalloff(vec2 lower, vec2 upper, vec2 point, float scale) { 193 | vec4 offsets = vec4(lower - point, point - upper) / scale; 194 | float a = max(max(offsets.x, offsets.z), 0.0); 195 | float b = max(max(offsets.y, offsets.w), 0.0); 196 | float dsqr = a*a + b*b; 197 | float invsqr = 1.0 / (dsqr + 1.0); 198 | return invsqr;//invsqr.x*invsqr.z * invsqr.y*invsqr.w; 199 | } 200 | 201 | 202 | /* Inverse square falloff integral over window edges (neon) */ 203 | 204 | vec4 barInvSqrFalloffIntegral(vec4 t, vec4 d, float z) { 205 | // FriCAS: integrate(1/(t^2+d^2+z^2), t) 206 | vec4 rsqr = d*d+z*z; 207 | vec4 r = sqrt(rsqr); 208 | return atan(t * r / rsqr) / r; 209 | } 210 | 211 | vec4 barInvCubicFalloffIntegral(vec4 t, vec4 d, float z) { 212 | // FriCAS: integrate(1/(t^2+d^2+z^2)^(3/2), t) 213 | vec4 rsqr = t*t+d*d+z*z; 214 | vec4 r = sqrt(rsqr); 215 | return -1.0/(t*r-rsqr); 216 | } 217 | 218 | float edgeInvSqrGlow(vec2 lower, vec2 upper, vec2 point, float scale) { 219 | // distance to edge left, top, right, bottom 220 | vec4 edgeDists = vec4(lower - point, upper - point); 221 | vec4 integralLower = barInvSqrFalloffIntegral(edgeDists.tsts, edgeDists, scale); 222 | vec4 integralUpper = barInvSqrFalloffIntegral(edgeDists.qpqp, edgeDists, scale); 223 | 224 | vec4 integral = integralUpper - integralLower; 225 | return (integral.s + integral.t + integral.p + integral.q); 226 | } 227 | 228 | float lightThreshold(float x, float minThreshold) { 229 | return max(x - minThreshold, 0.0); 230 | } 231 | 232 | vec4 dither(vec2 pos) { 233 | vec2 size = vec2(textureSize(dither_texture, 0)); 234 | return texture(dither_texture, pos / size) / 256.0 - 0.5 / 256.0; 235 | } 236 | 237 | vec4 shadow_color() 238 | { 239 | #if CIRCULAR_SHADOW 240 | return color * circularLightShadow(lower, upper, uvpos, radius); 241 | #elif SQUARE_SHADOW 242 | return color * squareShadow(lower, upper, uvpos, radius); 243 | #else // GAUSSIAN_SHADOW 244 | return color * boxGaussian(lower, upper, uvpos, radius / 2.7); 245 | #endif 246 | } 247 | 248 | /* Rectangle shadow+glow fragment shader */ 249 | 250 | void main() 251 | { 252 | #if GLOW 253 | float glow_value = edgeInvSqrGlow(glow_lower, glow_upper, uvpos, glow_spread), glow_neon_threshold; 254 | //float glow_value = boxInvQrtFalloff(glow_lower, glow_upper, uvpos, glow_spread); 255 | //float glow_value = boxGaussian(glow_lower, glow_upper, uvpos, glow_spread) 256 | //float glow_value = distInvSqrFalloff(glow_lower, glow_upper, uvpos, glow_spread); 257 | //float glow_value = orthoInvSqrFalloff(glow_lower, glow_upper, uvpos, glow_spread); 258 | vec4 out_color = 259 | shadow_color() + 260 | glow_intensity * glow_color * lightThreshold(glow_value, glow_threshold); 261 | #else 262 | vec4 out_color = shadow_color(); 263 | #endif 264 | out_color += dither(uvpos + lower*upper); 265 | fragColor = out_color; 266 | } 267 | 268 | )"; 269 | 270 | const std::string winshadows::shadow_renderer_t::frag_shader(const std::string &light_type, const bool glow) { 271 | return frag_header(light_type, glow) + frag_body; 272 | } -------------------------------------------------------------------------------- /testconfig/bluelight.ini: -------------------------------------------------------------------------------- 1 | # shadows + glow + decoration 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#3584E4FF 6 | glow_enabled = true 7 | glow_intensity = 0.5 8 | glow_radius_limit = 150 9 | glow_spread = 8.0 10 | glow_threshold = 0.03 11 | horizontal_offset = 5 12 | vertical_offset = 10 13 | shadow_color = \#00000078 14 | shadow_radius = 80 15 | 16 | [decoration] 17 | active_color = \#7A98BA76 18 | border_size = 4 19 | inactive_color = \#20201838 20 | title_height = 0 21 | 22 | [core] 23 | 24 | plugins = \ 25 | winshadows \ 26 | autostart \ 27 | command \ 28 | decoration \ 29 | move \ 30 | resize \ 31 | place \ 32 | vswitch \ 33 | follow-focus 34 | 35 | # Close focused window. 36 | close_top_view = KEY_Q 37 | 38 | # server-side decorations to make testing decorations easier 39 | preferred_decoration_mode = server 40 | 41 | xwayland = false 42 | 43 | 44 | # Startup commands ───────────────────────────────────────────────────────────── 45 | [autostart] 46 | 47 | # Disable panel, dock and default background 48 | autostart_wf_shell = false 49 | 50 | # Background might be useful if you are testing decorations 51 | background = swaybg --color "\#322d3d" 52 | 53 | # Start some terminal windows for testing here! 54 | test1 = sh -c "alacritty || foot || gnome-terminal" 55 | test2 = sh -c "alacritty || foot || gnome-terminal" 56 | 57 | # Bindings ─────────────────────────────────────────────────────────────── 58 | [command] 59 | 60 | # Start a terminal 61 | binding_terminal = KEY_ENTER 62 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 63 | 64 | # Drag windows by holding down Super and left mouse button. 65 | [move] 66 | activate = BTN_LEFT 67 | 68 | # Resize them with right mouse button + Super. 69 | [resize] 70 | activate = BTN_RIGHT 71 | 72 | 73 | # Place windows randomly 74 | [place] 75 | mode = random 76 | 77 | -------------------------------------------------------------------------------- /testconfig/circular.ini: -------------------------------------------------------------------------------- 1 | # extreme circle 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#3584E4FF 6 | glow_enabled = false 7 | glow_intensity = 0.5 8 | glow_radius_limit = 150 9 | glow_spread = 8.0 10 | glow_threshold = 0.03 11 | horizontal_offset = 100 12 | vertical_offset = 100 13 | shadow_color = \#000000FF 14 | shadow_radius = 100 15 | light_type = circular 16 | 17 | [core] 18 | 19 | plugins = \ 20 | winshadows \ 21 | autostart \ 22 | command \ 23 | move \ 24 | resize \ 25 | place \ 26 | vswitch \ 27 | follow-focus 28 | 29 | # Close focused window. 30 | close_top_view = KEY_Q 31 | 32 | # server-side decorations to make testing decorations easier 33 | preferred_decoration_mode = server 34 | 35 | xwayland = false 36 | 37 | # Background might be useful if you are testing decorations 38 | background_color = \#FFFFFFFF 39 | 40 | 41 | # Startup commands ───────────────────────────────────────────────────────────── 42 | [autostart] 43 | 44 | # Disable panel, dock and default background 45 | autostart_wf_shell = false 46 | 47 | 48 | # Start some terminal windows for testing here! 49 | test1 = sh -c "alacritty || foot || gnome-terminal" 50 | #test2 = sh -c "alacritty || foot || gnome-terminal" 51 | 52 | # Bindings ─────────────────────────────────────────────────────────────── 53 | [command] 54 | 55 | # Start a terminal 56 | binding_terminal = KEY_ENTER 57 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 58 | 59 | # Drag windows by holding down Super and left mouse button. 60 | [move] 61 | activate = BTN_LEFT 62 | 63 | # Resize them with right mouse button + Super. 64 | [resize] 65 | activate = BTN_RIGHT 66 | 67 | 68 | # Place windows randomly 69 | [place] 70 | mode = center 71 | 72 | -------------------------------------------------------------------------------- /testconfig/decoborder.ini: -------------------------------------------------------------------------------- 1 | # shadows + glow + decoration 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#FF00FFFF 6 | glow_enabled = true 7 | glow_intensity = 2.5 8 | glow_radius_limit = 50 9 | glow_spread = 3.0 10 | glow_threshold = 0.00 11 | horizontal_offset = 5 12 | vertical_offset = 10 13 | shadow_color = \#000000FF 14 | shadow_radius = 80 15 | 16 | [decoration] 17 | active_color = \#FFFFFFFF 18 | border_size = 30 19 | inactive_color = \#999999FF 20 | title_height = 30 21 | 22 | [core] 23 | 24 | plugins = \ 25 | winshadows \ 26 | autostart \ 27 | command \ 28 | decoration \ 29 | move \ 30 | resize \ 31 | place \ 32 | vswitch \ 33 | follow-focus 34 | 35 | # Close focused window. 36 | close_top_view = KEY_Q 37 | 38 | # server-side decorations to make testing decorations easier 39 | preferred_decoration_mode = server 40 | 41 | xwayland = false 42 | 43 | 44 | # Startup commands ───────────────────────────────────────────────────────────── 45 | [autostart] 46 | 47 | # Disable panel, dock and default background 48 | autostart_wf_shell = false 49 | 50 | # Background might be useful if you are testing decorations 51 | background = swaybg --color "\#322d3d" 52 | 53 | # Start some terminal windows for testing here! 54 | test1 = sh -c "alacritty || foot || gnome-terminal" 55 | test2 = sh -c "alacritty || foot || gnome-terminal" 56 | 57 | # Bindings ─────────────────────────────────────────────────────────────── 58 | [command] 59 | 60 | # Start a terminal 61 | binding_terminal = KEY_ENTER 62 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 63 | 64 | # Drag windows by holding down Super and left mouse button. 65 | [move] 66 | activate = BTN_LEFT 67 | 68 | # Resize them with right mouse button + Super. 69 | [resize] 70 | activate = BTN_RIGHT 71 | 72 | 73 | # Place windows randomly 74 | [place] 75 | mode = random 76 | 77 | -------------------------------------------------------------------------------- /testconfig/gaussian.ini: -------------------------------------------------------------------------------- 1 | # extreme gaussian 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#3584E4FF 6 | glow_enabled = false 7 | glow_intensity = 0.5 8 | glow_radius_limit = 150 9 | glow_spread = 8.0 10 | glow_threshold = 0.03 11 | horizontal_offset = 100 12 | vertical_offset = 100 13 | shadow_color = \#000000FF 14 | shadow_radius = 100 15 | light_type = gaussian 16 | 17 | [core] 18 | 19 | plugins = \ 20 | winshadows \ 21 | autostart \ 22 | command \ 23 | move \ 24 | resize \ 25 | place \ 26 | vswitch \ 27 | follow-focus \ 28 | showrepaint 29 | 30 | # Close focused window. 31 | close_top_view = KEY_Q 32 | 33 | # server-side decorations to make testing decorations easier 34 | preferred_decoration_mode = server 35 | 36 | xwayland = false 37 | 38 | # Background might be useful if you are testing decorations 39 | background_color = \#FFFFFFFF 40 | 41 | 42 | # Startup commands ───────────────────────────────────────────────────────────── 43 | [autostart] 44 | 45 | # Disable panel, dock and default background 46 | autostart_wf_shell = false 47 | 48 | 49 | # Start some terminal windows for testing here! 50 | test1 = sh -c "alacritty || foot || gnome-terminal" 51 | #test2 = sh -c "alacritty || foot || gnome-terminal" 52 | 53 | # Bindings ─────────────────────────────────────────────────────────────── 54 | [command] 55 | 56 | # Start a terminal 57 | binding_terminal = KEY_ENTER 58 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 59 | 60 | # Drag windows by holding down Super and left mouse button. 61 | [move] 62 | activate = BTN_LEFT 63 | 64 | # Resize them with right mouse button + Super. 65 | [resize] 66 | activate = BTN_RIGHT 67 | 68 | 69 | # Place windows randomly 70 | [place] 71 | mode = center 72 | 73 | [showrepaint] 74 | toggle = KEY_R -------------------------------------------------------------------------------- /testconfig/glow.ini: -------------------------------------------------------------------------------- 1 | # shadows and focus glow 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#3584E4FF 6 | glow_enabled = true 7 | glow_intensity = 0.5 8 | glow_radius_limit = 150 9 | glow_spread = 8.0 10 | glow_threshold = 0.03 11 | horizontal_offset = 5 12 | vertical_offset = 10 13 | shadow_color = \#00000078 14 | shadow_radius = 80 15 | 16 | [core] 17 | 18 | plugins = \ 19 | winshadows \ 20 | autostart \ 21 | command \ 22 | move \ 23 | resize \ 24 | place \ 25 | vswitch \ 26 | follow-focus 27 | 28 | # Close focused window. 29 | close_top_view = KEY_Q 30 | 31 | # server-side decorations to make testing decorations easier 32 | preferred_decoration_mode = server 33 | 34 | xwayland = false 35 | 36 | 37 | # Startup commands ───────────────────────────────────────────────────────────── 38 | [autostart] 39 | 40 | # Disable panel, dock and default background 41 | autostart_wf_shell = false 42 | 43 | # Background might be useful if you are testing decorations 44 | background = swaybg --color "\#322d3d" 45 | 46 | # Start some terminal windows for testing here! 47 | test1 = sh -c "alacritty || foot || gnome-terminal" 48 | test2 = sh -c "alacritty || foot || gnome-terminal" 49 | 50 | # Bindings ─────────────────────────────────────────────────────────────── 51 | [command] 52 | 53 | # Start a terminal 54 | binding_terminal = KEY_ENTER 55 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 56 | 57 | # Drag windows by holding down Super and left mouse button. 58 | [move] 59 | activate = BTN_LEFT 60 | 61 | # Resize them with right mouse button + Super. 62 | [resize] 63 | activate = BTN_RIGHT 64 | 65 | 66 | # Place windows randomly 67 | [place] 68 | mode = random 69 | 70 | -------------------------------------------------------------------------------- /testconfig/minimal.ini: -------------------------------------------------------------------------------- 1 | # only shadows 2 | 3 | [core] 4 | 5 | plugins = \ 6 | winshadows \ 7 | autostart \ 8 | command \ 9 | move \ 10 | resize \ 11 | place \ 12 | vswitch 13 | 14 | # Close focused window. 15 | close_top_view = KEY_Q 16 | 17 | # server-side decorations to make testing decorations easier 18 | preferred_decoration_mode = server 19 | 20 | xwayland = false 21 | 22 | 23 | # Startup commands ───────────────────────────────────────────────────────────── 24 | [autostart] 25 | 26 | # Disable panel, dock and default background 27 | autostart_wf_shell = false 28 | 29 | # Background might be useful if you are testing decorations 30 | background = swaybg --color "\#322d3d" 31 | 32 | # Start some terminal windows for testing here! 33 | test1 = sh -c "alacritty || foot || gnome-terminal" 34 | test2 = sh -c "alacritty || foot || gnome-terminal" 35 | 36 | # Bindings ─────────────────────────────────────────────────────────────── 37 | [command] 38 | 39 | # Start a terminal 40 | binding_terminal = KEY_ENTER 41 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 42 | 43 | # Drag windows by holding down Super and left mouse button. 44 | [move] 45 | activate = BTN_LEFT 46 | 47 | # Resize them with right mouse button + Super. 48 | [resize] 49 | activate = BTN_RIGHT 50 | 51 | 52 | # Place windows randomly 53 | [place] 54 | mode = random 55 | 56 | -------------------------------------------------------------------------------- /testconfig/overscale.ini: -------------------------------------------------------------------------------- 1 | # extreme square 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#3584E4FF 6 | glow_enabled = false 7 | glow_intensity = 0.5 8 | glow_radius_limit = 150 9 | glow_spread = 8.0 10 | glow_threshold = 0.03 11 | horizontal_offset = 20 12 | vertical_offset = 30 13 | shadow_color = \#00000080 14 | shadow_radius = 10 15 | light_type = gaussian 16 | overscale = 2.0 17 | 18 | [core] 19 | 20 | plugins = \ 21 | winshadows \ 22 | autostart \ 23 | command \ 24 | move \ 25 | resize \ 26 | place \ 27 | vswitch \ 28 | decoration \ 29 | follow-focus 30 | 31 | # Close focused window. 32 | close_top_view = KEY_Q 33 | 34 | # server-side decorations to make testing decorations easier 35 | preferred_decoration_mode = server 36 | 37 | xwayland = false 38 | 39 | # Background might be useful if you are testing decorations 40 | background_color = \#EEEEEEFF 41 | 42 | 43 | # Startup commands ───────────────────────────────────────────────────────────── 44 | [autostart] 45 | 46 | # Disable panel, dock and default background 47 | autostart_wf_shell = false 48 | 49 | 50 | # Start some terminal windows for testing here! 51 | test1 = sh -c "alacritty || foot || gnome-terminal" 52 | #test2 = sh -c "alacritty || foot || gnome-terminal" 53 | 54 | # Bindings ─────────────────────────────────────────────────────────────── 55 | [command] 56 | 57 | # Start a terminal 58 | binding_terminal = KEY_ENTER 59 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 60 | 61 | # Drag windows by holding down Super and left mouse button. 62 | [move] 63 | activate = BTN_LEFT 64 | 65 | # Resize them with right mouse button + Super. 66 | [resize] 67 | activate = BTN_RIGHT 68 | 69 | 70 | # Place windows randomly 71 | [place] 72 | mode = center 73 | 74 | -------------------------------------------------------------------------------- /testconfig/square.ini: -------------------------------------------------------------------------------- 1 | # extreme square 2 | 3 | [winshadows] 4 | clip_shadow_inside = true 5 | glow_color = \#3584E4FF 6 | glow_enabled = false 7 | glow_intensity = 0.5 8 | glow_radius_limit = 150 9 | glow_spread = 8.0 10 | glow_threshold = 0.03 11 | horizontal_offset = 100 12 | vertical_offset = 100 13 | shadow_color = \#000000FF 14 | shadow_radius = 100 15 | light_type = square 16 | 17 | [core] 18 | 19 | plugins = \ 20 | winshadows \ 21 | autostart \ 22 | command \ 23 | move \ 24 | resize \ 25 | place \ 26 | vswitch \ 27 | follow-focus 28 | 29 | # Close focused window. 30 | close_top_view = KEY_Q 31 | 32 | # server-side decorations to make testing decorations easier 33 | preferred_decoration_mode = server 34 | 35 | xwayland = false 36 | 37 | # Background might be useful if you are testing decorations 38 | background_color = \#FFFFFFFF 39 | 40 | 41 | # Startup commands ───────────────────────────────────────────────────────────── 42 | [autostart] 43 | 44 | # Disable panel, dock and default background 45 | autostart_wf_shell = false 46 | 47 | 48 | # Start some terminal windows for testing here! 49 | test1 = sh -c "alacritty || foot || gnome-terminal" 50 | #test2 = sh -c "alacritty || foot || gnome-terminal" 51 | 52 | # Bindings ─────────────────────────────────────────────────────────────── 53 | [command] 54 | 55 | # Start a terminal 56 | binding_terminal = KEY_ENTER 57 | command_terminal = sh -c "alacritty || foot || gnome-terminal" 58 | 59 | # Drag windows by holding down Super and left mouse button. 60 | [move] 61 | activate = BTN_LEFT 62 | 63 | # Resize them with right mouse button + Super. 64 | [resize] 65 | activate = BTN_RIGHT 66 | 67 | 68 | # Place windows randomly 69 | [place] 70 | mode = center 71 | 72 | -------------------------------------------------------------------------------- /winshadows.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "node.hpp" 13 | 14 | struct view_shadow_data : wf::custom_data_t { 15 | view_shadow_data(std::shared_ptr shadow_ptr) : shadow_ptr(shadow_ptr) {}; 16 | 17 | std::shared_ptr shadow_ptr; 18 | }; 19 | 20 | class wayfire_shadows : public wf::plugin_interface_t { 21 | const std::string surface_data_name = "shadow_surface"; 22 | 23 | wf::view_matcher_t enabled_views{"winshadows/enabled_views"}; 24 | wf::option_wrapper_t include_undecorated_views{"winshadows/include_undecorated_views"}; 25 | 26 | // update new views 27 | wf::signal::connection_t on_view_mapped = 28 | [=](auto *data) { update_view_decoration(data->view); }; 29 | // update when view enables or disables server side decoration 30 | wf::signal::connection_t on_view_updated = 31 | [=](auto *data) { update_view_decoration(data->view); }; 32 | // update on tile state change, such that it is possible to exclude tiled windows 33 | wf::signal::connection_t on_view_tiled = 34 | [=](auto *data) { update_view_decoration(data->view); }; 35 | 36 | public: 37 | void init() override { 38 | wf::get_core().connect(&on_view_mapped); 39 | wf::get_core().connect(&on_view_updated); 40 | wf::get_core().connect(&on_view_tiled); 41 | 42 | for (auto &view : wf::get_core().get_all_views()) { 43 | update_view_decoration(view); 44 | } 45 | } 46 | 47 | void fini() override { 48 | wf::get_core().disconnect(&on_view_mapped); 49 | wf::get_core().disconnect(&on_view_updated); 50 | wf::get_core().disconnect(&on_view_tiled); 51 | 52 | for (auto &view : wf::get_core().get_all_views()) { 53 | deinit_view(view); 54 | } 55 | } 56 | 57 | /** 58 | * Checks whether the given view has server side decoration and is in 59 | * the white list. 60 | * 61 | * @param view The view to match 62 | * @return Whether the view should get a shadow. 63 | */ 64 | bool is_view_shadow_enabled(wayfire_toplevel_view view) { 65 | return enabled_views.matches(view) && (is_view_decorated(view) || include_undecorated_views); 66 | } 67 | 68 | bool is_view_decorated(wayfire_toplevel_view view) { 69 | return view->should_be_decorated(); 70 | } 71 | 72 | const wf::scene::floating_inner_ptr& get_shadow_root_node(wayfire_view view) const { 73 | return view->get_surface_root_node(); 74 | } 75 | 76 | wf::wl_idle_call idle_deactivate; 77 | void update_view_decoration(wayfire_view view) { 78 | auto toplevel = wf::toplevel_cast(view); 79 | if (toplevel) { 80 | if (is_view_shadow_enabled(toplevel)) { 81 | auto shadow_data = view->get_data(surface_data_name); 82 | if (!shadow_data) { 83 | // No shadow yet, create it now. 84 | init_view(toplevel); 85 | } else { 86 | // in some situations the shadow node might have been removed due to unmap, but the view is reused (including the custom data) 87 | auto shadow_root = get_shadow_root_node(view); 88 | if (shadow_data->shadow_ptr->parent() != shadow_root.get()) { 89 | wf::scene::add_back(shadow_root, shadow_data->shadow_ptr); 90 | } 91 | } 92 | } else { 93 | deinit_view(view); 94 | } 95 | } 96 | } 97 | 98 | bool is_view_initialized(wayfire_view view) { 99 | return view->has_data(surface_data_name); 100 | } 101 | 102 | void init_view(wayfire_toplevel_view view) { 103 | // create the shadow node and add it to the view 104 | auto node = std::make_shared(view); 105 | wf::scene::add_back(get_shadow_root_node(view), node); 106 | 107 | // store the shadow node in the view so we can remove it later 108 | auto view_data = std::make_unique(node); 109 | view->store_data(std::move(view_data), surface_data_name); 110 | 111 | view->damage(); 112 | } 113 | 114 | void deinit_view(wayfire_view view) { 115 | auto view_data = view->get_data(surface_data_name); 116 | if (view_data != nullptr) { 117 | wf::scene::remove_child(view_data->shadow_ptr); 118 | view->damage(); 119 | view->erase_data(surface_data_name); 120 | } 121 | } 122 | }; 123 | 124 | DECLARE_WAYFIRE_PLUGIN(wayfire_shadows); 125 | -------------------------------------------------------------------------------- /winshadows.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_short>Window Shadows 5 | <_long>Server Side Shadows for windows on Wayfire 6 | Effects 7 | 12 | 17 | 22 | 23 | <_short>Shadow 24 | 29 | 34 | 39 | 44 | 61 | 67 | 68 | 69 | <_short>Glow 70 | <_long>Make windows edges emit light when focused 71 | 76 | 83 | 88 | 93 | 94 | <_short>Advanced 95 | 100 | 107 | 115 | 116 | 117 | 118 | 119 | --------------------------------------------------------------------------------