From 258b2689d1e6c5b84b8b046c5d170a0132e59cec Mon Sep 17 00:00:00 2001 From: Guanzhong Chen Date: Sun, 26 Apr 2020 00:35:19 -0400 Subject: [PATCH 1/2] Add --background option to run kitty on the Wayland desktop --background-monitor can be used to specify exactly which monitor to run kitty on. --- glfw/glfw.py | 11 +- glfw/glfw3.h | 2 + glfw/internal.h | 2 + glfw/source-info.json | 3 + glfw/window.c | 7 + glfw/wl_init.c | 7 + glfw/wl_platform.h | 7 + glfw/wl_window.c | 92 +++++++- glfw/wlr-layer-shell-unstable-v1.xml | 301 +++++++++++++++++++++++++++ kitty/cli.py | 14 ++ kitty/fast_data_types.pyi | 4 +- kitty/glfw-wrapper.h | 2 + kitty/glfw.c | 9 +- kitty/main.py | 3 +- 14 files changed, 455 insertions(+), 9 deletions(-) create mode 100644 glfw/wlr-layer-shell-unstable-v1.xml diff --git a/glfw/glfw.py b/glfw/glfw.py index bdc6bf4c48d..b6ddea7e36e 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -31,6 +31,7 @@ class Env: wayland_scanner: str = '' wayland_scanner_code: str = '' wayland_protocols: Tuple[str, ...] = () + custom_wayland_protocols: Tuple[str, ...] = () def __init__( self, cc: str = '', cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [], @@ -99,7 +100,8 @@ def init_env(env: Env, pkg_config: Callable, pkg_version: Callable, at_least_ver scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.'))) ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code' ans.wayland_protocols = tuple(sinfo[module]['protocols']) - for p in ans.wayland_protocols: + ans.custom_wayland_protocols = tuple(sinfo[module]['custom_protocols']) + for p in ans.wayland_protocols + ans.custom_wayland_protocols: ans.sources.append(wayland_protocol_file_name(p)) ans.all_headers.append(wayland_protocol_file_name(p, 'h')) for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split(): @@ -119,8 +121,11 @@ def init_env(env: Env, pkg_config: Callable, pkg_version: Callable, at_least_ver def build_wayland_protocols(env: Env, Command: Callable, parallel_run: Callable, emphasis: Callable, newer: Callable, dest_dir: str) -> None: items = [] - for protocol in env.wayland_protocols: - src = os.path.join(env.wayland_packagedir, protocol) + protocols = [(protocol, os.path.join(env.wayland_packagedir, protocol)) + for protocol in env.wayland_protocols] + protocols += [(protocol, os.path.join(base, protocol)) + for protocol in env.custom_wayland_protocols] + for protocol, src in protocols: if not os.path.exists(src): raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol)) for ext in 'hc': diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 3d1aafc2a71..dedf6d8d5c0 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1034,6 +1034,8 @@ typedef enum GLFWMouseButton { #define GLFW_X11_INSTANCE_NAME 0x00024002 #define GLFW_WAYLAND_APP_ID 0x00025001 +#define GLFW_WAYLAND_BACKGROUND 0x00025002 +#define GLFW_WAYLAND_BACKGROUND_MONITOR 0x00025003 /*! @} */ #define GLFW_NO_API 0 diff --git a/glfw/internal.h b/glfw/internal.h index a14d0ffb3d3..624809ec3ea 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -315,6 +315,8 @@ struct _GLFWwndconfig } x11; struct { char appId[256]; + bool background; + char backgroundMonitor[256]; } wl; }; diff --git a/glfw/source-info.json b/glfw/source-info.json index cc2fb7ee84e..93830bcaaf2 100644 --- a/glfw/source-info.json +++ b/glfw/source-info.json @@ -79,6 +79,9 @@ "unstable/primary-selection/primary-selection-unstable-v1.xml", "unstable/text-input/text-input-unstable-v3.xml" ], + "custom_protocols": [ + "wlr-layer-shell-unstable-v1.xml" + ], "sources": [ "wl_init.c", "wl_monitor.c", diff --git a/glfw/window.c b/glfw/window.c index 9ad3add667d..80838f8a02b 100644 --- a/glfw/window.c +++ b/glfw/window.c @@ -460,6 +460,9 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_REFRESH_RATE: _glfw.hints.refreshRate = value; return; + case GLFW_WAYLAND_BACKGROUND: + _glfw.hints.window.wl.background = value; + return; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint); @@ -489,6 +492,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value) strncpy(_glfw.hints.window.wl.appId, value, sizeof(_glfw.hints.window.wl.appId) - 1); return; + case GLFW_WAYLAND_BACKGROUND_MONITOR: + strncpy(_glfw.hints.window.wl.backgroundMonitor, value, + sizeof(_glfw.hints.window.wl.backgroundMonitor) - 1); + return; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint); diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 734a2d41bc7..ef4bbfee770 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -644,6 +644,13 @@ static void registryHandleGlobal(void* data UNUSED, _glfwSetupWaylandPrimarySelectionDevice(); } } + else if (strcmp(interface, "zwlr_layer_shell_v1") == 0) + { + _glfw.wl.layer_shell = + wl_registry_bind(registry, name, + &zwlr_layer_shell_v1_interface, + 1); + } } diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index f35b55e947c..43fc1a3054a 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -59,6 +59,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-primary-selection-unstable-v1-client-protocol.h" #include "wl_text_input.h" +#include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) @@ -146,6 +147,10 @@ typedef struct _GLFWwindowWayland struct zxdg_toplevel_decoration_v1* decoration; } xdg; + struct { + struct zwlr_layer_surface_v1* surface; + } layer; + _GLFWcursor* currentCursor; double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY; @@ -261,6 +266,7 @@ typedef struct _GLFWlibraryWayland struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager; struct zwp_primary_selection_device_v1* primarySelectionDevice; struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection; + struct zwlr_layer_shell_v1* layer_shell; int compositorVersion; int seatVersion; @@ -342,6 +348,7 @@ typedef struct _GLFWcursorWayland void _glfwAddOutputWayland(uint32_t name, uint32_t version); void _glfwSetupWaylandDataDevice(void); void _glfwSetupWaylandPrimarySelectionDevice(void); +struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle); void animateCursorImage(id_type timer_id, void *data); struct wl_cursor* _glfwLoadCursor(GLFWCursorShape, struct wl_cursor_theme*); void destroy_data_offer(_GLFWWaylandDataOffer*); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index f7e329609ad..0c5390db7d5 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -568,6 +568,86 @@ static bool createXdgSurface(_GLFWwindow* window) return true; } +static void layerSurfaceHandleConfigure(void* data, + struct zwlr_layer_surface_v1* surface, + uint32_t serial, + uint32_t width, + uint32_t height) { + _GLFWwindow* window = data; + dispatchChangesAfterConfigure(window, width, height); + zwlr_layer_surface_v1_ack_configure(surface, serial); +} + + +static void layerSurfaceHandleClosed(void* data, + struct zwlr_layer_surface_v1* surface UNUSED) { + _GLFWwindow* window = data; + _glfwInputWindowCloseRequest(window); +} + +static const struct zwlr_layer_surface_v1_listener layerSurfaceListener = { + layerSurfaceHandleConfigure, + layerSurfaceHandleClosed, +}; + +static struct wl_output* findWlOutput(const char* name) +{ + int count; + GLFWmonitor** monitors = glfwGetMonitors(&count); + + for (int i = 0; i < count; ++i) + { + if (strcmp(glfwGetMonitorName(monitors[i]), name) == 0) + { + return glfwGetWaylandMonitor(monitors[i]); + } + } + return NULL; +} + +static bool createLayerSurface(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig) +{ + if (!_glfw.wl.layer_shell) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: layer-shell protocol unsupported"); + return false; + } + + struct wl_output* output = findWlOutput(wndconfig->wl.backgroundMonitor); + window->wl.layer.surface = + zwlr_layer_shell_v1_get_layer_surface(_glfw.wl.layer_shell, + window->wl.surface, output, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, + "kitty"); + + if (!window->wl.layer.surface) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: layer-surface creation failed"); + return false; + } + + zwlr_layer_surface_v1_set_size(window->wl.layer.surface, 0, 0); + zwlr_layer_surface_v1_set_anchor(window->wl.layer.surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); + zwlr_layer_surface_v1_set_exclusive_zone(window->wl.layer.surface, -1); + zwlr_layer_surface_v1_set_margin(window->wl.layer.surface, 0, 0, 0, 0); + zwlr_layer_surface_v1_set_keyboard_interactivity(window->wl.layer.surface, 0); + + zwlr_layer_surface_v1_add_listener(window->wl.layer.surface, + &layerSurfaceListener, + window); + + wl_surface_commit(window->wl.surface); + wl_display_roundtrip(_glfw.wl.display); + + return true; +} + static void incrementCursorImage(_GLFWwindow* window) { if (window && window->wl.decorations.focus == CENTRAL_WINDOW && window->cursorMode != GLFW_CURSOR_HIDDEN) { @@ -746,8 +826,16 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, if (wndconfig->visible) { - if (!createXdgSurface(window)) - return false; + if (wndconfig->wl.background) + { + if (!createLayerSurface(window, wndconfig)) + return false; + } + else + { + if (!createXdgSurface(window)) + return false; + } window->wl.visible = true; } diff --git a/glfw/wlr-layer-shell-unstable-v1.xml b/glfw/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 00000000000..adc6a176c08 --- /dev/null +++ b/glfw/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,301 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/kitty/cli.py b/kitty/cli.py index af565ed0664..8311cb10881 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -657,6 +657,20 @@ def options_spec() -> str: Control how the initial kitty window is created. +--background +type=bool-set +On Wayland, show the kitty window as the desktop background. + + +--background-monitor +When using --background, the name of the monitor to create the window on. + + +--mouse-passthrough +type=bool-set +Make the window transparent to mouse input. This is useful with --background. + + # Debugging options --version -v diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 43073ba6b0c..1f36a32ce4e 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -493,7 +493,9 @@ def create_os_window( wm_class_class: str, load_programs: Optional[Callable[[bool], None]] = None, x: int = -1, - y: int = -1 + y: int = -1, + background: bool = False, + background_monitor: Optional[str] = None ) -> int: pass diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 74a7a7fab93..9ab2c8c0d51 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -772,6 +772,8 @@ typedef enum GLFWMouseButton { #define GLFW_X11_INSTANCE_NAME 0x00024002 #define GLFW_WAYLAND_APP_ID 0x00025001 +#define GLFW_WAYLAND_BACKGROUND 0x00025002 +#define GLFW_WAYLAND_BACKGROUND_MONITOR 0x00025003 /*! @} */ #define GLFW_NO_API 0 diff --git a/kitty/glfw.c b/kitty/glfw.c index 950c1ad341f..9d40f70f4d0 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -609,9 +609,11 @@ native_window_handle(GLFWwindow *w) { static PyObject* create_os_window(PyObject UNUSED *self, PyObject *args) { int x = -1, y = -1; - char *title, *wm_class_class, *wm_class_name; + int background = 0; + int mouse_passthrough = 0; + char *title, *wm_class_class, *wm_class_name, *background_monitor; PyObject *load_programs = NULL, *get_window_size, *pre_show_callback; - if (!PyArg_ParseTuple(args, "OOsss|Oii", &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL; + if (!PyArg_ParseTuple(args, "OOsss|Oiippz", &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y, &background, &mouse_passthrough, &background_monitor)) return NULL; static bool is_first_window = true; if (is_first_window) { @@ -636,6 +638,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name); glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class); glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class); + glfwWindowHint(GLFW_WAYLAND_BACKGROUND, background); + glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, mouse_passthrough); + if (background_monitor) glfwWindowHintString(GLFW_WAYLAND_BACKGROUND_MONITOR, background_monitor); #endif if (global_state.num_os_windows >= MAX_CHILDREN) { diff --git a/kitty/main.py b/kitty/main.py index 3a7ce90f768..5e3c1cf41c4 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -157,7 +157,8 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) run_app.initial_window_size_func(get_os_window_sizing_data(opts), cached_values), pre_show_callback, args.title or appname, args.name or args.cls or appname, - args.cls or appname, load_all_shaders) + args.cls or appname, load_all_shaders, -1, -1, + args.background, args.mouse_passthrough, args.background_monitor) boss = Boss(opts, args, cached_values, global_shortcuts) boss.start(window_id) if bad_lines: From 4e55a8cf761ad9c3cdf6e4ce8ba8da60bd50218d Mon Sep 17 00:00:00 2001 From: Quantum Date: Sat, 25 Apr 2020 22:49:32 -0700 Subject: [PATCH 2/2] Make foreground opacity configurable --- kittens/diff/options/types.py | 4 ++-- kitty/cell_vertex.glsl | 4 ++-- kitty/options/definition.py | 8 ++++++++ kitty/options/parse.py | 3 +++ kitty/options/to-c-generated.h | 15 +++++++++++++++ kitty/options/types.py | 2 ++ kitty/shaders.c | 3 ++- kitty/state.h | 2 +- 8 files changed, 35 insertions(+), 6 deletions(-) diff --git a/kittens/diff/options/types.py b/kittens/diff/options/types.py index fa51dcc90d7..f8bd83f31bc 100644 --- a/kittens/diff/options/types.py +++ b/kittens/diff/options/types.py @@ -52,7 +52,7 @@ class Options: hunk_margin_bg: Color = Color(red=219, green=237, blue=255) margin_bg: Color = Color(red=250, green=251, blue=252) margin_fg: Color = Color(red=170, green=170, blue=170) - margin_filler_bg: typing.Optional[kitty.rgb.Color] = None + margin_filler_bg: typing.Union[kitty.rgb.Color, None] = None num_context_lines: int = 3 pygments_style: str = 'default' removed_bg: Color = Color(red=255, green=238, blue=240) @@ -61,7 +61,7 @@ class Options: search_bg: Color = Color(red=68, green=68, blue=68) search_fg: Color = Color(red=255, green=255, blue=255) select_bg: Color = Color(red=180, green=213, blue=254) - select_fg: typing.Optional[kitty.rgb.Color] = Color(red=0, green=0, blue=0) + select_fg: typing.Union[kitty.rgb.Color, None] = Color(red=0, green=0, blue=0) syntax_aliases: typing.Dict[str, str] = {'pyj': 'py', 'pyi': 'py', 'recipe': 'py'} title_bg: Color = Color(red=255, green=255, blue=255) title_fg: Color = Color(red=0, green=0, blue=0) diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index d57bb1f490f..4121b7f569b 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -12,7 +12,7 @@ // Inputs {{{ layout(std140) uniform CellRenderData { - float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg; + float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, foreground_opacity, cursor_text_uses_bg; uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted; @@ -184,7 +184,7 @@ void main() { fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + 1 + mark] + (ONE - has_mark) * fg_as_uint; foreground = color_to_vec(fg_as_uint); float has_dim = float((text_attrs >> DIM_SHIFT) & ONE); - effective_text_alpha = inactive_text_alpha * mix(1.0, dim_opacity, has_dim); + effective_text_alpha = foreground_opacity * inactive_text_alpha * mix(1.0, dim_opacity, has_dim); float in_url = float((is_selected & TWO) >> 1); decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint)); #ifdef USE_SELECTION_FG diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 413455f6f0d..657cd39c483 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1002,6 +1002,14 @@ ''' ) +opt('foreground_opacity', '1.0', + option_type='unit_float', ctype='float', + long_text=''' +The opacity of the foreground. A number between 0 and 1, where 1 is opaque and +0 is fully transparent. This has similar constraints to :opt:`background_opacity`. +''' + ) + opt('background_image', 'none', option_type='config_or_absolute_path', ctype='!background_image', long_text='Path to a background image. Must be in PNG format.' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index b98d19b2bd2..9cf1425bd5f 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -952,6 +952,9 @@ def force_ltr(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def foreground(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['foreground'] = to_color(val) + def foreground_opacity(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + ans['foreground_opacity'] = unit_float(val) + def hide_window_decorations(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['hide_window_decorations'] = hide_window_decorations(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 89d13e5bb15..ada1a1d8416 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -603,6 +603,19 @@ convert_from_opts_background_opacity(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } +static void +convert_from_python_foreground_opacity(PyObject *val, Options *opts) { + opts->foreground_opacity = PyFloat_AsFloat(val); +} + +static void +convert_from_opts_foreground_opacity(PyObject *py_opts, Options *opts) { + PyObject *ret = PyObject_GetAttrString(py_opts, "foreground_opacity"); + if (ret == NULL) return; + convert_from_python_foreground_opacity(ret, opts); + Py_DECREF(ret); +} + static void convert_from_python_background_image(PyObject *val, Options *opts) { background_image(val, opts); @@ -970,6 +983,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_background_opacity(py_opts, opts); if (PyErr_Occurred()) return false; + convert_from_opts_foreground_opacity(py_opts, opts); + if (PyErr_Occurred()) return false; convert_from_opts_background_image(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_image_layout(py_opts, opts); diff --git a/kitty/options/types.py b/kitty/options/types.py index bf7b9d2d21a..aa19ca207a6 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -346,6 +346,7 @@ 'font_size', 'force_ltr', 'foreground', + 'foreground_opacity', 'hide_window_decorations', 'inactive_border_color', 'inactive_tab_background', @@ -478,6 +479,7 @@ class Options: font_size: float = 11.0 force_ltr: bool = False foreground: Color = Color(red=221, green=221, blue=221) + foreground_opacity: float = 1.0 hide_window_decorations: int = 0 inactive_border_color: Color = Color(red=204, green=204, blue=204) inactive_tab_background: Color = Color(red=153, green=153, blue=153) diff --git a/kitty/shaders.c b/kitty/shaders.c index 60433c32803..74c0d6e6ce1 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -252,7 +252,7 @@ send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRende static inline void cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) { struct CellRenderData { - GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg; + GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, foreground_opacity, cursor_text_uses_bg; GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted; @@ -295,6 +295,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y; rd->inverted = inverted ? 1 : 0; rd->background_opacity = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f; + rd->foreground_opacity = os_window->is_semi_transparent ? OPT(foreground_opacity) : 1.0f; #define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name) rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg); diff --git a/kitty/state.h b/kitty/state.h index 9971565ad7f..14a6d797884 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -42,7 +42,7 @@ typedef struct { WindowTitleIn macos_show_window_title_in; int adjust_line_height_px, adjust_column_width_px; float adjust_line_height_frac, adjust_column_width_frac; - float background_opacity, dim_opacity; + float background_opacity, foreground_opacity, dim_opacity; char* background_image; BackgroundImageLayout background_image_layout;