macOS: Implement background blurring

Uses a private API that allows us to control the amount of blurring.
While using a private API is obviously not ideal, it is used by both
iTerm.app and Apple's own Terminal.app, so hopefully it should stick
around. Fixes #6135
This commit is contained in:
Kovid Goyal 2023-06-27 08:48:16 +05:30
parent 326b81a970
commit 7a1bdb4ff1
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
15 changed files with 89 additions and 1 deletions

View file

@ -139,6 +139,7 @@ typedef struct _GLFWwindowNS
int width, height;
int fbWidth, fbHeight;
float xscale, yscale;
int blur_radius;
// The total sum of the distances the cursor has been warped
// since the last cursor motion event was processed

View file

@ -36,6 +36,8 @@
#include <string.h>
GLFWAPI int glfwCocoaSetBackgroundBlur(GLFWwindow *w, int radius);
static const char*
polymorphic_string_as_utf8(id string) {
if (string == nil) return "(nil)";
@ -1835,6 +1837,7 @@ static bool createNativeWindow(_GLFWwindow* window,
_glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height);
_glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight);
if (wndconfig->ns.blur_radius > 0) glfwCocoaSetBackgroundBlur((GLFWwindow*)window, wndconfig->ns.blur_radius);
return true;
}
@ -2969,6 +2972,18 @@ GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun
requestRenderFrame((_GLFWwindow*)w, callback);
}
GLFWAPI int glfwCocoaSetBackgroundBlur(GLFWwindow *w, int radius) {
_GLFWwindow* window = (_GLFWwindow*)w;
int orig = window->ns.blur_radius;
if (radius > -1 && radius != window->ns.blur_radius) {
extern OSStatus CGSSetWindowBackgroundBlurRadius(void* connection, NSInteger windowNumber, int radius);
extern void* CGSDefaultConnectionForThread(void);
CGSSetWindowBackgroundBlurRadius(CGSDefaultConnectionForThread(), [window->ns.object windowNumber], radius);
window->ns.blur_radius = radius;
}
return orig;
}
GLFWAPI int glfwGetCurrentSystemColorTheme(void) {
int theme_type = 0;
NSAppearance *changedAppearance = NSApp.effectiveAppearance;

View file

@ -249,6 +249,7 @@ def generate_wrappers(glfw_header: str) -> None:
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int* cocoa_mods)
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
int glfwCocoaSetBackgroundBlur(GLFWwindow *w, int blur_radius)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)

4
glfw/glfw3.h vendored
View file

