diff --git a/kitty/border_fragment.glsl b/kitty/border_fragment.glsl index b3f9653aa..01832299d 100644 --- a/kitty/border_fragment.glsl +++ b/kitty/border_fragment.glsl @@ -1,7 +1,8 @@ #version GLSL_VERSION +uniform float background_opacity; in vec3 color; out vec4 final_color; void main() { - final_color = vec4(color, 1); + final_color = vec4(color, background_opacity); } diff --git a/kitty/config.py b/kitty/config.py index 3ea009a31..f12f968c8 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -214,6 +214,10 @@ def positive_float(x): return max(0, float(x)) +def unit_float(x): + return max(0, min(float(x), 1)) + + def adjust_line_height(x): if x.endswith('%'): return float(x[:-1].strip()) / 100.0 @@ -258,6 +262,7 @@ type_map = { 'macos_option_as_alt': to_bool, 'box_drawing_scale': box_drawing_scale, 'x11_bell_volume': int, + 'background_opacity': unit_float, } for name in ( diff --git a/kitty/glfw.c b/kitty/glfw.c index feda8e7ce..aad92d88b 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -226,6 +226,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { return NULL; } glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE); + bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false; + glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent); GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL); if (glfw_window == NULL) { fprintf(stderr, "Failed to create a window at size: %dx%d, using a standard size instead.\n", width, height); @@ -274,6 +276,14 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { w->is_focused = true; w->cursor_blink_zero_time = now; w->last_mouse_activity_at = now; + w->is_semi_transparent = glfwGetWindowAttrib(w->handle, GLFW_TRANSPARENT_FRAMEBUFFER); + if (want_semi_transparent && !w->is_semi_transparent) { + static bool warned = false; + if (!warned) { + fprintf(stderr, "Failed to enable transparency. This happens when your desktop environment does not support compositing.\n"); + warned = true; + } + } return PyLong_FromUnsignedLongLong(w->id); } diff --git a/kitty/kitty.conf b/kitty/kitty.conf index d8e0d73d6..591e47d79 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -43,6 +43,11 @@ foreground #dddddd # The background color background #000000 +# The opacity of the background. A number between 0 and 1, where 1 is opaque and 0 is fully transparent. +# Note that this will only work if supported by the OS (for instance, when using a compositor under X11). +# Be aware that using a value less than 1.0 is a performance hit. +background_opacity 1.0 + # The foreground for selections selection_foreground #000000 diff --git a/kitty/shaders.c b/kitty/shaders.c index df1187099..9000bc26b 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -337,7 +337,9 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL #undef SCALE static bool cell_constants_set = false; if (!cell_constants_set) { + bind_program(CELL_PROGRAM); glUniform1i(glGetUniformLocation(program_id(CELL_PROGRAM), "sprites"), SPRITE_MAP_UNIT); + bind_program(CELL_FOREGROUND_PROGRAM); glUniform1i(glGetUniformLocation(program_id(CELL_FOREGROUND_PROGRAM), "sprites"), SPRITE_MAP_UNIT); cell_constants_set = true; } @@ -377,7 +379,7 @@ draw_cursor(CursorRenderInfo *cursor, bool is_focused) { // }}} // Borders {{{ -enum BorderUniforms { BORDER_viewport, NUM_BORDER_UNIFORMS }; +enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, NUM_BORDER_UNIFORMS }; static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0}; static void @@ -385,9 +387,10 @@ init_borders_program() { Program *p = programs + BORDERS_PROGRAM; int left = NUM_BORDER_UNIFORMS; for (int i = 0; i < p->num_of_uniforms; i++, left--) { -#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) border_uniform_locations[BORDER_##which] = p->uniforms[i].location - SET_LOC(viewport); - else { fatal("Unknown uniform in borders program"); return; } +#define SET_LOC(which) (strcmp(p->uniforms[i].name, #which) == 0) border_uniform_locations[BORDER_##which] = p->uniforms[i].location + if SET_LOC(viewport); + else if SET_LOC(background_opacity); + else { fatal("Unknown uniform in borders program: %s", p->uniforms[i].name); return; } } if (left) { fatal("Left over uniforms in borders program"); return; } #undef SET_LOC @@ -416,6 +419,11 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu unmap_vao_buffer(vao_idx, 0); } bind_program(BORDERS_PROGRAM); + static bool constants_set = false; + if (!constants_set) { + constants_set = true; + glUniform1f(border_uniform_locations[BORDER_background_opacity], OPT(background_opacity)); + } glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height); bind_vertex_array(vao_idx); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects); diff --git a/kitty/state.c b/kitty/state.c index 500af4ec2..6981898f0 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -307,6 +307,7 @@ PYWRAP1(set_options) { S(focus_follows_mouse, PyObject_IsTrue); S(cursor_blink_interval, PyFloat_AsDouble); S(cursor_stop_blinking_after, PyFloat_AsDouble); + S(background_opacity, PyFloat_AsDouble); S(cursor_shape, PyLong_AsLong); S(x11_bell_volume, PyLong_AsLong); S(mouse_hide_wait, PyFloat_AsDouble); diff --git a/kitty/state.h b/kitty/state.h index caa38fb03..c0c8694ce 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -23,6 +23,7 @@ typedef struct { int adjust_line_height_px; int x11_bell_volume; float adjust_line_height_frac; + float background_opacity; } Options; typedef struct { @@ -101,6 +102,7 @@ typedef struct { bool viewport_size_dirty; double last_resize_at; bool has_pending_resizes; + bool is_semi_transparent; } OSWindow;