Userspace: Start work on GUI and WindowServer

Current implementation can create custom windows and each window has
its own framebuffer. When window wants to write its framebuffer to the
screen it will send a message to the WindowServer using unix sockets.
This commit is contained in:
Bananymous 2024-05-29 16:00:54 +03:00
parent d4d530e6c8
commit c2b6ba0d5a
16 changed files with 1116 additions and 0 deletions

View File

@ -21,6 +21,7 @@ add_subdirectory(bootloader)
add_subdirectory(BAN)
add_subdirectory(libc)
add_subdirectory(LibELF)
add_subdirectory(LibGUI)
add_subdirectory(LibInput)
add_subdirectory(userspace)
@ -34,6 +35,7 @@ add_custom_target(headers
DEPENDS ban-headers
DEPENDS libc-headers
DEPENDS libelf-headers
DEPENDS libgui-headers
DEPENDS libinput-headers
)
@ -43,6 +45,7 @@ add_custom_target(install-sysroot
DEPENDS libc-install
DEPENDS userspace-install
DEPENDS libelf-install
DEPENDS libgui-install
DEPENDS libinput-install
)

24
LibGUI/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.26)
project(libgui CXX)
set(LIBGUI_SOURCES
Window.cpp
)
add_custom_target(libgui-headers
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/include/ ${BANAN_INCLUDE}/
DEPENDS sysroot
)
add_library(libgui ${LIBGUI_SOURCES})
add_dependencies(libgui headers libc-install)
target_link_libraries(libgui PUBLIC libc)
add_custom_target(libgui-install
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/libgui.a ${BANAN_LIB}/
DEPENDS libgui
BYPRODUCTS ${BANAN_LIB}/libgui.a
)
set(CMAKE_STATIC_LIBRARY_PREFIX "")

115
LibGUI/Window.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "LibGUI/Window.h"
#include <fcntl.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace LibGUI
{
Window::~Window()
{
munmap(m_framebuffer, m_width * m_height * 4);
close(m_server_fd);
}
BAN::ErrorOr<BAN::UniqPtr<Window>> Window::create(uint32_t width, uint32_t height)
{
int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (server_fd == -1)
return BAN::Error::from_errno(errno);
sockaddr_un server_address;
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, s_window_server_socket.data());
if (connect(server_fd, (sockaddr*)&server_address, sizeof(server_address)) == -1)
{
close(server_fd);
return BAN::Error::from_errno(errno);
}
WindowCreatePacket packet;
packet.width = width;
packet.height = height;
if (send(server_fd, &packet, sizeof(packet), 0) != sizeof(packet))
{
close(server_fd);
return BAN::Error::from_errno(errno);
}
WindowCreateResponse response;
if (recv(server_fd, &response, sizeof(response), 0) != sizeof(response))
{
close(server_fd);
return BAN::Error::from_errno(errno);
}
void* framebuffer_addr = smo_map(response.framebuffer_smo_key);
if (framebuffer_addr == nullptr)
{
close(server_fd);
return BAN::Error::from_errno(errno);
}
return TRY(BAN::UniqPtr<Window>::create(
server_fd,
static_cast<uint32_t*>(framebuffer_addr),
width,
height
));
}
bool Window::invalidate()
{
WindowInvalidatePacket packet;
packet.x = 0;
packet.y = 0;
packet.width = m_width;
packet.height = m_height;
return send(m_server_fd, &packet, sizeof(packet), 0) == sizeof(packet);
}
void Window::poll_events()
{
for (;;)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_server_fd, &fds);
timeval timeout { .tv_sec = 0, .tv_usec = 0 };
select(m_server_fd + 1, &fds, nullptr, nullptr, &timeout);
if (!FD_ISSET(m_server_fd, &fds))
break;
EventPacket packet;
if (recv(m_server_fd, &packet, sizeof(packet), 0) <= 0)
break;
switch (packet.type)
{
case EventPacket::Type::KeyEvent:
if (m_key_event_callback)
m_key_event_callback(packet.key_event);
break;
case EventPacket::Type::MouseButtonEvent:
if (m_mouse_button_event_callback)
m_mouse_button_event_callback(packet.mouse_button_event);
break;
case EventPacket::Type::MouseMoveEvent:
if (m_mouse_move_event_callback)
m_mouse_move_event_callback(packet.mouse_move_event);
break;
case EventPacket::Type::MouseScrollEvent:
if (m_mouse_scroll_event_callback)
m_mouse_scroll_event_callback(packet.mouse_scroll_event);
break;
}
}
}
}

