Files
banan-os/userspace/programs/WindowServer/WindowServer.cpp
Bananymous a4ba1da65a LibGUI/WindowServer: Rework packet serialization
Instead of sending while serializing (what even was that), we serialize
the whole packet into a buffer which can be sent in one go. First of all
this reduces the number of sends by a lot. This also fixes WindowServer
ending up sending partial packets when client is not responsive.
Previously we would just try sending once, if any send failed the send
was aborted while partial packet was already transmitted. This lead to
packet stream being out of sync leading to the client killing itself.
Now we allow 64 KiB outgoing buffer per client. If this buffer ever fills
up, we will not send partial packets.
2026-04-11 03:30:52 +03:00

1640 lines
48 KiB
C++

#include "Cursor.h"
#include "WindowServer.h"
#include <BAN/Debug.h>
#include <BAN/ScopeGuard.h>
#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>
#include <stdlib.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
#include <emmintrin.h>
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<void> WindowServer::set_background_image(BAN::UniqPtr<LibImage::Image> 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<Window>::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<int32_t>((m_framebuffer.width - window->client_width()) / 2),
static_cast<int32_t>((m_framebuffer.height - window->client_height()) / 2),
});
LibGUI::EventPacket::ResizeWindowEvent response;
response.width = window->client_width();
response.height = window->client_height();
response.smo_key = window->smo_key();
if (auto ret = append_serialized_packet(response, 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;
invalidate({
target_window->client_x() + static_cast<int32_t>(packet.x),
target_window->client_y() + static_cast<int32_t>(packet.y),
BAN::Math::min<int32_t>(packet.width, target_window->client_width()),
BAN::Math::min<int32_t>(packet.height, target_window->client_height())
});
}
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;
const auto new_client_area = target_window->full_area();
invalidate(new_client_area.get_bounding_box(old_client_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);
const auto new_client_area = target_window->full_area();
invalidate(new_client_area.get_bounding_box(old_client_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.x, m_non_full_screen_rect.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;
auto event_packet = LibGUI::EventPacket::WindowShownEvent {
.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(target_window->full_area().get_bounding_box(old_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<int32_t>(packet.width), static_cast<int32_t>(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<int32_t>(packet.width), static_cast<int32_t>(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.x, m_non_full_screen_rect.y });
}
auto event_packet = LibGUI::EventPacket::WindowFullscreenEvent {
.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;
}
auto event_packet = LibGUI::EventPacket::WindowFullscreenEvent {
.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_bar_area());
}
void WindowServer::on_window_set_cursor(int fd, const LibGUI::WindowPacket::WindowSetCursor& packet)
{
auto target_window = find_window_with_fd(fd);
if (!target_window)
{
dwarnln("client tried to set cursor while not owning a window");
return;
}
if (BAN::Math::will_multiplication_overflow(packet.width, packet.height))
{
dwarnln("client tried to set cursor with invalid size {}x{}", packet.width, packet.height);
return;
}
if (packet.width * packet.height != packet.pixels.size())
{
dwarnln("client tried to set cursor with buffer size mismatch {}x{}, {} pixels", packet.width, packet.height, packet.pixels.size());
return;
}
auto old_cursor = cursor_area();
if (packet.width == 0 || packet.height == 0)
target_window->remove_cursor();
else
{
Window::Cursor cursor;
cursor.width = packet.width;
cursor.height = packet.height;
cursor.origin_x = packet.origin_x;
cursor.origin_y = packet.origin_y;
if (auto ret = cursor.pixels.resize(packet.pixels.size()); ret.is_error())
{
dwarnln("failed to set cursor: {}", ret.error());
return;
}
for (size_t i = 0; i < cursor.pixels.size(); i++)
cursor.pixels[i] = packet.pixels[i];
target_window->set_cursor(BAN::move(cursor));
}
if (find_hovered_window() == target_window)
invalidate(cursor_area().get_bounding_box(old_cursor));
}
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)
{
LibGUI::EventPacket::CloseWindowEvent packet;
if (auto ret = append_serialized_packet(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.x, m_non_full_screen_rect.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;
}
auto event_packet = LibGUI::EventPacket::WindowFullscreenEvent {
.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;
}
LibGUI::EventPacket::KeyEvent packet;
packet.event = event;
if (auto ret = append_serialized_packet(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);
LibGUI::EventPacket::MouseButtonEvent packet;
packet.event.button = event.button;
packet.event.pressed = event.pressed;
packet.event.x = 0;
packet.event.y = 0;
if (auto ret = append_serialized_packet(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<size_t>(event.button);
if (button_idx >= m_mouse_button_windows.size())
{
dwarnln("invalid mouse button {}", button_idx);
return;
}
BAN::RefPtr<Window> 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))
{
LibGUI::EventPacket::CloseWindowEvent packet;
if (auto ret = append_serialized_packet(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)))
{
LibGUI::EventPacket::MouseButtonEvent packet;
packet.event.button = event.button;
packet.event.pressed = event.pressed;
packet.event.x = m_cursor.x - target_window->client_x();
packet.event.y = m_cursor.y - target_window->client_y();
if (auto ret = append_serialized_packet(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.get_bounding_box(m_focused_window->full_area()));
const auto old_area = 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.x, resize_area.y + m_focused_window->title_bar_height() });
LibGUI::EventPacket::ResizeWindowEvent event;
event.width = m_focused_window->client_width();
event.height = m_focused_window->client_height();
event.smo_key = m_focused_window->smo_key();
if (auto ret = append_serialized_packet(event, 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().get_bounding_box(old_area));
}
break;
}
}
void WindowServer::on_mouse_move_impl(int32_t new_x, int32_t new_y)
{
LibInput::MouseMoveEvent event;
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;
auto old_cursor = cursor_area();
m_cursor.x = new_x;
m_cursor.y = new_y;
auto new_cursor = cursor_area();
invalidate(old_cursor);
invalidate(new_cursor);
// TODO: Really no need to loop over every window
for (auto& window : m_client_windows)
{
if (!window->get_attributes().shown)
continue;
auto title_bar = window->title_bar_area();
if (title_bar.get_overlap(old_cursor).has_value() || title_bar.get_overlap(new_cursor).has_value())
invalidate(title_bar);
}
if (!m_focused_window)
return;
switch (m_state)
{
case State::Normal:
case State::Fullscreen:
{
LibGUI::EventPacket::MouseMoveEvent packet;
packet.event.x = m_cursor.x - m_focused_window->client_x();
packet.event.y = m_cursor.y - m_focused_window->client_y();
if (auto ret = append_serialized_packet(packet, m_focused_window->client_fd()); ret.is_error())
{
dwarnln("could not send mouse move event: {}", ret.error());
return;
}
break;
}
case State::Moving:
{
auto old_window = 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,
});
auto new_window = m_focused_window->full_area();
invalidate(old_window);
invalidate(new_window);
break;
}
case State::Resizing:
{
const auto old_resize_area = resize_area({ old_cursor.x, old_cursor.y });
const auto new_resize_area = resize_area({ new_cursor.x, new_cursor.y });
invalidate(old_resize_area.get_bounding_box(new_resize_area));
break;
}
}
}
void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event)
{
if (m_is_mouse_relative)
{
ASSERT(m_focused_window);
LibGUI::EventPacket::MouseMoveEvent packet;
packet.event.x = event.rel_x;
packet.event.y = -event.rel_y;
if (auto ret = append_serialized_packet(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)
{
LibGUI::EventPacket::MouseScrollEvent packet;
packet.event.scroll = event.scroll;
if (auto ret = append_serialized_packet(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> 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)
{
LibGUI::EventPacket::WindowFocusEvent packet;
packet.event.focused = false;
if (auto ret = append_serialized_packet(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)
{
LibGUI::EventPacket::WindowFocusEvent packet;
packet.event.focused = true;
if (auto ret = append_serialized_packet(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<uint32_t>
{
if (window_cursor)
{
const auto pixel = window_cursor->pixels[rel_y * window_cursor->width + rel_x];
if ((pixel >> 24) == 0)
return {};
return pixel & 0xFFFFFF;
}
const uint32_t offset = (rel_y * s_default_cursor_width + rel_x) * 4;
uint32_t r = (((s_default_cursor_data[offset + 0] - 33) << 2) | ((s_default_cursor_data[offset + 1] - 33) >> 4));
uint32_t g = ((((s_default_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_default_cursor_data[offset + 2] - 33) >> 2));
uint32_t b = ((((s_default_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_default_cursor_data[offset + 3] - 33)));
uint32_t color = (r << 16) | (g << 8) | b;
if (color == 0xFF00FF)
return {};
return color;
};
if (m_state == State::Fullscreen)
{
ASSERT(m_focused_window);
area.x -= m_focused_window->client_x();
area.y -= m_focused_window->client_y();
const Rectangle client_area {
.x = 0,
.y = 0,
.width = m_focused_window->client_width(),
.height = 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.y; y < area.y + area.height; y++)
{
memcpy(
&m_framebuffer.mmap[y * m_framebuffer.width + area.x],
&client_ptr[y * client_width + area.x],
area.width * sizeof(uint32_t)
);
}
}
else
{
for (int32_t y = area.y; y < area.y + area.height; y++)
{
const uint32_t* window_row = &client_ptr[y * client_width + area.x];
const uint32_t* image_row = &m_background_image[y * m_framebuffer.width + area.x];
uint32_t* frameb_row = &m_framebuffer.mmap[y * m_framebuffer.width + area.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 {
.x = area.x * m_framebuffer.width / m_focused_window->client_width(),
.y = area.y * m_framebuffer.height / m_focused_window->client_height(),
.width = BAN::Math::div_round_up(area.width * m_framebuffer.width, m_focused_window->client_width()),
.height = BAN::Math::div_round_up(area.height * 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.y; dst_y < dst_area.y + dst_area.height; dst_y++)
{
for (int32_t dst_x = dst_area.x; dst_x < dst_area.x + dst_area.width; dst_x++)
{
const int32_t src_x = BAN::Math::clamp<int32_t>(dst_x * m_focused_window->client_width() / m_framebuffer.width, 0, m_focused_window->client_width());
const int32_t src_y = BAN::Math::clamp<int32_t>(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.x -= m_focused_window->client_x();
cursor_area.y -= m_focused_window->client_y();
if (!area.get_overlap(cursor_area).has_value())
return;
const int32_t cursor_tl_dst_x = cursor_area.x * m_framebuffer.width / m_focused_window->client_width();
const int32_t cursor_tl_dst_y = cursor_area.y * m_framebuffer.height / m_focused_window->client_height();
for (int32_t rel_y = 0; rel_y < 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.y; y < area.y + area.height; y++)
{
memcpy(
&m_framebuffer.mmap[y * m_framebuffer.width + area.x],
&m_background_image[y * m_framebuffer.width + area.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 Rectangle fast_areas[] {
{
window.full_x() + m_corner_radius,
window.full_y(),
window.full_width() - 2 * m_corner_radius,
m_corner_radius
},
{
window.full_x(),
window.full_y() + m_corner_radius,
window.full_width(),
window.full_height() - 2 * m_corner_radius
},
{
window.full_x() + m_corner_radius,
window.full_y() + window.full_height() - m_corner_radius,
window.full_width() - 2 * m_corner_radius,
m_corner_radius
}
};
const Position corner_centers[] {
{
window.full_x() + m_corner_radius,
window.full_y() + m_corner_radius,
},
{
window.full_x() + (window.full_width() - 1) - m_corner_radius,
window.full_y() + m_corner_radius,
},
{
window.full_x() + m_corner_radius,
window.full_y() + (window.full_height() - 1) - m_corner_radius,
},
{
window.full_x() + (window.full_width() - 1) - m_corner_radius,
window.full_y() + (window.full_height() - 1) - m_corner_radius,
},
};
const Rectangle corner_areas[] {
{
window.full_x(),
window.full_y(),
m_corner_radius,
m_corner_radius
},
{
window.full_x() + window.full_width() - m_corner_radius,
window.full_y(),
m_corner_radius,
m_corner_radius
},
{
window.full_x(),
window.full_y() + window.full_height() - m_corner_radius,
m_corner_radius,
m_corner_radius
},
{
window.full_x() + window.full_width() - m_corner_radius,
window.full_y() + window.full_height() - m_corner_radius,
m_corner_radius,
m_corner_radius
}
};
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 title_overlap = window.title_bar_area().get_overlap(area); title_overlap.has_value())
{
for (int32_t y_off = 0; y_off < title_overlap->height; y_off++)
{
for (int32_t x_off = 0; x_off < title_overlap->width; x_off++)
{
const int32_t abs_x = title_overlap->x + x_off;
const int32_t abs_y = title_overlap->y + y_off;
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 client_overlap = window.client_area().get_overlap(area); client_overlap.has_value())
{
const bool should_alpha_blend = window.get_attributes().alpha_channel;
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 y_off = 0; y_off < fast_overlap.height; y_off++)
{
const int32_t abs_row_y = fast_overlap.y + y_off;
const int32_t abs_row_x = fast_overlap.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 y_off = 0; y_off < corner_overlap.height; y_off++)
{
for (int32_t x_off = 0; x_off < corner_overlap.width; x_off++)
{
const int32_t abs_x = corner_overlap.x + x_off;
const int32_t abs_y = corner_overlap.y + y_off;
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.y; y < overlap.y + overlap.height; y++)
{
uint32_t* frameb_row = &m_framebuffer.mmap[y * m_framebuffer.width + overlap.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 (const auto overlap = cursor_area().get_overlap(area); 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;
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
{
for (int32_t x_off = 0; x_off < overlap->width; x_off++)
{
const int32_t rel_x = overlap->x - m_cursor.x + x_off + origin_x;
const int32_t rel_y = overlap->y - m_cursor.y + y_off + origin_y;
const auto pixel = get_cursor_pixel(rel_x, rel_y);
if (pixel.has_value())
m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + (overlap->x + x_off)] = 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 y_off = 0; y_off < to_sync.height; y_off++)
m_pending_syncs[to_sync.y + y_off].add_range({ static_cast<uint32_t>(to_sync.x), static_cast<uint32_t>(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,
});
auto new_window = m_focused_window->full_area();
invalidate(old_window);
invalidate(new_window);
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 { m_cursor.x - origin_x, m_cursor.y - origin_y, width, 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;
return {
.x = off_x + m_focused_window->full_x(),
.y = off_y + m_focused_window->full_y(),
.width = diff_x + m_focused_window->full_width(),
.height = diff_y + m_focused_window->full_height(),
};
}
BAN::RefPtr<Window> WindowServer::find_window_with_fd(int fd) const
{
for (auto window : m_client_windows)
if (window->client_fd() == fd)
return window;
return {};
}
BAN::RefPtr<Window> 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> 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;
}
LibGUI::EventPacket::ResizeWindowEvent response;
response.width = window->client_width();
response.height = window->client_height();
response.smo_key = window->smo_key();
if (auto ret = append_serialized_packet(response, window->client_fd()); ret.is_error())
{
dwarnln("could not respond to window resize request: {}", ret.error());
return false;
}
return true;
}
BAN::ErrorOr<void> 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 <sys/epoll.h>
extern int g_epoll_fd;
template<typename T>
BAN::ErrorOr<void> 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 {};
}