From c2b6ba0d5a35e3c762bedb4ed8c65a5d2cf32bc3 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Wed, 29 May 2024 16:00:54 +0300 Subject: [PATCH] Userspace: Start work on GUI and WindowServer Current implementation can create custom windows and each window has its own framebuffer. When window wants to write its framebuffer to the screen it will send a message to the WindowServer using unix sockets. --- CMakeLists.txt | 3 + LibGUI/CMakeLists.txt | 24 +++ LibGUI/Window.cpp | 115 ++++++++++ LibGUI/include/LibGUI/Window.h | 142 +++++++++++++ userspace/CMakeLists.txt | 2 + userspace/WindowServer/CMakeLists.txt | 18 ++ userspace/WindowServer/Cursor.h | 35 ++++ userspace/WindowServer/Framebuffer.cpp | 45 ++++ userspace/WindowServer/Framebuffer.h | 16 ++ userspace/WindowServer/Utils.h | 59 ++++++ userspace/WindowServer/Window.h | 45 ++++ userspace/WindowServer/WindowServer.cpp | 224 ++++++++++++++++++++ userspace/WindowServer/WindowServer.h | 44 ++++ userspace/WindowServer/main.cpp | 267 ++++++++++++++++++++++++ userspace/test-window/CMakeLists.txt | 16 ++ userspace/test-window/main.cpp | 61 ++++++ 16 files changed, 1116 insertions(+) create mode 100644 LibGUI/CMakeLists.txt create mode 100644 LibGUI/Window.cpp create mode 100644 LibGUI/include/LibGUI/Window.h create mode 100644 userspace/WindowServer/CMakeLists.txt create mode 100644 userspace/WindowServer/Cursor.h create mode 100644 userspace/WindowServer/Framebuffer.cpp create mode 100644 userspace/WindowServer/Framebuffer.h create mode 100644 userspace/WindowServer/Utils.h create mode 100644 userspace/WindowServer/Window.h create mode 100644 userspace/WindowServer/WindowServer.cpp create mode 100644 userspace/WindowServer/WindowServer.h create mode 100644 userspace/WindowServer/main.cpp create mode 100644 userspace/test-window/CMakeLists.txt create mode 100644 userspace/test-window/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ae89d560e5..4987a9a38b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(bootloader) add_subdirectory(BAN) add_subdirectory(libc) add_subdirectory(LibELF) +add_subdirectory(LibGUI) add_subdirectory(LibInput) add_subdirectory(userspace) @@ -34,6 +35,7 @@ add_custom_target(headers DEPENDS ban-headers DEPENDS libc-headers DEPENDS libelf-headers + DEPENDS libgui-headers DEPENDS libinput-headers ) @@ -43,6 +45,7 @@ add_custom_target(install-sysroot DEPENDS libc-install DEPENDS userspace-install DEPENDS libelf-install + DEPENDS libgui-install DEPENDS libinput-install ) diff --git a/LibGUI/CMakeLists.txt b/LibGUI/CMakeLists.txt new file mode 100644 index 0000000000..a915ad5900 --- /dev/null +++ b/LibGUI/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.26) + +project(libgui CXX) + +set(LIBGUI_SOURCES + Window.cpp +) + +add_custom_target(libgui-headers + COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/include/ ${BANAN_INCLUDE}/ + DEPENDS sysroot +) + +add_library(libgui ${LIBGUI_SOURCES}) +add_dependencies(libgui headers libc-install) +target_link_libraries(libgui PUBLIC libc) + +add_custom_target(libgui-install + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/libgui.a ${BANAN_LIB}/ + DEPENDS libgui + BYPRODUCTS ${BANAN_LIB}/libgui.a +) + +set(CMAKE_STATIC_LIBRARY_PREFIX "") diff --git a/LibGUI/Window.cpp b/LibGUI/Window.cpp new file mode 100644 index 0000000000..c7fa5c1974 --- /dev/null +++ b/LibGUI/Window.cpp @@ -0,0 +1,115 @@ +#include "LibGUI/Window.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace LibGUI +{ + + Window::~Window() + { + munmap(m_framebuffer, m_width * m_height * 4); + close(m_server_fd); + } + + BAN::ErrorOr> Window::create(uint32_t width, uint32_t height) + { + int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (server_fd == -1) + return BAN::Error::from_errno(errno); + + sockaddr_un server_address; + server_address.sun_family = AF_UNIX; + strcpy(server_address.sun_path, s_window_server_socket.data()); + if (connect(server_fd, (sockaddr*)&server_address, sizeof(server_address)) == -1) + { + close(server_fd); + return BAN::Error::from_errno(errno); + } + + WindowCreatePacket packet; + packet.width = width; + packet.height = height; + if (send(server_fd, &packet, sizeof(packet), 0) != sizeof(packet)) + { + close(server_fd); + return BAN::Error::from_errno(errno); + } + + WindowCreateResponse response; + if (recv(server_fd, &response, sizeof(response), 0) != sizeof(response)) + { + close(server_fd); + return BAN::Error::from_errno(errno); + } + + void* framebuffer_addr = smo_map(response.framebuffer_smo_key); + if (framebuffer_addr == nullptr) + { + close(server_fd); + return BAN::Error::from_errno(errno); + } + + return TRY(BAN::UniqPtr::create( + server_fd, + static_cast(framebuffer_addr), + width, + height + )); + } + + bool Window::invalidate() + { + WindowInvalidatePacket packet; + packet.x = 0; + packet.y = 0; + packet.width = m_width; + packet.height = m_height; + return send(m_server_fd, &packet, sizeof(packet), 0) == sizeof(packet); + } + + void Window::poll_events() + { + for (;;) + { + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_server_fd, &fds); + timeval timeout { .tv_sec = 0, .tv_usec = 0 }; + select(m_server_fd + 1, &fds, nullptr, nullptr, &timeout); + + if (!FD_ISSET(m_server_fd, &fds)) + break; + + EventPacket packet; + if (recv(m_server_fd, &packet, sizeof(packet), 0) <= 0) + break; + + switch (packet.type) + { + case EventPacket::Type::KeyEvent: + if (m_key_event_callback) + m_key_event_callback(packet.key_event); + break; + case EventPacket::Type::MouseButtonEvent: + if (m_mouse_button_event_callback) + m_mouse_button_event_callback(packet.mouse_button_event); + break; + case EventPacket::Type::MouseMoveEvent: + if (m_mouse_move_event_callback) + m_mouse_move_event_callback(packet.mouse_move_event); + break; + case EventPacket::Type::MouseScrollEvent: + if (m_mouse_scroll_event_callback) + m_mouse_scroll_event_callback(packet.mouse_scroll_event); + break; + } + } + } + +} diff --git a/LibGUI/include/LibGUI/Window.h b/LibGUI/include/LibGUI/Window.h new file mode 100644 index 0000000000..d13a2add3d --- /dev/null +++ b/LibGUI/include/LibGUI/Window.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include + +namespace LibGUI +{ + + static constexpr BAN::StringView s_window_server_socket = "/tmp/window-server.socket"sv; + + enum WindowPacketType : uint8_t + { + INVALID, + CreateWindow, + Invalidate, + }; + + struct WindowCreatePacket + { + WindowPacketType type = WindowPacketType::CreateWindow; + uint32_t width; + uint32_t height; + }; + + struct WindowInvalidatePacket + { + WindowPacketType type = WindowPacketType::Invalidate; + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; + }; + + struct WindowCreateResponse + { + long framebuffer_smo_key; + }; + + struct WindowPacket + { + WindowPacket() + : type(WindowPacketType::INVALID) + { } + + union + { + WindowPacketType type; + WindowCreatePacket create; + WindowInvalidatePacket invalidate; + }; + }; + + struct EventPacket + { + enum class Type : uint8_t + { + KeyEvent, + MouseButtonEvent, + MouseMoveEvent, + MouseScrollEvent, + }; + using KeyEvent = LibInput::KeyEvent; + using MouseButton = LibInput::MouseButton; + struct MouseButtonEvent + { + MouseButton button; + bool pressed; + int32_t x; + int32_t y; + }; + struct MouseMoveEvent + { + int32_t x; + int32_t y; + }; + using MouseScrollEvent = LibInput::MouseScrollEvent; + + Type type; + union + { + KeyEvent key_event; + MouseButtonEvent mouse_button_event; + MouseMoveEvent mouse_move_event; + MouseScrollEvent mouse_scroll_event; + }; + }; + + class Window + { + public: + ~Window(); + + static BAN::ErrorOr> create(uint32_t width, uint32_t height); + + void set_pixel(uint32_t x, uint32_t y, uint32_t color) + { + ASSERT(x < m_width); + ASSERT(y < m_height); + m_framebuffer[y * m_width + x] = color; + } + + bool invalidate(); + + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + + void poll_events(); + void set_key_event_callback(BAN::Function callback) { m_key_event_callback = callback; } + void set_mouse_button_event_callback(BAN::Function callback) { m_mouse_button_event_callback = callback; } + void set_mouse_move_event_callback(BAN::Function callback) { m_mouse_move_event_callback = callback; } + void set_mouse_scroll_event_callback(BAN::Function callback) { m_mouse_scroll_event_callback = callback; } + + private: + Window(int server_fd, uint32_t* framebuffer, uint32_t width, uint32_t height) + : m_server_fd(server_fd) + , m_framebuffer(framebuffer) + , m_width(width) + , m_height(height) + { } + + private: + int m_server_fd; + uint32_t* m_framebuffer; + uint32_t m_width; + uint32_t m_height; + + BAN::Function m_key_event_callback; + BAN::Function m_mouse_button_event_callback; + BAN::Function m_mouse_move_event_callback; + BAN::Function m_mouse_scroll_event_callback; + + friend class BAN::UniqPtr; + }; + +} diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt index 7e59a16d3e..a0f7e20821 100644 --- a/userspace/CMakeLists.txt +++ b/userspace/CMakeLists.txt @@ -39,9 +39,11 @@ set(USERSPACE_PROJECTS test-tcp test-udp test-unix-socket + test-window touch u8sum whoami + WindowServer yes ) diff --git a/userspace/WindowServer/CMakeLists.txt b/userspace/WindowServer/CMakeLists.txt new file mode 100644 index 0000000000..a9567e010c --- /dev/null +++ b/userspace/WindowServer/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.26) + +project(WindowServer CXX) + +set(SOURCES + main.cpp + Framebuffer.cpp + WindowServer.cpp +) + +add_executable(WindowServer ${SOURCES}) +target_compile_options(WindowServer PUBLIC -O2 -g) +target_link_libraries(WindowServer PUBLIC libc ban libgui libinput) + +add_custom_target(WindowServer-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/WindowServer ${BANAN_BIN}/ + DEPENDS WindowServer +) diff --git a/userspace/WindowServer/Cursor.h b/userspace/WindowServer/Cursor.h new file mode 100644 index 0000000000..bda61bdb0a --- /dev/null +++ b/userspace/WindowServer/Cursor.h @@ -0,0 +1,35 @@ +/* GIMP header image file format (RGB) */ + +#include + +static int32_t s_cursor_width = 17; +static int32_t s_cursor_height = 26; +static const char* s_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$`" + "`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$`" + "`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$`!!!!````````````" + "````````````````````````````````!!!!`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$``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$``Q$`" + "`Q$``Q$`!!!!!!!!!!!!`Q$``Q$``Q$``Q$``Q$`"; diff --git a/userspace/WindowServer/Framebuffer.cpp b/userspace/WindowServer/Framebuffer.cpp new file mode 100644 index 0000000000..b1afb34cd4 --- /dev/null +++ b/userspace/WindowServer/Framebuffer.cpp @@ -0,0 +1,45 @@ +#include "Framebuffer.h" + +#include +#include +#include +#include +#include +#include + +Framebuffer open_framebuffer() +{ + int framebuffer_fd = open("/dev/fb0", O_RDWR); + if (framebuffer_fd == -1) + { + perror("open"); + exit(1); + } + + framebuffer_info_t framebuffer_info; + if (pread(framebuffer_fd, &framebuffer_info, sizeof(framebuffer_info), -1) == -1) + { + perror("pread"); + exit(1); + } + + const size_t framebuffer_bytes = framebuffer_info.width * framebuffer_info.height * (BANAN_FB_BPP / 8); + + uint32_t* framebuffer_mmap = (uint32_t*)mmap(NULL, framebuffer_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, 0); + if (framebuffer_mmap == MAP_FAILED) + { + perror("mmap"); + exit(1); + } + + memset(framebuffer_mmap, 0, framebuffer_bytes); + msync(framebuffer_mmap, framebuffer_bytes, MS_SYNC); + + Framebuffer framebuffer; + framebuffer.fd = framebuffer_fd; + framebuffer.mmap = framebuffer_mmap; + framebuffer.width = framebuffer_info.width; + framebuffer.height = framebuffer_info.height; + framebuffer.bpp = BANAN_FB_BPP; + return framebuffer; +} diff --git a/userspace/WindowServer/Framebuffer.h b/userspace/WindowServer/Framebuffer.h new file mode 100644 index 0000000000..aa1630415c --- /dev/null +++ b/userspace/WindowServer/Framebuffer.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Utils.h" + +struct Framebuffer +{ + int fd; + uint32_t* mmap; + int32_t width; + int32_t height; + uint8_t bpp; + + Rectangle area() const { return { 0, 0, width, height }; } +}; + +Framebuffer open_framebuffer(); diff --git a/userspace/WindowServer/Utils.h b/userspace/WindowServer/Utils.h new file mode 100644 index 0000000000..32f8f7f7c1 --- /dev/null +++ b/userspace/WindowServer/Utils.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include + +struct Position +{ + int32_t x; + int32_t y; +}; + +struct Rectangle +{ + int32_t x; + int32_t y; + int32_t width; + int32_t height; + + bool contains(Position position) const + { + if (position.x < x || position.x >= x + width) + return false; + if (position.y < y || position.y >= y + height) + return false; + return true; + } + + BAN::Optional get_overlap(Rectangle other) const + { + const auto min_x = BAN::Math::max(x, other.x); + const auto min_y = BAN::Math::max(y, other.y); + const auto max_x = BAN::Math::min(x + width, other.x + other.width); + const auto max_y = BAN::Math::min(y + height, other.y + other.height); + if (min_x >= max_x || min_y >= max_y) + return {}; + return Rectangle { + .x = min_x, + .y = min_y, + .width = max_x - min_x, + .height = max_y - min_y, + }; + } + + Rectangle get_bounding_box(Rectangle other) const + { + const auto min_x = BAN::Math::min(x, other.x); + const auto min_y = BAN::Math::min(y, other.y); + const auto max_x = BAN::Math::max(x + width, other.x + other.width); + const auto max_y = BAN::Math::max(y + height, other.y + other.height); + return Rectangle { + .x = min_x, + .y = min_y, + .width = max_x - min_x, + .height = max_y - min_y, + }; + } +}; diff --git a/userspace/WindowServer/Window.h b/userspace/WindowServer/Window.h new file mode 100644 index 0000000000..41d5d755cf --- /dev/null +++ b/userspace/WindowServer/Window.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Utils.h" + +#include + +class Window : public BAN::RefCounted +{ +public: + Window(int fd) + : m_client_fd(fd) + { } + + void set_position(Position position) + { + m_area.x = position.x; + m_area.y = position.y; + } + + void set_size(Position size, uint32_t* fb_addr) + { + m_area.width = size.x; + m_area.height = size.y; + m_fb_addr = fb_addr; + } + + bool is_deleted() const { return m_deleted; } + void mark_deleted() { m_deleted = true; } + + int client_fd() const { return m_client_fd; } + + int32_t x() const { return m_area.x; } + int32_t y() const { return m_area.y; } + uint32_t width() const { return m_area.width; } + uint32_t height() const { return m_area.height; } + Rectangle size() const { return { 0, 0, m_area.width, m_area.height }; } + const Rectangle& area() const { return m_area; } + const uint32_t* framebuffer() const { return m_fb_addr; } + +private: + const int m_client_fd { -1 }; + uint32_t* m_fb_addr { nullptr }; + Rectangle m_area { 0, 0, 0, 0 }; + bool m_deleted { false }; +}; diff --git a/userspace/WindowServer/WindowServer.cpp b/userspace/WindowServer/WindowServer.cpp new file mode 100644 index 0000000000..d944c0f893 --- /dev/null +++ b/userspace/WindowServer/WindowServer.cpp @@ -0,0 +1,224 @@ +#include "Cursor.h" +#include "WindowServer.h" + +#include +#include + +#include +#include +#include + +void WindowServer::add_window(int fd, BAN::RefPtr window) +{ + MUST(m_windows_ordered.insert(0, window)); + MUST(m_windows.insert(fd, window)); + set_focused_window(window); +} + +void WindowServer::for_each_window(const BAN::Function& callback) +{ + BAN::Vector deleted_windows; + for (auto it = m_windows.begin(); it != m_windows.end(); it++) + { + auto ret = callback(it->key, *it->value); + if (it->value->is_deleted()) + MUST(deleted_windows.push_back(it->key)); + if (ret == BAN::Iteration::Break) + break; + ASSERT(ret == BAN::Iteration::Continue); + } + for (int fd : deleted_windows) + { + auto window = m_windows[fd]; + m_windows.remove(fd); + for (size_t i = 0; i < m_windows_ordered.size(); i++) + { + if (m_windows_ordered[i] == window) + { + m_windows_ordered.remove(i); + break; + } + } + } +} + +void WindowServer::on_key_event(LibInput::KeyEvent event) +{ + // Mod key is not passed to clients + if (event.key == LibInput::Key::Super) + { + m_is_mod_key_held = event.pressed(); + return; + } + + // Quick hack to stop the window server + if (event.pressed() && event.key == LibInput::Key::Escape) + exit(0); + + if (m_focused_window) + { + LibGUI::EventPacket packet; + packet.type = LibGUI::EventPacket::Type::KeyEvent; + packet.key_event = event; + send(m_focused_window->client_fd(), &packet, sizeof(packet), 0); + } +} + +void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event) +{ + BAN::RefPtr target_window; + for (size_t i = m_windows_ordered.size(); i > 0; i--) + { + if (m_windows_ordered[i - 1]->area().contains(m_cursor)) + { + target_window = m_windows_ordered[i - 1]; + break; + } + } + + // Ignore mouse button events which are not on top of a window + if (!target_window) + return; + + set_focused_window(target_window); + + // Handle window moving when mod key is held + if (m_is_mod_key_held && event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window) + m_is_moving_window = true; + else if (m_is_moving_window && !event.pressed) + m_is_moving_window = false; + else + { + // NOTE: we always have target window if code reaches here + LibGUI::EventPacket packet; + packet.type = LibGUI::EventPacket::Type::MouseButtonEvent; + packet.mouse_button_event.button = event.button; + packet.mouse_button_event.pressed = event.pressed; + packet.mouse_button_event.x = m_cursor.x - m_focused_window->x(); + packet.mouse_button_event.y = m_cursor.y - m_focused_window->y(); + send(m_focused_window->client_fd(), &packet, sizeof(packet), 0); + } +} + +void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event) +{ + Rectangle old_cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height }; + + const int32_t new_x = BAN::Math::clamp(m_cursor.x + event.rel_x, 0, m_framebuffer.width); + const int32_t new_y = BAN::Math::clamp(m_cursor.y - event.rel_y, 0, m_framebuffer.height); + + event.rel_x = new_x - m_cursor.x; + event.rel_y = new_y - m_cursor.y; + if (event.rel_x == 0 && event.rel_y == 0) + return; + + m_cursor.x = new_x; + m_cursor.y = new_y; + + Rectangle new_cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height }; + invalidate(old_cursor.get_bounding_box(old_cursor)); + invalidate(new_cursor.get_bounding_box(old_cursor)); + + if (m_is_moving_window) + { + auto old_window = m_focused_window->area(); + m_focused_window->set_position({ + m_focused_window->x() + event.rel_x, + m_focused_window->y() + event.rel_y, + }); + invalidate(old_window); + invalidate(m_focused_window->area()); + return; + } + + if (m_focused_window) + { + LibGUI::EventPacket packet; + packet.type = LibGUI::EventPacket::Type::MouseMoveEvent; + packet.mouse_move_event.x = m_cursor.x - m_focused_window->x(); + packet.mouse_move_event.y = m_cursor.y - m_focused_window->y(); + send(m_focused_window->client_fd(), &packet, sizeof(packet), 0); + } +} + +void WindowServer::on_mouse_scroll(LibInput::MouseScrollEvent event) +{ + if (m_focused_window) + { + LibGUI::EventPacket packet; + packet.type = LibGUI::EventPacket::Type::MouseScrollEvent; + packet.mouse_scroll_event = event; + send(m_focused_window->client_fd(), &packet, sizeof(packet), 0); + } +} + +void WindowServer::set_focused_window(BAN::RefPtr window) +{ + if (m_focused_window == window) + return; + + for (size_t i = m_windows_ordered.size(); i > 0; i--) + { + if (m_windows_ordered[i - 1] == window) + { + m_focused_window = window; + m_windows_ordered.remove(i - 1); + MUST(m_windows_ordered.push_back(window)); + invalidate(window->area()); + break; + } + } +} + +void WindowServer::invalidate(Rectangle area) +{ + auto fb_overlap = area.get_overlap(m_framebuffer.area()); + if (!fb_overlap.has_value()) + return; + area = fb_overlap.release_value(); + + for (int32_t y = area.y; y < area.y + area.height; y++) + memset(&m_framebuffer.mmap[y * m_framebuffer.width + area.x], 0, area.width * 4); + + for (auto& pwindow : m_windows_ordered) + { + auto& window = *pwindow; + + auto overlap = window.area().get_overlap(area); + if (!overlap.has_value()) + continue; + + const int32_t src_x = overlap->x - window.x(); + const int32_t src_y = overlap->y - window.y(); + for (int32_t y_off = 0; y_off < overlap->height; y_off++) + memcpy( + &m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + overlap->x], + &window.framebuffer()[(src_y + y_off) * window.width() + src_x], + overlap->width * 4 + ); + } + + Rectangle cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height }; + auto overlap = cursor.get_overlap(area); + if (overlap.has_value()) + { + for (int32_t dy = overlap->y - cursor.y; dy < overlap->height; dy++) + { + for (int32_t dx = overlap->x - cursor.x; dx < overlap->width; dx++) + { + const uint32_t offset = (dy * s_cursor_width + dx) * 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))); + uint32_t color = (r << 16) | (g << 8) | b; + if (color != 0xFF00FF) + m_framebuffer.mmap[(overlap->y + dy) * m_framebuffer.width + (overlap->x + dx)] = color; + } + } + } + + uintptr_t mmap_start = reinterpret_cast(m_framebuffer.mmap) + area.y * m_framebuffer.width * 4; + uintptr_t mmap_end = mmap_start + (area.height + 1) * m_framebuffer.width * 4; + mmap_start &= ~(uintptr_t)0xFFF; + msync(reinterpret_cast(mmap_start), mmap_end - mmap_start, MS_SYNC); +} diff --git a/userspace/WindowServer/WindowServer.h b/userspace/WindowServer/WindowServer.h new file mode 100644 index 0000000000..e3b7b3f9b8 --- /dev/null +++ b/userspace/WindowServer/WindowServer.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Framebuffer.h" +#include "Window.h" + +#include +#include +#include +#include + +#include +#include + +class WindowServer +{ +public: + WindowServer(Framebuffer& framebuffer) + : m_framebuffer(framebuffer) + , m_cursor({ framebuffer.width / 2, framebuffer.height / 2 }) + { + invalidate(m_framebuffer.area()); + } + + void add_window(int fd, BAN::RefPtr window); + void for_each_window(const BAN::Function& callback); + + void on_key_event(LibInput::KeyEvent event); + void on_mouse_button(LibInput::MouseButtonEvent event); + void on_mouse_move(LibInput::MouseMoveEvent event); + void on_mouse_scroll(LibInput::MouseScrollEvent event); + + void set_focused_window(BAN::RefPtr window); + void invalidate(Rectangle area); + +private: + Framebuffer& m_framebuffer; + BAN::Vector> m_windows_ordered; + BAN::HashMap> m_windows; + + bool m_is_mod_key_held { false }; + bool m_is_moving_window { false }; + BAN::RefPtr m_focused_window; + Position m_cursor; +}; diff --git a/userspace/WindowServer/main.cpp b/userspace/WindowServer/main.cpp new file mode 100644 index 0000000000..e6626a7a77 --- /dev/null +++ b/userspace/WindowServer/main.cpp @@ -0,0 +1,267 @@ +#include "WindowServer.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int open_server_fd() +{ + struct stat st; + if (stat(LibGUI::s_window_server_socket.data(), &st) != -1) + unlink(LibGUI::s_window_server_socket.data()); + + int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (server_fd == -1) + { + perror("socket"); + exit(1); + } + + sockaddr_un server_addr; + server_addr.sun_family = AF_UNIX; + strcpy(server_addr.sun_path, LibGUI::s_window_server_socket.data()); + if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) + { + perror("bind"); + exit(1); + } + + if (chmod("/tmp/resolver.sock", 0777) == -1) + { + perror("chmod"); + exit(1); + } + + if (listen(server_fd, SOMAXCONN) == -1) + { + perror("listen"); + exit(1); + } + + return server_fd; +} + +int main() +{ + srand(time(nullptr)); + + int server_fd = open_server_fd(); + auto framebuffer = open_framebuffer(); + if (framebuffer.bpp != 32) + { + dwarnln("Window server requires 32 bpp"); + return 1; + } + + if (tty_ctrl(STDIN_FILENO, TTY_CMD_UNSET, TTY_FLAG_ENABLE_INPUT) == -1) + { + perror("tty_ctrl"); + exit(1); + } + + atexit([]() { tty_ctrl(STDIN_FILENO, TTY_CMD_SET, TTY_FLAG_ENABLE_INPUT); }); + + MUST(LibInput::KeyboardLayout::initialize()); + MUST(LibInput::KeyboardLayout::get().load_from_file("/usr/share/keymaps/us.keymap"sv)); + + int keyboard_fd = open("/dev/input0", O_RDONLY); + if (keyboard_fd == -1) + perror("open"); + + int mouse_fd = open("/dev/input1", O_RDONLY); + if (mouse_fd == -1) + perror("open"); + + dprintln("Window server started"); + + for (int i = 0; i < 2; i++) + { + if (fork() == 0) + { + execl("/bin/test-window", "test-window", NULL); + exit(1); + } + } + + WindowServer window_server(framebuffer); + for (;;) + { + int max_socket = server_fd; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(server_fd, &fds); + if (keyboard_fd != -1) + { + FD_SET(keyboard_fd, &fds); + max_socket = BAN::Math::max(max_socket, keyboard_fd); + } + if (mouse_fd != -1) + { + FD_SET(mouse_fd, &fds); + max_socket = BAN::Math::max(max_socket, mouse_fd); + } + window_server.for_each_window( + [&](int fd, Window&) -> BAN::Iteration + { + FD_SET(fd, &fds); + max_socket = BAN::Math::max(max_socket, fd); + return BAN::Iteration::Continue; + } + ); + + if (select(max_socket + 1, &fds, nullptr, nullptr, nullptr) == -1) + { + dwarnln("select: {}", strerror(errno)); + break; + } + + if (FD_ISSET(server_fd, &fds)) + { + int window_fd = accept(server_fd, nullptr, nullptr); + if (window_fd == -1) + { + dwarnln("accept: {}", strerror(errno)); + continue; + } + auto window = MUST(BAN::RefPtr::create(window_fd)); + window_server.add_window(window_fd, window); + } + + if (keyboard_fd != -1 && FD_ISSET(keyboard_fd, &fds)) + { + LibInput::RawKeyEvent event; + if (read(keyboard_fd, &event, sizeof(event)) == -1) + { + perror("read"); + continue; + } + window_server.on_key_event(LibInput::KeyboardLayout::get().key_event_from_raw(event)); + } + + if (mouse_fd != -1 && FD_ISSET(mouse_fd, &fds)) + { + LibInput::MouseEvent event; + if (read(mouse_fd, &event, sizeof(event)) == -1) + { + perror("read"); + continue; + } + switch (event.type) + { + case LibInput::MouseEventType::MouseButtonEvent: + window_server.on_mouse_button(event.button_event); + break; + case LibInput::MouseEventType::MouseMoveEvent: + window_server.on_mouse_move(event.move_event); + break; + case LibInput::MouseEventType::MouseScrollEvent: + window_server.on_mouse_scroll(event.scroll_event); + break; + } + } + + window_server.for_each_window( + [&](int fd, Window& window) -> BAN::Iteration + { + if (!FD_ISSET(fd, &fds)) + return BAN::Iteration::Continue; + + LibGUI::WindowPacket packet; + ssize_t nrecv = recv(fd, &packet, sizeof(packet), 0); + if (nrecv < 0) + dwarnln("recv: {}", strerror(errno)); + if (nrecv <= 0) + { + window.mark_deleted(); + return BAN::Iteration::Continue; + } + + switch (packet.type) + { + case LibGUI::WindowPacketType::CreateWindow: + { + if (nrecv != sizeof(LibGUI::WindowCreatePacket)) + { + dwarnln("Invalid WindowCreate packet size"); + break; + } + + const size_t window_fb_bytes = packet.create.width * packet.create.height * 4; + + long smo_key = smo_create(window_fb_bytes, PROT_READ | PROT_WRITE); + if (smo_key == -1) + { + dwarnln("smo_create: {}", strerror(errno)); + break; + } + + void* smo_address = smo_map(smo_key); + if (smo_address == nullptr) + { + dwarnln("smo_map: {}", strerror(errno)); + break; + } + memset(smo_address, 0, window_fb_bytes); + + LibGUI::WindowCreateResponse response; + response.framebuffer_smo_key = smo_key; + if (send(fd, &response, sizeof(response), 0) != sizeof(response)) + { + dwarnln("send: {}", strerror(errno)); + break; + } + + window.set_size({ + static_cast(packet.create.width), + static_cast(packet.create.height) + }, reinterpret_cast(smo_address)); + window.set_position({ + static_cast(window.width() / 2), + static_cast(window.height() / 2) + }); + + break; + } + case LibGUI::WindowPacketType::Invalidate: + { + if (nrecv != sizeof(LibGUI::WindowInvalidatePacket)) + { + dwarnln("Invalid Invalidate packet size"); + break; + } + if (packet.invalidate.x + packet.invalidate.width > window.width() || packet.invalidate.y + packet.invalidate.height > window.height()) + { + dwarnln("Invalid Invalidate packet parameters"); + break; + } + + window_server.invalidate({ + window.x() + static_cast(packet.invalidate.x), + window.y() + static_cast(packet.invalidate.y), + static_cast(packet.invalidate.width), + static_cast(packet.invalidate.height), + }); + + break; + } + default: + dwarnln("Invalid window packet from {}", fd); + } + + return BAN::Iteration::Continue; + } + ); + } +} diff --git a/userspace/test-window/CMakeLists.txt b/userspace/test-window/CMakeLists.txt new file mode 100644 index 0000000000..766b9ea30c --- /dev/null +++ b/userspace/test-window/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.26) + +project(test-window CXX) + +set(SOURCES + main.cpp +) + +add_executable(test-window ${SOURCES}) +target_compile_options(test-window PUBLIC -O2 -g) +target_link_libraries(test-window PUBLIC libc ban libgui) + +add_custom_target(test-window-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/test-window ${BANAN_BIN}/ + DEPENDS test-window +) diff --git a/userspace/test-window/main.cpp b/userspace/test-window/main.cpp new file mode 100644 index 0000000000..896cedd8b3 --- /dev/null +++ b/userspace/test-window/main.cpp @@ -0,0 +1,61 @@ +#include + +#include + +#include +#include + +void randomize_color(BAN::UniqPtr& window) +{ + uint32_t color = ((rand() % 255) << 16) | ((rand() % 255) << 8) | ((rand() % 255) << 0); + for (uint32_t y = 0; y < window->height(); y++) + for (uint32_t x = 0; x < window->width(); x++) + window->set_pixel(x, y, color); + window->invalidate(); +} + +int main() +{ + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + srand(ts.tv_nsec); + + auto window_or_error = LibGUI::Window::create(300, 200); + if (window_or_error.is_error()) + { + dprintln("{}", window_or_error.error()); + return 1; + } + + auto window = window_or_error.release_value(); + window->set_mouse_button_event_callback( + [&](LibGUI::EventPacket::MouseButtonEvent event) + { + if (event.pressed && event.button == LibGUI::EventPacket::MouseButton::Left) + randomize_color(window); + + const char* button; + switch (event.button) + { + case LibGUI::EventPacket::MouseButton::Left: button = "left"; break; + case LibGUI::EventPacket::MouseButton::Right: button = "right"; break; + case LibGUI::EventPacket::MouseButton::Middle: button = "middle"; break; + case LibGUI::EventPacket::MouseButton::Extra1: button = "extra1"; break; + case LibGUI::EventPacket::MouseButton::Extra2: button = "extra2"; break; + } + dprintln("mouse button '{}' {} at {}, {}", button, event.pressed ? "pressed" : "released", event.x, event.y); + } + ); + + randomize_color(window); + + for (;;) + { + window->poll_events(); + + timespec duration; + duration.tv_sec = 0; + duration.tv_nsec = 16'666'666; + nanosleep(&duration, nullptr); + } +}