View File

@ -0,0 +1,142 @@
#pragma once
#include <BAN/Function.h>
#include <BAN/StringView.h>
#include <BAN/UniqPtr.h>
#include <LibInput/KeyEvent.h>
#include <LibInput/MouseEvent.h>
#include <limits.h>
#include <stdint.h>
namespace LibGUI
{
static constexpr BAN::StringView s_window_server_socket = "/tmp/window-server.socket"sv;
enum WindowPacketType : uint8_t
{
INVALID,
CreateWindow,
Invalidate,
};
struct WindowCreatePacket
{
WindowPacketType type = WindowPacketType::CreateWindow;
uint32_t width;
uint32_t height;
};
struct WindowInvalidatePacket
{
WindowPacketType type = WindowPacketType::Invalidate;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
};
struct WindowCreateResponse
{
long framebuffer_smo_key;
};
struct WindowPacket
{
WindowPacket()
: type(WindowPacketType::INVALID)
{ }
union
{
WindowPacketType type;
WindowCreatePacket create;
WindowInvalidatePacket invalidate;
};
};
struct EventPacket
{
enum class Type : uint8_t
{
KeyEvent,
MouseButtonEvent,
MouseMoveEvent,
MouseScrollEvent,
};
using KeyEvent = LibInput::KeyEvent;
using MouseButton = LibInput::MouseButton;
struct MouseButtonEvent
{
MouseButton button;
bool pressed;
int32_t x;
int32_t y;
};
struct MouseMoveEvent
{
int32_t x;
int32_t y;
};
using MouseScrollEvent = LibInput::MouseScrollEvent;
Type type;
union
{
KeyEvent key_event;
MouseButtonEvent mouse_button_event;
MouseMoveEvent mouse_move_event;
MouseScrollEvent mouse_scroll_event;
};
};
class Window
{
public:
~Window();
static BAN::ErrorOr<BAN::UniqPtr<Window>> create(uint32_t width, uint32_t height);
void set_pixel(uint32_t x, uint32_t y, uint32_t color)
{
ASSERT(x < m_width);
ASSERT(y < m_height);
m_framebuffer[y * m_width + x] = color;
}
bool invalidate();
uint32_t width() const { return m_width; }
uint32_t height() const { return m_height; }
void poll_events();
void set_key_event_callback(BAN::Function<void(EventPacket::KeyEvent)> callback) { m_key_event_callback = callback; }
void set_mouse_button_event_callback(BAN::Function<void(EventPacket::MouseButtonEvent)> callback) { m_mouse_button_event_callback = callback; }
void set_mouse_move_event_callback(BAN::Function<void(EventPacket::MouseMoveEvent)> callback) { m_mouse_move_event_callback = callback; }
void set_mouse_scroll_event_callback(BAN::Function<void(EventPacket::MouseScrollEvent)> callback) { m_mouse_scroll_event_callback = callback; }
private:
Window(int server_fd, uint32_t* framebuffer, uint32_t width, uint32_t height)
: m_server_fd(server_fd)
, m_framebuffer(framebuffer)
, m_width(width)
, m_height(height)
{ }
private:
int m_server_fd;
uint32_t* m_framebuffer;
uint32_t m_width;
uint32_t m_height;
BAN::Function<void(EventPacket::KeyEvent)> m_key_event_callback;
BAN::Function<void(EventPacket::MouseButtonEvent)> m_mouse_button_event_callback;
BAN::Function<void(EventPacket::MouseMoveEvent)> m_mouse_move_event_callback;
BAN::Function<void(EventPacket::MouseScrollEvent)> m_mouse_scroll_event_callback;
friend class BAN::UniqPtr<Window>;
};
}