@ -1034,6 +1034,10 @@ typedef enum {
SRGB_COLORSPACE = 1,
DISPLAY_P3_COLORSPACE = 2,
} GlfwCocoaColorSpaces;
/*! @brief macOS specific
* [window hint](@ref GLFW_COCOA_BLUR_RADIUS_hint).
*/
#define GLFW_COCOA_BLUR_RADIUS 0x00023005
/*! @brief X11 specific
* [window hint](@ref GLFW_X11_CLASS_NAME_hint).

1
glfw/internal.h vendored
View file

@ -313,6 +313,7 @@ struct _GLFWwndconfig
struct {
bool retina;
int color_space;
int blur_radius;
char frameName[256];
} ns;
struct {

5
glfw/window.c vendored
View file

@ -334,6 +334,8 @@ void glfwDefaultWindowHints(void)
_glfw.hints.window.ns.retina = true;
// use the default colorspace assigned by the system
_glfw.hints.window.ns.color_space = 0;
// no blur
_glfw.hints.window.ns.blur_radius = 0;
}
GLFWAPI void glfwWindowHint(int hint, int value)
@ -417,6 +419,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_COCOA_COLOR_SPACE:
_glfw.hints.window.ns.color_space = value;
return;
case GLFW_COCOA_BLUR_RADIUS:
_glfw.hints.window.ns.blur_radius = value;
return;
case GLFW_COCOA_GRAPHICS_SWITCHING:
_glfw.hints.context.nsgl.offline = value ? true : false;
return;

3
kitty/glfw-wrapper.c generated
View file

@ -443,6 +443,9 @@ load_glfw(const char* path) {
*(void **) (&glfwCocoaRequestRenderFrame_impl) = dlsym(handle, "glfwCocoaRequestRenderFrame");
if (glfwCocoaRequestRenderFrame_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwCocoaSetBackgroundBlur_impl) = dlsym(handle, "glfwCocoaSetBackgroundBlur");
if (glfwCocoaSetBackgroundBlur_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwGetX11Display_impl) = dlsym(handle, "glfwGetX11Display");
if (glfwGetX11Display_impl == NULL) dlerror(); // clear error indicator

8
kitty/glfw-wrapper.h generated
View file

@ -772,6 +772,10 @@ typedef enum {
SRGB_COLORSPACE = 1,
DISPLAY_P3_COLORSPACE = 2,
} GlfwCocoaColorSpaces;
/*! @brief macOS specific
* [window hint](@ref GLFW_COCOA_BLUR_RADIUS_hint).
*/
#define GLFW_COCOA_BLUR_RADIUS 0x00023005
/*! @brief X11 specific
* [window hint](@ref GLFW_X11_CLASS_NAME_hint).
@ -2216,6 +2220,10 @@ typedef void (*glfwCocoaRequestRenderFrame_func)(GLFWwindow*, GLFWcocoarenderfra
GFW_EXTERN glfwCocoaRequestRenderFrame_func glfwCocoaRequestRenderFrame_impl;
#define glfwCocoaRequestRenderFrame glfwCocoaRequestRenderFrame_impl
typedef int (*glfwCocoaSetBackgroundBlur_func)(GLFWwindow*, int);
GFW_EXTERN glfwCocoaSetBackgroundBlur_func glfwCocoaSetBackgroundBlur_impl;
#define glfwCocoaSetBackgroundBlur glfwCocoaSetBackgroundBlur_impl
typedef void* (*glfwGetX11Display_func)(void);
GFW_EXTERN glfwGetX11Display_func glfwGetX11Display_impl;
#define glfwGetX11Display glfwGetX11Display_impl

View file

@ -902,6 +902,11 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
#ifdef __APPLE__
glfwWindowHint(GLFW_COCOA_COLOR_SPACE, OPT(macos_colorspace));
if (OPT(background_blur) > 0 && OPT(background_opacity) < 1.f) {
glfwWindowHint(GLFW_COCOA_BLUR_RADIUS, MIN(OPT(background_blur), 128));
} else {
glfwWindowHint(GLFW_COCOA_BLUR_RADIUS, 0);
}
#else
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
@ -1081,6 +1086,17 @@ os_window_update_size_increments(OSWindow *window) {
}
}
void
update_background_blur(OSWindow *os_window) {
#ifdef __APPLE__
int new_blur_radius = 0;
if (os_window->background_opacity < 1.f && OPT(background_blur) > -1) new_blur_radius = OPT(background_blur);
glfwCocoaSetBackgroundBlur(os_window->handle, new_blur_radius);
#else
(void)os_window;
#endif
}
#ifdef __APPLE__
static bool
window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) {

View file

@ -1371,6 +1371,17 @@ config.
'''
)
opt('background_blur', '0', option_type='int', ctype='int',
long_text='''
Set to a positive value to enable background blur (blurring of the visuals
behind a transparent window) on platforms that support it. Only takes effect
when :opt:`background_opacity` is less than one. On macOS, this will also
control the :italic:`blur radius` (amount of blurring). Setting it to too high
a value will cause severe performance issues and/or rendering artifacts.
Usually, values up to 64 work well. Note that this might cause performance issues,
depending on how the platform implements it, so use with care.
''')
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.'

View file

@ -65,6 +65,9 @@ class Parser:
def background(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['background'] = to_color(val)
def background_blur(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['background_blur'] = int(val)
def background_image(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['background_image'] = config_or_absolute_path(val)

View file

@ -733,6 +733,19 @@ convert_from_opts_background_opacity(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_background_blur(PyObject *val, Options *opts) {
opts->background_blur = PyLong_AsLong(val);
}
static void
convert_from_opts_background_blur(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "background_blur");
if (ret == NULL) return;
convert_from_python_background_blur(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_background_image(PyObject *val, Options *opts) {
background_image(val, opts);
@ -1159,6 +1172,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_background_blur(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);

View file

@ -61,6 +61,7 @@ option_names = ( # {{{
'allow_hyperlinks',
'allow_remote_control',
'background',
'background_blur',
'background_image',
'background_image_layout',
'background_image_linear',
@ -480,6 +481,7 @@ class Options:
allow_hyperlinks: int = 1
allow_remote_control: choices_for_allow_remote_control = 'no'
background: Color = Color(0, 0, 0)
background_blur: int = 0
background_image: typing.Optional[str] = None
background_image_layout: choices_for_background_image_layout = 'tiled'
background_image_linear: bool = False
@ -511,7 +513,7 @@ class Options:
cursor_underline_thickness: float = 2.0
default_pointer_shape: choices_for_default_pointer_shape = 'beam'
detect_urls: bool = True
dim_opacity: float = 0.75
dim_opacity: float = 0.4
disable_ligatures: int = 0
draw_minimal_borders: bool = True
dynamic_background_opacity: bool = False

View file

@ -1093,6 +1093,7 @@ PYWRAP0(apply_options_update) {
get_platform_dependent_config_values(os_window->handle);
os_window->background_opacity = OPT(background_opacity);
os_window->is_damaged = true;
update_background_blur(os_window);
for (size_t t = 0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) {

View file

@ -89,6 +89,7 @@ typedef struct {
float val; AdjustmentUnit unit;
} underline_position, underline_thickness, strikethrough_position, strikethrough_thickness, cell_width, cell_height, baseline;
bool show_hyperlink_targets;
int background_blur;
} Options;
typedef struct WindowLogoRenderData {
@ -365,3 +366,4 @@ void update_ime_position(Window* w, Screen *screen);
bool update_ime_position_for_window(id_type window_id, bool force, int update_focus);
void set_ignore_os_keyboard_processing(bool enabled);
void update_menu_bar_title(PyObject *title UNUSED);
void update_background_blur(OSWindow *);