pax_global_header00006660000000000000000000000064146011245530014513gustar00rootroot0000000000000052 comment=81699f6e4be65dcf3f7ad5155dfb4247b37b7997 wayfire-shadows-0.0~git20240327.81699f6/000077500000000000000000000000001460112455300171475ustar00rootroot00000000000000wayfire-shadows-0.0~git20240327.81699f6/.gitignore000066400000000000000000000000151460112455300211330ustar00rootroot00000000000000build .cache wayfire-shadows-0.0~git20240327.81699f6/LICENSE000066400000000000000000000020721460112455300201550ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2021 Tim Göttlicher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wayfire-shadows-0.0~git20240327.81699f6/README.md000066400000000000000000000033661460112455300204360ustar00rootroot00000000000000# winshadows ### Window Shadows Plugin for Wayfire This is a plugin for wayfire that adds window shadows. The code was initially a fork of but only a small part of that remains in the code. ## Screenshots 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. ![image](https://github.com/timgott/wayfire-shadows/assets/18331942/94d27159-573c-4613-8bf6-4527502539f5)
Config used in screenshot ```ini [decoration] active_color = \#7A98BA76 border_size = 4 inactive_color = \#20201838 title_height = 0 [winshadows] glow_enabled = true shadow_radius = 50 vertical_offset = 10 horizontal_offset = 5 # remaining values are default ```
## Install 1. Get the sources ```bash git clone https://github.com/timgott/wayfire-shadows.git cd wayfire-shadows ``` 2. ⚠️ Switch to the backport0.7 branch if you use wayfire version 0.7 (last stable release) ```bash git checkout backport0.7 # (if necessary) ``` 3. Configure with meson and build & install with ninja. ```bash meson build --buildtype=release cd build # meson configure --prefix=... # if wayfire is not installed in /usr/local ninja sudo ninja install ``` ## Install with [wfplug](https://github.com/timgott/wfplug) As above, you have to change the branch on wayfire 0.7. ```bash # enable wfplug source ~/wfplug/activate # edit path if necessary # get plugin wfplug-goto-plugins git clone https://github.com/timgott/wayfire-shadows.git winshadows # build and install to wfplug wfplug-build winshadows ``` Try a testconfig with ```bash wfplug-test winshadows bluelight ``` wayfire-shadows-0.0~git20240327.81699f6/meson.build000066400000000000000000000021121460112455300213050ustar00rootroot00000000000000project( 'wayfire-plugin-winshadows', 'c', 'cpp', version: '0.1', license: 'MIT', meson_version: '>=0.51.0', default_options: [ 'cpp_std=c++17', 'c_std=c11', 'warning_level=2', 'werror=false', ], ) wayfire = dependency('wayfire') wlroots = dependency('wlroots') wfconfig = dependency('wf-config') add_project_arguments(['-DWLR_USE_UNSTABLE'], language: ['cpp', 'c']) add_project_arguments(['-DWAYFIRE_PLUGIN'], language: ['cpp', 'c']) add_project_link_arguments(['-rdynamic'], language:'cpp') shadows = shared_module( 'winshadows', [ 'winshadows.cpp', 'node.cpp', 'renderer.cpp', 'shaders.glsl.cpp', ], dependencies: [ wlroots, wfconfig, wayfire ], install: true, install_dir: join_paths( get_option( 'libdir' ), 'wayfire' ) ) install_data( 'winshadows.xml', install_dir: wayfire.get_variable( pkgconfig: 'metadatadir' ) ) summary = [ '', '----------------', 'wayfire-plugin-winshadows @0@'.format( meson.project_version() ), '----------------', '' ] message('\n'.join(summary)) wayfire-shadows-0.0~git20240327.81699f6/node.cpp000066400000000000000000000046621460112455300206100ustar00rootroot00000000000000#include "node.hpp" namespace winshadows { shadow_node_t::shadow_node_t( wayfire_toplevel_view view ): wf::scene::node_t(false) { this->view = view; on_geometry_changed.set_callback([this] (auto) { update_geometry(); }); on_activated_changed.set_callback([this] (auto) { this->view->damage(); }); view->connect(&on_geometry_changed); view->connect(&on_activated_changed); update_geometry(); } shadow_node_t::~shadow_node_t() { view->disconnect(&on_geometry_changed); } wf::geometry_t shadow_node_t::get_bounding_box() { return geometry; } void shadow_node_t::gen_render_instances(std::vector &instances, wf::scene::damage_callback push_damage, wf::output_t *output) { // define renderer class shadow_render_instance_t : public wf::scene::simple_render_instance_t { public: using simple_render_instance_t::simple_render_instance_t; void render(const wf::render_target_t& target, const wf::region_t& region) override { // coordinates relative to view origin (not bounding box origin) wf::point_t frame_origin = self->frame_offset; wf::region_t paint_region = self->shadow_region + frame_origin; paint_region &= region; for (const auto& box : paint_region) { self->shadow.render(target, frame_origin, wlr_box_from_pixman_box(box), self->view->activated); } self->_was_activated = self->view->activated; } }; instances.push_back(std::make_unique(this, push_damage, output)); } void shadow_node_t::update_geometry() { wf::geometry_t frame_geometry = view->get_geometry(); shadow.resize(frame_geometry.width, frame_geometry.height); // TODO: Check whether this can be done in a nicer/easier way wf::pointf_t view_origin_f = view->get_surface_root_node()->to_global({0, 0}); wf::point_t view_origin {(int)view_origin_f.x, (int)view_origin_f.y}; // Offset between view origin and frame top left corner frame_offset = wf::origin(frame_geometry) - view_origin; // Shadow geometry is relative to the top left corner of the frame (not the view) wf::geometry_t shadow_geometry = shadow.get_geometry(); // move to view-relative coordinates geometry = shadow_geometry + frame_offset; this->shadow_region = shadow.calculate_region(); } } wayfire-shadows-0.0~git20240327.81699f6/node.hpp000066400000000000000000000023051460112455300206050ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "renderer.hpp" namespace winshadows { class shadow_node_t : public wf::scene::node_t { private: int _was_activated = 1; // used to check whether redrawing on focus is necessary // geometry of the node relative to the view origin wf::geometry_t geometry; // offset between the node origin and the frame origin (i.e. top-left borders) wf::point_t frame_offset; wayfire_toplevel_view view; int width = 100, height = 100; wf::region_t shadow_region; shadow_renderer_t shadow; wf::signal::connection_t on_geometry_changed; wf::signal::connection_t on_activated_changed; void update_geometry(); public: shadow_node_t(wayfire_toplevel_view view); virtual ~shadow_node_t(); void gen_render_instances(std::vector &instances, wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override; wf::geometry_t get_bounding_box() override; }; } wayfire-shadows-0.0~git20240327.81699f6/renderer.cpp000066400000000000000000000155201460112455300214640ustar00rootroot00000000000000#include #include #include #include "renderer.hpp" namespace winshadows { shadow_renderer_t::shadow_renderer_t() { OpenGL::render_begin(); generate_dither_texture(); recompile_shaders(); OpenGL::render_end(); light_type_option.set_callback([this] () { recompile_shaders(); }); } void shadow_renderer_t::recompile_shaders() { OpenGL::render_begin(); shadow_program.free_resources(); shadow_glow_program.free_resources(); shadow_program.set_simple( OpenGL::compile_program(shadow_vert_shader, frag_shader(light_type_option, /*no glow*/ false)) ); shadow_glow_program.set_simple( OpenGL::compile_program(shadow_vert_shader, frag_shader(light_type_option, /*glow*/ true)) ); OpenGL::render_end(); } void shadow_renderer_t::generate_dither_texture() { const int size = 32; GLuint data[size*size]; std::mt19937_64 gen{std::random_device{}()}; std::uniform_int_distribution distrib; for (int i = 0; i < size*size; i++) { data[i] = distrib(gen); } GL_CALL(glGenTextures(1, &dither_texture)); GL_CALL(glBindTexture(GL_TEXTURE_2D, dither_texture)); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)); } shadow_renderer_t::~shadow_renderer_t() { OpenGL::render_begin(); shadow_program.free_resources(); shadow_glow_program.free_resources(); GL_CALL(glDeleteTextures(1, &dither_texture)); OpenGL::render_end(); } void shadow_renderer_t::render(const wf::render_target_t& fb, wf::point_t window_origin, const wf::geometry_t& scissor, const bool glow) { float radius = shadow_radius_option; wf::color_t color = shadow_color_option; // Premultiply alpha for shader glm::vec4 premultiplied = { color.r * color.a, color.g * color.a, color.b * color.a, color.a }; // Glow color, alpha=0 => additive blending (exploiting premultiplied alpha) wf::color_t glow_color = glow_color_option; glm::vec4 glow_premultiplied = { glow_color.r * glow_color.a, glow_color.g * glow_color.a, glow_color.b * glow_color.a, glow_color.a * (1.0 - glow_emissivity_option) }; // Enable glow shader only when glow radius > 0 and view is focused bool use_glow = (glow && is_glow_enabled()); OpenGL::program_t &program = use_glow ? shadow_glow_program : shadow_program; OpenGL::render_begin(fb); fb.logic_scissor(scissor); program.use(wf::TEXTURE_TYPE_RGBA); // Compute vertex rectangle geometry wf::geometry_t bounds = outer_geometry + window_origin; float left = bounds.x; float right = bounds.x + bounds.width; float top = bounds.y; float bottom = bounds.y + bounds.height; GLfloat vertexData[] = { left, bottom, right, bottom, right, top, left, top }; glm::mat4 matrix = fb.get_orthographic_projection(); // vertex parameters program.attrib_pointer("position", 2, 0, vertexData); program.uniformMatrix4f("MVP", matrix); // fragment parameters program.uniform1f("radius", radius); program.uniform4f("color", premultiplied); const auto inner = window_geometry + window_origin; const auto shadow_inner = shadow_projection_geometry + window_origin; program.uniform2f("lower", shadow_inner.x, shadow_inner.y); program.uniform2f("upper", shadow_inner.x + shadow_inner.width, shadow_inner.y + shadow_inner.height); if (use_glow) { program.uniform2f("glow_lower", inner.x, inner.y); program.uniform2f("glow_upper", inner.x + inner.width, inner.y + inner.height); program.uniform1f("glow_spread", glow_spread_option); program.uniform4f("glow_color", glow_premultiplied); program.uniform1f("glow_intensity", glow_intensity_option); program.uniform1f("glow_threshold", glow_threshold_option); } // dither texture program.uniform1i("dither_texture", 0); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, dither_texture)); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); program.deactivate(); OpenGL::render_end(); } wf::region_t shadow_renderer_t::calculate_region() const { // TODO: geometry and region depending on whether glow is active or not wf::region_t region = wf::region_t(shadow_geometry) | wf::region_t(glow_geometry); if (clip_shadow_inside) { region ^= window_geometry; } return region; } wf::geometry_t shadow_renderer_t::get_geometry() const { return outer_geometry; } wf::geometry_t expand_geometry(const wf::geometry_t& geometry, const int marginX, const int marginY) { return { geometry.x - marginX, geometry.y - marginY, geometry.width + marginX * 2, geometry.height + marginY * 2 }; } wf::geometry_t expand_geometry(const wf::geometry_t& geometry, const int margin) { return expand_geometry(geometry, margin, margin); } wf::geometry_t inflate_geometry(const wf::geometry_t& geometry, const float inflation) { int expandX = geometry.width * inflation * 0.5; int expandY = geometry.height * inflation * 0.5; return expand_geometry(geometry, expandX, expandY); } void shadow_renderer_t::resize(const int window_width, const int window_height) { window_geometry = { 0, 0, window_width, window_height }; float overscale = overscale_option / 100.0; const wf::point_t offset { horizontal_offset, vertical_offset }; shadow_projection_geometry = inflate_geometry(window_geometry, overscale) + offset; shadow_geometry = expand_geometry(shadow_projection_geometry, shadow_radius_option); int glow_radius = is_glow_enabled() ? glow_radius_limit_option : 0; glow_geometry = expand_geometry(shadow_projection_geometry, glow_radius); int left = std::min(shadow_geometry.x, glow_geometry.x); int top = std::min(shadow_geometry.y, glow_geometry.y); int right = std::max(shadow_geometry.x + shadow_geometry.width, glow_geometry.x + glow_geometry.width); int bottom = std::max(shadow_geometry.y + shadow_geometry.height, glow_geometry.y + glow_geometry.height); outer_geometry = { left, top, right - left, bottom - top }; } bool shadow_renderer_t::is_glow_enabled() const { return glow_enabled_option && (glow_radius_limit_option > 0) && (glow_intensity_option > 0); } } wayfire-shadows-0.0~git20240327.81699f6/renderer.hpp000066400000000000000000000050121460112455300214640ustar00rootroot00000000000000#pragma once #include #include #include namespace winshadows { /** * A class that can render shadows. * It manages the shader and calculates the necessary padding. */ class shadow_renderer_t { public: shadow_renderer_t(); ~shadow_renderer_t(); void recompile_shaders(); void render(const wf::render_target_t& fb, wf::point_t origin, const wf::geometry_t& scissor, const bool glow); void resize(const int width, const int height); wf::region_t calculate_region() const; wf::geometry_t get_geometry() const; bool is_glow_enabled() const; private: OpenGL::program_t shadow_program; OpenGL::program_t shadow_glow_program; GLuint dither_texture; void generate_dither_texture(); wf::geometry_t glow_geometry; wf::geometry_t shadow_geometry; wf::geometry_t shadow_projection_geometry; // projected window geometry wf::geometry_t outer_geometry; wf::geometry_t window_geometry; wlr_box calculate_padding(const wf::geometry_t window_geometry) const; wf::option_wrapper_t shadow_color_option { "winshadows/shadow_color" }; wf::option_wrapper_t shadow_radius_option { "winshadows/shadow_radius" }; wf::option_wrapper_t clip_shadow_inside { "winshadows/clip_shadow_inside" }; wf::option_wrapper_t vertical_offset { "winshadows/vertical_offset" }; wf::option_wrapper_t horizontal_offset { "winshadows/horizontal_offset" }; wf::option_wrapper_t light_type_option { "winshadows/light_type" }; wf::option_wrapper_t overscale_option { "winshadows/overscale" }; wf::option_wrapper_t glow_enabled_option { "winshadows/glow_enabled" }; wf::option_wrapper_t glow_color_option { "winshadows/glow_color" }; wf::option_wrapper_t glow_emissivity_option { "winshadows/glow_emissivity" }; wf::option_wrapper_t glow_spread_option { "winshadows/glow_spread" }; wf::option_wrapper_t glow_intensity_option { "winshadows/glow_intensity" }; wf::option_wrapper_t glow_threshold_option { "winshadows/glow_threshold" }; wf::option_wrapper_t glow_radius_limit_option { "winshadows/glow_radius_limit" }; static const std::string shadow_vert_shader; const std::string frag_shader(const std::string &light_type, const bool glow); }; } wayfire-shadows-0.0~git20240327.81699f6/shaders.glsl.cpp000066400000000000000000000205211460112455300222440ustar00rootroot00000000000000// GLSL as cpp string constant (.glsl extension for syntax highlighting) #include "renderer.hpp" /* Vertex shader */ const std::string winshadows::shadow_renderer_t::shadow_vert_shader = R"( #version 300 es in mediump vec2 position; out mediump vec2 uvpos; uniform mat4 MVP; void main() { gl_Position = MVP * vec4(position.xy, 0.0, 1.0); uvpos = position.xy; })"; /* Base fragment shader definitions */ const std::string flag_define(const std::string& name, const bool value) { return "#define " + name + " " + (value? "1" : "0") + "\n"; } const std::string frag_header(const std::string& light_type, const bool glow) { return "#version 300 es\n" + flag_define("CIRCULAR_SHADOW", light_type == "circular") + flag_define("GAUSSIAN_SHADOW", light_type == "gaussian") + flag_define("SQUARE_SHADOW", light_type == "square") + flag_define("GLOW", glow); } // All definitions are inserted in the shader, the shader compiler will remove unused ones const std::string frag_body = R"( precision highp float; in vec2 uvpos; out vec4 fragColor; uniform vec2 lower; uniform vec2 upper; uniform vec4 color; uniform float radius; uniform sampler2D dither_texture; /* Gaussian shadow */ // Adapted from http://madebyevan.com/shaders/fast-rounded-rectangle-shadows/ // License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/) // This approximates the error function, needed for the gaussian integral vec4 erf(vec4 x) { vec4 s = sign(x), a = abs(x); x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; x *= x; return s - s / (x * x); } // Computes a gaussian convolution of a box from lower to upper float boxGaussian(vec2 lower, vec2 upper, vec2 point, float sigma) { vec4 query = vec4(lower - point, upper - point); vec4 integral = 0.5 + 0.5 * erf(query * (sqrt(0.5) / sigma)); return (integral.z - integral.x) * (integral.w - integral.y); } /* Circular shadow */ // Antiderivative of sqrt(1-x^2) float circleIntegral(float x) { return (sqrt(1.0-x*x)*x+asin(x)) / 2.0; } #define M_PI 3.14159265358 float circleSegment(float dist) { return sqrt(1.0-dist*dist); } // assuming fullArea is the area of two parts of a circle cut by a horizontal stripe // (top and bottom are the cut lines) // compute the remaining area when cut vertically at right float circleMinusWall(float top, float bottom, float right, float fullArea) { if (right <= -1.0) { return fullArea; // entire circle } else if (right >= 1.0) { return 0.0; // nothing } else { // compute circle segment half width float w = circleSegment(right); // circle segment area float segmentTop = max(top, -w); float segmentBottom = min(bottom, w); float area = circleIntegral(segmentBottom) - circleIntegral(segmentTop) - (segmentBottom - segmentTop) * abs(right); if (right < 0.0) { return fullArea - area; } else { return area; } } } // Circle-rectangle overlap // circle is at (0,0) radius 1 // only one top-left corner of rectangle is considered (assume rectangle >> circle) float circleOverlap(vec2 lower, vec2 upper) { // left/right half integral with vertical bounds float top = max(lower.y, -1.0); float bottom = min(upper.y, 1.0); float inner = 2.0 * (circleIntegral(bottom) - circleIntegral(top)); // left/right outer integrals for horizontal bounds float outerLeft = circleMinusWall(top, bottom, -lower.x, inner); float outerRight = circleMinusWall(top, bottom, upper.x, inner); return (inner - outerLeft - outerRight) / M_PI; } // Shadow of rectangle under circular area light float circularLightShadow(vec2 lower, vec2 upper, vec2 point, float radius) { vec4 query = vec4(lower - point, upper - point) / radius; return max(circleOverlap(query.st, query.pq), 0.0); } // Shadow of rectangle under square area light float squareShadow(vec2 lower, vec2 upper, vec2 point, float radius) { vec2 squareLower = point - radius; vec2 squareUpper = point + radius; vec2 overlapLower = max(lower, squareLower); vec2 overlapUpper = min(upper, squareUpper); vec2 overlap = max(overlapUpper - overlapLower, 0.0); float maxArea = radius * radius * 4.0; return overlap.x * overlap.y / maxArea; // area } /* Glow */ uniform vec4 glow_color; uniform float glow_spread; uniform vec2 glow_lower; uniform vec2 glow_upper; uniform float glow_intensity; uniform float glow_threshold; /* Inverse quartic falloff */ vec2 invQrtIntegralPartial(float xmin, float xmax, vec2 y, float z) { // Rectangle integral over 1/(x^2+y^2+1)^2 // Computed using FriCAS: // formatExpression(integrate(integrate(1/((x^2+y^2+z^2)^2), x=a..b, "noPole"), y))$Format1D // Then some rewriting to make it valid glsl and simplified a bit float a = xmin; float b = xmax; float zsqr = z*z; float s1 = zsqr+a*a; float s2 = zsqr+b*b; vec2 s3 = zsqr+y*y; float t1 = sqrt(s1); float t2 = sqrt(s2); vec2 t3 = sqrt(s3); 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); } float boxInvQrtFalloff(vec2 lower, vec2 upper, vec2 point, float scale) { vec4 query = vec4(lower - point, upper - point) / scale; vec2 integralBounds = invQrtIntegralPartial(query.x, query.z, query.yw, 1.0); return (integralBounds.y - integralBounds.x); } /* Inverse square falloff, but only vertically and horizontally */ float orthoInvSqrFalloff(vec2 lower, vec2 upper, vec2 point, float scale) { // f = (x^2+1)^(-1) // F = arctan(x) vec4 query = vec4(lower - point, upper - point) / scale; vec4 integral = atan(query); return (integral.z - integral.x) * (integral.w - integral.y); } /* Inverse square falloff, but only 1d based on distance to window edge */ float distInvSqrFalloff(vec2 lower, vec2 upper, vec2 point, float scale) { vec4 offsets = vec4(lower - point, point - upper) / scale; float a = max(max(offsets.x, offsets.z), 0.0); float b = max(max(offsets.y, offsets.w), 0.0); float dsqr = a*a + b*b; float invsqr = 1.0 / (dsqr + 1.0); return invsqr;//invsqr.x*invsqr.z * invsqr.y*invsqr.w; } /* Inverse square falloff integral over window edges (neon) */ vec4 barInvSqrFalloffIntegral(vec4 t, vec4 d, float z) { // FriCAS: integrate(1/(t^2+d^2+z^2), t) vec4 rsqr = d*d+z*z; vec4 r = sqrt(rsqr); return atan(t * r / rsqr) / r; } vec4 barInvCubicFalloffIntegral(vec4 t, vec4 d, float z) { // FriCAS: integrate(1/(t^2+d^2+z^2)^(3/2), t) vec4 rsqr = t*t+d*d+z*z; vec4 r = sqrt(rsqr); return -1.0/(t*r-rsqr); } float edgeInvSqrGlow(vec2 lower, vec2 upper, vec2 point, float scale) { // distance to edge left, top, right, bottom vec4 edgeDists = vec4(lower - point, upper - point); vec4 integralLower = barInvSqrFalloffIntegral(edgeDists.tsts, edgeDists, scale); vec4 integralUpper = barInvSqrFalloffIntegral(edgeDists.qpqp, edgeDists, scale); vec4 integral = integralUpper - integralLower; return (integral.s + integral.t + integral.p + integral.q); } float lightThreshold(float x, float minThreshold) { return max(x - minThreshold, 0.0); } vec4 dither(vec2 pos) { vec2 size = vec2(textureSize(dither_texture, 0)); return texture(dither_texture, pos / size) / 256.0 - 0.5 / 256.0; } vec4 shadow_color() { #if CIRCULAR_SHADOW return color * circularLightShadow(lower, upper, uvpos, radius); #elif SQUARE_SHADOW return color * squareShadow(lower, upper, uvpos, radius); #else // GAUSSIAN_SHADOW return color * boxGaussian(lower, upper, uvpos, radius / 2.7); #endif } /* Rectangle shadow+glow fragment shader */ void main() { #if GLOW float glow_value = edgeInvSqrGlow(glow_lower, glow_upper, uvpos, glow_spread), glow_neon_threshold; //float glow_value = boxInvQrtFalloff(glow_lower, glow_upper, uvpos, glow_spread); //float glow_value = boxGaussian(glow_lower, glow_upper, uvpos, glow_spread) //float glow_value = distInvSqrFalloff(glow_lower, glow_upper, uvpos, glow_spread); //float glow_value = orthoInvSqrFalloff(glow_lower, glow_upper, uvpos, glow_spread); vec4 out_color = shadow_color() + glow_intensity * glow_color * lightThreshold(glow_value, glow_threshold); #else vec4 out_color = shadow_color(); #endif out_color += dither(uvpos + lower*upper); fragColor = out_color; } )"; const std::string winshadows::shadow_renderer_t::frag_shader(const std::string &light_type, const bool glow) { return frag_header(light_type, glow) + frag_body; }wayfire-shadows-0.0~git20240327.81699f6/testconfig/000077500000000000000000000000001460112455300213145ustar00rootroot00000000000000wayfire-shadows-0.0~git20240327.81699f6/testconfig/bluelight.ini000066400000000000000000000034041460112455300237750ustar00rootroot00000000000000# shadows + glow + decoration [winshadows] clip_shadow_inside = true glow_color = \#3584E4FF glow_enabled = true glow_intensity = 0.5 glow_radius_limit = 150 glow_spread = 8.0 glow_threshold = 0.03 horizontal_offset = 5 vertical_offset = 10 shadow_color = \#00000078 shadow_radius = 80 [decoration] active_color = \#7A98BA76 border_size = 4 inactive_color = \#20201838 title_height = 0 [core] plugins = \ winshadows \ autostart \ command \ decoration \ move \ resize \ place \ vswitch \ follow-focus # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Background might be useful if you are testing decorations background = swaybg --color "\#322d3d" # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = random wayfire-shadows-0.0~git20240327.81699f6/testconfig/circular.ini000066400000000000000000000032301460112455300236170ustar00rootroot00000000000000# extreme circle [winshadows] clip_shadow_inside = true glow_color = \#3584E4FF glow_enabled = false glow_intensity = 0.5 glow_radius_limit = 150 glow_spread = 8.0 glow_threshold = 0.03 horizontal_offset = 100 vertical_offset = 100 shadow_color = \#000000FF shadow_radius = 100 light_type = circular [core] plugins = \ winshadows \ autostart \ command \ move \ resize \ place \ vswitch \ follow-focus # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Background might be useful if you are testing decorations background_color = \#FFFFFFFF # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" #test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = center wayfire-shadows-0.0~git20240327.81699f6/testconfig/decoborder.ini000066400000000000000000000034051460112455300241270ustar00rootroot00000000000000# shadows + glow + decoration [winshadows] clip_shadow_inside = true glow_color = \#FF00FFFF glow_enabled = true glow_intensity = 2.5 glow_radius_limit = 50 glow_spread = 3.0 glow_threshold = 0.00 horizontal_offset = 5 vertical_offset = 10 shadow_color = \#000000FF shadow_radius = 80 [decoration] active_color = \#FFFFFFFF border_size = 30 inactive_color = \#999999FF title_height = 30 [core] plugins = \ winshadows \ autostart \ command \ decoration \ move \ resize \ place \ vswitch \ follow-focus # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Background might be useful if you are testing decorations background = swaybg --color "\#322d3d" # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = random wayfire-shadows-0.0~git20240327.81699f6/testconfig/gaussian.ini000066400000000000000000000033151460112455300236310ustar00rootroot00000000000000# extreme gaussian [winshadows] clip_shadow_inside = true glow_color = \#3584E4FF glow_enabled = false glow_intensity = 0.5 glow_radius_limit = 150 glow_spread = 8.0 glow_threshold = 0.03 horizontal_offset = 100 vertical_offset = 100 shadow_color = \#000000FF shadow_radius = 100 light_type = gaussian [core] plugins = \ winshadows \ autostart \ command \ move \ resize \ place \ vswitch \ follow-focus \ showrepaint # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Background might be useful if you are testing decorations background_color = \#FFFFFFFF # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" #test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = center [showrepaint] toggle = KEY_Rwayfire-shadows-0.0~git20240327.81699f6/testconfig/glow.ini000066400000000000000000000032141460112455300227650ustar00rootroot00000000000000# shadows and focus glow [winshadows] clip_shadow_inside = true glow_color = \#3584E4FF glow_enabled = true glow_intensity = 0.5 glow_radius_limit = 150 glow_spread = 8.0 glow_threshold = 0.03 horizontal_offset = 5 vertical_offset = 10 shadow_color = \#00000078 shadow_radius = 80 [core] plugins = \ winshadows \ autostart \ command \ move \ resize \ place \ vswitch \ follow-focus # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Background might be useful if you are testing decorations background = swaybg --color "\#322d3d" # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = random wayfire-shadows-0.0~git20240327.81699f6/testconfig/minimal.ini000066400000000000000000000025571460112455300234540ustar00rootroot00000000000000# only shadows [core] plugins = \ winshadows \ autostart \ command \ move \ resize \ place \ vswitch # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Background might be useful if you are testing decorations background = swaybg --color "\#322d3d" # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = random wayfire-shadows-0.0~git20240327.81699f6/testconfig/overscale.ini000066400000000000000000000032641460112455300240050ustar00rootroot00000000000000# extreme square [winshadows] clip_shadow_inside = true glow_color = \#3584E4FF glow_enabled = false glow_intensity = 0.5 glow_radius_limit = 150 glow_spread = 8.0 glow_threshold = 0.03 horizontal_offset = 20 vertical_offset = 30 shadow_color = \#00000080 shadow_radius = 10 light_type = gaussian overscale = 2.0 [core] plugins = \ winshadows \ autostart \ command \ move \ resize \ place \ vswitch \ decoration \ follow-focus # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Background might be useful if you are testing decorations background_color = \#EEEEEEFF # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" #test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = center wayfire-shadows-0.0~git20240327.81699f6/testconfig/square.ini000066400000000000000000000032261460112455300233200ustar00rootroot00000000000000# extreme square [winshadows] clip_shadow_inside = true glow_color = \#3584E4FF glow_enabled = false glow_intensity = 0.5 glow_radius_limit = 150 glow_spread = 8.0 glow_threshold = 0.03 horizontal_offset = 100 vertical_offset = 100 shadow_color = \#000000FF shadow_radius = 100 light_type = square [core] plugins = \ winshadows \ autostart \ command \ move \ resize \ place \ vswitch \ follow-focus # Close focused window. close_top_view = KEY_Q # server-side decorations to make testing decorations easier preferred_decoration_mode = server xwayland = false # Background might be useful if you are testing decorations background_color = \#FFFFFFFF # Startup commands ───────────────────────────────────────────────────────────── [autostart] # Disable panel, dock and default background autostart_wf_shell = false # Start some terminal windows for testing here! test1 = sh -c "alacritty || foot || gnome-terminal" #test2 = sh -c "alacritty || foot || gnome-terminal" # Bindings ─────────────────────────────────────────────────────────────── [command] # Start a terminal binding_terminal = KEY_ENTER command_terminal = sh -c "alacritty || foot || gnome-terminal" # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Place windows randomly [place] mode = center wayfire-shadows-0.0~git20240327.81699f6/winshadows.cpp000066400000000000000000000107471460112455300220520ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "node.hpp" struct view_shadow_data : wf::custom_data_t { view_shadow_data(std::shared_ptr shadow_ptr) : shadow_ptr(shadow_ptr) {}; std::shared_ptr shadow_ptr; }; class wayfire_shadows : public wf::plugin_interface_t { const std::string surface_data_name = "shadow_surface"; wf::view_matcher_t enabled_views{"winshadows/enabled_views"}; wf::option_wrapper_t include_undecorated_views{"winshadows/include_undecorated_views"}; // update new views wf::signal::connection_t on_view_mapped = [=](auto *data) { update_view_decoration(data->view); }; // update when view enables or disables server side decoration wf::signal::connection_t on_view_updated = [=](auto *data) { update_view_decoration(data->view); }; // update on tile state change, such that it is possible to exclude tiled windows wf::signal::connection_t on_view_tiled = [=](auto *data) { update_view_decoration(data->view); }; public: void init() override { wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_view_updated); wf::get_core().connect(&on_view_tiled); for (auto &view : wf::get_core().get_all_views()) { update_view_decoration(view); } } void fini() override { wf::get_core().disconnect(&on_view_mapped); wf::get_core().disconnect(&on_view_updated); wf::get_core().disconnect(&on_view_tiled); for (auto &view : wf::get_core().get_all_views()) { deinit_view(view); } } /** * Checks whether the given view has server side decoration and is in * the white list. * * @param view The view to match * @return Whether the view should get a shadow. */ bool is_view_shadow_enabled(wayfire_toplevel_view view) { return enabled_views.matches(view) && (is_view_decorated(view) || include_undecorated_views); } bool is_view_decorated(wayfire_toplevel_view view) { return view->should_be_decorated(); } const wf::scene::floating_inner_ptr& get_shadow_root_node(wayfire_view view) const { return view->get_surface_root_node(); } wf::wl_idle_call idle_deactivate; void update_view_decoration(wayfire_view view) { auto toplevel = wf::toplevel_cast(view); if (toplevel) { if (is_view_shadow_enabled(toplevel)) { auto shadow_data = view->get_data(surface_data_name); if (!shadow_data) { // No shadow yet, create it now. init_view(toplevel); } else { // in some situations the shadow node might have been removed due to unmap, but the view is reused (including the custom data) auto shadow_root = get_shadow_root_node(view); if (shadow_data->shadow_ptr->parent() != shadow_root.get()) { wf::scene::add_back(shadow_root, shadow_data->shadow_ptr); } } } else { deinit_view(view); } } } bool is_view_initialized(wayfire_view view) { return view->has_data(surface_data_name); } void init_view(wayfire_toplevel_view view) { // create the shadow node and add it to the view auto node = std::make_shared(view); wf::scene::add_back(get_shadow_root_node(view), node); // store the shadow node in the view so we can remove it later auto view_data = std::make_unique(node); view->store_data(std::move(view_data), surface_data_name); view->damage(); } void deinit_view(wayfire_view view) { auto view_data = view->get_data(surface_data_name); if (view_data != nullptr) { wf::scene::remove_child(view_data->shadow_ptr); view->damage(); view->erase_data(surface_data_name); } } }; DECLARE_WAYFIRE_PLUGIN(wayfire_shadows); wayfire-shadows-0.0~git20240327.81699f6/winshadows.xml000066400000000000000000000114121460112455300220560ustar00rootroot00000000000000 <_short>Window Shadows <_long>Server Side Shadows for windows on Wayfire Effects <_short>Shadow <_short>Glow <_long>Make windows edges emit light when focused <_short>Advanced