From 1881b01d3fe1805d371589969b4a75b9cccd8d26 Mon Sep 17 00:00:00 2001 From: Erik Reider <35975961+ErikReider@users.noreply.github.com> Date: Sat, 12 Nov 2022 01:38:09 +0100 Subject: [PATCH] Per application color saturation support (#21) * Initial implementation without fullscreen support * Limit saturation to 2 * Fixed saturation not working for fullscreen applications like CSGO * Fixed saturation ignoring border radius * Updated README and sway.5 man page * Rebased from Master * Added command to README * Fixed nitpicks --- README.md | 4 +++ include/sway/commands.h | 1 + include/sway/desktop/fx_renderer.h | 5 +-- include/sway/tree/container.h | 2 ++ sway/commands.c | 1 + sway/commands/saturation.c | 43 +++++++++++++++++++++++ sway/desktop/fx_renderer.c | 9 ++--- sway/desktop/output.c | 4 ++- sway/desktop/render.c | 48 ++++++++++++++++++-------- sway/desktop/shaders/tex_external.frag | 13 ++++++- sway/desktop/shaders/tex_rgba.frag | 13 ++++++- sway/desktop/shaders/tex_rgbx.frag | 12 ++++++- sway/meson.build | 1 + sway/sway.5.scd | 5 +++ sway/tree/container.c | 1 + 15 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 sway/commands/saturation.c diff --git a/README.md b/README.md index c7b09db6..0223fc39 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,17 @@ Sway is an incredible window manager, and certainly one of the most well established wayland window managers. However, it is restricted to only include the functionality that existed in i3. This fork ditches the simple wlr_renderer, and replaces it with our fx_renderer, capable of rendering with fancy GLES2 effects. This, along with a couple of minor changes, expands sway's featureset to include the following: + **Anti-aliased rounded corners, borders, and titlebars** ++ **Per application saturation control**: Allows the user to set the saturation (Digital Vibrance) for specific applications. Great for some FPS games! + **Scratchpad treated as minimize**: Allows docks, or panels with a taskbar, to correctly interpret minimize / unminimize requests ([thanks to LCBCrion](https://github.com/swaywm/sway/issues/6457)) + **Add a nix flake to the repo**: Allows nixos users to easily contribute to and test this project ## New Configuration Options + + Corner radius: `corner_radius ` ++ Application saturation: `for_window [CRITERIA HERE] saturation 2.0>` ## Roadmap + + fade in / out animations + window movement animations + drop shadows diff --git a/include/sway/commands.h b/include/sway/commands.h index 1383295d..6f39c0c8 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -159,6 +159,7 @@ sway_cmd cmd_new_float; sway_cmd cmd_new_window; sway_cmd cmd_nop; sway_cmd cmd_opacity; +sway_cmd cmd_saturation; sway_cmd cmd_new_float; sway_cmd cmd_new_window; sway_cmd cmd_no_focus; diff --git a/include/sway/desktop/fx_renderer.h b/include/sway/desktop/fx_renderer.h index e3b23411..0bbfcec3 100644 --- a/include/sway/desktop/fx_renderer.h +++ b/include/sway/desktop/fx_renderer.h @@ -16,6 +16,7 @@ struct gles2_tex_shader { GLint size; GLint position; GLint radius; + GLint saturation; GLint has_titlebar; }; @@ -79,10 +80,10 @@ void fx_renderer_scissor(struct wlr_box *box); bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], - float alpha, int radius, const bool has_titlebar); + float alpha, int radius, float saturation, const bool has_titlebar); bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, - const struct wlr_box *dst_box, const float matrix[static 9], float alpha, int radius, + const struct wlr_box *dst_box, const float matrix[static 9], float alpha, int radius, float saturation, const bool has_titlebar); void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index e8aa778a..ebe9568c 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -113,6 +113,8 @@ struct sway_container { // Hidden scratchpad containers have a NULL parent. bool scratchpad; + float saturation; + float alpha; int corner_radius; diff --git a/sway/commands.c b/sway/commands.c index d60cf84b..986da495 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -127,6 +127,7 @@ static const struct cmd_handler command_handlers[] = { { "reload", cmd_reload }, { "rename", cmd_rename }, { "resize", cmd_resize }, + { "saturation", cmd_saturation }, { "scratchpad", cmd_scratchpad }, { "shortcuts_inhibitor", cmd_shortcuts_inhibitor }, { "split", cmd_split }, diff --git a/sway/commands/saturation.c b/sway/commands/saturation.c new file mode 100644 index 00000000..35f02128 --- /dev/null +++ b/sway/commands/saturation.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include "sway/commands.h" +#include "sway/tree/view.h" +#include "log.h" + +struct cmd_results *cmd_saturation(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "saturation", EXPECTED_AT_LEAST, 1))) { + return error; + } + + struct sway_container *con = config->handler_context.container; + + if (con == NULL) { + return cmd_results_new(CMD_FAILURE, "No current container"); + } + + char *err; + float val = strtof(argc == 1 ? argv[0] : argv[1], &err); + if (*err) { + return cmd_results_new(CMD_INVALID, "saturation float invalid"); + } + + if (!strcasecmp(argv[0], "plus")) { + val = con->saturation + val; + } else if (!strcasecmp(argv[0], "minus")) { + val = con->saturation - val; + } else if (argc > 1 && strcasecmp(argv[0], "set")) { + return cmd_results_new(CMD_INVALID, + "Expected: set|plus|minus <0..2>: %s", argv[0]); + } + + if (val < 0 || val > 2) { + return cmd_results_new(CMD_FAILURE, "saturation value out of bounds"); + } + + con->saturation = val; + container_damage_whole(con); + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/desktop/fx_renderer.c b/sway/desktop/fx_renderer.c index 2b5ba060..8963f06e 100644 --- a/sway/desktop/fx_renderer.c +++ b/sway/desktop/fx_renderer.c @@ -99,6 +99,7 @@ bool init_frag_shader(struct gles2_tex_shader *shader, GLuint prog) { shader->size = glGetUniformLocation(prog, "size"); shader->position = glGetUniformLocation(prog, "position"); shader->radius = glGetUniformLocation(prog, "radius"); + shader->saturation = glGetUniformLocation(prog, "saturation"); shader->has_titlebar = glGetUniformLocation(prog, "has_titlebar"); return true; } @@ -254,8 +255,7 @@ void fx_renderer_scissor(struct wlr_box *box) { bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], - float alpha, int radius, const bool has_titlebar) { - + float alpha, int radius, float saturation, const bool has_titlebar) { assert(wlr_texture_is_gles2(wlr_texture)); struct wlr_gles2_texture_attribs texture_attrs; wlr_gles2_texture_get_attribs(wlr_texture, &texture_attrs); @@ -312,6 +312,7 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t glUniform1i(shader->tex, 0); glUniform1f(shader->alpha, alpha); glUniform1f(shader->has_titlebar, has_titlebar); + glUniform1f(shader->saturation, saturation); // rounded corners glUniform2f(shader->size, dst_box->width, dst_box->height); @@ -347,14 +348,14 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, const struct wlr_box *dst_box, const float matrix[static 9], float alpha, int radius, - const bool has_titlebar) { + float saturation, const bool has_titlebar) { struct wlr_fbox src_box = { .x = 0, .y = 0, .width = wlr_texture->width, .height = wlr_texture->height, }; - return fx_render_subtexture_with_matrix(renderer, wlr_texture, &src_box, dst_box, matrix, alpha, radius, has_titlebar); + return fx_render_subtexture_with_matrix(renderer, wlr_texture, &src_box, dst_box, matrix, alpha, radius, saturation, has_titlebar); } void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 107e5080..b6d19dd6 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -509,7 +509,9 @@ static int output_repaint_timer_handler(void *data) { fullscreen_con = workspace->current.fullscreen; } - if (fullscreen_con && fullscreen_con->view && !debug.noscanout) { + if (fullscreen_con && fullscreen_con->view && !debug.noscanout + // Only output to monitor without compositing when saturation is changed + && fullscreen_con->saturation == 1.0f) { // Try to scan-out the fullscreen view static bool last_scanned_out = false; bool scanned_out = diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 83fe8ca6..628718c0 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -1,4 +1,5 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include #include @@ -32,6 +33,7 @@ struct render_data { pixman_region32_t *damage; float alpha; + float saturation; int corner_radius; bool has_titlebar; struct wlr_box *clip_box; @@ -103,7 +105,7 @@ static void set_scale_filter(struct wlr_output *wlr_output, static void render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, struct wlr_texture *texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, - const float matrix[static 9], float alpha, int corner_radius, bool has_titlebar) { + const float matrix[static 9], float alpha, int corner_radius, float saturation, bool has_titlebar) { struct sway_output *output = wlr_output->data; struct fx_renderer *renderer = output->server->renderer; @@ -123,9 +125,11 @@ static void render_texture(struct wlr_output *wlr_output, scissor_output(wlr_output, &rects[i]); set_scale_filter(wlr_output, texture, output->scale_filter); if (src_box != NULL) { - fx_render_subtexture_with_matrix(renderer, texture, src_box, dst_box, matrix, alpha, corner_radius, has_titlebar); + fx_render_subtexture_with_matrix(renderer, texture, src_box, dst_box, matrix, + alpha, corner_radius, saturation, has_titlebar); } else { - fx_render_texture_with_matrix(renderer, texture, dst_box, matrix, alpha, corner_radius, has_titlebar); + fx_render_texture_with_matrix(renderer, texture, dst_box, matrix, + alpha, corner_radius, saturation, has_titlebar); } } @@ -140,6 +144,7 @@ static void render_surface_iterator(struct sway_output *output, struct wlr_output *wlr_output = output->wlr_output; pixman_region32_t *output_damage = data->damage; float alpha = data->alpha; + float saturation = data->saturation; int corner_radius = data->corner_radius; bool has_titlebar = data->has_titlebar; @@ -169,7 +174,7 @@ static void render_surface_iterator(struct sway_output *output, scale_box(&dst_box, wlr_output->scale); render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, - matrix, alpha, corner_radius * wlr_output->scale, has_titlebar); + matrix, alpha, corner_radius * wlr_output->scale, saturation, has_titlebar); wlr_presentation_surface_sampled_on_output(server.presentation, surface, wlr_output); @@ -180,6 +185,7 @@ static void render_layer_toplevel(struct sway_output *output, struct render_data data = { .damage = damage, .alpha = 1.0f, + .saturation = 1.0f, .corner_radius = 0, .has_titlebar = false, }; @@ -192,6 +198,7 @@ static void render_layer_popups(struct sway_output *output, struct render_data data = { .damage = damage, .alpha = 1.0f, + .saturation = 1.0f, .corner_radius = 0, .has_titlebar = false, }; @@ -205,6 +212,7 @@ static void render_unmanaged(struct sway_output *output, struct render_data data = { .damage = damage, .alpha = 1.0f, + .saturation = 1.0f, .corner_radius = 0, .has_titlebar = false, }; @@ -218,6 +226,7 @@ static void render_drag_icons(struct sway_output *output, struct render_data data = { .damage = damage, .alpha = 1.0f, + .saturation = 1.0f, .corner_radius = 0, .has_titlebar = false, }; @@ -335,10 +344,12 @@ void premultiply_alpha(float color[4], float opacity) { } static void render_view_toplevels(struct sway_view *view, struct sway_output *output, - pixman_region32_t *damage, float alpha, int corner_radius, bool has_titlebar) { + pixman_region32_t *damage, float alpha, int corner_radius, + float saturation, bool has_titlebar) { struct render_data data = { .damage = damage, .alpha = alpha, + .saturation = saturation, .corner_radius = corner_radius, .has_titlebar = has_titlebar, }; @@ -360,7 +371,8 @@ static void render_view_toplevels(struct sway_view *view, struct sway_output *ou } static void render_view_popups(struct sway_view *view, struct sway_output *output, - pixman_region32_t *damage, float alpha, int corner_radius, bool has_titlebar) { + pixman_region32_t *damage, float alpha, int corner_radius, + float saturation, bool has_titlebar) { struct render_data data = { .damage = damage, .alpha = alpha, @@ -372,7 +384,8 @@ static void render_view_popups(struct sway_view *view, struct sway_output *outpu } static void render_saved_view(struct sway_view *view, struct sway_output *output, - pixman_region32_t *damage, float alpha, int corner_radius, bool has_titlebar) { + pixman_region32_t *damage, float alpha, int corner_radius, + float saturation, bool has_titlebar) { struct wlr_output *wlr_output = output->wlr_output; if (wl_list_empty(&view->saved_buffers)) { @@ -424,7 +437,8 @@ static void render_saved_view(struct sway_view *view, struct sway_output *output scale_box(&dst_box, wlr_output->scale); render_texture(wlr_output, damage, saved_buf->buffer->texture, - &saved_buf->source_box, &dst_box, matrix, alpha, corner_radius * wlr_output->scale, has_titlebar); + &saved_buf->source_box, &dst_box, matrix, alpha, corner_radius * wlr_output->scale, + saturation, has_titlebar); } // FIXME: we should set the surface that this saved buffer originates from @@ -439,9 +453,11 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage, struct sway_container *con, struct border_colors *colors, bool has_titlebar) { struct sway_view *view = con->view; if (!wl_list_empty(&view->saved_buffers)) { - render_saved_view(view, output, damage, con->alpha, con->corner_radius, has_titlebar); + render_saved_view(view, output, damage, con->alpha, con->corner_radius, + con->saturation, has_titlebar); } else if (view->surface) { - render_view_toplevels(view, output, damage, con->alpha, con->corner_radius, has_titlebar); + render_view_toplevels(view, output, damage, con->alpha, con->corner_radius, + con->saturation, has_titlebar); } if (con->current.border == B_NONE || con->current.border == B_CSD) { @@ -679,7 +695,7 @@ static void render_titlebar(struct sway_output *output, texture_box.width = ob_inner_width; } render_texture(output->wlr_output, output_damage, marks_texture, - NULL, &texture_box, matrix, con->alpha, 0, false); + NULL, &texture_box, matrix, con->alpha, 0, 1.0f, false); // Padding above memcpy(&color, colors->background, sizeof(float) * 4); @@ -755,7 +771,7 @@ static void render_titlebar(struct sway_output *output, } render_texture(output->wlr_output, output_damage, title_texture, - NULL, &texture_box, matrix, con->alpha, 0, false); + NULL, &texture_box, matrix, con->alpha, 0, 1.0f, false); // Padding above memcpy(&color, colors->background, sizeof(float) * 4); @@ -1274,9 +1290,11 @@ void output_render(struct sway_output *output, struct timespec *when, if (fullscreen_con->view) { if (!wl_list_empty(&fullscreen_con->view->saved_buffers)) { - render_saved_view(fullscreen_con->view, output, damage, 1.0f, 0, false); + render_saved_view(fullscreen_con->view, output, damage, 1.0f, 0, + fullscreen_con->saturation, false); } else if (fullscreen_con->view->surface) { - render_view_toplevels(fullscreen_con->view, output, damage, 1.0f, 0, false); + render_view_toplevels(fullscreen_con->view, output, damage, 1.0f, 0, + fullscreen_con->saturation, false); } } else { render_container(output, damage, fullscreen_con, @@ -1330,7 +1348,7 @@ void output_render(struct sway_output *output, struct timespec *when, struct sway_container *focus = seat_get_focused_container(seat); if (focus && focus->view) { render_view_popups(focus->view, output, damage, focus->alpha, - focus->corner_radius, focus->current.border == B_NORMAL); + focus->corner_radius, focus->saturation, focus->current.border == B_NORMAL); } render_overlay: diff --git a/sway/desktop/shaders/tex_external.frag b/sway/desktop/shaders/tex_external.frag index 0703a05f..d31cc990 100644 --- a/sway/desktop/shaders/tex_external.frag +++ b/sway/desktop/shaders/tex_external.frag @@ -9,9 +9,20 @@ uniform vec2 size; uniform vec2 position; uniform float radius; uniform bool has_titlebar; +uniform float saturation; +const vec3 saturation_weight = vec3(0.2125, 0.7154, 0.0721); void main() { - gl_FragColor = texture2D(texture0, v_texcoord) * alpha; + // Saturation + if (saturation != 1.0) { + vec4 pixColor = texture2D(texture0, v_texcoord); + vec3 irgb = pixColor.rgb; + vec3 target = vec3(dot(irgb, saturation_weight)); + gl_FragColor = vec4(mix(target, irgb, saturation), pixColor.a) * alpha; + } else { + gl_FragColor = texture2D(texture0, v_texcoord) * alpha; + } + if (!has_titlebar || gl_FragCoord.y - position.y > radius) { vec2 corner_distance = min(gl_FragCoord.xy - position, size + position - gl_FragCoord.xy); if (max(corner_distance.x, corner_distance.y) < radius) { diff --git a/sway/desktop/shaders/tex_rgba.frag b/sway/desktop/shaders/tex_rgba.frag index 95f58987..2a9dbccb 100644 --- a/sway/desktop/shaders/tex_rgba.frag +++ b/sway/desktop/shaders/tex_rgba.frag @@ -7,9 +7,20 @@ uniform vec2 size; uniform vec2 position; uniform float radius; uniform bool has_titlebar; +uniform float saturation; +const vec3 saturation_weight = vec3(0.2125, 0.7154, 0.0721); void main() { - gl_FragColor = texture2D(tex, v_texcoord) * alpha; + // Saturation + if (saturation != 1.0) { + vec4 pixColor = texture2D(tex, v_texcoord); + vec3 irgb = pixColor.rgb; + vec3 target = vec3(dot(irgb, saturation_weight)); + gl_FragColor = vec4(mix(target, irgb, saturation), pixColor.a) * alpha; + } else { + gl_FragColor = texture2D(tex, v_texcoord) * alpha; + } + if (!has_titlebar || gl_FragCoord.y - position.y > radius) { vec2 corner_distance = min(gl_FragCoord.xy - position, size + position - gl_FragCoord.xy); if (max(corner_distance.x, corner_distance.y) < radius) { diff --git a/sway/desktop/shaders/tex_rgbx.frag b/sway/desktop/shaders/tex_rgbx.frag index 4a8b3756..b31c1bfd 100644 --- a/sway/desktop/shaders/tex_rgbx.frag +++ b/sway/desktop/shaders/tex_rgbx.frag @@ -7,9 +7,19 @@ uniform vec2 size; uniform vec2 position; uniform float radius; uniform bool has_titlebar; +uniform float saturation; +const vec3 saturation_weight = vec3(0.2125, 0.7154, 0.0721); void main() { - gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha; + // Saturation + if (saturation != 1.0) { + vec3 irgb = texture2D(tex, v_texcoord).rgb; + vec3 target = vec3(dot(irgb, saturation_weight)); + gl_FragColor = vec4(mix(target, irgb, saturation), 1.0) * alpha; + } else { + gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha; + } + if (!has_titlebar || gl_FragCoord.y - position.y > radius) { vec2 corner_distance = min(gl_FragCoord.xy - position, size + position - gl_FragCoord.xy); if (max(corner_distance.x, corner_distance.y) < radius) { diff --git a/sway/meson.build b/sway/meson.build index 85bd04ff..461fee87 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -73,6 +73,7 @@ sway_sources = files( 'commands/mark.c', 'commands/max_render_time.c', 'commands/opacity.c', + 'commands/saturation.c', 'commands/include.c', 'commands/input.c', 'commands/layout.c', diff --git a/sway/sway.5.scd b/sway/sway.5.scd index a245ae14..800c3b2a 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -767,6 +767,11 @@ The default colors are: Adjusts the opacity of the window between 0 (completely transparent) and 1 (completely opaque). If the operation is omitted, _set_ will be used. +*saturation* [set|plus|minus] + Adjusts the saturation (Digital Vibrance) of the window between 0 (black and + white) and 2 (over saturated which is suited for some FPS games) while 1 is + the default saturation. If the operation is omitted, _set_ will be used. + *tiling_drag* enable|disable|toggle Sets whether or not tiling containers can be dragged with the mouse. If _enabled_ (default), the _floating_mod_ can be used to drag tiling, as well diff --git a/sway/tree/container.c b/sway/tree/container.c index a2aa130b..684304c8 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -40,6 +40,7 @@ struct sway_container *container_create(struct sway_view *view) { c->pending.layout = L_NONE; c->view = view; c->alpha = 1.0f; + c->saturation = 1.0f; c->corner_radius = config->corner_radius; if (!view) {