Initial commit

This commit is contained in:
2026-02-07 18:32:40 +02:00
commit 219734a813
134 changed files with 20257 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
set(SOURCES
main.cpp
Framebuffer.cpp
Window.cpp
WindowServer.cpp
)
add_executable(WindowServer ${SOURCES})
banan_include_headers(WindowServer libgui)
banan_link_library(WindowServer ban)
banan_link_library(WindowServer libfont)
banan_link_library(WindowServer libimage)
banan_link_library(WindowServer libinput)
target_link_libraries(WindowServer PRIVATE SDL2)
install(TARGETS WindowServer OPTIONAL)

35
WindowServer/Cursor.h Normal file
View File

@@ -0,0 +1,35 @@
/* GIMP header image file format (RGB) */
#include <stdint.h>
static int32_t s_default_cursor_width = 17;
static int32_t s_default_cursor_height = 26;
static const char* s_default_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,61 @@
#include "Framebuffer.h"
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_video.h>
#include <cstdint>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <SDL2/SDL.h>
static constexpr size_t window_width { 1280 };
static constexpr size_t window_height { 800 };
static constexpr size_t window_bpp { 32 };
SDL_Renderer* g_renderer { nullptr };
SDL_Texture* g_texture { nullptr };
SDL_Window* g_window { nullptr };
Framebuffer open_framebuffer()
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) == -1)
{
fprintf(stderr, "Could not initialize SDL: %s\n", SDL_GetError());
exit(1);
}
g_window = SDL_CreateWindow("banan gui", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height, SDL_WINDOW_SHOWN);
if (g_window == nullptr)
{
fprintf(stderr, "Could not create SDL window: %s\n", SDL_GetError());
exit(1);
}
g_renderer = SDL_CreateRenderer(g_window, -1, SDL_RENDERER_ACCELERATED);
if (g_renderer == nullptr)
{
fprintf(stderr, "Could not get SDL renderer: %s\n", SDL_GetError());
exit(1);
}
g_texture = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, window_width, window_height);
if (g_texture == nullptr)
{
fprintf(stderr, "Could not get SDL texture: %s\n", SDL_GetError());
exit(1);
}
uint32_t* pixels = new uint32_t[window_width * window_height];
memset(pixels, 0, window_width * window_height * 4);
Framebuffer framebuffer;
framebuffer.pixels = pixels;
framebuffer.width = window_width;
framebuffer.height = window_height;
framebuffer.bpp = window_bpp;
return framebuffer;
}

View File

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

111
WindowServer/Utils.h Normal file
View File

@@ -0,0 +1,111 @@
#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
{
if (height == 0 || width == 0 || other.width == 0 || other.height == 0)
return {};
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,
};
}
bool operator==(const Rectangle& other) const
{
return x == other.x && y == other.y && width == other.width && height == other.height;
}
};
struct Circle
{
int32_t x;
int32_t y;
int32_t radius;
bool contains(Position position) const
{
int32_t dx = position.x - x;
int32_t dy = position.y - y;
return dx * dx + dy * dy <= radius * radius;
}
};
struct Range
{
uint32_t start { 0 };
uint32_t count { 0 };
bool is_continuous_with(const Range& range) const
{
return start <= range.start + range.count && range.start <= start + count;
}
uint32_t distance_between(const Range& range) const
{
if (is_continuous_with(range))
return 0;
if (start < range.start)
return range.start - (start + count);
return start - (range.start + range.count);
}
void merge_with(const Range& range)
{
const uint32_t new_start = BAN::Math::min(start, range.start);
const uint32_t new_end = BAN::Math::max(start + count, range.start + range.count);
start = new_start;
count = new_end - new_start;
}
};

106
WindowServer/Window.cpp Normal file
View File

