diff --git a/CMakeLists.txt b/CMakeLists.txt index ae89d560..4987a9a3 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 00000000..a915ad59 --- /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 00000000..c7fa5c19 --- /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 00000000..d13a2add --- /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 7e59a16d..a0f7e208 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 00000000..a9567e01 --- /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 00000000..bda61bdb --- /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 00000000..b1afb34c --- /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 00000000..aa163041 --- /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 00000000..32f8f7f7 --- /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 00000000..41d5d755 --- /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 00000000..d944c0f8 --- /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 00000000..e3b7b3f9 --- /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 00000000..e6626a7a --- /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 00000000..766b9ea3 --- /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 00000000..896cedd8 --- /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); + } +}