View File

@ -39,9 +39,11 @@ set(USERSPACE_PROJECTS
test-tcp
test-udp
test-unix-socket
test-window
touch
u8sum
whoami
WindowServer
yes
)

View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.26)
project(WindowServer CXX)
set(SOURCES
main.cpp
Framebuffer.cpp
WindowServer.cpp
)
add_executable(WindowServer ${SOURCES})
target_compile_options(WindowServer PUBLIC -O2 -g)
target_link_libraries(WindowServer PUBLIC libc ban libgui libinput)
add_custom_target(WindowServer-install
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/WindowServer ${BANAN_BIN}/
DEPENDS WindowServer
)

View File

@ -0,0 +1,35 @@
/* GIMP header image file format (RGB) */
#include <stdint.h>
static int32_t s_cursor_width = 17;
static int32_t s_cursor_height = 26;
static const char* s_cursor_data =
"!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$`!!!!````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$`!!!!````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$``Q$`!!!!````````````````!!!!`Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````!!!!`Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````!!!!`Q$`"
"`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````````"
"!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````"
"````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````"
"````````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````"
"````````````````````````!!!!`Q$``Q$``Q$``Q$``Q$`!!!!````````````"
"````````````````````````````````!!!!`Q$``Q$``Q$``Q$`!!!!````````"
"````````````````````````````````````````!!!!`Q$``Q$``Q$`!!!!````"
"````````````````````````````````````````````````!!!!`Q$``Q$`!!!!"
"````````````````````````````````````````````````````````!!!!`Q$`"
"!!!!````````````````````````````````!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
"!!!!!!!!````````````````````````````````!!!!`Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$`!!!!````````````````!!!!````````````````!!!!`Q$``Q$``Q$`"
"`Q$``Q$``Q$`!!!!````````````!!!!`Q$`!!!!````````````!!!!`Q$``Q$`"
"`Q$``Q$``Q$``Q$`!!!!````````!!!!`Q$``Q$`!!!!````````````````!!!!"
"`Q$``Q$``Q$``Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$`!!!!````````````"
"!!!!`Q$``Q$``Q$``Q$``Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$`!!!!````````"
"````````!!!!`Q$``Q$``Q$``Q$`!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!"
"````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"!!!!````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$`!!!!!!!!!!!!`Q$``Q$``Q$``Q$``Q$`";

View File

@ -0,0 +1,45 @@
#include "Framebuffer.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/framebuffer.h>
#include <sys/mman.h>
Framebuffer open_framebuffer()
{
int framebuffer_fd = open("/dev/fb0", O_RDWR);
if (framebuffer_fd == -1)
{
perror("open");
exit(1);
}
framebuffer_info_t framebuffer_info;
if (pread(framebuffer_fd, &framebuffer_info, sizeof(framebuffer_info), -1) == -1)
{
perror("pread");
exit(1);
}
const size_t framebuffer_bytes = framebuffer_info.width * framebuffer_info.height * (BANAN_FB_BPP / 8);
uint32_t* framebuffer_mmap = (uint32_t*)mmap(NULL, framebuffer_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, 0);
if (framebuffer_mmap == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(framebuffer_mmap, 0, framebuffer_bytes);
msync(framebuffer_mmap, framebuffer_bytes, MS_SYNC);
Framebuffer framebuffer;
framebuffer.fd = framebuffer_fd;
framebuffer.mmap = framebuffer_mmap;
framebuffer.width = framebuffer_info.width;
framebuffer.height = framebuffer_info.height;
framebuffer.bpp = BANAN_FB_BPP;
return framebuffer;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "Utils.h"
struct Framebuffer
{
int fd;
uint32_t* mmap;
int32_t width;
int32_t height;
uint8_t bpp;
Rectangle area() const { return { 0, 0, width, height }; }
};
Framebuffer open_framebuffer();

View File

@ -0,0 +1,59 @@
#pragma once
#include <BAN/Math.h>
#include <BAN/Optional.h>
#include <stdint.h>
struct Position
{
int32_t x;
int32_t y;
};
struct Rectangle
{
int32_t x;
int32_t y;
int32_t width;
int32_t height;
bool contains(Position position) const
{
if (position.x < x || position.x >= x + width)
return false;
if (position.y < y || position.y >= y + height)
return false;
return true;
}
BAN::Optional<Rectangle> get_overlap(Rectangle other) const
{
const auto min_x = BAN::Math::max(x, other.x);
const auto min_y = BAN::Math::max(y, other.y);
const auto max_x = BAN::Math::min(x + width, other.x + other.width);
const auto max_y = BAN::Math::min(y + height, other.y + other.height);
if (min_x >= max_x || min_y >= max_y)
return {};
return Rectangle {
.x = min_x,
.y = min_y,
.width = max_x - min_x,
.height = max_y - min_y,
};
}
Rectangle get_bounding_box(Rectangle other) const
{
const auto min_x = BAN::Math::min(x, other.x);
const auto min_y = BAN::Math::min(y, other.y);
const auto max_x = BAN::Math::max(x + width, other.x + other.width);
const auto max_y = BAN::Math::max(y + height, other.y + other.height);
return Rectangle {
.x = min_x,
.y = min_y,
.width = max_x - min_x,
.height = max_y - min_y,
};
}
};

View File

@ -0,0 +1,45 @@
#pragma once
#include "Utils.h"
#include <BAN/RefPtr.h>
class Window : public BAN::RefCounted<Window>
{
public:
Window(int fd)
: m_client_fd(fd)
{ }
void set_position(Position position)
{
m_area.x = position.x;
m_area.y = position.y;
}
void set_size(Position size, uint32_t* fb_addr)
{
m_area.width = size.x;
m_area.height = size.y;
m_fb_addr = fb_addr;
}
bool is_deleted() const { return m_deleted; }
void mark_deleted() { m_deleted = true; }
int client_fd() const { return m_client_fd; }
int32_t x() const { return m_area.x; }
int32_t y() const { return m_area.y; }
uint32_t width() const { return m_area.width; }
uint32_t height() const { return m_area.height; }
Rectangle size() const { return { 0, 0, m_area.width, m_area.height }; }
const Rectangle& area() const { return m_area; }
const uint32_t* framebuffer() const { return m_fb_addr; }
private:
const int m_client_fd { -1 };
uint32_t* m_fb_addr { nullptr };
Rectangle m_area { 0, 0, 0, 0 };
bool m_deleted { false };
};

View File

@ -0,0 +1,224 @@
#include "Cursor.h"
#include "WindowServer.h"
#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/socket.h>
void WindowServer::add_window(int fd, BAN::RefPtr<Window> window)
{
MUST(m_windows_ordered.insert(0, window));
MUST(m_windows.insert(fd, window));
set_focused_window(window);
}
void WindowServer::for_each_window(const BAN::Function<BAN::Iteration(int, Window&)>& callback)
{
BAN::Vector<int> deleted_windows;
for (auto it = m_windows.begin(); it != m_windows.end(); it++)
{
auto ret = callback(it->key, *it->value);
if (it->value->is_deleted())
MUST(deleted_windows.push_back(it->key));
if (ret == BAN::Iteration::Break)
break;
ASSERT(ret == BAN::Iteration::Continue);
}
for (int fd : deleted_windows)
{
auto window = m_windows[fd];
m_windows.remove(fd);
for (size_t i = 0; i < m_windows_ordered.size(); i++)
{
if (m_windows_ordered[i] == window)
{
m_windows_ordered.remove(i);
break;
}
}
}
}
void WindowServer::on_key_event(LibInput::KeyEvent event)
{
// Mod key is not passed to clients
if (event.key == LibInput::Key::Super)
{
m_is_mod_key_held = event.pressed();
return;
}
// Quick hack to stop the window server
if (event.pressed() && event.key == LibInput::Key::Escape)
exit(0);
if (m_focused_window)
{
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::KeyEvent;
packet.key_event = event;
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event)
{
BAN::RefPtr<Window> target_window;
for (size_t i = m_windows_ordered.size(); i > 0; i--)
{
if (m_windows_ordered[i - 1]->area().contains(m_cursor))
{
target_window = m_windows_ordered[i - 1];
break;
}
}
// Ignore mouse button events which are not on top of a window
if (!target_window)
return;
set_focused_window(target_window);
// Handle window moving when mod key is held
if (m_is_mod_key_held && event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window)
m_is_moving_window = true;
else if (m_is_moving_window && !event.pressed)
m_is_moving_window = false;
else
{
// NOTE: we always have target window if code reaches here
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::MouseButtonEvent;
packet.mouse_button_event.button = event.button;
packet.mouse_button_event.pressed = event.pressed;
packet.mouse_button_event.x = m_cursor.x - m_focused_window->x();
packet.mouse_button_event.y = m_cursor.y - m_focused_window->y();
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event)
{
Rectangle old_cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
const int32_t new_x = BAN::Math::clamp(m_cursor.x + event.rel_x, 0, m_framebuffer.width);
const int32_t new_y = BAN::Math::clamp(m_cursor.y - event.rel_y, 0, m_framebuffer.height);
event.rel_x = new_x - m_cursor.x;
event.rel_y = new_y - m_cursor.y;
if (event.rel_x == 0 && event.rel_y == 0)
return;
m_cursor.x = new_x;
m_cursor.y = new_y;
Rectangle new_cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
invalidate(old_cursor.get_bounding_box(old_cursor));
invalidate(new_cursor.get_bounding_box(old_cursor));
if (m_is_moving_window)
{
auto old_window = m_focused_window->area();
m_focused_window->set_position({
m_focused_window->x() + event.rel_x,
m_focused_window->y() + event.rel_y,
});
invalidate(old_window);
invalidate(m_focused_window->area());
return;
}
if (m_focused_window)
{
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::MouseMoveEvent;
packet.mouse_move_event.x = m_cursor.x - m_focused_window->x();
packet.mouse_move_event.y = m_cursor.y - m_focused_window->y();
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::on_mouse_scroll(LibInput::MouseScrollEvent event)
{
if (m_focused_window)
{
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::MouseScrollEvent;
packet.mouse_scroll_event = event;
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::set_focused_window(BAN::RefPtr<Window> window)
{
if (m_focused_window == window)
return;
for (size_t i = m_windows_ordered.size(); i > 0; i--)
{
if (m_windows_ordered[i - 1] == window)
{
m_focused_window = window;
m_windows_ordered.remove(i - 1);
MUST(m_windows_ordered.push_back(window));
invalidate(window->area());
break;
}
}
}
void WindowServer::invalidate(Rectangle area)
{
auto fb_overlap = area.get_overlap(m_framebuffer.area());
if (!fb_overlap.has_value())
return;
area = fb_overlap.release_value();
for (int32_t y = area.y; y < area.y + area.height; y++)
memset(&m_framebuffer.mmap[y * m_framebuffer.width + area.x], 0, area.width * 4);
for (auto& pwindow : m_windows_ordered)
{
auto& window = *pwindow;
auto overlap = window.area().get_overlap(area);
if (!overlap.has_value())
continue;
const int32_t src_x = overlap->x - window.x();
const int32_t src_y = overlap->y - window.y();
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
memcpy(
&m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + overlap->x],
&window.framebuffer()[(src_y + y_off) * window.width() + src_x],
overlap->width * 4
);
}
Rectangle cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
auto overlap = cursor.get_overlap(area);
if (overlap.has_value())
{
for (int32_t dy = overlap->y - cursor.y; dy < overlap->height; dy++)
{
for (int32_t dx = overlap->x - cursor.x; dx < overlap->width; dx++)
{
const uint32_t offset = (dy * s_cursor_width + dx) * 4;
uint32_t r = (((s_cursor_data[offset + 0] - 33) << 2) | ((s_cursor_data[offset + 1] - 33) >> 4));
uint32_t g = ((((s_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_cursor_data[offset + 2] - 33) >> 2));
uint32_t b = ((((s_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_cursor_data[offset + 3] - 33)));
uint32_t color = (r << 16) | (g << 8) | b;
if (color != 0xFF00FF)
m_framebuffer.mmap[(overlap->y + dy) * m_framebuffer.width + (overlap->x + dx)] = color;
}
}
}
uintptr_t mmap_start = reinterpret_cast<uintptr_t>(m_framebuffer.mmap) + area.y * m_framebuffer.width * 4;
uintptr_t mmap_end = mmap_start + (area.height + 1) * m_framebuffer.width * 4;
mmap_start &= ~(uintptr_t)0xFFF;
msync(reinterpret_cast<void*>(mmap_start), mmap_end - mmap_start, MS_SYNC);
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "Framebuffer.h"
#include "Window.h"
#include <BAN/Function.h>
#include <BAN/Iteration.h>
#include <BAN/Vector.h>
#include <BAN/HashMap.h>
#include <LibInput/KeyEvent.h>
#include <LibInput/MouseEvent.h>
class WindowServer
{
public:
WindowServer(Framebuffer& framebuffer)
: m_framebuffer(framebuffer)
, m_cursor({ framebuffer.width / 2, framebuffer.height / 2 })
{
invalidate(m_framebuffer.area());
}
void add_window(int fd, BAN::RefPtr<Window> window);
void for_each_window(const BAN::Function<BAN::Iteration(int, Window&)>& callback);
void on_key_event(LibInput::KeyEvent event);
void on_mouse_button(LibInput::MouseButtonEvent event);
void on_mouse_move(LibInput::MouseMoveEvent event);
void on_mouse_scroll(LibInput::MouseScrollEvent event);
void set_focused_window(BAN::RefPtr<Window> window);
void invalidate(Rectangle area);
private:
Framebuffer& m_framebuffer;
BAN::Vector<BAN::RefPtr<Window>> m_windows_ordered;
BAN::HashMap<int, BAN::RefPtr<Window>> m_windows;
bool m_is_mod_key_held { false };
bool m_is_moving_window { false };
BAN::RefPtr<Window> m_focused_window;
Position m_cursor;
};

View File

@ -0,0 +1,267 @@
#include "WindowServer.h"
#include <BAN/Debug.h>
#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
int open_server_fd()
{
struct stat st;
if (stat(LibGUI::s_window_server_socket.data(), &st) != -1)
unlink(LibGUI::s_window_server_socket.data());
int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (server_fd == -1)
{
perror("socket");
exit(1);
}
sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, LibGUI::s_window_server_socket.data());
if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind");
exit(1);
}
if (chmod("/tmp/resolver.sock", 0777) == -1)
{
perror("chmod");
exit(1);
}
if (listen(server_fd, SOMAXCONN) == -1)
{
perror("listen");
exit(1);
}
return server_fd;
}
int main()
{
srand(time(nullptr));
int server_fd = open_server_fd();
auto framebuffer = open_framebuffer();
if (framebuffer.bpp != 32)
{
dwarnln("Window server requires 32 bpp");
return 1;
}
if (tty_ctrl(STDIN_FILENO, TTY_CMD_UNSET, TTY_FLAG_ENABLE_INPUT) == -1)
{
perror("tty_ctrl");
exit(1);
}
atexit([]() { tty_ctrl(STDIN_FILENO, TTY_CMD_SET, TTY_FLAG_ENABLE_INPUT); });
MUST(LibInput::KeyboardLayout::initialize());
MUST(LibInput::KeyboardLayout::get().load_from_file("/usr/share/keymaps/us.keymap"sv));
int keyboard_fd = open("/dev/input0", O_RDONLY);
if (keyboard_fd == -1)
perror("open");
int mouse_fd = open("/dev/input1", O_RDONLY);
if (mouse_fd == -1)
perror("open");
dprintln("Window server started");
for (int i = 0; i < 2; i++)
{
if (fork() == 0)
{
execl("/bin/test-window", "test-window", NULL);
exit(1);
}
}
WindowServer window_server(framebuffer);
for (;;)
{
int max_socket = server_fd;
fd_set fds;
FD_ZERO(&fds);
FD_SET(server_fd, &fds);
if (keyboard_fd != -1)
{
FD_SET(keyboard_fd, &fds);
max_socket = BAN::Math::max(max_socket, keyboard_fd);
}
if (mouse_fd != -1)
{
FD_SET(mouse_fd, &fds);
max_socket = BAN::Math::max(max_socket, mouse_fd);
}
window_server.for_each_window(
[&](int fd, Window&) -> BAN::Iteration
{
FD_SET(fd, &fds);
max_socket = BAN::Math::max(max_socket, fd);
return BAN::Iteration::Continue;
}
);
if (select(max_socket + 1, &fds, nullptr, nullptr, nullptr) == -1)
{
dwarnln("select: {}", strerror(errno));
break;
}
if (FD_ISSET(server_fd, &fds))
{
int window_fd = accept(server_fd, nullptr, nullptr);
if (window_fd == -1)
{
dwarnln("accept: {}", strerror(errno));
continue;
}
auto window = MUST(BAN::RefPtr<Window>::create(window_fd));
window_server.add_window(window_fd, window);
}
if (keyboard_fd != -1 && FD_ISSET(keyboard_fd, &fds))
{
LibInput::RawKeyEvent event;
if (read(keyboard_fd, &event, sizeof(event)) == -1)
{
perror("read");
continue;
}
window_server.on_key_event(LibInput::KeyboardLayout::get().key_event_from_raw(event));
}
if (mouse_fd != -1 && FD_ISSET(mouse_fd, &fds))
{
LibInput::MouseEvent event;
if (read(mouse_fd, &event, sizeof(event)) == -1)
{
perror("read");
continue;
}
switch (event.type)
{
case LibInput::MouseEventType::MouseButtonEvent:
window_server.on_mouse_button(event.button_event);
break;
case LibInput::MouseEventType::MouseMoveEvent:
window_server.on_mouse_move(event.move_event);
break;
case LibInput::MouseEventType::MouseScrollEvent:
window_server.on_mouse_scroll(event.scroll_event);
break;
}
}
window_server.for_each_window(
[&](int fd, Window& window) -> BAN::Iteration
{
if (!FD_ISSET(fd, &fds))
return BAN::Iteration::Continue;
LibGUI::WindowPacket packet;
ssize_t nrecv = recv(fd, &packet, sizeof(packet), 0);
if (nrecv < 0)
dwarnln("recv: {}", strerror(errno));
if (nrecv <= 0)
{
window.mark_deleted();
return BAN::Iteration::Continue;
}
switch (packet.type)
{
case LibGUI::WindowPacketType::CreateWindow:
{
if (nrecv != sizeof(LibGUI::WindowCreatePacket))
{
dwarnln("Invalid WindowCreate packet size");
break;
}
const size_t window_fb_bytes = packet.create.width * packet.create.height * 4;
long smo_key = smo_create(window_fb_bytes, PROT_READ | PROT_WRITE);
if (smo_key == -1)
{
dwarnln("smo_create: {}", strerror(errno));
break;
}
void* smo_address = smo_map(smo_key);
if (smo_address == nullptr)
{
dwarnln("smo_map: {}", strerror(errno));
break;
}
memset(smo_address, 0, window_fb_bytes);
LibGUI::WindowCreateResponse response;
response.framebuffer_smo_key = smo_key;
if (send(fd, &response, sizeof(response), 0) != sizeof(response))
{
dwarnln("send: {}", strerror(errno));
break;
}
window.set_size({
static_cast<int32_t>(packet.create.width),
static_cast<int32_t>(packet.create.height)
}, reinterpret_cast<uint32_t*>(smo_address));
window.set_position({
static_cast<int32_t>(window.width() / 2),
static_cast<int32_t>(window.height() / 2)
});
break;
}
case LibGUI::WindowPacketType::Invalidate:
{
if (nrecv != sizeof(LibGUI::WindowInvalidatePacket))
{
dwarnln("Invalid Invalidate packet size");
break;
}
if (packet.invalidate.x + packet.invalidate.width > window.width() || packet.invalidate.y + packet.invalidate.height > window.height())
{
dwarnln("Invalid Invalidate packet parameters");
break;
}
window_server.invalidate({
window.x() + static_cast<int32_t>(packet.invalidate.x),
window.y() + static_cast<int32_t>(packet.invalidate.y),
static_cast<int32_t>(packet.invalidate.width),
static_cast<int32_t>(packet.invalidate.height),
});
break;
}
default:
dwarnln("Invalid window packet from {}", fd);
}
return BAN::Iteration::Continue;
}
);
}
}

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.26)
project(test-window CXX)
set(SOURCES
main.cpp
)
add_executable(test-window ${SOURCES})
target_compile_options(test-window PUBLIC -O2 -g)
target_link_libraries(test-window PUBLIC libc ban libgui)
add_custom_target(test-window-install
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/test-window ${BANAN_BIN}/
DEPENDS test-window
)

View File

@ -0,0 +1,61 @@
#include <BAN/Debug.h>
#include <LibGUI/Window.h>
#include <stdlib.h>
#include <unistd.h>
void randomize_color(BAN::UniqPtr<LibGUI::Window>& window)
{
uint32_t color = ((rand() % 255) << 16) | ((rand() % 255) << 8) | ((rand() % 255) << 0);
for (uint32_t y = 0; y < window->height(); y++)
for (uint32_t x = 0; x < window->width(); x++)
window->set_pixel(x, y, color);
window->invalidate();
}
int main()
{
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
srand(ts.tv_nsec);
auto window_or_error = LibGUI::Window::create(300, 200);
if (window_or_error.is_error())
{
dprintln("{}", window_or_error.error());
return 1;
}
auto window = window_or_error.release_value();
window->set_mouse_button_event_callback(
[&](LibGUI::EventPacket::MouseButtonEvent event)
{
if (event.pressed && event.button == LibGUI::EventPacket::MouseButton::Left)
randomize_color(window);
const char* button;
switch (event.button)
{
case LibGUI::EventPacket::MouseButton::Left: button = "left"; break;
case LibGUI::EventPacket::MouseButton::Right: button = "right"; break;
case LibGUI::EventPacket::MouseButton::Middle: button = "middle"; break;
case LibGUI::EventPacket::MouseButton::Extra1: button = "extra1"; break;
case LibGUI::EventPacket::MouseButton::Extra2: button = "extra2"; break;
}
dprintln("mouse button '{}' {} at {}, {}", button, event.pressed ? "pressed" : "released", event.x, event.y);
}
);
randomize_color(window);
for (;;)
{
window->poll_events();
timespec duration;
duration.tv_sec = 0;
duration.tv_nsec = 16'666'666;
nanosleep(&duration, nullptr);
}
}