diff --git a/userspace/WindowServer/CMakeLists.txt b/userspace/WindowServer/CMakeLists.txt index a9567e010c..2dc7827d0c 100644 --- a/userspace/WindowServer/CMakeLists.txt +++ b/userspace/WindowServer/CMakeLists.txt @@ -5,12 +5,13 @@ project(WindowServer CXX) set(SOURCES main.cpp Framebuffer.cpp + Window.cpp WindowServer.cpp ) add_executable(WindowServer ${SOURCES}) target_compile_options(WindowServer PUBLIC -O2 -g) -target_link_libraries(WindowServer PUBLIC libc ban libgui libinput) +target_link_libraries(WindowServer PUBLIC libc ban libfont libgui libinput) add_custom_target(WindowServer-install COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/WindowServer ${BANAN_BIN}/ diff --git a/userspace/WindowServer/Utils.h b/userspace/WindowServer/Utils.h index 6f673cbbec..f5f9206c34 100644 --- a/userspace/WindowServer/Utils.h +++ b/userspace/WindowServer/Utils.h @@ -63,3 +63,18 @@ struct Rectangle } }; + +struct Circle +{ + int32_t x; + int32_t y; + int32_t radius; + + bool contains(Position position) const + { + int32_t dx = position.x - x; + int32_t dy = position.y - y; + return dx * dx + dy * dy <= radius * radius; + } + +}; diff --git a/userspace/WindowServer/Window.cpp b/userspace/WindowServer/Window.cpp new file mode 100644 index 0000000000..7cf36d03df --- /dev/null +++ b/userspace/WindowServer/Window.cpp @@ -0,0 +1,72 @@ +#include "Window.h" + +#include + +#include + +#include +#include +#include +#include + +Window::Window(int fd, Rectangle area, long smo_key, BAN::StringView title, const LibFont::Font& font) + : m_client_fd(fd) + , m_client_area(area) + , m_smo_key(smo_key) +{ + MUST(m_title.append(title)); + prepare_title_bar(font); + + m_fb_addr = static_cast(smo_map(smo_key)); + ASSERT(m_fb_addr); + memset(m_fb_addr, 0, client_width() * client_height() * 4); +} + +Window::~Window() +{ + munmap(m_fb_addr, client_width() * client_height() * 4); + smo_delete(m_smo_key); + + LibGUI::EventPacket event; + event.type = LibGUI::EventPacket::Type::DestroyWindow; + send(m_client_fd, &event, sizeof(event), 0); + close(m_client_fd); +} + +void Window::prepare_title_bar(const LibFont::Font& font) +{ + const size_t title_bar_bytes = title_bar_width() * title_bar_height() * 4; + uint32_t* title_bar_data = new uint32_t[title_bar_bytes]; + ASSERT(title_bar_data); + for (size_t i = 0; i < title_bar_bytes; i++) + title_bar_data[i] = 0xFFFFFF; + + const auto text_area = title_text_area(); + + for (size_t i = 0; i < m_title.size() && (i + 1) * font.width() < static_cast(text_area.width); i++) + { + const auto* glyph = font.glyph(m_title[i]); + if (glyph == nullptr) + continue; + + const int32_t y_off = (font.height() < (uint32_t)title_bar_height()) ? (title_bar_height() - font.height()) / 2 : 0; + const int32_t x_off = y_off + i * font.width(); + for (int32_t y = 0; (uint32_t)y < font.height(); y++) + { + if (y + y_off >= title_bar_height()) + break; + for (int32_t x = 0; (uint32_t)x < font.width(); x++) + { + if (x + x_off >= text_area.width) + break; + const uint8_t bitmask = 1 << (font.width() - x - 1); + if (glyph[y * font.pitch()] & bitmask) + title_bar_data[(y_off + y) * title_bar_width() + (x_off + x)] = 0x000000; + } + } + } + + if (m_title_bar_data) + delete[] m_title_bar_data; + m_title_bar_data = title_bar_data; +} diff --git a/userspace/WindowServer/Window.h b/userspace/WindowServer/Window.h index 7250b17510..a235b8b77f 100644 --- a/userspace/WindowServer/Window.h +++ b/userspace/WindowServer/Window.h @@ -3,13 +3,15 @@ #include "Utils.h" #include +#include + +#include class Window : public BAN::RefCounted { public: - Window(int fd) - : m_client_fd(fd) - { } + Window(int fd, Rectangle area, long smo_key, BAN::StringView title, const LibFont::Font& font); + ~Window(); void set_position(Position position) { @@ -17,16 +19,6 @@ public: m_client_area.y = position.y; } - void set_size(Position size, uint32_t* fb_addr) - { - m_client_area.width = size.x; - m_client_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 client_x() const { return m_client_area.x; } @@ -56,24 +48,29 @@ public: { ASSERT(title_bar_area().contains({ abs_x, abs_y })); - Rectangle close_button = { - title_bar_x() + title_bar_width() - title_bar_height() + 1, - title_bar_y() + 1, - title_bar_height() - 2, - title_bar_height() - 2 - }; + if (auto close_button = close_button_area(); close_button.contains({ abs_x, abs_y })) + return close_button.contains(cursor) ? 0xFF0000 : 0xD00000; - if (close_button.contains({ abs_x, abs_y })) - return close_button.contains(cursor) ? 0xFF0000 : 0xA00000; - - return 0xFFFFFF; + int32_t rel_x = abs_x - title_bar_x(); + int32_t rel_y = abs_y - title_bar_y(); + return m_title_bar_data[rel_y * title_bar_width() + rel_x]; } + Circle close_button_area() const { return { title_bar_x() + title_bar_width() - title_bar_height() / 2, title_bar_y() + title_bar_height() / 2, title_bar_height() * 3 / 8 }; } + Rectangle title_text_area() const { return { title_bar_x(), title_bar_y(), title_bar_width() - title_bar_height(), title_bar_height() }; } + +private: + void prepare_title_bar(const LibFont::Font& font); + private: static constexpr int32_t m_title_bar_height { 20 }; const int m_client_fd { -1 }; - uint32_t* m_fb_addr { nullptr }; Rectangle m_client_area { 0, 0, 0, 0 }; - bool m_deleted { false }; + long m_smo_key { 0 }; + uint32_t* m_fb_addr { nullptr }; + uint32_t* m_title_bar_data { nullptr }; + BAN::String m_title; + + friend class BAN::RefPtr; }; diff --git a/userspace/WindowServer/WindowServer.cpp b/userspace/WindowServer/WindowServer.cpp index d70ea51c6a..c3ef9adba9 100644 --- a/userspace/WindowServer/WindowServer.cpp +++ b/userspace/WindowServer/WindowServer.cpp @@ -1,44 +1,110 @@ #include "Cursor.h" #include "WindowServer.h" +#include + #include #include #include +#include #include #include -void WindowServer::add_window(int fd, BAN::RefPtr window) +void WindowServer::on_window_packet(int fd, LibGUI::WindowPacket packet) { - 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++) + switch (packet.type) { - 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++) + case LibGUI::WindowPacketType::CreateWindow: { - if (m_windows_ordered[i] == window) + // FIXME: This should be probably allowed + for (auto& window : m_client_windows) { - m_windows_ordered.remove(i); + if (window->client_fd() == fd) + { + dwarnln("client {} tried to create window while already owning a window", fd); + return; + } + } + + 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; } + + Rectangle window_area { + static_cast((m_framebuffer.width - packet.create.width) / 2), + static_cast((m_framebuffer.height - packet.create.height) / 2), + static_cast(packet.create.width), + static_cast(packet.create.height) + }; + + packet.create.title[sizeof(packet.create.title) - 1] = '\0'; + + // Window::Window(int fd, Rectangle area, long smo_key, BAN::StringView title, const LibFont::Font& font) + auto window = MUST(BAN::RefPtr::create( + fd, + window_area, + smo_key, + packet.create.title, + m_font + )); + MUST(m_client_windows.push_back(window)); + set_focused_window(window); + + LibGUI::WindowCreateResponse response; + response.framebuffer_smo_key = smo_key; + if (send(window->client_fd(), &response, sizeof(response), 0) != sizeof(response)) + { + dwarnln("send: {}", strerror(errno)); + break; + } + + break; } + case LibGUI::WindowPacketType::Invalidate: + { + if (packet.invalidate.width == 0 || packet.invalidate.height == 0) + break; + + BAN::RefPtr target_window; + for (auto& window : m_client_windows) + { + if (window->client_fd() == fd) + { + target_window = window; + break; + } + } + if (!target_window) + { + dwarnln("client {} tried to invalidate window while not owning a window", fd); + break; + } + + const int32_t br_x = packet.invalidate.x + packet.invalidate.width - 1; + const int32_t br_y = packet.invalidate.y + packet.invalidate.height - 1; + if (!target_window->client_size().contains({ br_x, br_y })) + { + dwarnln("Invalid Invalidate packet parameters"); + break; + } + + invalidate({ + target_window->client_x() + static_cast(packet.invalidate.x), + target_window->client_y() + static_cast(packet.invalidate.y), + static_cast(packet.invalidate.width), + static_cast(packet.invalidate.height), + }); + + break; + } + default: + ASSERT_NOT_REACHED(); } } @@ -53,7 +119,18 @@ void WindowServer::on_key_event(LibInput::KeyEvent event) // Quick hack to stop the window server if (event.pressed() && event.key == LibInput::Key::Escape) - exit(0); + { + m_is_stopped = true; + return; + } + + // Kill window with mod+Q + if (m_is_mod_key_held && event.pressed() && event.key == LibInput::Key::Q) + { + if (m_focused_window) + remove_client_fd(m_focused_window->client_fd()); + return; + } if (m_focused_window) { @@ -67,11 +144,11 @@ void WindowServer::on_key_event(LibInput::KeyEvent event) void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event) { BAN::RefPtr target_window; - for (size_t i = m_windows_ordered.size(); i > 0; i--) + for (size_t i = m_client_windows.size(); i > 0; i--) { - if (m_windows_ordered[i - 1]->full_area().contains(m_cursor)) + if (m_client_windows[i - 1]->full_area().contains(m_cursor)) { - target_window = m_windows_ordered[i - 1]; + target_window = m_client_windows[i - 1]; break; } } @@ -83,10 +160,18 @@ void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event) set_focused_window(target_window); // Handle window moving when mod key is held or mouse press on title bar - if (event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window && (target_window->title_bar_area().contains(m_cursor) || m_is_mod_key_held)) + const bool can_start_move = m_is_mod_key_held || target_window->title_text_area().contains(m_cursor); + if (event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window && can_start_move) m_is_moving_window = true; else if (m_is_moving_window && !event.pressed) m_is_moving_window = false; + else if (!event.pressed && event.button == LibInput::MouseButton::Left && target_window->close_button_area().contains(m_cursor)) + { + // NOTE: we always have target window if code reaches here + LibGUI::EventPacket packet; + packet.type = LibGUI::EventPacket::Type::CloseWindow; + send(m_focused_window->client_fd(), &packet, sizeof(packet), 0); + } else if (target_window->client_area().contains(m_cursor)) { // NOTE: we always have target window if code reaches here @@ -119,7 +204,7 @@ void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event) invalidate(new_cursor); // TODO: Really no need to loop over every window - for (auto& window : m_windows_ordered) + for (auto& window : m_client_windows) { auto title_bar = window->title_bar_area(); if (title_bar.get_overlap(old_cursor).has_value() || title_bar.get_overlap(new_cursor).has_value()) @@ -165,13 +250,13 @@ 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--) + for (size_t i = m_client_windows.size(); i > 0; i--) { - if (m_windows_ordered[i - 1] == window) + if (m_client_windows[i - 1] == window) { m_focused_window = window; - m_windows_ordered.remove(i - 1); - MUST(m_windows_ordered.push_back(window)); + m_client_windows.remove(i - 1); + MUST(m_client_windows.push_back(window)); invalidate(window->full_area()); break; } @@ -188,7 +273,7 @@ void WindowServer::invalidate(Rectangle area) 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) + for (auto& pwindow : m_client_windows) { auto& window = *pwindow; @@ -255,3 +340,65 @@ Rectangle WindowServer::cursor_area() const { return { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height }; } + + +void WindowServer::add_client_fd(int fd) +{ + MUST(m_client_fds.push_back(fd)); +} + +void WindowServer::remove_client_fd(int fd) +{ + for (size_t i = 0; i < m_client_fds.size(); i++) + { + if (m_client_fds[i] == fd) + { + m_client_fds.remove(i); + break; + } + } + + for (size_t i = 0; i < m_client_windows.size(); i++) + { + auto window = m_client_windows[i]; + if (window->client_fd() == fd) + { + auto window_area = window->full_area(); + m_client_windows.remove(i); + invalidate(window_area); + + if (window == m_focused_window) + { + m_focused_window = nullptr; + if (!m_client_windows.empty()) + set_focused_window(m_client_windows.back()); + } + + break; + } + } + + m_deleted_window = true; +} + +int WindowServer::get_client_fds(fd_set& fds) const +{ + int max_fd = 0; + for (int fd : m_client_fds) + { + FD_SET(fd, &fds); + max_fd = BAN::Math::max(max_fd, fd); + } + return max_fd; +} + +void WindowServer::for_each_client_fd(const BAN::Function& callback) +{ + m_deleted_window = false; + for (int fd : m_client_fds) + { + if (m_deleted_window) + break; + callback(fd); + } +} diff --git a/userspace/WindowServer/WindowServer.h b/userspace/WindowServer/WindowServer.h index f7a216240f..f8cba48b9b 100644 --- a/userspace/WindowServer/WindowServer.h +++ b/userspace/WindowServer/WindowServer.h @@ -8,21 +8,25 @@ #include #include +#include +#include #include #include +#include + class WindowServer { public: WindowServer(Framebuffer& framebuffer) : m_framebuffer(framebuffer) , m_cursor({ framebuffer.width / 2, framebuffer.height / 2 }) + , m_font(MUST(LibFont::Font::load("/usr/share/fonts/lat0-16.psfu"sv))) { invalidate(m_framebuffer.area()); } - void add_window(int fd, BAN::RefPtr window); - void for_each_window(const BAN::Function& callback); + void on_window_packet(int fd, LibGUI::WindowPacket); void on_key_event(LibInput::KeyEvent event); void on_mouse_button(LibInput::MouseButtonEvent event); @@ -34,13 +38,25 @@ public: Rectangle cursor_area() const; + void add_client_fd(int fd); + void remove_client_fd(int fd); + int get_client_fds(fd_set& fds) const; + void for_each_client_fd(const BAN::Function& callback); + + bool is_stopped() const { return m_is_stopped; } + private: Framebuffer& m_framebuffer; - BAN::Vector> m_windows_ordered; - BAN::HashMap> m_windows; + BAN::Vector> m_client_windows; + BAN::Vector m_client_fds; bool m_is_mod_key_held { false }; bool m_is_moving_window { false }; BAN::RefPtr m_focused_window; Position m_cursor; + + bool m_deleted_window { false }; + bool m_is_stopped { false }; + + LibFont::Font m_font; }; diff --git a/userspace/WindowServer/main.cpp b/userspace/WindowServer/main.cpp index a26eb729c4..c92f26dc0e 100644 --- a/userspace/WindowServer/main.cpp +++ b/userspace/WindowServer/main.cpp @@ -85,19 +85,16 @@ int main() dprintln("Window server started"); - for (int i = 0; i < 2; i++) - { - if (fork() == 0) - { - execl("/bin/test-window", "test-window", NULL); - exit(1); - } - } + size_t window_packet_sizes[LibGUI::WindowPacketType::COUNT] {}; + window_packet_sizes[LibGUI::WindowPacketType::INVALID] = 0; + window_packet_sizes[LibGUI::WindowPacketType::CreateWindow] = sizeof(LibGUI::WindowCreatePacket); + window_packet_sizes[LibGUI::WindowPacketType::Invalidate] = sizeof(LibGUI::WindowInvalidatePacket); + static_assert(LibGUI::WindowPacketType::COUNT == 3); WindowServer window_server(framebuffer); - for (;;) + while (!window_server.is_stopped()) { - int max_socket = server_fd; + int max_fd = server_fd; fd_set fds; FD_ZERO(&fds); @@ -105,23 +102,16 @@ int main() if (keyboard_fd != -1) { FD_SET(keyboard_fd, &fds); - max_socket = BAN::Math::max(max_socket, keyboard_fd); + max_fd = BAN::Math::max(max_fd, keyboard_fd); } if (mouse_fd != -1) { FD_SET(mouse_fd, &fds); - max_socket = BAN::Math::max(max_socket, mouse_fd); + max_fd = BAN::Math::max(max_fd, 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; - } - ); + max_fd = BAN::Math::max(max_fd, window_server.get_client_fds(fds)); - if (select(max_socket + 1, &fds, nullptr, nullptr, nullptr) == -1) + if (select(max_fd + 1, &fds, nullptr, nullptr, nullptr) == -1) { dwarnln("select: {}", strerror(errno)); break; @@ -135,8 +125,7 @@ int main() dwarnln("accept: {}", strerror(errno)); continue; } - auto window = MUST(BAN::RefPtr::create(window_fd)); - window_server.add_window(window_fd, window); + window_server.add_client_fd(window_fd); } if (keyboard_fd != -1 && FD_ISSET(keyboard_fd, &fds)) @@ -172,8 +161,8 @@ int main() } } - window_server.for_each_window( - [&](int fd, Window& window) -> BAN::Iteration + window_server.for_each_client_fd( + [&](int fd) -> BAN::Iteration { if (!FD_ISSET(fd, &fds)) return BAN::Iteration::Continue; @@ -184,89 +173,16 @@ int main() dwarnln("recv: {}", strerror(errno)); if (nrecv <= 0) { - window.mark_deleted(); + window_server.remove_client_fd(fd); 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((framebuffer.width - window.client_width()) / 2), - static_cast((framebuffer.height - window.client_height()) / 2) - }); - window_server.invalidate(window.full_area()); - - break; - } - case LibGUI::WindowPacketType::Invalidate: - { - if (nrecv != sizeof(LibGUI::WindowInvalidatePacket)) - { - dwarnln("Invalid Invalidate packet size"); - break; - } - - if (packet.invalidate.width == 0 || packet.invalidate.height == 0) - break; - - const int32_t br_x = packet.invalidate.x + packet.invalidate.width - 1; - const int32_t br_y = packet.invalidate.y + packet.invalidate.height - 1; - if (!window.client_size().contains({ br_x, br_y })) - { - dwarnln("Invalid Invalidate packet parameters"); - break; - } - - window_server.invalidate({ - window.client_x() + static_cast(packet.invalidate.x), - window.client_y() + static_cast(packet.invalidate.y), - static_cast(packet.invalidate.width), - static_cast(packet.invalidate.height), - }); - - break; - } - default: - dwarnln("Invalid window packet from {}", fd); - } - + if (packet.type == LibGUI::WindowPacketType::INVALID || packet.type >= LibGUI::WindowPacketType::COUNT) + dwarnln("Invalid WindowPacket (type {})", (int)packet.type); + if (static_cast(nrecv) != window_packet_sizes[packet.type]) + dwarnln("Invalid WindowPacket size (type {}, size {})", (int)packet.type, nrecv); + else + window_server.on_window_packet(fd, packet); return BAN::Iteration::Continue; } ); diff --git a/userspace/test-window/main.cpp b/userspace/test-window/main.cpp index 896cedd8b3..feae833062 100644 --- a/userspace/test-window/main.cpp +++ b/userspace/test-window/main.cpp @@ -20,14 +20,17 @@ int main() clock_gettime(CLOCK_MONOTONIC, &ts); srand(ts.tv_nsec); - auto window_or_error = LibGUI::Window::create(300, 200); + auto window_or_error = LibGUI::Window::create(300, 200, "test-window"); if (window_or_error.is_error()) { dprintln("{}", window_or_error.error()); return 1; } + bool running = true; + auto window = window_or_error.release_value(); + window->set_close_window_event_callback([&] { running = false; }); window->set_mouse_button_event_callback( [&](LibGUI::EventPacket::MouseButtonEvent event) { @@ -49,7 +52,7 @@ int main() randomize_color(window); - for (;;) + while (running) { window->poll_events();