#include "Cursor.h" #include "WindowServer.h" #include #include #include #include #include #include #include #include #include #include WindowServer::WindowServer(Framebuffer& framebuffer, int32_t corner_radius) : m_framebuffer(framebuffer) , m_corner_radius(corner_radius) , m_cursor({ framebuffer.width / 2, framebuffer.height / 2 }) , m_font(MUST(LibFont::Font::load("/usr/share/fonts/lat0-16.psfu"_sv))) { MUST(m_background_image.resize(m_framebuffer.width * m_framebuffer.height, 0xFF101010)); MUST(m_pending_syncs.resize(m_framebuffer.height)); invalidate(m_framebuffer.area()); } BAN::ErrorOr WindowServer::set_background_image(BAN::UniqPtr image) { if (image->width() != (uint64_t)m_framebuffer.width || image->height() != (uint64_t)m_framebuffer.height) image = TRY(image->resize(m_framebuffer.width, m_framebuffer.height)); for (int32_t y = 0; y < m_framebuffer.height; y++) for (int32_t x = 0; x < m_framebuffer.width; x++) m_background_image[y * m_framebuffer.width + x] = image->get_color(x, y).as_argb(); invalidate(m_framebuffer.area()); return {}; } void WindowServer::on_window_create(int fd, const LibGUI::WindowPacket::WindowCreate& packet) { for (auto& window : m_client_windows) { if (window->client_fd() != fd) continue; dwarnln("client with window tried to create another one"); return; } const uint32_t width = packet.width ? packet.width : m_framebuffer.width; const uint32_t height = packet.height ? packet.height : m_framebuffer.height; auto window_or_error = BAN::RefPtr::create(fd, m_font); if (window_or_error.is_error()) { dwarnln("could not create window for client: {}", window_or_error.error()); return; } auto window = window_or_error.release_value(); if (auto ret = m_client_windows.push_back(window); ret.is_error()) { dwarnln("could not create window for client: {}", ret.error()); return; } BAN::ScopeGuard window_popper([&] { m_client_windows.pop_back(); }); if (auto ret = window->initialize(packet.title, width, height); ret.is_error()) { dwarnln("could not create window for client: {}", ret.error()); return; } window->set_attributes(packet.attributes); window->set_position({ static_cast((m_framebuffer.width - window->client_width()) / 2), static_cast((m_framebuffer.height - window->client_height()) / 2), }); const LibGUI::EventPacket::ResizeWindowEvent event_packet { .width = static_cast(window->client_width()), .height = static_cast(window->client_height()), .smo_key = window->smo_key(), }; if (auto ret = append_serialized_packet(event_packet, fd); ret.is_error()) { dwarnln("could not respond to window create request: {}", ret.error()); return; } window_popper.disable(); if (packet.attributes.shown && packet.attributes.focusable && m_state == State::Normal) set_focused_window(window); else if (m_client_windows.size() > 1) BAN::swap(m_client_windows[m_client_windows.size() - 1], m_client_windows[m_client_windows.size() - 2]); } void WindowServer::on_window_invalidate(int fd, const LibGUI::WindowPacket::WindowInvalidate& packet) { if (packet.width == 0 || packet.height == 0) return; if (m_state == State::Fullscreen) { ASSERT(m_focused_window); if (m_focused_window->client_fd() != fd) return; } auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to invalidate window while not owning a window"); return; } if (!target_window->get_attributes().shown) return; const auto client_area = target_window->client_area(); const Rectangle invalidate_area { .min_x = client_area.min_x + static_cast(packet.x), .min_y = client_area.min_y + static_cast(packet.y), .max_x = client_area.min_x + static_cast(packet.x + packet.width), .max_y = client_area.min_y + static_cast(packet.y + packet.height), }; if (auto opt_overlap = invalidate_area.get_overlap(client_area); opt_overlap.has_value()) invalidate(opt_overlap.release_value()); } void WindowServer::on_window_set_position(int fd, const LibGUI::WindowPacket::WindowSetPosition& packet) { if (m_state == State::Fullscreen) { ASSERT(m_focused_window); if (m_focused_window->client_fd() != fd) return; } auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window position while not owning a window"); return; } const auto old_client_area = target_window->full_area(); target_window->set_position({ .x = packet.x, .y = packet.y, }); if (!target_window->get_attributes().shown) return; invalidate(old_client_area); invalidate(target_window->full_area()); } void WindowServer::on_window_set_attributes(int fd, const LibGUI::WindowPacket::WindowSetAttributes& packet) { auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window attributes while not owning a window"); return; } const bool send_shown_event = target_window->get_attributes().shown != packet.attributes.shown; const auto old_client_area = target_window->full_area(); target_window->set_attributes(packet.attributes); invalidate(old_client_area); invalidate(target_window->full_area()); if ((!packet.attributes.focusable || !packet.attributes.shown) && m_focused_window == target_window) { if (m_state == State::Fullscreen && m_focused_window->get_attributes().resizable) { if (!resize_window(m_focused_window, m_non_full_screen_rect.width(), m_non_full_screen_rect.height())) return; m_focused_window->set_position({ m_non_full_screen_rect.min_x, m_non_full_screen_rect.min_y }); } m_focused_window = nullptr; m_state = State::Normal; for (size_t i = m_client_windows.size(); i > 0; i--) { auto& window = m_client_windows[i - 1]; if (auto attributes = window->get_attributes(); attributes.focusable && attributes.shown) { set_focused_window(window); break; } } } if (!send_shown_event) return; const LibGUI::EventPacket::WindowShownEvent event_packet { .event = { .shown = target_window->get_attributes().shown, }}; if (auto ret = append_serialized_packet(event_packet, target_window->client_fd()); ret.is_error()) dwarnln("could not send window shown event: {}", ret.error()); if (packet.attributes.focusable && packet.attributes.shown && m_state == State::Normal) set_focused_window(target_window); } void WindowServer::on_window_set_mouse_relative(int fd, const LibGUI::WindowPacket::WindowSetMouseRelative& packet) { if (m_is_mouse_relative && packet.enabled) { ASSERT(m_focused_window); if (fd != m_focused_window->client_fd()) dwarnln("client tried to set mouse relative while other window has it already"); return; } auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set mouse capture while not owning a window"); return; } if (!target_window->get_attributes().shown) { dwarnln("client tried to set mouse capture while hidden window"); return; } if (packet.enabled == m_is_mouse_relative) return; set_focused_window(target_window); m_is_mouse_relative = packet.enabled; invalidate(cursor_area()); } void WindowServer::on_window_set_size(int fd, const LibGUI::WindowPacket::WindowSetSize& packet) { auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window size while not owning a window"); return; } const auto old_area = target_window->full_area(); const uint32_t width = packet.width ? packet.width : m_framebuffer.width; const uint32_t height = packet.height ? packet.height : m_framebuffer.height; if (!resize_window(target_window, width, height)) return; if (!target_window->get_attributes().shown) return; invalidate(old_area); invalidate(target_window->full_area()); } void WindowServer::on_window_set_min_size(int fd, const LibGUI::WindowPacket::WindowSetMinSize& packet) { auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window min size while not owning a window"); return; } // FIXME: should this resize window target_window->set_min_size({ 0, 0, static_cast(packet.width), static_cast(packet.height) }); } void WindowServer::on_window_set_max_size(int fd, const LibGUI::WindowPacket::WindowSetMaxSize& packet) { auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window max size while not owning a window"); return; } // FIXME: should this resize window target_window->set_max_size({ 0, 0, static_cast(packet.width), static_cast(packet.height) }); } void WindowServer::on_window_set_fullscreen(int fd, const LibGUI::WindowPacket::WindowSetFullscreen& packet) { if (m_state == State::Fullscreen) { if (m_focused_window->client_fd() != fd) { dwarnln("client tried to set fullscreen state while another window is fullscreen"); return; } if (packet.fullscreen) return; if (m_focused_window->get_attributes().resizable) { if (!resize_window(m_focused_window, m_non_full_screen_rect.width(), m_non_full_screen_rect.height())) return; m_focused_window->set_position({ m_non_full_screen_rect.min_x, m_non_full_screen_rect.min_y }); } const LibGUI::EventPacket::WindowFullscreenEvent event_packet { .event = { .fullscreen = false, }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send window fullscreen event: {}", ret.error()); m_state = State::Normal; invalidate(m_framebuffer.area()); return; } if (!packet.fullscreen) return; auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window fullscreen while not owning a window"); return; } if (!target_window->get_attributes().shown) { dwarnln("client tried to set a hidden window fullscreen"); return; } if (target_window->get_attributes().resizable) { const auto old_area = target_window->client_area(); if (!resize_window(target_window, m_framebuffer.width, m_framebuffer.height)) return; target_window->set_position({ 0, 0 }); m_non_full_screen_rect = old_area; } const LibGUI::EventPacket::WindowFullscreenEvent event_packet { .event = { .fullscreen = true, }}; if (auto ret = append_serialized_packet(event_packet, target_window->client_fd()); ret.is_error()) dwarnln("could not send window fullscreen event: {}", ret.error()); m_state = State::Fullscreen; set_focused_window(target_window); invalidate(m_framebuffer.area()); } void WindowServer::on_window_set_title(int fd, const LibGUI::WindowPacket::WindowSetTitle& packet) { auto target_window = find_window_with_fd(fd); if (!target_window) { dwarnln("client tried to set window title while not owning a window"); return; } if (auto ret = target_window->set_title(packet.title); ret.is_error()) { dwarnln("failed to set window title: {}", ret.error()); return; } if (!target_window->get_attributes().shown) return; invalidate(target_window->title_text_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; } const auto old_cursor_area = cursor_area(); if (packet.width == 0 || packet.height == 0) target_window->remove_cursor(); else { Window::Cursor cursor { .width = packet.width, .height = packet.height, .origin_x = packet.origin_x, .origin_y = packet.origin_y, .pixels = {}, }; 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(old_cursor_area); invalidate(cursor_area()); } } static void update_volume(const char* new_volume) { char command[128]; sprintf(command, "audioctl --volume %s && kill -USR1 TaskBar", new_volume); system(command); } void WindowServer::on_key_event(LibInput::KeyEvent event) { if (event.key == LibInput::Key::Super) m_is_mod_key_held = event.pressed(); if (event.pressed() && event.key == LibInput::Key::VolumeDown) update_volume("-5"); if (event.pressed() && event.key == LibInput::Key::VolumeUp) update_volume("+5"); // Stop WindowServer with mod+shift+E if (m_is_mod_key_held && event.pressed() && event.shift() && event.key == LibInput::Key::E) { m_is_stopped = true; return; } // Start terminal with mod+Enter if (m_is_mod_key_held && event.pressed() && event.key == LibInput::Key::Enter) { pid_t pid = fork(); if (pid == 0) { execl("/usr/bin/Terminal", "Terminal", nullptr); exit(1); } if (pid == -1) perror("fork"); return; } // Start program launcher with mod+d if (m_is_mod_key_held && event.pressed() && event.key == LibInput::Key::D) { pid_t pid = fork(); if (pid == 0) { execl("/usr/bin/ProgramLauncher", "ProgramLauncher", nullptr); exit(1); } if (pid == -1) perror("fork"); return; } // Toggle window bounce with F2 if (event.pressed() && event.key == LibInput::Key::F2) { m_is_bouncing_window = !m_is_bouncing_window; return; } if (!m_focused_window) return; // Kill window with mod+Q if (m_is_mod_key_held && event.pressed() && event.key == LibInput::Key::Q) { const LibGUI::EventPacket::CloseWindowEvent event_packet {}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send window close event: {}", ret.error()); return; } if (m_is_mod_key_held && event.pressed() && event.key == LibInput::Key::F) { if (m_state == State::Fullscreen) { if (m_focused_window->get_attributes().resizable) { if (!resize_window(m_focused_window, m_non_full_screen_rect.width(), m_non_full_screen_rect.height())) return; m_focused_window->set_position({ m_non_full_screen_rect.min_x, m_non_full_screen_rect.min_y }); } m_state = State::Normal; } else { if (m_focused_window->get_attributes().resizable) { const auto old_area = m_focused_window->client_area(); if (!resize_window(m_focused_window, m_framebuffer.width, m_framebuffer.height)) return; m_focused_window->set_position({ 0, 0 }); m_non_full_screen_rect = old_area; } m_state = State::Fullscreen; } const LibGUI::EventPacket::WindowFullscreenEvent event_packet { .event = { .fullscreen = (m_state == State::Fullscreen), }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send window fullscreen event: {}", ret.error()); invalidate(m_framebuffer.area()); return; } const LibGUI::EventPacket::KeyEvent event_packet { .event = event, }; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send key event: {}", ret.error()); } void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event) { if (m_is_mouse_relative) { ASSERT(m_focused_window); const LibGUI::EventPacket::MouseButtonEvent event_packet { .event = { .button = event.button, .pressed = event.pressed, .x = 0, .y = 0, }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send mouse button event: {}", ret.error()); return; } const size_t button_idx = static_cast(event.button); if (button_idx >= m_mouse_button_windows.size()) { dwarnln("invalid mouse button {}", button_idx); return; } BAN::RefPtr target_window; if (m_state == State::Fullscreen) target_window = m_focused_window; if (!event.pressed && !target_window) target_window = m_mouse_button_windows[button_idx]; for (size_t i = m_client_windows.size(); i > 0 && !target_window; i--) if (m_client_windows[i - 1]->full_area().contains(m_cursor) && m_client_windows[i - 1]->get_attributes().shown) target_window = m_client_windows[i - 1]; switch (m_state) { case State::Normal: if (!target_window) break; if (target_window->get_attributes().focusable) set_focused_window(target_window); if (event.button == LibInput::MouseButton::Left && event.pressed) { const bool can_start_move = m_is_mod_key_held || target_window->title_text_area().contains(m_cursor); if (can_start_move && target_window->get_attributes().movable) { m_state = State::Moving; break; } } if (event.button == LibInput::MouseButton::Right && event.pressed) { const bool can_start_resize = m_is_mod_key_held; if (can_start_resize && target_window->get_attributes().resizable) { m_state = State::Resizing; m_resize_start = m_cursor; const bool right = m_cursor.x >= target_window->full_x() + target_window->full_width() / 2; const bool bottom = m_cursor.y >= target_window->full_y() + target_window->full_height() / 2; m_resize_quadrant = right + 2 * bottom; break; } } if (event.button == LibInput::MouseButton::Left && !event.pressed && target_window->close_button_area().contains(m_cursor)) { const LibGUI::EventPacket::CloseWindowEvent event_packet {}; if (auto ret = append_serialized_packet(event_packet, target_window->client_fd()); ret.is_error()) dwarnln("could not send close window event: {}", ret.error()); break; } [[fallthrough]]; case State::Fullscreen: if (target_window && (!event.pressed || target_window->client_area().contains(m_cursor))) { const LibGUI::EventPacket::MouseButtonEvent event_packet { .event = { .button = event.button, .pressed = event.pressed, .x = m_cursor.x - target_window->client_x(), .y = m_cursor.y - target_window->client_y(), }}; if (auto ret = append_serialized_packet(event_packet, target_window->client_fd()); ret.is_error()) { dwarnln("could not send mouse button event: {}", ret.error()); return; } m_mouse_button_windows[button_idx] = event.pressed ? target_window : nullptr; } break; case State::Moving: if (event.button == LibInput::MouseButton::Left && !event.pressed) m_state = State::Normal; break; case State::Resizing: if (event.button == LibInput::MouseButton::Right && !event.pressed) { const auto resize_area = this->resize_area(m_cursor); m_state = State::Normal; invalidate(resize_area); invalidate(m_focused_window->full_area()); if (auto ret = m_focused_window->resize(resize_area.width(), resize_area.height() - m_focused_window->title_bar_height()); ret.is_error()) { dwarnln("could not resize client window {}", ret.error()); return; } m_focused_window->set_position({ resize_area.min_x, resize_area.min_y + m_focused_window->title_bar_height() }); const LibGUI::EventPacket::ResizeWindowEvent event_packet { .width = static_cast(m_focused_window->client_width()), .height = static_cast(m_focused_window->client_height()), .smo_key = m_focused_window->smo_key(), }; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) { dwarnln("could not respond to window resize request: {}", ret.error()); return; } invalidate(m_focused_window->full_area()); } break; } } void WindowServer::on_mouse_move_impl(int32_t new_x, int32_t new_y) { const LibInput::MouseMoveEvent event { .rel_x = new_x - m_cursor.x, .rel_y = new_y - m_cursor.y, }; if (event.rel_x == 0 && event.rel_y == 0) return; const auto old_cursor_area = cursor_area(); m_cursor.x = new_x; m_cursor.y = new_y; invalidate(old_cursor_area); invalidate(cursor_area()); // TODO: Really no need to loop over every window for (auto& window : m_client_windows) { if (!window->get_attributes().shown) continue; const auto title_bar_area = window->title_bar_area(); if (title_bar_area.get_overlap(old_cursor_area).has_value() || title_bar_area.get_overlap(cursor_area()).has_value()) invalidate(title_bar_area); } if (!m_focused_window) return; switch (m_state) { case State::Normal: case State::Fullscreen: { const LibGUI::EventPacket::MouseMoveEvent event_packet { .event = { .x = m_cursor.x - m_focused_window->client_x(), .y = m_cursor.y - m_focused_window->client_y(), }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) { dwarnln("could not send mouse move event: {}", ret.error()); return; } break; } case State::Moving: { const auto old_window_area = m_focused_window->full_area(); m_focused_window->set_position({ m_focused_window->client_x() + event.rel_x, m_focused_window->client_y() + event.rel_y, }); invalidate(old_window_area); invalidate(m_focused_window->full_area()); break; } case State::Resizing: { invalidate(resize_area({ .x = old_cursor_area.min_x, .y = old_cursor_area.min_y })); invalidate(resize_area(m_cursor)); break; } } } void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event) { if (m_is_mouse_relative) { ASSERT(m_focused_window); const LibGUI::EventPacket::MouseMoveEvent event_packet { .event = { .x = event.rel_x, .y = -event.rel_y, }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send mouse move event: {}", ret.error()); return; } int32_t min_x, max_x; int32_t min_y, max_y; if (m_state == State::Fullscreen) { min_x = m_focused_window->client_x(); min_y = m_focused_window->client_y(); max_x = m_focused_window->client_x() + m_focused_window->client_width(); max_y = m_focused_window->client_y() + m_focused_window->client_height(); } else { min_x = 0; min_y = 0; max_x = m_framebuffer.width; max_y = m_framebuffer.height; } const int32_t new_x = BAN::Math::clamp(m_cursor.x + event.rel_x, min_x, max_x); const int32_t new_y = BAN::Math::clamp(m_cursor.y - event.rel_y, min_y, max_y); return on_mouse_move_impl(new_x, new_y); } void WindowServer::on_mouse_move_abs(LibInput::MouseMoveAbsEvent event) { constexpr auto map = [](int32_t val, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max) -> int32_t { return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }; if (m_is_mouse_relative) { dwarnln("relative mouse not supported with absolute mouse"); return; } int32_t out_min_x, out_max_x; int32_t out_min_y, out_max_y; if (m_state == State::Fullscreen) { out_min_x = m_focused_window->client_x(); out_min_y = m_focused_window->client_y(); out_max_x = m_focused_window->client_x() + m_focused_window->client_width(); out_max_y = m_focused_window->client_y() + m_focused_window->client_height(); } else { out_min_x = 0; out_min_y = 0; out_max_x = m_framebuffer.width; out_max_y = m_framebuffer.height; } const int32_t new_x = map(event.abs_x, event.min_x, event.max_x, out_min_x, out_max_x); const int32_t new_y = map(event.abs_y, event.min_y, event.max_y, out_min_y, out_max_y); return on_mouse_move_impl(new_x, new_y); } void WindowServer::on_mouse_scroll(LibInput::MouseScrollEvent event) { if (!m_focused_window) return; const LibGUI::EventPacket::MouseScrollEvent event_packet { .event = { .scroll = event.scroll, }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) { dwarnln("could not send mouse scroll event: {}", ret.error()); return; } } void WindowServer::set_focused_window(BAN::RefPtr window) { ASSERT(window->get_attributes().focusable); if (m_focused_window == window) return; if (m_is_mouse_relative) { m_is_mouse_relative = false; invalidate(cursor_area()); } if (m_focused_window) { const LibGUI::EventPacket::WindowFocusEvent event_packet { .event = { .focused = false, }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send window focus event: {}", ret.error()); } for (size_t i = m_client_windows.size(); i > 0; i--) { if (m_client_windows[i - 1] == window) { m_focused_window = window; m_client_windows.remove(i - 1); MUST(m_client_windows.push_back(window)); invalidate(window->full_area()); break; } } if (m_focused_window) { const LibGUI::EventPacket::WindowFocusEvent event_packet { .event = { .focused = true, }}; if (auto ret = append_serialized_packet(event_packet, m_focused_window->client_fd()); ret.is_error()) dwarnln("could not send window focus event: {}", ret.error()); } } static uint32_t alpha_blend(uint32_t color_a, uint32_t color_b) { const uint32_t a_a = color_a >> 24; const uint32_t a_b = 0xFF - a_a; const uint32_t rb1 = (a_a * (color_a & 0xFF00FF)) >> 8; const uint32_t rb2 = (a_b * (color_b & 0xFF00FF)) >> 8; const uint32_t g1 = (a_a * (color_a & 0x00FF00)) >> 8; const uint32_t g2 = (a_b * (color_b & 0x00FF00)) >> 8; const uint32_t a = a_a + (((color_b >> 24) * a_b) >> 8); return (a << 24) | ((rb1 + rb2) & 0xFF00FF) | ((g1 + g2) & 0x00FF00); } static void alpha_blend4(const uint32_t color_a[4], const uint32_t color_b[4], uint32_t color_out[4]) { // load colors const __m128i ca = _mm_loadu_si128((const __m128i*)color_a); const __m128i cb = _mm_loadu_si128((const __m128i*)color_b); // unpack colors to 16 bit words const __m128i zero = _mm_setzero_si128(); const __m128i ca_lo = _mm_unpacklo_epi8(ca, zero); const __m128i ca_hi = _mm_unpackhi_epi8(ca, zero); const __m128i cb_lo = _mm_unpacklo_epi8(cb, zero); const __m128i cb_hi = _mm_unpackhi_epi8(cb, zero); // extract alpha channel from color_a const __m128i a1_lo = _mm_shufflehi_epi16(_mm_shufflelo_epi16(ca_lo, 0b11'11'11'11), 0b11'11'11'11); const __m128i a1_hi = _mm_shufflehi_epi16(_mm_shufflelo_epi16(ca_hi, 0b11'11'11'11), 0b11'11'11'11); // calculate inverse alpha const __m128i low_byte16 = _mm_set1_epi16(0xFF); const __m128i a2_lo = _mm_sub_epi16(low_byte16, a1_lo); const __m128i a2_hi = _mm_sub_epi16(low_byte16, a1_hi); // blend and pack rgb (a*c1 + c2*(255-a)) / 256 const __m128i rgb_lo = _mm_srli_epi16(_mm_add_epi16(_mm_mullo_epi16(ca_lo, a1_lo), _mm_mullo_epi16(cb_lo, a2_lo)), 8); const __m128i rgb_hi = _mm_srli_epi16(_mm_add_epi16(_mm_mullo_epi16(ca_hi, a1_hi), _mm_mullo_epi16(cb_hi, a2_hi)), 8); const __m128i rgb = _mm_and_si128(_mm_set1_epi32(0x00FFFFFF), _mm_packus_epi16(rgb_lo, rgb_hi)); // extract alpha channel from color_b const __m128i ab_lo = _mm_shufflehi_epi16(_mm_shufflelo_epi16(cb_lo, 0b11'11'11'11), 0b11'11'11'11); const __m128i ab_hi = _mm_shufflehi_epi16(_mm_shufflelo_epi16(cb_hi, 0b11'11'11'11), 0b11'11'11'11); // blend and pack alpha (a + ab*(255-a)) / 256 const __m128i alpha_lo = _mm_add_epi16(a1_lo, _mm_srli_epi16(_mm_mullo_epi16(ab_lo, a2_lo), 8)); const __m128i alpha_hi = _mm_add_epi16(a1_hi, _mm_srli_epi16(_mm_mullo_epi16(ab_hi, a2_hi), 8)); const __m128i alpha = _mm_slli_epi32(_mm_packus_epi16(alpha_lo, alpha_hi), 24); _mm_storeu_si128((__m128i*)color_out, _mm_or_si128(alpha, rgb)); } void WindowServer::invalidate(Rectangle area) { 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 = [window_cursor](int32_t rel_x, int32_t rel_y) -> BAN::Optional { 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; const uint32_t r = ((( s_default_cursor_data[offset + 0] - 33 ) << 2) | ((s_default_cursor_data[offset + 1] - 33) >> 4)); const uint32_t g = ((((s_default_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_default_cursor_data[offset + 2] - 33) >> 2)); const uint32_t b = ((((s_default_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_default_cursor_data[offset + 3] - 33) )); const uint32_t color = (r << 16) | (g << 8) | b; if (color == 0xFF00FF) return {}; return color; }; if (m_state == State::Fullscreen) { ASSERT(m_focused_window); area.min_x -= m_focused_window->client_x(); area.max_x -= m_focused_window->client_x(); area.min_y -= m_focused_window->client_y(); area.max_y -= m_focused_window->client_y(); const Rectangle client_area { .min_x = 0, .min_y = 0, .max_x = m_focused_window->client_width(), .max_y = m_focused_window->client_height(), }; auto focused_overlap = area.get_overlap(client_area); if (!focused_overlap.has_value()) return; area = focused_overlap.release_value(); const bool should_alpha_blend = m_focused_window->get_attributes().alpha_channel; if (client_area == m_framebuffer.area()) { const uint32_t* client_ptr = m_focused_window->framebuffer(); const size_t client_width = m_focused_window->client_width(); if (!should_alpha_blend) { for (int32_t y = area.min_y; y < area.max_y; y++) { memcpy( &m_framebuffer.mmap[y * m_framebuffer.width + area.min_x], &client_ptr[y * client_width + area.min_x], area.width() * sizeof(uint32_t) ); } } else { for (int32_t y = area.min_y; y < area.max_y; y++) { const uint32_t* window_row = &client_ptr[y * client_width + area.min_x]; const uint32_t* image_row = &m_background_image[y * m_framebuffer.width + area.min_x]; uint32_t* frameb_row = &m_framebuffer.mmap[y * m_framebuffer.width + area.min_x]; int32_t pixels = area.width(); for (; pixels >= 4; pixels -= 4) { alpha_blend4(window_row, image_row, frameb_row); frameb_row += 4; window_row += 4; image_row += 4; } for (; pixels > 0; pixels--) { *frameb_row = alpha_blend(*window_row, *image_row); frameb_row++; window_row++; image_row++; } } } mark_pending_sync(area); } else { auto opt_dst_area = Rectangle { .min_x = area.min_x * m_framebuffer.width / m_focused_window->client_width(), .min_y = area.min_y * m_framebuffer.height / m_focused_window->client_height(), .max_x = BAN::Math::div_round_up(area.max_x * m_framebuffer.width, m_focused_window->client_width()), .max_y = BAN::Math::div_round_up(area.max_y * m_framebuffer.height, m_focused_window->client_height()) }.get_overlap(m_framebuffer.area()); if (!opt_dst_area.has_value()) return; const auto dst_area = opt_dst_area.release_value(); for (int32_t dst_y = dst_area.min_y; dst_y < dst_area.max_y; dst_y++) { for (int32_t dst_x = dst_area.min_x; dst_x < dst_area.max_x; dst_x++) { const int32_t src_x = BAN::Math::clamp(dst_x * m_focused_window->client_width() / m_framebuffer.width, 0, m_focused_window->client_width()); const int32_t src_y = BAN::Math::clamp(dst_y * m_focused_window->client_height() / m_framebuffer.height, 0, m_focused_window->client_height()); const uint32_t src_pixel = m_focused_window->framebuffer()[src_y * m_focused_window->client_width() + src_x]; const uint32_t bg_pixel = m_background_image[dst_y * m_framebuffer.width + dst_x]; uint32_t& dst_pixel = m_framebuffer.mmap[dst_y * m_framebuffer.width + dst_x]; dst_pixel = should_alpha_blend ? alpha_blend(src_pixel, bg_pixel) : src_pixel; } } mark_pending_sync(dst_area); } if (!m_is_mouse_relative) { auto cursor_area = this->cursor_area(); cursor_area.min_x -= m_focused_window->client_x(); cursor_area.max_x -= m_focused_window->client_x(); cursor_area.min_y -= m_focused_window->client_y(); cursor_area.max_y -= m_focused_window->client_y(); if (!area.get_overlap(cursor_area).has_value()) return; const int32_t cursor_tl_dst_x = cursor_area.min_x * m_framebuffer.width / m_focused_window->client_width(); const int32_t cursor_tl_dst_y = cursor_area.min_y * m_framebuffer.height / m_focused_window->client_height(); for (int32_t rel_y = 0; rel_y < cursor_area.height(); rel_y++) { 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()) continue; const int32_t dst_x = cursor_tl_dst_x + rel_x; const int32_t dst_y = cursor_tl_dst_y + rel_y; if (dst_x < 0 || dst_x >= m_framebuffer.width) continue; if (dst_y < 0 || dst_y >= m_framebuffer.height) continue; m_framebuffer.mmap[dst_y * m_framebuffer.width + dst_x] = pixel.value(); } } if (auto fb_overlap = cursor_area.get_overlap(m_framebuffer.area()); fb_overlap.has_value()) mark_pending_sync(fb_overlap.value()); } return; } 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.min_y; y < area.max_y; y++) { memcpy( &m_framebuffer.mmap[y * m_framebuffer.width + area.min_x], &m_background_image[y * m_framebuffer.width + area.min_x], area.width() * sizeof(uint32_t) ); } // FIXME: this loop should be inverse order and terminate // after window without alpha channel is found for (auto& pwindow : m_client_windows) { auto& window = *pwindow; if (!window.get_attributes().shown) continue; const auto window_full_area = window.full_area(); const Rectangle fast_areas[] { { .min_x = window_full_area.min_x, .min_y = window_full_area.min_y + m_corner_radius, .max_x = window_full_area.max_x, .max_y = window_full_area.max_y - m_corner_radius, }, { .min_x = window_full_area.min_x + m_corner_radius, .min_y = window_full_area.min_y, .max_x = window_full_area.max_x - m_corner_radius, .max_y = window_full_area.min_y + m_corner_radius, }, { .min_x = window_full_area.min_x + m_corner_radius, .min_y = window_full_area.max_y - m_corner_radius, .max_x = window_full_area.max_x - m_corner_radius, .max_y = window_full_area.max_y, }, }; const Position corner_centers[] { { .x = window_full_area.min_x + m_corner_radius, .y = window_full_area.min_y + m_corner_radius, }, { .x = window_full_area.max_x - m_corner_radius - 1, .y = window_full_area.min_y + m_corner_radius, }, { .x = window_full_area.min_x + m_corner_radius, .y = window_full_area.max_y - m_corner_radius - 1, }, { .x = window_full_area.max_x - m_corner_radius - 1, .y = window_full_area.max_y - m_corner_radius - 1, }, }; const Rectangle corner_areas[] { { .min_x = window_full_area.min_x, .min_y = window_full_area.min_y, .max_x = window_full_area.min_x + m_corner_radius, .max_y = window_full_area.min_y + m_corner_radius, }, { .min_x = window_full_area.max_x - m_corner_radius, .min_y = window_full_area.min_y, .max_x = window_full_area.max_x, .max_y = window_full_area.min_y + m_corner_radius, }, { .min_x = window_full_area.min_x, .min_y = window_full_area.max_y - m_corner_radius, .max_x = window_full_area.min_x + m_corner_radius, .max_y = window_full_area.max_y, }, { .min_x = window_full_area.max_x - m_corner_radius, .min_y = window_full_area.max_y - m_corner_radius, .max_x = window_full_area.max_x, .max_y = window_full_area.max_y, }, }; const auto is_rounded_off = [&](const Window& window, Position pos) -> bool { if (!window.get_attributes().rounded_corners) return false; for (int32_t i = 0; i < 4; i++) { if (!corner_areas[i].contains(pos)) continue; const int32_t dx = pos.x - corner_centers[i].x; const int32_t dy = pos.y - corner_centers[i].y; if (2 * (dy > 0) + (dx > 0) != i) continue; if (dx * dx + dy * dy >= m_corner_radius * m_corner_radius) return true; } return false; }; // window title bar if (auto opt_title_overlap = window.title_bar_area().get_overlap(area); opt_title_overlap.has_value()) { const auto title_overlap = opt_title_overlap.release_value(); for (int32_t abs_y = title_overlap.min_y; abs_y < title_overlap.max_y; abs_y++) { for (int32_t abs_x = title_overlap.min_x; abs_x < title_overlap.max_x; abs_x++) { if (is_rounded_off(window, { abs_x, abs_y })) continue; m_framebuffer.mmap[abs_y * m_framebuffer.width + abs_x] = window.title_bar_pixel(abs_x, abs_y, m_cursor); } } } // window client area if (auto opt_client_overlap = window.client_area().get_overlap(area); opt_client_overlap.has_value()) { const bool should_alpha_blend = window.get_attributes().alpha_channel; const auto client_overlap = opt_client_overlap.release_value(); for (const auto& fast_area : fast_areas) { auto opt_fast_overlap = client_overlap.get_overlap(fast_area); if (!opt_fast_overlap.has_value()) continue; const auto fast_overlap = opt_fast_overlap.release_value(); for (int32_t abs_row_y = fast_overlap.min_y; abs_row_y < fast_overlap.max_y; abs_row_y++) { const int32_t abs_row_x = fast_overlap.min_x; const int32_t src_row_y = abs_row_y - window.client_y(); const int32_t src_row_x = abs_row_x - window.client_x(); auto* window_row = &window.framebuffer()[src_row_y * window.client_width() + src_row_x]; auto* frameb_row = &m_framebuffer.mmap[abs_row_y * m_framebuffer.width + abs_row_x]; if (!should_alpha_blend) memcpy(frameb_row, window_row, fast_overlap.width() * sizeof(uint32_t)); else { int32_t pixels = fast_overlap.width(); for (; pixels >= 4; pixels -= 4) { alpha_blend4(window_row, frameb_row, frameb_row); window_row += 4; frameb_row += 4; } for (; pixels; pixels--) { *frameb_row = alpha_blend(*window_row, *frameb_row); window_row++; frameb_row++; } } } } for (const auto& corner_area : corner_areas) { auto opt_corner_overlap = client_overlap.get_overlap(corner_area); if (!opt_corner_overlap.has_value()) continue; const auto corner_overlap = opt_corner_overlap.release_value(); for (int32_t abs_y = corner_overlap.min_y; abs_y < corner_overlap.max_y; abs_y++) { for (int32_t abs_x = corner_overlap.min_x; abs_x < corner_overlap.max_x; abs_x++) { if (is_rounded_off(window, { abs_x, abs_y })) continue; const int32_t src_x = abs_x - window.client_x(); const int32_t src_y = abs_y - window.client_y(); const uint32_t color_a = window.framebuffer()[src_y * window.client_width() + src_x]; const uint32_t color_b = m_framebuffer.mmap[abs_y * m_framebuffer.width + abs_x]; // NOTE: corners are small so we do them one pixel at a time to keep the code simple m_framebuffer.mmap[abs_y * m_framebuffer.width + abs_x] = should_alpha_blend ? alpha_blend(color_a, color_b) : color_a; } } } } } if (m_state == State::Resizing) { if (auto opt_overlap = resize_area(m_cursor).get_overlap(area); opt_overlap.has_value()) { constexpr uint32_t blend_color = 0x80000000; const auto overlap = opt_overlap.release_value(); for (int32_t y = overlap.min_y; y < overlap.max_y; y++) { uint32_t* frameb_row = &m_framebuffer.mmap[y * m_framebuffer.width + overlap.min_x]; int32_t pixels = overlap.width(); for (; pixels >= 4; pixels -= 4) { const uint32_t blend_colors[] { blend_color, blend_color, blend_color, blend_color }; alpha_blend4(blend_colors, frameb_row, frameb_row); frameb_row += 4; } for (; pixels > 0; pixels--) { *frameb_row = alpha_blend(blend_color, *frameb_row); frameb_row++; } } } } if (!m_is_mouse_relative) { if (auto opt_overlap = cursor_area().get_overlap(area); opt_overlap.has_value()) { const int32_t origin_x = window_cursor ? window_cursor->origin_x : 0; const int32_t origin_y = window_cursor ? window_cursor->origin_y : 0; const auto overlap = opt_overlap.release_value(); for (int32_t abs_y = overlap.min_y; abs_y < overlap.max_y; abs_y++) { for (int32_t abs_x = overlap.min_x; abs_x < overlap.max_x; abs_x++) { const int32_t rel_x = abs_x - m_cursor.x + origin_x; const int32_t rel_y = abs_y - m_cursor.y + origin_y; const auto pixel = get_cursor_pixel(rel_x, rel_y); if (pixel.has_value()) m_framebuffer.mmap[abs_y * m_framebuffer.width + abs_x] = pixel.value(); } } } } mark_pending_sync(area); } void WindowServer::RangeList::add_range(const Range& range) { if (range_count == 0) { ranges[0] = range; range_count++; return; } size_t min_distance_value = SIZE_MAX; size_t min_distance_index = 0; for (size_t i = 0; i < range_count; i++) { if (ranges[i].is_continuous_with(range)) { ranges[i].merge_with(range); size_t last_continuous = i; for (size_t j = i + 1; j < range_count; j++) { if (!ranges[i].is_continuous_with(ranges[j])) break; last_continuous = j; } if (last_continuous != i) { ranges[i].merge_with(ranges[last_continuous]); for (size_t j = 1; last_continuous + j < range_count; j++) ranges[i + j] = ranges[last_continuous + j]; range_count -= last_continuous - i; } return; } const auto distance = ranges[i].distance_between(range); if (distance < min_distance_value) { min_distance_value = distance; min_distance_index = i; } } if (range_count >= ranges.size()) { ranges[min_distance_index].merge_with(range); return; } size_t insert_idx = 0; for (; insert_idx < range_count; insert_idx++) if (range.start < ranges[insert_idx].start) break; for (size_t i = range_count; i > insert_idx; i--) ranges[i] = ranges[i - 1]; ranges[insert_idx] = range; range_count++; } void WindowServer::mark_pending_sync(Rectangle to_sync) { ASSERT(to_sync == to_sync.get_overlap(m_framebuffer.area()).value()); for (int32_t abs_y = to_sync.min_y; abs_y < to_sync.max_y; abs_y++) m_pending_syncs[abs_y].add_range({ static_cast(to_sync.min_x), static_cast(to_sync.width()) }); } void WindowServer::sync() { if (m_focused_window && m_is_bouncing_window) { static int32_t dir_x = 7; static int32_t dir_y = 4; auto old_window = m_focused_window->full_area(); m_focused_window->set_position({ m_focused_window->client_x() + dir_x, m_focused_window->client_y() + dir_y, }); invalidate(old_window); invalidate(m_focused_window->full_area()); if ((m_focused_window->full_x() < 0 && dir_x < 0) || (m_focused_window->full_x() + m_focused_window->full_width() >= m_framebuffer.width && dir_x > 0)) dir_x = -dir_x; if ((m_focused_window->full_y() < 0 && dir_y < 0) || (m_focused_window->full_y() + m_focused_window->full_height() >= m_framebuffer.height && dir_y > 0)) dir_y = -dir_y; } size_t range_start = 0; size_t range_count = 0; for (int32_t y = 0; y < m_framebuffer.height; y++) { auto& range_list = m_pending_syncs[y]; for (size_t i = 0; i < range_list.range_count; i++) { const size_t cur_start = y * m_framebuffer.width + range_list.ranges[i].start; const size_t cur_count = range_list.ranges[i].count; if (range_count == 0) { range_start = cur_start; range_count = cur_count; } else { const size_t distance = cur_start - (range_start + range_count); // combine nearby ranges to reduce msync calls // NOTE: value of 128 is an arbitary constant that *just* felt nice if (distance <= 128) range_count = (cur_start + cur_count) - range_start; else { msync(m_framebuffer.mmap + range_start, range_count * 4, MS_SYNC); range_start = cur_start; range_count = cur_count; } } } range_list.range_count = 0; } if (range_count) msync(m_framebuffer.mmap + range_start, range_count * 4, MS_SYNC); } Rectangle WindowServer::cursor_area() const { int32_t width = s_default_cursor_width; int32_t height = s_default_cursor_height; int32_t origin_x = 0; int32_t origin_y = 0; 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; origin_x = window->cursor().origin_x; origin_y = window->cursor().origin_y; } } return Rectangle { .min_x = m_cursor.x - origin_x, .min_y = m_cursor.y - origin_y, .max_x = m_cursor.x - origin_x + width, .max_y = m_cursor.y - origin_y + height, }; } Rectangle WindowServer::resize_area(Position cursor) const { const auto min_size = m_focused_window->get_min_size(); const auto max_size = m_focused_window->get_max_size(); int32_t diff_x = m_resize_start.x - cursor.x; if (m_resize_quadrant % 2) diff_x = -diff_x; diff_x = BAN::Math::clamp(diff_x, -m_focused_window->client_width() + min_size.width(), -m_focused_window->client_width() + max_size.width() ); int32_t diff_y = m_resize_start.y - cursor.y; if (m_resize_quadrant / 2) diff_y = -diff_y; diff_y = BAN::Math::clamp(diff_y, -m_focused_window->client_height() + min_size.height(), -m_focused_window->client_height() + max_size.height() ); int32_t off_x = 0; if (m_resize_quadrant % 2 == 0) off_x = -diff_x; int32_t off_y = 0; if (m_resize_quadrant / 2 == 0) off_y = -diff_y; const int min_x = off_x + m_focused_window->full_x(); const int min_y = off_y + m_focused_window->full_y(); return Rectangle { .min_x = min_x, .min_y = min_y, .max_x = min_x + diff_x + m_focused_window->full_width(), .max_y = min_y + diff_y + m_focused_window->full_height(), }; } BAN::RefPtr WindowServer::find_window_with_fd(int fd) const { for (auto window : m_client_windows) if (window->client_fd() == fd) return window; return {}; } BAN::RefPtr WindowServer::find_hovered_window() const { for (size_t i = m_client_windows.size(); i > 0; i--) if (auto window = m_client_windows[i - 1]; window->client_area().contains(m_cursor)) return window; return {}; } bool WindowServer::resize_window(BAN::RefPtr window, uint32_t width, uint32_t height) { if (auto ret = window->resize(width, height); ret.is_error()) { dwarnln("could not resize client window {}", ret.error()); return false; } const LibGUI::EventPacket::ResizeWindowEvent event_packet { .width = static_cast(window->client_width()), .height = static_cast(window->client_height()), .smo_key = window->smo_key(), }; if (auto ret = append_serialized_packet(event_packet, window->client_fd()); ret.is_error()) { dwarnln("could not respond to window resize request: {}", ret.error()); return false; } return true; } BAN::ErrorOr WindowServer::add_client_fd(int fd) { TRY(m_client_data.emplace(fd)); return {}; } void WindowServer::remove_client_fd(int fd) { auto it = m_client_data.find(fd); if (it == m_client_data.end()) return; m_client_data.remove(it); if (m_state == State::Fullscreen && m_focused_window->client_fd() == fd) { m_state = State::Normal; invalidate(m_framebuffer.area()); } for (auto& window : m_mouse_button_windows) if (window && window->client_fd() == fd) window.clear(); 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; for (size_t j = m_client_windows.size(); j > 0; j--) { auto& client_window = m_client_windows[j - 1]; if (!client_window->get_attributes().focusable) continue; set_focused_window(client_window); break; } } break; } } } WindowServer::ClientData& WindowServer::get_client_data(int fd) { auto it = m_client_data.find(fd); if (it != m_client_data.end()) return it->value; dwarnln("could not find client {}", fd); for (auto& [client_fd, _] : m_client_data) dwarnln(" {}", client_fd); ASSERT_NOT_REACHED(); } // TODO: this epoll stuff is very hacky #include extern int g_epoll_fd; template BAN::ErrorOr WindowServer::append_serialized_packet(const T& packet, int fd) { const size_t serialized_size = packet.serialized_size(); auto& client_data = m_client_data[fd]; if (client_data.out_buffer_size + serialized_size > client_data.out_buffer.size()) return BAN::Error::from_errno(ENOBUFS); if (client_data.out_buffer_size == 0) { epoll_event event { .events = EPOLLIN | EPOLLOUT, .data = { .fd = fd } }; if (epoll_ctl(g_epoll_fd, EPOLL_CTL_MOD, fd, &event) == -1) dwarnln("epoll_ctl add EPOLLOUT: {}", strerror(errno)); } packet.serialize(client_data.out_buffer.span().slice(client_data.out_buffer_size, serialized_size)); client_data.out_buffer_size += serialized_size; return {}; }