From cf07b747fe8de145fe771a8db2b6286d51694cc0 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Fri, 27 Jun 2025 14:12:02 +0300 Subject: [PATCH] LibGUI/WindowServer: Implement per-window custom cursors --- .../libraries/LibGUI/include/LibGUI/Packet.h | 8 ++ .../libraries/LibGUI/include/LibGUI/Window.h | 1 + userspace/programs/WindowServer/Cursor.h | 6 +- userspace/programs/WindowServer/Window.h | 15 +++ .../programs/WindowServer/WindowServer.cpp | 97 ++++++++++++++++--- .../programs/WindowServer/WindowServer.h | 1 + userspace/programs/WindowServer/main.cpp | 1 + 7 files changed, 110 insertions(+), 19 deletions(-) diff --git a/userspace/libraries/LibGUI/include/LibGUI/Packet.h b/userspace/libraries/LibGUI/include/LibGUI/Packet.h index 47a6e63d..8eb236d0 100644 --- a/userspace/libraries/LibGUI/include/LibGUI/Packet.h +++ b/userspace/libraries/LibGUI/include/LibGUI/Packet.h @@ -211,6 +211,7 @@ namespace LibGUI WindowSetMaxSize, WindowSetFullscreen, WindowSetTitle, + WindowSetCursor, DestroyWindowEvent, CloseWindowEvent, @@ -297,6 +298,13 @@ namespace LibGUI BAN::String, title ); + DEFINE_PACKET( + WindowSetCursor, + uint32_t, width, + uint32_t, height, + BAN::Vector, pixels + ); + } namespace EventPacket diff --git a/userspace/libraries/LibGUI/include/LibGUI/Window.h b/userspace/libraries/LibGUI/include/LibGUI/Window.h index 7be5ccfa..27ce6153 100644 --- a/userspace/libraries/LibGUI/include/LibGUI/Window.h +++ b/userspace/libraries/LibGUI/include/LibGUI/Window.h @@ -50,6 +50,7 @@ namespace LibGUI void set_position(int32_t x, int32_t y); void set_cursor_visible(bool visible); + void set_cursor(uint32_t width, uint32_t height, BAN::Span pixels); Attributes get_attributes() const { return m_attributes; } void set_attributes(Attributes attributes); diff --git a/userspace/programs/WindowServer/Cursor.h b/userspace/programs/WindowServer/Cursor.h index bda61bdb..43390653 100644 --- a/userspace/programs/WindowServer/Cursor.h +++ b/userspace/programs/WindowServer/Cursor.h @@ -2,9 +2,9 @@ #include -static int32_t s_cursor_width = 17; -static int32_t s_cursor_height = 26; -static const char* s_cursor_data = +static int32_t s_default_cursor_width = 17; +static int32_t s_default_cursor_height = 26; +static const char* s_default_cursor_data = "!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`" "`Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`" "`Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`" diff --git a/userspace/programs/WindowServer/Window.h b/userspace/programs/WindowServer/Window.h index 910a557b..00ecca9d 100644 --- a/userspace/programs/WindowServer/Window.h +++ b/userspace/programs/WindowServer/Window.h @@ -11,6 +11,14 @@ class Window : public BAN::RefCounted { +public: + struct Cursor + { + uint32_t width; + uint32_t height; + BAN::Vector pixels; + }; + public: Window(int fd, const LibFont::Font& font) : m_font(font) @@ -49,6 +57,11 @@ public: Rectangle full_size() const { return { 0, 0, full_width(), full_height() }; } Rectangle full_area() const { return { full_x(), full_y(), full_width(), full_height() }; } + bool has_cursor() const { return m_cursor.has_value(); } + const Cursor& cursor() const { return m_cursor.value(); } + void set_cursor(Cursor&& cursor) { m_cursor = BAN::move(cursor); } + void remove_cursor() { m_cursor.clear(); } + LibGUI::Window::Attributes get_attributes() const { return m_attributes; }; void set_attributes(LibGUI::Window::Attributes attributes) { m_attributes = attributes; }; @@ -96,6 +109,8 @@ private: uint32_t* m_fb_addr { nullptr }; BAN::String m_title; + BAN::Optional m_cursor; + BAN::Vector m_title_bar_data; LibGUI::Window::Attributes m_attributes { LibGUI::Window::default_attributes }; diff --git a/userspace/programs/WindowServer/WindowServer.cpp b/userspace/programs/WindowServer/WindowServer.cpp index e8c20cc0..b21073df 100644 --- a/userspace/programs/WindowServer/WindowServer.cpp +++ b/userspace/programs/WindowServer/WindowServer.cpp @@ -350,6 +350,50 @@ void WindowServer::on_window_set_title(int fd, const LibGUI::WindowPacket::Windo invalidate(target_window->title_bar_area()); } +void WindowServer::on_window_set_cursor(int fd, const LibGUI::WindowPacket::WindowSetCursor& packet) +{ + auto target_window = find_window_with_fd(fd); + if (!target_window) + { + dwarnln("client tried to set cursor while not owning a window"); + return; + } + + if (BAN::Math::will_multiplication_overflow(packet.width, packet.height)) + { + dwarnln("client tried to set cursor with invalid size {}x{}", packet.width, packet.height); + return; + } + + if (packet.width * packet.height != packet.pixels.size()) + { + dwarnln("client tried to set cursor with buffer size mismatch {}x{}, {} pixels", packet.width, packet.height, packet.pixels.size()); + return; + } + + auto old_cursor = cursor_area(); + + if (packet.width == 0 || packet.height == 0) + target_window->remove_cursor(); + else + { + Window::Cursor cursor; + cursor.width = packet.width; + cursor.height = packet.height; + if (auto ret = cursor.pixels.resize(packet.pixels.size()); ret.is_error()) + { + dwarnln("failed to set cursor: {}", ret.error()); + return; + } + for (size_t i = 0; i < cursor.pixels.size(); i++) + cursor.pixels[i] = packet.pixels[i]; + target_window->set_cursor(BAN::move(cursor)); + } + + if (find_hovered_window() == target_window) + invalidate(cursor_area().get_bounding_box(old_cursor)); +} + void WindowServer::on_key_event(LibInput::KeyEvent event) { // Mod key is not passed to clients @@ -725,13 +769,25 @@ void WindowServer::invalidate(Rectangle area) ASSERT(m_background_image->width() == (uint64_t)m_framebuffer.width); ASSERT(m_background_image->height() == (uint64_t)m_framebuffer.height); + const Window::Cursor* window_cursor = nullptr; + if (auto window = this->find_hovered_window(); window && window->has_cursor()) + window_cursor = &window->cursor(); + const auto get_cursor_pixel = - [](int32_t rel_x, int32_t rel_y) -> BAN::Optional + [window_cursor](int32_t rel_x, int32_t rel_y) -> BAN::Optional { - const uint32_t offset = (rel_y * s_cursor_width + rel_x) * 4; - uint32_t r = (((s_cursor_data[offset + 0] - 33) << 2) | ((s_cursor_data[offset + 1] - 33) >> 4)); - uint32_t g = ((((s_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_cursor_data[offset + 2] - 33) >> 2)); - uint32_t b = ((((s_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_cursor_data[offset + 3] - 33))); + if (window_cursor) + { + const auto pixel = window_cursor->pixels[rel_y * window_cursor->width + rel_x]; + if ((pixel >> 24) == 0) + return {}; + return pixel & 0xFFFFFF; + } + + const uint32_t offset = (rel_y * s_default_cursor_width + rel_x) * 4; + uint32_t r = (((s_default_cursor_data[offset + 0] - 33) << 2) | ((s_default_cursor_data[offset + 1] - 33) >> 4)); + uint32_t g = ((((s_default_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_default_cursor_data[offset + 2] - 33) >> 2)); + uint32_t b = ((((s_default_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_default_cursor_data[offset + 3] - 33))); uint32_t color = (r << 16) | (g << 8) | b; if (color == 0xFF00FF) return {}; @@ -812,12 +868,9 @@ void WindowServer::invalidate(Rectangle area) if (!m_is_mouse_captured) { - const Rectangle cursor_area { - .x = m_cursor.x - m_focused_window->client_x(), - .y = m_cursor.y - m_focused_window->client_y(), - .width = s_cursor_width, - .height = s_cursor_height, - }; + auto cursor_area = this->cursor_area(); + cursor_area.x -= m_focused_window->client_x(); + cursor_area.y -= m_focused_window->client_y(); if (!area.get_overlap(cursor_area).has_value()) return; @@ -825,9 +878,9 @@ void WindowServer::invalidate(Rectangle area) const int32_t cursor_tl_dst_x = cursor_area.x * m_framebuffer.width / m_focused_window->client_width(); const int32_t cursor_tl_dst_y = cursor_area.y * m_framebuffer.height / m_focused_window->client_height(); - for (int32_t rel_y = 0; rel_y < s_cursor_height; rel_y++) + for (int32_t rel_y = 0; rel_y < cursor_area.height; rel_y++) { - for (int32_t rel_x = 0; rel_x < s_cursor_width; rel_x++) + for (int32_t rel_x = 0; rel_x < cursor_area.width; rel_x++) { const auto pixel = get_cursor_pixel(rel_x, rel_y); if (!pixel.has_value()) @@ -1201,9 +1254,21 @@ void WindowServer::sync() Rectangle WindowServer::cursor_area() const { - if (auto window = find_hovered_window(); window && !window->get_attributes().cursor_visible) - return { m_cursor.x, m_cursor.y, 0, 0 }; - return { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height }; + int32_t width = s_default_cursor_width; + int32_t height = s_default_cursor_height; + + if (auto window = find_hovered_window()) + { + if (!window->get_attributes().cursor_visible) + width = height = 0; + else if (window->has_cursor()) + { + width = window->cursor().width; + height = window->cursor().height; + } + } + + return { m_cursor.x, m_cursor.y, width, height }; } Rectangle WindowServer::resize_area(Position cursor) const diff --git a/userspace/programs/WindowServer/WindowServer.h b/userspace/programs/WindowServer/WindowServer.h index 796a21a0..338eb929 100644 --- a/userspace/programs/WindowServer/WindowServer.h +++ b/userspace/programs/WindowServer/WindowServer.h @@ -41,6 +41,7 @@ public: void on_window_set_max_size(int fd, const LibGUI::WindowPacket::WindowSetMaxSize&); void on_window_set_fullscreen(int fd, const LibGUI::WindowPacket::WindowSetFullscreen&); void on_window_set_title(int fd, const LibGUI::WindowPacket::WindowSetTitle&); + void on_window_set_cursor(int fd, const LibGUI::WindowPacket::WindowSetCursor&); void on_key_event(LibInput::KeyEvent event); void on_mouse_button(LibInput::MouseButtonEvent event); diff --git a/userspace/programs/WindowServer/main.cpp b/userspace/programs/WindowServer/main.cpp index 237539a4..c23e9570 100644 --- a/userspace/programs/WindowServer/main.cpp +++ b/userspace/programs/WindowServer/main.cpp @@ -371,6 +371,7 @@ int main() WINDOW_PACKET_CASE(WindowSetMaxSize, on_window_set_max_size); WINDOW_PACKET_CASE(WindowSetFullscreen, on_window_set_fullscreen); WINDOW_PACKET_CASE(WindowSetTitle, on_window_set_title); + WINDOW_PACKET_CASE(WindowSetCursor, on_window_set_cursor); #undef WINDOW_PACKET_CASE default: dprintln("unhandled packet type: {}", *reinterpret_cast(client_data.packet_buffer.data()));