@@ -0,0 +1,106 @@
#include "Window.h"
#include <BAN/Debug.h>
#include <BAN/ScopeGuard.h>
#include <LibGUI/Window.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <unistd.h>
Window::~Window()
{
shmdt(m_fb_addr);
LibGUI::EventPacket::DestroyWindowEvent packet;
(void)packet.send_serialized(m_client_fd);
close(m_client_fd);
}
BAN::ErrorOr<void> Window::initialize(BAN::StringView title, uint32_t width, uint32_t height)
{
m_title.clear();
TRY(m_title.append(title));
TRY(resize(width, height));
return {};
}
BAN::ErrorOr<void> Window::resize(uint32_t width, uint32_t height)
{
const size_t fb_bytes = width * height * 4;
int shmid = shmget(rand(), fb_bytes, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
return BAN::Error::from_errno(errno);
uint32_t* fb_addr = static_cast<uint32_t*>(shmat(shmid, nullptr, 0));
if (fb_addr == MAP_FAILED)
return BAN::Error::from_errno(errno);
memset(fb_addr, 0xFF, fb_bytes);
BAN::ScopeGuard shmdetacher([&]() { shmdt(fb_addr); });
{
const auto old_area = m_client_area;
m_client_area.width = width;
m_client_area.height = height;
auto title_bar_ret = prepare_title_bar();
m_client_area = old_area;
if (title_bar_ret.is_error())
return title_bar_ret.release_error();
}
shmdetacher.disable();
if (m_fb_addr)
shmdt(m_fb_addr);
m_fb_addr = fb_addr;
m_smo_key = shmid;
m_client_area.width = width;
m_client_area.height = height;
return {};
}
BAN::ErrorOr<void> Window::prepare_title_bar()
{
const uint32_t font_w = m_font.width();
const uint32_t font_h = m_font.height();
const uint32_t font_p = m_font.pitch();
TRY(m_title_bar_data.resize(title_bar_width() * title_bar_height()));
for (auto& pixel : m_title_bar_data)
pixel = 0xFFFFFFFF;
const auto text_area = title_text_area();
for (size_t i = 0; i < m_title.size() && (i + 1) * font_w < static_cast<uint32_t>(text_area.width); i++)
{
const auto* glyph = m_font.glyph(m_title[i]);
if (glyph == nullptr)
continue;
const int32_t y_off = (font_h < (uint32_t)title_bar_height()) ? (title_bar_height() - font_h) / 2 : 0;
const int32_t x_off = y_off + i * font_w;
for (int32_t y = 0; (uint32_t)y < font_h; y++)
{
if (y + y_off >= title_bar_height())
break;
for (int32_t x = 0; (uint32_t)x < font_w; x++)
{
if (x + x_off >= text_area.width)
break;
const uint8_t bitmask = 1 << (font_w - x - 1);
if (glyph[y * font_p] & bitmask)
m_title_bar_data[(y_off + y) * title_bar_width() + (x_off + x)] = 0xFF000000;
}
}
}
return {};
}

119
WindowServer/Window.h Normal file
View File

@@ -0,0 +1,119 @@
#pragma once
#include "Utils.h"
#include <BAN/RefPtr.h>
#include <BAN/String.h>
#include <BAN/Vector.h>
#include <LibFont/Font.h>
#include <LibGUI/Window.h>
class Window : public BAN::RefCounted<Window>
{
public:
struct Cursor
{
uint32_t width;
uint32_t height;
BAN::Vector<uint32_t> pixels;
};
public:
Window(int fd, const LibFont::Font& font)
: m_font(font)
, m_client_fd(fd)
{ }
~Window();
void set_position(Position position)
{
m_client_area.x = position.x;
m_client_area.y = position.y;
}
int client_fd() const { return m_client_fd; }
long smo_key() const { return m_smo_key; }
int32_t client_x() const { return m_client_area.x; }
int32_t client_y() const { return m_client_area.y; }
int32_t client_width() const { return m_client_area.width; }
int32_t client_height() const { return m_client_area.height; }
Rectangle client_size() const { return { 0, 0, client_width(), client_height() }; }
Rectangle client_area() const { return m_client_area; }
int32_t title_bar_x() const { return client_x(); }
int32_t title_bar_y() const { return m_attributes.title_bar ? client_y() - title_bar_height() : client_y(); }
int32_t title_bar_width() const { return client_width(); }
int32_t title_bar_height() const { return m_attributes.title_bar ? m_title_bar_height : 0; }
Rectangle title_bar_size() const { return { 0, 0, title_bar_width(), title_bar_height() }; }
Rectangle title_bar_area() const { return { title_bar_x(), title_bar_y(), title_bar_width(), title_bar_height() }; }
int32_t full_x() const { return title_bar_x(); }
int32_t full_y() const { return title_bar_y(); }
int32_t full_width() const { return client_width(); }
int32_t full_height() const { return client_height() + title_bar_height(); }
Rectangle full_size() const { return { 0, 0, full_width(), full_height() }; }
Rectangle full_area() const { return { full_x(), full_y(), full_width(), full_height() }; }
bool has_cursor() const { return m_cursor.has_value(); }
const Cursor& cursor() const { return m_cursor.value(); }
void set_cursor(Cursor&& cursor) { m_cursor = BAN::move(cursor); }
void remove_cursor() { m_cursor.clear(); }
LibGUI::Window::Attributes get_attributes() const { return m_attributes; };
void set_attributes(LibGUI::Window::Attributes attributes) { m_attributes = attributes; };
Rectangle get_min_size() const { return m_min_size; }
void set_min_size(Rectangle min_size) { m_min_size = min_size.get_bounding_box({ 0, 0, m_title_bar_height, 0 }); }
Rectangle get_max_size() const { return m_max_size; }
void set_max_size(Rectangle max_size) { m_max_size = max_size; }
BAN::ErrorOr<void> set_title(BAN::StringView title) { m_title.clear(); TRY(m_title.append(title)); TRY(prepare_title_bar()); return {}; }
const uint32_t* framebuffer() const { return m_fb_addr; }
uint32_t title_bar_pixel(int32_t abs_x, int32_t abs_y, Position cursor) const
{
ASSERT(title_bar_area().contains({ abs_x, abs_y }));
if (auto close_button = close_button_area(); close_button.contains({ abs_x, abs_y }))
return close_button.contains(cursor) ? 0xFFFF0000 : 0xFFD00000;
int32_t rel_x = abs_x - title_bar_x();
int32_t rel_y = abs_y - title_bar_y();
return m_title_bar_data[rel_y * title_bar_width() + rel_x];
}
Circle close_button_area() const { return { title_bar_x() + title_bar_width() - title_bar_height() / 2, title_bar_y() + title_bar_height() / 2, title_bar_height() * 3 / 8 }; }
Rectangle title_text_area() const { return { title_bar_x(), title_bar_y(), title_bar_width() - title_bar_height(), title_bar_height() }; }
BAN::ErrorOr<void> initialize(BAN::StringView title, uint32_t width, uint32_t height);
BAN::ErrorOr<void> resize(uint32_t width, uint32_t height);
private:
BAN::ErrorOr<void> prepare_title_bar();
private:
static constexpr int32_t m_title_bar_height { 20 };
const LibFont::Font& m_font;
const int m_client_fd { -1 };
Rectangle m_client_area { 0, 0, 0, 0 };
Rectangle m_min_size { 0, 0, m_title_bar_height, 0 };
Rectangle m_max_size { 0, 0, 10'000, 10'000 };
long m_smo_key { 0 };
uint32_t* m_fb_addr { nullptr };
BAN::String m_title;
BAN::Optional<Cursor> m_cursor;
BAN::Vector<uint32_t> m_title_bar_data;
LibGUI::Window::Attributes m_attributes { LibGUI::Window::default_attributes };
friend class BAN::RefPtr<Window>;
};

File diff suppressed because it is too large Load Diff

114
WindowServer/WindowServer.h Normal file
View File

@@ -0,0 +1,114 @@
#pragma once
#include "Framebuffer.h"
#include "Utils.h"
#include "Window.h"
#include <BAN/Array.h>
#include <BAN/Function.h>
#include <BAN/Iteration.h>
#include <BAN/Vector.h>
#include <BAN/HashMap.h>
#include <LibFont/Font.h>
#include <LibGUI/Window.h>
#include <LibImage/Image.h>
#include <LibInput/KeyEvent.h>
#include <LibInput/MouseEvent.h>
class WindowServer
{
public:
struct ClientData
{
size_t packet_buffer_nread = 0;
BAN::Vector<uint8_t> packet_buffer;
};
public:
WindowServer(Framebuffer& framebuffer, int32_t corner_radius);
BAN::ErrorOr<void> set_background_image(BAN::UniqPtr<LibImage::Image>);
void on_window_create(int fd, const LibGUI::WindowPacket::WindowCreate&);
void on_window_invalidate(int fd, const LibGUI::WindowPacket::WindowInvalidate&);
void on_window_set_position(int fd, const LibGUI::WindowPacket::WindowSetPosition&);
void on_window_set_attributes(int fd, const LibGUI::WindowPacket::WindowSetAttributes&);
void on_window_set_mouse_relative(int fd, const LibGUI::WindowPacket::WindowSetMouseRelative&);
void on_window_set_size(int fd, const LibGUI::WindowPacket::WindowSetSize&);
void on_window_set_min_size(int fd, const LibGUI::WindowPacket::WindowSetMinSize&);
void on_window_set_max_size(int fd, const LibGUI::WindowPacket::WindowSetMaxSize&);
void on_window_set_fullscreen(int fd, const LibGUI::WindowPacket::WindowSetFullscreen&);
void on_window_set_title(int fd, const LibGUI::WindowPacket::WindowSetTitle&);
void on_window_set_cursor(int fd, const LibGUI::WindowPacket::WindowSetCursor&);
void on_key_event(LibInput::KeyEvent event);
void on_mouse_button(LibInput::MouseButtonEvent event);
void on_mouse_move(LibInput::MouseMoveEvent event);
void on_mouse_move_abs(LibInput::MouseMoveAbsEvent event);
void on_mouse_scroll(LibInput::MouseScrollEvent event);
void set_focused_window(BAN::RefPtr<Window> window);
void invalidate(Rectangle area);
void invalidate_impl(Rectangle area);
void sync();
Rectangle cursor_area() const;
Rectangle resize_area(Position cursor) const;
void add_client_fd(int fd);
void remove_client_fd(int fd);
ClientData& get_client_data(int fd);
bool is_stopped() const { return m_is_stopped; }
void stop() { m_is_stopped = true; }
private:
void on_mouse_move_impl(int32_t new_x, int32_t new_y);
void mark_pending_sync(Rectangle area);
bool resize_window(BAN::RefPtr<Window> window, uint32_t width, uint32_t height) const;
BAN::RefPtr<Window> find_window_with_fd(int fd) const;
BAN::RefPtr<Window> find_hovered_window() const;
private:
enum class State
{
Normal,
Fullscreen,
Moving,
Resizing,
};
private:
Framebuffer& m_framebuffer;
BAN::Vector<BAN::RefPtr<Window>> m_client_windows;
BAN::HashMap<int, ClientData> m_client_data;
const int32_t m_corner_radius;
Rectangle m_dirty_rect;
BAN::UniqPtr<LibImage::Image> m_background_image;
State m_state { State::Normal };
bool m_is_mod_key_held { false };
BAN::RefPtr<Window> m_focused_window;
BAN::Array<BAN::RefPtr<Window>, 5> m_mouse_button_windows;
Position m_cursor;
Rectangle m_non_full_screen_rect;
uint8_t m_resize_quadrant { 0 };
Position m_resize_start;
bool m_is_mouse_relative { false };
bool m_is_stopped { false };
bool m_is_bouncing_window = false;
LibFont::Font m_font;
};

610
WindowServer/main.cpp Normal file
View File

@@ -0,0 +1,610 @@
#include "LibInput/KeyEvent.h"
#include "LibInput/MouseEvent.h"
#include "WindowServer.h"
#include <BAN/Debug.h>
#include <BAN/ScopeGuard.h>
#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_mouse.h>
#include <SDL2/SDL_video.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include <SDL2/SDL.h>
extern SDL_Renderer* g_renderer;
extern SDL_Surface* g_surface;
extern SDL_Texture* g_texture;
extern SDL_Window* g_window;
struct Keymap
{
consteval Keymap()
{
for (auto& scancode : map)
scancode = 0;
using LibInput::keycode_normal;
using LibInput::keycode_function;
using LibInput::keycode_numpad;
map[SDL_SCANCODE_GRAVE] = keycode_normal(0, 0);
map[SDL_SCANCODE_1] = keycode_normal(0, 1);
map[SDL_SCANCODE_2] = keycode_normal(0, 2);
map[SDL_SCANCODE_3] = keycode_normal(0, 3);
map[SDL_SCANCODE_4] = keycode_normal(0, 4);
map[SDL_SCANCODE_5] = keycode_normal(0, 5);
map[SDL_SCANCODE_6] = keycode_normal(0, 6);
map[SDL_SCANCODE_7] = keycode_normal(0, 7);
map[SDL_SCANCODE_8] = keycode_normal(0, 8);
map[SDL_SCANCODE_9] = keycode_normal(0, 9);
map[SDL_SCANCODE_0] = keycode_normal(0, 10);
map[SDL_SCANCODE_MINUS] = keycode_normal(0, 11);
map[SDL_SCANCODE_EQUALS] = keycode_normal(0, 12);
map[SDL_SCANCODE_BACKSPACE] = keycode_normal(0, 13);
map[SDL_SCANCODE_TAB] = keycode_normal(1, 0);
map[SDL_SCANCODE_Q] = keycode_normal(1, 1);
map[SDL_SCANCODE_W] = keycode_normal(1, 2);
map[SDL_SCANCODE_E] = keycode_normal(1, 3);
map[SDL_SCANCODE_R] = keycode_normal(1, 4);
map[SDL_SCANCODE_T] = keycode_normal(1, 5);
map[SDL_SCANCODE_Y] = keycode_normal(1, 6);
map[SDL_SCANCODE_U] = keycode_normal(1, 7);
map[SDL_SCANCODE_I] = keycode_normal(1, 8);
map[SDL_SCANCODE_O] = keycode_normal(1, 9);
map[SDL_SCANCODE_P] = keycode_normal(1, 10);
map[SDL_SCANCODE_LEFTBRACKET] = keycode_normal(1, 11);
map[SDL_SCANCODE_RIGHTBRACKET] = keycode_normal(1, 12);
map[SDL_SCANCODE_CAPSLOCK] = keycode_normal(2, 0);
map[SDL_SCANCODE_A] = keycode_normal(2, 1);
map[SDL_SCANCODE_S] = keycode_normal(2, 2);
map[SDL_SCANCODE_D] = keycode_normal(2, 3);
map[SDL_SCANCODE_F] = keycode_normal(2, 4);
map[SDL_SCANCODE_G] = keycode_normal(2, 5);
map[SDL_SCANCODE_H] = keycode_normal(2, 6);
map[SDL_SCANCODE_J] = keycode_normal(2, 7);
map[SDL_SCANCODE_K] = keycode_normal(2, 8);
map[SDL_SCANCODE_L] = keycode_normal(2, 9);
map[SDL_SCANCODE_SEMICOLON] = keycode_normal(2, 10);
map[SDL_SCANCODE_APOSTROPHE] = keycode_normal(2, 11);
map[SDL_SCANCODE_BACKSLASH] = keycode_normal(2, 12);
map[SDL_SCANCODE_RETURN] = keycode_normal(2, 13);
map[SDL_SCANCODE_LSHIFT] = keycode_normal(3, 0);
map[SDL_SCANCODE_NONUSBACKSLASH] = keycode_normal(3, 1);
map[SDL_SCANCODE_Z] = keycode_normal(3, 2);
map[SDL_SCANCODE_X] = keycode_normal(3, 3);
map[SDL_SCANCODE_C] = keycode_normal(3, 4);
map[SDL_SCANCODE_V] = keycode_normal(3, 5);
map[SDL_SCANCODE_B] = keycode_normal(3, 6);
map[SDL_SCANCODE_N] = keycode_normal(3, 7);
map[SDL_SCANCODE_M] = keycode_normal(3, 8);
map[SDL_SCANCODE_COMMA] = keycode_normal(3, 9);
map[SDL_SCANCODE_PERIOD] = keycode_normal(3, 10);
map[SDL_SCANCODE_SLASH] = keycode_normal(3, 11);
map[SDL_SCANCODE_RSHIFT] = keycode_normal(3, 12);
map[SDL_SCANCODE_LCTRL] = keycode_normal(4, 0);
map[SDL_SCANCODE_LGUI] = keycode_normal(4, 1);
map[SDL_SCANCODE_LALT] = keycode_normal(4, 2);
map[SDL_SCANCODE_SPACE] = keycode_normal(4, 3);
map[SDL_SCANCODE_RALT] = keycode_normal(4, 5);
map[SDL_SCANCODE_RCTRL] = keycode_normal(4, 6);
map[SDL_SCANCODE_UP] = keycode_normal(5, 0);
map[SDL_SCANCODE_LEFT] = keycode_normal(5, 1);
map[SDL_SCANCODE_DOWN] = keycode_normal(5, 2);
map[SDL_SCANCODE_RIGHT] = keycode_normal(5, 3);
map[SDL_SCANCODE_ESCAPE] = keycode_function(0);
map[SDL_SCANCODE_F1] = keycode_function(1);
map[SDL_SCANCODE_F2] = keycode_function(2);
map[SDL_SCANCODE_F3] = keycode_function(3);
map[SDL_SCANCODE_F4] = keycode_function(4);
map[SDL_SCANCODE_F5] = keycode_function(5);
map[SDL_SCANCODE_F6] = keycode_function(6);
map[SDL_SCANCODE_F7] = keycode_function(7);
map[SDL_SCANCODE_F8] = keycode_function(8);
map[SDL_SCANCODE_F9] = keycode_function(9);
map[SDL_SCANCODE_F10] = keycode_function(10);
map[SDL_SCANCODE_F11] = keycode_function(11);
map[SDL_SCANCODE_F12] = keycode_function(12);
map[SDL_SCANCODE_INSERT] = keycode_function(13);
map[SDL_SCANCODE_PRINTSCREEN] = keycode_function(14);
map[SDL_SCANCODE_DELETE] = keycode_function(15);
map[SDL_SCANCODE_HOME] = keycode_function(16);
map[SDL_SCANCODE_END] = keycode_function(17);
map[SDL_SCANCODE_PAGEUP] = keycode_function(18);
map[SDL_SCANCODE_PAGEDOWN] = keycode_function(19);
map[SDL_SCANCODE_SCROLLLOCK] = keycode_function(20);
map[SDL_SCANCODE_NUMLOCKCLEAR] = keycode_numpad(0, 0);
map[SDL_SCANCODE_KP_DIVIDE] = keycode_numpad(0, 1);
map[SDL_SCANCODE_KP_MULTIPLY] = keycode_numpad(0, 2);
map[SDL_SCANCODE_KP_MINUS] = keycode_numpad(0, 3);
map[SDL_SCANCODE_KP_7] = keycode_numpad(1, 0);
map[SDL_SCANCODE_KP_8] = keycode_numpad(1, 1);
map[SDL_SCANCODE_KP_9] = keycode_numpad(1, 2);
map[SDL_SCANCODE_KP_PLUS] = keycode_numpad(1, 3);
map[SDL_SCANCODE_KP_4] = keycode_numpad(2, 0);
map[SDL_SCANCODE_KP_5] = keycode_numpad(2, 1);
map[SDL_SCANCODE_KP_6] = keycode_numpad(2, 2);
map[SDL_SCANCODE_KP_1] = keycode_numpad(3, 0);
map[SDL_SCANCODE_KP_2] = keycode_numpad(3, 1);
map[SDL_SCANCODE_KP_3] = keycode_numpad(3, 2);
map[SDL_SCANCODE_KP_ENTER] = keycode_numpad(3, 3);
map[SDL_SCANCODE_KP_0] = keycode_numpad(4, 0);
map[SDL_SCANCODE_KP_COMMA] = keycode_numpad(4, 1);
};
uint8_t map[SDL_NUM_SCANCODES];
};
static Keymap s_keymap;
struct Config
{
BAN::UniqPtr<LibImage::Image> background_image;
int32_t corner_radius = 0;
};
BAN::Optional<BAN::String> file_read_line(FILE* file)
{
BAN::String line;
char buffer[128];
while (fgets(buffer, sizeof(buffer), file))
{
MUST(line.append(buffer));
if (line.back() == '\n')
{
line.pop_back();
return BAN::move(line);
}
}
if (line.empty())
return {};
return BAN::move(line);
}
Config parse_config()
{
Config config;
auto home_env = getenv("HOME");
if (!home_env)
{
dprintln("HOME environment variable not set");
return config;
}
auto config_path = MUST(BAN::String::formatted("{}/.config/WindowServer.conf", home_env));
FILE* fconfig = fopen(config_path.data(), "r");
if (!fconfig)
{
dprintln("Could not open '{}'", config_path);
return config;
}
BAN::ScopeGuard _([fconfig] { fclose(fconfig); });
while (true)
{
auto line = file_read_line(fconfig);
if (!line.has_value())
break;
if (line->empty())
continue;
auto parts = MUST(line->sv().split('='));
if (parts.size() != 2)
{
dwarnln("Invalid config line: {}", line.value());
break;
}
auto variable = parts[0];
auto value = parts[1];
if (variable == "bg"_sv)
{
auto image = LibImage::Image::load_from_file(value);
if (image.is_error())
dwarnln("Could not load image: {}", image.error());
else
config.background_image = image.release_value();
}
else if (variable == "corner-radius"_sv)
{
char* endptr = nullptr;
long long corner_radius = strtoll(value.data(), &endptr, 0);
if (corner_radius < 0 || corner_radius == LONG_MAX || corner_radius >= INT32_MAX)
dwarnln("invalid corner-radius: '{}'", value);
else
config.corner_radius = corner_radius;
}
else
{
dwarnln("Unknown config variable: {}", variable);
break;
}
}
return config;
}
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_STREAM | SOCK_CLOEXEC, 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(LibGUI::s_window_server_socket.data(), 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;
}
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1)
{
dwarnln("epoll_create1: {}", strerror(errno));
return 1;
}
{
epoll_event event {
.events = EPOLLIN,
.data = { .fd = server_fd },
};
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1)
{
dwarnln("epoll_ctl server: {}", strerror(errno));
return 1;
}
}
constexpr int non_terminating_signals[] {
SIGCHLD,
SIGCONT,
SIGSTOP,
SIGTSTP,
SIGTTIN,
SIGTTOU,
};
constexpr int ignored_signals[] {
SIGPIPE,
};
for (int sig = 1; sig < _NSIG; sig++)
signal(sig, exit);
for (int sig : non_terminating_signals)
signal(sig, SIG_DFL);
for (int sig : ignored_signals)
signal(sig, SIG_IGN);
MUST(LibInput::KeyboardLayout::initialize());
MUST(LibInput::KeyboardLayout::get().load_from_file("./us.keymap"_sv));
dprintln("Window server started");
auto config = parse_config();
WindowServer window_server(framebuffer, config.corner_radius);
if (config.background_image)
if (auto ret = window_server.set_background_image(BAN::move(config.background_image)); ret.is_error())
dwarnln("Could not set background image: {}", ret.error());
const auto get_current_us =
[]() -> uint64_t
{
timespec current_ts;
clock_gettime(CLOCK_MONOTONIC, &current_ts);
return (current_ts.tv_sec * 1'000'000) + (current_ts.tv_nsec / 1000);
};
constexpr uint64_t sync_interval_us = 1'000'000 / 60;
uint64_t last_sync_us = get_current_us() - sync_interval_us;
while (!window_server.is_stopped())
{
const auto current_us = get_current_us();
if (current_us - last_sync_us > sync_interval_us)
{
window_server.sync();
//last_sync_us += sync_interval_us;
last_sync_us = get_current_us();
}
timespec timeout = {};
if (auto current_us = get_current_us(); current_us - last_sync_us < sync_interval_us)
timeout.tv_nsec = (sync_interval_us - (current_us - last_sync_us)) * 1000;
epoll_event events[16];
int epoll_events = epoll_pwait2(epoll_fd, events, 16, &timeout, nullptr);
if (epoll_events == -1 && errno != EINTR)
{
dwarnln("epoll_pwait2: {}", strerror(errno));
break;
}
{
static int prev_x { 0 };
static int prev_y { 0 };
int x, y;
SDL_GetMouseState(&x, &y);
if (prev_x != x || prev_y != y)
{
int w, h;
SDL_GetWindowSize(g_window, &w, &h);
window_server.on_mouse_move_abs({
.abs_x = x,
.abs_y = y,
.min_x = 0,
.min_y = 0,
.max_x = w,
.max_y = h,
});
}
prev_x = x;
prev_y = y;
}
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
window_server.stop();
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
SDL_ShowCursor(SDL_DISABLE);
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST)
SDL_ShowCursor(SDL_ENABLE);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
{
uint16_t modifier = 0;
if (event.key.keysym.mod & KMOD_LSHIFT)
modifier |= LibInput::KeyEvent::Modifier::LShift;
if (event.key.keysym.mod & KMOD_RSHIFT)
modifier |= LibInput::KeyEvent::Modifier::RShift;
if (event.key.keysym.mod & KMOD_LCTRL)
modifier |= LibInput::KeyEvent::Modifier::LCtrl;
if (event.key.keysym.mod & KMOD_RCTRL)
modifier |= LibInput::KeyEvent::Modifier::RCtrl;
if (event.key.keysym.mod & KMOD_LALT)
modifier |= LibInput::KeyEvent::Modifier::LAlt;
if (event.key.keysym.mod & KMOD_RALT)
modifier |= LibInput::KeyEvent::Modifier::RAlt;
if (event.key.keysym.mod & KMOD_NUM)
modifier |= LibInput::KeyEvent::Modifier::NumLock;
if (event.key.keysym.mod & KMOD_SCROLL)
modifier |= LibInput::KeyEvent::Modifier::ScrollLock;
if (event.key.keysym.mod & KMOD_CAPS)
modifier |= LibInput::KeyEvent::Modifier::CapsLock;
if (event.key.state == SDL_PRESSED)
modifier |= LibInput::KeyEvent::Modifier::Pressed;
window_server.on_key_event(LibInput::KeyboardLayout::get().key_event_from_raw({
.modifier = modifier,
.keycode = s_keymap.map[event.key.keysym.scancode],
}));
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
LibInput::MouseButton button_map[] {
[0] = LibInput::MouseButton::Left,
[SDL_BUTTON_LEFT] = LibInput::MouseButton::Left,
[SDL_BUTTON_MIDDLE] = LibInput::MouseButton::Middle,
[SDL_BUTTON_RIGHT] = LibInput::MouseButton::Right,
[SDL_BUTTON_X1] = LibInput::MouseButton::Extra1,
[SDL_BUTTON_X2] = LibInput::MouseButton::Extra2,
};
window_server.on_mouse_button({
.button = button_map[event.button.button],
.pressed = (event.button.state == SDL_PRESSED),
});
break;
}
case SDL_MOUSEWHEEL:
{
window_server.on_mouse_scroll({
.scroll = event.wheel.y,
});
break;
}
}
}
for (int i = 0; i < epoll_events; i++)
{
if (events[i].data.fd == server_fd)
{
ASSERT(events[i].events & EPOLLIN);
int window_fd = accept4(server_fd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (window_fd == -1)
{
dwarnln("accept: {}", strerror(errno));
continue;
}
epoll_event event {
.events = EPOLLIN,
.data = { .fd = window_fd },
};
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, window_fd, &event) == -1)
{
dwarnln("epoll_ctl: {}", strerror(errno));
close(window_fd);
continue;
}
window_server.add_client_fd(window_fd);
continue;
}
const int client_fd = events[i].data.fd;
if (events[i].events & EPOLLHUP)
{
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
window_server.remove_client_fd(client_fd);
continue;
}
ASSERT(events[i].events & EPOLLIN);
auto& client_data = window_server.get_client_data(client_fd);
if (client_data.packet_buffer.empty())
{
uint32_t packet_size;
const ssize_t nrecv = recv(client_fd, &packet_size, sizeof(uint32_t), 0);
if (nrecv < 0)
dwarnln("recv 1: {}", strerror(errno));
if (nrecv > 0 && nrecv != sizeof(uint32_t))
dwarnln("could not read packet size with a single recv call, closing connection...");
if (nrecv != sizeof(uint32_t))
{
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
window_server.remove_client_fd(client_fd);
break;
}
if (packet_size < 4)
{
dwarnln("client sent invalid packet, closing connection...");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
window_server.remove_client_fd(client_fd);
break;
}
// this is a bit harsh, but i don't want to work on skipping streaming packets
if (client_data.packet_buffer.resize(packet_size).is_error())
{
dwarnln("could not allocate memory for client packet, closing connection...");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
window_server.remove_client_fd(client_fd);
break;
}
client_data.packet_buffer_nread = 0;
continue;
}
const ssize_t nrecv = recv(
client_fd,
client_data.packet_buffer.data() + client_data.packet_buffer_nread,
client_data.packet_buffer.size() - client_data.packet_buffer_nread,
0
);
if (nrecv < 0)
dwarnln("recv 2: {}", strerror(errno));
if (nrecv <= 0)
{
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
window_server.remove_client_fd(client_fd);
break;
}
client_data.packet_buffer_nread += nrecv;
if (client_data.packet_buffer_nread < client_data.packet_buffer.size())
continue;
ASSERT(client_data.packet_buffer.size() >= sizeof(uint32_t));
switch (*reinterpret_cast<LibGUI::PacketType*>(client_data.packet_buffer.data()))
{
#define WINDOW_PACKET_CASE(enum, function) \
case LibGUI::PacketType::enum: \
if (auto ret = LibGUI::WindowPacket::enum::deserialize(client_data.packet_buffer.span()); !ret.is_error()) \
window_server.function(client_fd, ret.release_value()); \
break
WINDOW_PACKET_CASE(WindowCreate, on_window_create);
WINDOW_PACKET_CASE(WindowInvalidate, on_window_invalidate);
WINDOW_PACKET_CASE(WindowSetPosition, on_window_set_position);
WINDOW_PACKET_CASE(WindowSetAttributes, on_window_set_attributes);
WINDOW_PACKET_CASE(WindowSetMouseRelative, on_window_set_mouse_relative);
WINDOW_PACKET_CASE(WindowSetSize, on_window_set_size);
WINDOW_PACKET_CASE(WindowSetMinSize, on_window_set_min_size);
WINDOW_PACKET_CASE(WindowSetMaxSize, on_window_set_max_size);
WINDOW_PACKET_CASE(WindowSetFullscreen, on_window_set_fullscreen);
WINDOW_PACKET_CASE(WindowSetTitle, on_window_set_title);
WINDOW_PACKET_CASE(WindowSetCursor, on_window_set_cursor);
#undef WINDOW_PACKET_CASE
default:
dprintln("unhandled packet type: {}", *reinterpret_cast<uint32_t*>(client_data.packet_buffer.data()));
}
client_data.packet_buffer.clear();
client_data.packet_buffer_nread = 0;
}
}
}