feat(linux): measure the active system cursor size

This commit is contained in:
fufesou
2026-09-11 16:02:52 +08:00
parent c983d00437
commit eeff9eb121
3 changed files with 111 additions and 0 deletions

View File

@@ -53,6 +53,7 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(XCURSOR REQUIRED IMPORTED_TARGET xcursor)
# Wayland protocol for keyboard shortcuts inhibit
pkg_check_modules(WAYLAND_CLIENT IMPORTED_TARGET wayland-client)
@@ -126,6 +127,7 @@ apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::XCURSOR)
target_link_libraries(${BINARY_NAME} PRIVATE ${CMAKE_DL_LIBS})
# target_link_libraries(${BINARY_NAME} PRIVATE librustdesk)

106
flutter/linux/cursor_size.h Normal file
View File

@@ -0,0 +1,106 @@
#ifndef RUSTDESK_CURSOR_SIZE_H_
#define RUSTDESK_CURSOR_SIZE_H_
#include <flutter_linux/flutter_linux.h>
#include <X11/Xcursor/Xcursor.h>
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/gdkwayland.h>
#endif
#include <algorithm>
#include <memory>
#include <stdexcept>
namespace cursor_size {
template <typename IsVisible>
double VisibleSize(int width, int height, IsVisible visible) {
int left = width, top = height, right = -1, bottom = -1;
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
if (!visible(x, y)) continue;
left = std::min(left, x);
right = std::max(right, x);
top = std::min(top, y);
bottom = std::max(bottom, y);
}
}
if (right < left) throw std::runtime_error("System cursor has no visible pixels");
return std::max(right - left + 1, bottom - top + 1);
}
inline double WaylandSize(GtkWidget* view) {
g_autofree gchar* theme = nullptr;
gint size = 0;
g_object_get(gtk_widget_get_settings(view), "gtk-cursor-theme-name", &theme,
"gtk-cursor-theme-size", &size, nullptr);
// GTK uses 24 when the theme size is unset, and loads at the window scale.
constexpr int kDefaultThemeSize = 24;
if (size == 0) size = kDefaultThemeSize;
const int scale = gtk_widget_get_scale_factor(view);
if (size < 0 || scale <= 0 || size > G_MAXINT / scale) {
throw std::runtime_error("Invalid GTK cursor theme size or window scale");
}
auto* loaded = XcursorLibraryLoadImage("default", theme, size * scale);
// Match GTK's CSS default -> traditional left_ptr name mapping.
if (!loaded) loaded = XcursorLibraryLoadImage("left_ptr", theme, size * scale);
std::unique_ptr<XcursorImage, decltype(&XcursorImageDestroy)> image(
loaded, XcursorImageDestroy);
if (!image) throw std::runtime_error("Could not load the GTK system cursor");
// GTK reduces the buffer scale until both theme dimensions are divisible.
// For example, a 64px theme at 3x is displayed using a buffer scale of 2.
int cursor_scale = scale;
while (image->width % cursor_scale != 0 || image->height % cursor_scale != 0) {
--cursor_scale;
}
constexpr XcursorPixel kAlphaMask = 0xff000000;
return VisibleSize(image->width, image->height, [&](int x, int y) {
return (image->pixels[y * image->width + x] & kAlphaMask) != 0;
}) / cursor_scale;
}
inline double SystemSize(GtkWidget* view) {
GdkDisplay* display = gtk_widget_get_display(view);
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY(display)) return WaylandSize(view);
#endif
g_autoptr(GdkCursor) cursor = gdk_cursor_new_from_name(display, "default");
if (!cursor) throw std::runtime_error("Could not load the GDK system cursor");
g_autoptr(GdkPixbuf) image = gdk_cursor_get_image(cursor);
if (!image) throw std::runtime_error("Could not read the GDK system cursor");
const auto* pixels = gdk_pixbuf_read_pixels(image);
const int stride = gdk_pixbuf_get_rowstride(image);
const int channels = gdk_pixbuf_get_n_channels(image);
return VisibleSize(gdk_pixbuf_get_width(image), gdk_pixbuf_get_height(image),
[&](int x, int y) {
return !gdk_pixbuf_get_has_alpha(image) ||
pixels[y * stride + x * channels + channels - 1] != 0;
});
}
inline void Register(FlView* view) {
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
FlMethodChannel* channel = fl_method_channel_new(
fl_engine_get_binary_messenger(fl_view_get_engine(view)),
"org.rustdesk.rustdesk/cursor", FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(channel,
[](FlMethodChannel*, FlMethodCall* call, gpointer data) {
g_autoptr(GError) error = nullptr;
if (g_strcmp0(fl_method_call_get_name(call), "getSystemCursorSize") != 0) {
fl_method_call_respond_not_implemented(call, &error);
} else {
try {
g_autoptr(FlValue) value = fl_value_new_float(SystemSize(GTK_WIDGET(data)));
fl_method_call_respond_success(call, value, &error);
} catch (const std::exception& failure) {
fl_method_call_respond_error(call, "cursor_size", failure.what(), nullptr, &error);
}
}
if (error) g_warning("Cursor size response failed: %s", error->message);
}, view, nullptr);
g_object_set_data_full(G_OBJECT(view), "cursor-size-channel", channel, g_object_unref);
}
} // namespace cursor_size
#endif // RUSTDESK_CURSOR_SIZE_H_

View File

@@ -1,6 +1,7 @@
#include "my_application.h"
#include "bump_mouse.h"
#include "cursor_size.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
@@ -94,6 +95,7 @@ static void on_subwindow_created(FlPluginRegistry* registry) {
// Set up side button forwarding for sub-windows.
if (registry == NULL || !FL_IS_VIEW(registry)) return;
FlView* view = FL_VIEW(registry);
cursor_size::Register(view);
GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
if (toplevel != NULL && GTK_IS_WINDOW(toplevel)) {
FlMethodChannel* channel = side_buttons_create_channel(fl_view_get_engine(view));
@@ -178,6 +180,7 @@ static void my_application_activate(GApplication* application) {
(WindowCreatedCallback)on_subwindow_created);
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
cursor_size::Register(view);
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->host_channel = fl_method_channel_new(