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:
@@ -39,9 +39,11 @@ set(USERSPACE_PROJECTS
|
||||
test-tcp
|
||||
test-udp
|
||||
test-unix-socket
|
||||
test-window
|
||||
touch
|
||||
u8sum
|
||||
whoami
|
||||
WindowServer
|
||||
yes
|
||||
)
|
||||
|
||||
|
||||
18
userspace/WindowServer/CMakeLists.txt
Normal file
18
userspace/WindowServer/CMakeLists.txt
Normal 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
|
||||
)
|
||||
35
userspace/WindowServer/Cursor.h
Normal file
35
userspace/WindowServer/Cursor.h
Normal 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$`";
|
||||
45
userspace/WindowServer/Framebuffer.cpp
Normal file
45
userspace/WindowServer/Framebuffer.cpp
Normal 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;
|
||||
}
|
||||
16
userspace/WindowServer/Framebuffer.h
Normal file
16
userspace/WindowServer/Framebuffer.h
Normal 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();
|
||||
59
userspace/WindowServer/Utils.h
Normal file
59
userspace/WindowServer/Utils.h
Normal 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,
|
||||
};
|
||||
}
|
||||
};
|
||||
45
userspace/WindowServer/Window.h
Normal file
45
userspace/WindowServer/Window.h
Normal 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 };
|
||||
};
|
||||
224
userspace/WindowServer/WindowServer.cpp
Normal file
224
userspace/WindowServer/WindowServer.cpp
Normal 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);
|
||||
}
|
||||
44
userspace/WindowServer/WindowServer.h
Normal file
44
userspace/WindowServer/WindowServer.h
Normal 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;
|
||||
};
|
||||
267
userspace/WindowServer/main.cpp
Normal file
267
userspace/WindowServer/main.cpp
Normal 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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
16
userspace/test-window/CMakeLists.txt
Normal file
16
userspace/test-window/CMakeLists.txt
Normal 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
|
||||
)
|
||||
61
userspace/test-window/main.cpp
Normal file
61
userspace/test-window/main.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user