Compare commits

...

35 Commits

Author SHA1 Message Date
1bd6cf048b
libos: Remove some shared pointers and change them to owned/live on the stack
All checks were successful
continuous-integration/drone/pr Build is passing
2023-08-08 12:51:06 +02:00
2c42b088e0
wind: Spawn a new client process after startup
Also, create the socket after dropping privileges.
2023-08-08 12:51:06 +02:00
d0b5eb11ef
apps: Add gclient 2023-08-08 12:51:05 +02:00
5f7b055c36
libos: Add os::LocalClient 2023-08-08 12:51:05 +02:00
9306b98d17
libui: Change 'into' to 'onto' 2023-08-08 12:51:05 +02:00
fe65be8a89
libui: Document ui::Font 2023-08-08 12:51:05 +02:00
41b54610b0
libui+wind: Move some static variables inside functions 2023-08-08 12:51:05 +02:00
a77d698544
wind: Generate random windows on keypresses 2023-08-08 12:51:05 +02:00
2ad53db448
wind: Make sure windows have a minimum size to fit the titlebar 2023-08-08 12:51:05 +02:00
871b5c6a35
libui: Properly cut off the last drawn character if necessary 2023-08-08 12:51:04 +02:00
a38dbce9bd
libui: Add Rect::contains(Rect) 2023-08-08 12:51:04 +02:00
93d74a29b5
libui: Render font characters properly with no spacing, matching the width calculations 2023-08-08 12:51:04 +02:00
3d3d1aeed5
wind: Render an actual TGA mouse cursor 2023-08-08 12:51:04 +02:00
60385c4614
wind: Add a close button to windows using a TGA icon 2023-08-08 12:51:04 +02:00
38c00a8384
libui: Add support for TGA image loading 2023-08-08 12:51:04 +02:00
ae5dd3f340
libui: Add an interface to fill a Canvas with an array of pixels 2023-08-08 12:51:03 +02:00
a843ef943e
wind: Add window titlebars using ui::Font 2023-08-08 12:51:03 +02:00
4cb2c5ea83
libui: Add PSF font loading and rendering 2023-08-08 12:51:03 +02:00
0bc7af62df
libui: Add Color::GRAY 2023-08-08 12:51:03 +02:00
98a0049e54
libui: Rename Rect::absolute to normalized and add a new absolute function 2023-08-08 12:51:03 +02:00
9c78ec605c
libluna: Add assignment operators to Buffer 2023-08-08 12:51:03 +02:00
d8ba20b23d
wind: Reorder drag sequence 2023-08-08 12:51:02 +02:00
0927dd71cc
libui: Add Rect::relative 2023-08-08 12:51:02 +02:00
3d559cb8c8
libui: Remove redundant statement 2023-08-08 12:51:02 +02:00
42dca14d09
libui: Add getters for separate color values 2023-08-08 12:51:02 +02:00
69b36b0b09
libui: Remove unnecessary stuff 2023-08-08 12:51:02 +02:00
ee0bdd59d3
base: Remove startup items not necessary for GUI startup 2023-08-08 12:51:01 +02:00
d8150113c5
libui+wind: (Draggable) windows 2023-08-08 12:51:01 +02:00
b48daad58a
wind: Create a local server object 2023-08-08 12:51:01 +02:00
a1f47cacd4
libos: Add a new LocalServer class for local domain sockets 2023-08-08 12:51:01 +02:00
78a4c4f111
kernel: Support listening sockets in poll() 2023-08-08 12:51:01 +02:00
cab45a414c
base: Start wind on startup instead of the shell 2023-08-08 12:51:01 +02:00
646ccb2854
wind: Add a simple display server skeleton using libui
No client functionality yet, but it's a start.
2023-08-08 12:51:01 +02:00
1984a4de08
libui: Add a GUI and graphics library 2023-08-08 12:51:00 +02:00
5c09fdfbc6
kernel: Fix negative movement in the PS/2 mouse driver 2023-08-08 12:50:56 +02:00
44 changed files with 1534 additions and 21 deletions

6
.gitignore vendored
View File

@ -4,7 +4,11 @@ build/
initrd/boot/moon
env-local.sh
initrd/bin/**
base/usr/**
base/usr/bin/**
base/usr/include/**
base/usr/lib/**
base/usr/share/pkgdb/**
!base/usr/share/fonts/*
.fakeroot
kernel/config.cmake
ports/out/

View File

@ -45,8 +45,10 @@ endif()
add_subdirectory(libluna)
add_subdirectory(libos)
add_subdirectory(libui)
add_subdirectory(libc)
add_subdirectory(kernel)
add_subdirectory(apps)
add_subdirectory(tests)
add_subdirectory(shell)
add_subdirectory(wind)

View File

@ -46,3 +46,4 @@ luna_app(socket-test.cpp socket-test)
luna_app(socket-client.cpp socket-client)
luna_app(input.cpp input)
luna_app(shmem-test.cpp shmem-test)
luna_app(gclient.cpp gclient)

20
apps/gclient.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <os/ArgumentParser.h>
#include <os/LocalClient.h>
Result<int> luna_main(int argc, char** argv)
{
StringView socket_path = "/tmp/wind.sock";
os::ArgumentParser parser;
parser.add_description("A graphical user interface client."_sv);
parser.add_system_program_info("gclient"_sv);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.parse(argc, argv);
auto client = TRY(os::LocalClient::connect(socket_path, false));
StringView message = "hello";
TRY(client->send((const u8*)message.chars(), message.length()));
return 0;
}

View File

@ -1,4 +0,0 @@
Name=motd
Description=Show the message of the day to the user.
Command=/usr/bin/cat /etc/motd
Wait=true

View File

@ -1,6 +0,0 @@
Name=listen
Description=Start a Unix domain socket test server.
Command=/usr/bin/socket-test
StandardOutput=/dev/uart0
StandardError=/dev/uart0
Restart=true

View File

@ -1,4 +1,6 @@
Name=login
Description=Start the command-line login program.
Command=/usr/bin/login
Description=Start the display server.
Command=/usr/bin/wind --user=selene
StandardOutput=/dev/uart0
StandardError=/dev/uart0
Restart=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -43,8 +43,8 @@ static void process_mouse_event(u8 data)
packet.buttons = 0;
u8 flags = g_mouse_packet[0];
if (flags & PS2_MOUSE_X_SIGN) packet.xdelta = -packet.xdelta;
if (flags & PS2_MOUSE_Y_SIGN) packet.ydelta = -packet.ydelta;
if (flags & PS2_MOUSE_X_SIGN) packet.xdelta = -(256 - packet.xdelta);
if (flags & PS2_MOUSE_Y_SIGN) packet.ydelta = -(256 - packet.ydelta);
if (flags & PS2_MOUSE_MIDDLE_BTN) packet.buttons |= moon::MouseButton::Middle;
if (flags & PS2_MOUSE_RIGHT_BTN) packet.buttons |= moon::MouseButton::Right;

View File

@ -65,6 +65,10 @@ class Socket : public VFS::FileInode
m_metadata.nlinks--;
}
virtual bool can_accept_connections() const = 0;
virtual bool can_read_data() const = 0;
virtual ~Socket() = default;
protected:

View File

@ -17,6 +17,16 @@ class UnixSocket : public Socket
return (m_state == Connected || m_state == Reset) && !m_data.size();
}
bool can_read_data() const override
{
return (m_state == Connected || m_state == Reset) && m_data.size();
}
bool can_accept_connections() const override
{
return !m_listen_queue.is_empty();
}
Result<usize> send(const u8*, usize, int) override;
Result<usize> recv(u8*, usize, int) const override;

View File

@ -1,6 +1,7 @@
#include "Log.h"
#include "fs/VFS.h"
#include "memory/MemoryManager.h"
#include "net/Socket.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/poll.h>
@ -43,10 +44,25 @@ Result<u64> sys_poll(Registers*, SyscallArgs args)
auto& inode = inodes[i];
if (!inode) continue;
if (kfds[i].events & POLLIN && !inode->will_block_if_read())
if (kfds[i].events & POLLIN)
{
fds_with_events++;
kfds[i].revents |= POLLIN;
if (inode->type() == VFS::InodeType::Socket)
{
auto socket = (Socket*)inode.ptr();
if (socket->can_read_data() || socket->can_accept_connections())
{
fds_with_events++;
kfds[i].revents |= POLLIN;
}
}
else
{
if (!inode->will_block_if_read())
{
fds_with_events++;
kfds[i].revents |= POLLIN;
}
}
}
}

View File

@ -11,6 +11,9 @@ class Buffer
Buffer(Buffer&& other);
Buffer(const Buffer& other) = delete; // For now.
Buffer& operator=(Buffer&&);
Buffer& operator=(const Buffer&) = delete;
static Result<Buffer> create_sized(usize size);
Result<void> try_resize(usize new_size);

View File

@ -16,7 +16,7 @@ template <typename T, usize Size> class CircularQueue
{
}
bool is_empty()
bool is_empty() const
{
return m_tail.load() == m_head.load();
}
@ -76,7 +76,7 @@ template <typename T> class DynamicCircularQueue
if (m_data) free_impl(m_data);
}
bool is_empty()
bool is_empty() const
{
return m_tail.load() == m_head.load();
}

View File

@ -15,6 +15,16 @@ Buffer::Buffer(Buffer&& other) : m_data(other.data()), m_size(other.size())
other.m_data = nullptr;
}
Buffer& Buffer::operator=(Buffer&& other)
{
if (&other == this) return *this;
if (m_data) free_impl(m_data);
m_data = other.m_data;
m_size = other.m_size;
other.m_data = nullptr;
return *this;
}
Buffer::~Buffer()
{
if (m_data) free_impl(m_data);

View File

@ -13,6 +13,8 @@ set(SOURCES
src/Path.cpp
src/Mode.cpp
src/Prompt.cpp
src/LocalServer.cpp
src/LocalClient.cpp
)
add_library(os ${SOURCES})

View File

@ -0,0 +1,90 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/StringView.h>
namespace os
{
/**
* @brief A client used to connect to a local server socket.
*/
class LocalClient
{
public:
/**
* @brief Create a new client object and connect it to a local server.
*
* @param path The path of the server socket to connect to.
* @param blocking Whether the client should block if no data is available and recv() is called.
* @return Result<OwnedPtr<LocalClient>> An error, or a new client object.
*/
static Result<OwnedPtr<LocalClient>> connect(StringView path, bool blocking);
/**
* @brief Return the underlying socket file descriptor used by this object.
*
* @return int The file descriptor.
*/
int fd() const
{
return m_fd;
}
/**
* @brief Read arbitrary data from the server. The call will block if there is no data and this object has not
* been created as non-blocking.
*
* @param buf The buffer to read data into.
* @param length The maximum amount of bytes to read.
* @return Result<usize> An error, or the number of bytes read.
*/
Result<usize> recv(u8* buf, usize length);
/**
* @brief Read an object from the server. The call will block if there is no data and this object has not been
* created as non-blocking.
*
* @tparam T The type of the object.
* @param out A reference to the object to read data into.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> recv_typed(T& out)
{
TRY(recv((u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Send arbitrary data to the server.
*
* @param buf The buffer to send data from.
* @param length The amount of bytes to send.
* @return Result<usize> An error, or the number of bytes actually sent.
*/
Result<usize> send(const u8* buf, usize length);
/**
* @brief Send an object to the server.
*
* @tparam T The type of the object.
* @param out A reference to the object to send data from.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> send_typed(const T& out)
{
TRY(send((const u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Disconnect from the attached server.
*
* This will make any further reads on this connection return ECONNRESET, and will make this object invalid.
*/
void disconnect();
~LocalClient();
private:
int m_fd;
};
}

View File

@ -0,0 +1,133 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/Result.h>
#include <luna/StringView.h>
namespace os
{
/**
* @brief A local domain server, used to communicate between processes on the same machine.
*/
class LocalServer
{
public:
/**
* @brief Create a new server object and bind it to a local address.
*
* @param path The path to use for the server socket.
* @param blocking Whether the server should block if no connections are available when calling accept().
* @return Result<OwnedPtr<LocalServer>> An error, or a new server object.
*/
static Result<OwnedPtr<LocalServer>> create(StringView path, bool blocking);
/**
* @brief Activate the server and start listening for connections.
*
* @param backlog The number of unaccepted connections to keep.
* @return Result<void> Whether the operation succeded.
*/
Result<void> listen(int backlog);
/**
* @brief Return the underlying socket file descriptor used by this object.
*
* @return int The file descriptor.
*/
int fd() const
{
return m_fd;
}
/**
* @brief An interface to communicate with clients connected to a local server.
*/
class Client
{
public:
/**
* @brief Read arbitrary data from the client. The call will block if there is no data and the parent server
* object has not been created as non-blocking.
*
* @param buf The buffer to read data into.
* @param length The maximum amount of bytes to read.
* @return Result<usize> An error, or the number of bytes read.
*/
Result<usize> recv(u8* buf, usize length);
/**
* @brief Read an object from the client. The call will block if there is no data and the parent server
* object has not been created as non-blocking.
*
* @tparam T The type of the object.
* @param out A reference to the object to read data into.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> recv_typed(T& out)
{
TRY(recv((u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Send arbitrary data to the client.
*
* @param buf The buffer to send data from.
* @param length The amount of bytes to send.
* @return Result<usize> An error, or the number of bytes actually sent.
*/
Result<usize> send(const u8* buf, usize length);
/**
* @brief Send an object to the client.
*
* @tparam T The type of the object.
* @param out A reference to the object to send data from.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> send_typed(const T& out)
{
TRY(send((const u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Disconnect from the attached client.
*
* This will make any further reads on the client return ECONNRESET, and will make this object invalid.
*/
void disconnect();
/**
* @brief Return the underlying socket file descriptor used by this object.
*
* @return int The file descriptor.
*/
int fd() const
{
return m_fd;
}
Client(Client&& other);
Client(int fd);
~Client();
private:
int m_fd;
};
/**
* @brief Accept a new incoming connection and return a handle to it. If there are no incoming connections,
* accept() either blocks until there is one (if the object was created with blocking=true), or returns EAGAIN
* (if the object was created with blocking=false).
*
* @return Result<Client> An error, or a handle to the new connection.
*/
Result<Client> accept();
~LocalServer();
private:
int m_fd;
bool m_blocking;
};
}

59
libos/src/LocalClient.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <errno.h>
#include <fcntl.h>
#include <os/LocalClient.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace os
{
Result<OwnedPtr<LocalClient>> LocalClient::connect(StringView path, bool blocking)
{
auto client = TRY(make_owned<LocalClient>());
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) return err(errno);
struct sockaddr_un un;
un.sun_family = AF_UNIX;
strncpy(un.sun_path, path.chars(), sizeof(un.sun_path));
if (::connect(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0)
{
close(sockfd);
return err(errno);
}
if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); }
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
client->m_fd = sockfd;
return client;
}
LocalClient::~LocalClient()
{
close(m_fd);
}
Result<usize> LocalClient::recv(u8* buf, usize length)
{
ssize_t nread = read(m_fd, buf, length);
if (nread < 0) return err(errno);
return nread;
}
Result<usize> LocalClient::send(const u8* buf, usize length)
{
ssize_t nwrite = write(m_fd, buf, length);
if (nwrite < 0) return err(errno);
return nwrite;
}
void LocalClient::disconnect()
{
close(m_fd);
m_fd = -1;
}
}

92
libos/src/LocalServer.cpp Normal file
View File

@ -0,0 +1,92 @@
#include <errno.h>
#include <fcntl.h>
#include <os/FileSystem.h>
#include <os/LocalServer.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace os
{
Result<OwnedPtr<LocalServer>> LocalServer::create(StringView path, bool blocking)
{
auto server = TRY(make_owned<LocalServer>());
(void)os::FileSystem::remove(path); // We explicitly ignore any error here, either it doesn't exist (which is
// fine), or it cannot be removed, which will make bind() fail later.
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) return err(errno);
struct sockaddr_un un;
un.sun_family = AF_UNIX;
strncpy(un.sun_path, path.chars(), sizeof(un.sun_path));
if (bind(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0)
{
close(sockfd);
return err(errno);
}
if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); }
server->m_blocking = blocking;
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
server->m_fd = sockfd;
return server;
}
Result<void> LocalServer::listen(int backlog)
{
if (::listen(m_fd, backlog) < 0) return err(errno);
return {};
}
Result<LocalServer::Client> LocalServer::accept()
{
int fd = ::accept(m_fd, nullptr, nullptr);
if (fd < 0) return err(errno);
if (!m_blocking) fcntl(fd, F_SETFL, O_NONBLOCK);
return Client { fd };
}
LocalServer::~LocalServer()
{
close(m_fd);
}
LocalServer::Client::Client(Client&& other) : m_fd(other.m_fd)
{
other.m_fd = -1;
}
LocalServer::Client::Client(int fd) : m_fd(fd)
{
}
LocalServer::Client::~Client()
{
if (m_fd >= 0) close(m_fd);
}
Result<usize> LocalServer::Client::recv(u8* buf, usize length)
{
ssize_t nread = read(m_fd, buf, length);
if (nread < 0) return err(errno);
return nread;
}
Result<usize> LocalServer::Client::send(const u8* buf, usize length)
{
ssize_t nwrite = write(m_fd, buf, length);
if (nwrite < 0) return err(errno);
return nwrite;
}
void LocalServer::Client::disconnect()
{
close(m_fd);
m_fd = -1;
}
}

17
libui/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
# The UI and graphics library for Luna.
file(GLOB HEADERS include/ui/*.h)
set(SOURCES
${HEADERS}
src/Canvas.cpp
src/Rect.cpp
src/Font.cpp
src/Image.cpp
)
add_library(ui ${SOURCES})
target_compile_options(ui PRIVATE ${COMMON_FLAGS} -fno-threadsafe-statics)
target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/)
target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include)
target_link_libraries(ui PUBLIC os)

64
libui/include/ui/Canvas.h Normal file
View File

@ -0,0 +1,64 @@
#pragma once
#include <luna/Result.h>
#include <luna/Types.h>
#include <ui/Color.h>
#include <ui/Point.h>
#include <ui/Rect.h>
namespace ui
{
/**
* @brief A drawable surface.
*/
struct Canvas
{
int width;
int height;
int stride;
u8* ptr;
/**
* @brief Create a new Canvas object.
*
* @param ptr The memory to use for the canvas. It must be of at least width * height * 4 bytes of length.
* @param width The width of the canvas.
* @param height The height of the canvas.
* @return Canvas The new Canvas object.
*/
static Canvas create(u8* ptr, int width, int height);
/**
* @brief Return a new Canvas that represents a subsection of the current one.
*
* @param rect The dimensions of the new canvas. If these exceed the bounds of the current canvas, they will be
* clamped.
* @return Canvas The new Canvas object.
*/
Canvas subcanvas(Rect rect);
/**
* @brief Return the dimensions of the current canvas.
*
* @return Rect This canvas's dimensions, as a Rect object.
*/
Rect rect()
{
return Rect { .pos = { 0, 0 }, .width = width, .height = height };
}
/**
* @brief Fill the entire canvas with one color.
*
* @param color The color to use.
*/
void fill(Color color);
/**
* @brief Fill the canvas with pixels.
*
* @param pixels The array of pixels (must be at least width*height).
* @param stride The number of pixels to skip to go to the next line.
*/
void fill(u32* pixels, int stride);
};
};

104
libui/include/ui/Color.h Normal file
View File

@ -0,0 +1,104 @@
#pragma once
#include <luna/Types.h>
namespace ui
{
/**
* @brief A 32-bit ARGB color.
*/
struct Color
{
union {
u32 raw;
u8 colors[4];
};
/**
* @brief Return the blue value of this color.
*
* @return constexpr u8 The blue value.
*/
constexpr u8 red() const
{
return colors[2];
}
/**
* @brief Return the green value of this color.
*
* @return constexpr u8 The green value.
*/
constexpr u8 green() const
{
return colors[1];
}
/**
* @brief Return the blue value of this color.
*
* @return constexpr u8 The blue value.
*/
constexpr u8 blue() const
{
return colors[0];
}
/**
* @brief Return the alpha value of this color.
*
* @return constexpr u8 The alpha value.
*/
constexpr u8 alpha() const
{
return colors[3];
}
/**
* @brief Construct a new color from a 32-bit ARGB integer.
*
* @param raw The integer representing the color.
* @return constexpr Color The new color.
*/
static constexpr Color from_u32(u32 raw)
{
return Color { .raw = raw };
}
/**
* @brief Construct a new color from its separate RGBA values (from 0 to 255).
*
* @param red The red value.
* @param green The green value.
* @param blue The blue value.
* @param alpha The alpha value.
* @return constexpr Color The new color.
*/
static constexpr Color from_rgba(u8 red, u8 green, u8 blue, u8 alpha)
{
return Color { .colors = { blue, green, red, alpha } };
}
/**
* @brief Construct a new color from its separate RGB values (from 0 to 255).
*
* @param red The red value.
* @param green The green value.
* @param blue The blue value.
* @return constexpr Color The new color.
*/
static constexpr Color from_rgb(u8 red, u8 green, u8 blue)
{
return from_rgba(red, green, blue, 0xff);
}
};
static constexpr Color WHITE = Color::from_rgb(0xff, 0xff, 0xff);
static constexpr Color BLACK = Color::from_rgb(0x00, 0x00, 0x00);
static constexpr Color GRAY = Color::from_rgb(0x80, 0x80, 0x80);
static constexpr Color BLUE = Color::from_rgb(0x00, 0x00, 0xff);
static constexpr Color GREEN = Color::from_rgb(0x00, 0xff, 0x00);
static constexpr Color RED = Color::from_rgb(0xff, 0x00, 0x00);
static constexpr Color CYAN = Color::from_rgb(0x00, 0xff, 0xff);
};

111
libui/include/ui/Font.h Normal file
View File

@ -0,0 +1,111 @@
#pragma once
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.h>
#include <ui/Canvas.h>
#define PSF_FONT_MAGIC 0x864ab572
namespace ui
{
/**
* @brief A class holding PSF font data, used for direct rendering of glyphs into a canvas.
*/
class Font : public Shareable
{
public:
/**
* @brief An enum used to select a font weight when loading a font.
*/
enum FontWeight
{
Regular,
Bold,
};
/**
* @brief Load a Font object from a font file.
*
* @param path The full path to the font file.
* @return Result<SharedPtr<Font>> An error, or the loaded Font object.
*/
static Result<SharedPtr<Font>> load(const os::Path& path);
/**
* @brief Load a system font by name.
*
* @param name The name of the font to load (the default system font is "Tamsyn").
* @param weight The weight of the font (regular or bold).
* @return Result<SharedPtr<Font>> An error, or the loaded Font object.
*/
static Result<SharedPtr<Font>> load_builtin(StringView name, FontWeight weight);
/**
* @brief Return a pointer to the system's default font.
*
* @return SharedPtr<Font> The default font.
*/
static SharedPtr<Font> default_font();
/**
* @brief Return a pointer to the system's default bold font.
*
* @return SharedPtr<Font> The default bold font.
*/
static SharedPtr<Font> default_bold_font();
/**
* @brief Render a single Unicode code point onto a canvas, using this font's glyphs.
*
* @param codepoint The code point to render.
* @param color The color to draw the code point in.
* @param canvas The canvas to use.
*/
void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas);
/**
* @brief Render a Unicode text string onto a canvas, using this font's glyphs.
*
* @param text The string to render (must be null-terminated).
* @param color The color to draw the code point in.
* @param canvas The canvas to use.
*/
void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas);
/**
* @brief Return the width of this font's glyphs.
*
* @return int The width.
*/
int width() const
{
return m_psf_header.width;
}
/**
* @brief Return the height of this font's glyphs.
*
* @return int The height.
*/
int height() const
{
return m_psf_header.height;
}
private:
struct PSFHeader
{
u32 magic;
u32 version; // zero
u32 headersize;
u32 flags; // 0 if there's no unicode table
u32 numglyph;
u32 bytesperglyph;
int height;
int width;
};
PSFHeader m_psf_header;
Buffer m_font_data;
};
};

71
libui/include/ui/Image.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.h>
namespace ui
{
/**
* @brief An image in the TGA file format.
*/
class Image : public Shareable
{
public:
/**
* @brief Load a new TGA image from a file.
*
* @param path The path to open.
* @return Result<SharedPtr<Image>> An error, or a new Image object.
*/
static Result<SharedPtr<Image>> load(const os::Path& path);
/**
* @brief Return the array of pixels contained in the image.
*
* @return u32* The array of pixels.
*/
u32* pixels()
{
return (u32*)m_image_data.data();
}
/**
* @brief Return the width of the image.
*
* @return u16 The width.
*/
u16 width()
{
return m_tga_header.w;
}
/**
* @brief Return the height of the image.
*
* @return u16 The height.
*/
u16 height()
{
return m_tga_header.h;
}
private:
struct [[gnu::packed]] TGAHeader
{
u8 idlen;
u8 colormap;
u8 encoding;
u16 cmaporig, cmaplen;
u8 cmapent;
u16 x;
u16 y;
u16 w;
u16 h;
u8 bpp;
u8 pixeltype;
};
TGAHeader m_tga_header;
Buffer m_image_data;
};
}

13
libui/include/ui/Point.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
namespace ui
{
/**
* @brief A point in 2D space.
*/
struct Point
{
int x { 0 };
int y { 0 };
};
}

72
libui/include/ui/Rect.h Normal file
View File

@ -0,0 +1,72 @@
#pragma once
#include <ui/Point.h>
namespace ui
{
/**
* @brief A simple rectangle.
*/
struct Rect
{
Point pos;
int width;
int height;
/**
* @brief Check if a point is contained in this rectangle.
*
* @param point The point to check.
* @return true The point is contained inside the rectangle.
* @return false The point is not contained inside the rectangle.
*/
bool contains(Point point);
/**
* @brief Check if another rectangle is contained in this one.
*
* @param point The rectangle to check.
* @return true The other rectangle is contained inside this one.
* @return false The other rectangle is not contained inside this one.
*/
bool contains(Rect rect);
/**
* @brief Normalize a point to fit inside this rectangle.
*
* @param point The original point.
* @return Point The normalized point.
*/
Point normalize(Point point);
/**
* @brief Transform an absolute position to a position relative to this rectangle.
*
* @param pos The original absolute position.
* @return Point The position relative to this rectangle.
*/
Point relative(Point pos);
/**
* @brief Transform a position relative to this rectangle to an absolute position.
*
* @param pos The original relative position.
* @return Point The absolute position.
*/
Point absolute(Point pos);
/**
* @brief Transform another rectangle relative to this one to an absolute rectangle.
*
* @param rect The original relative rectangle.
* @return Point The absolute rectangle.
*/
Rect absolute(Rect rect);
/**
* @brief Return a copy of this rectangle with no negative values (normalized to 0).
*
* @return Rect The new rectangle.
*/
Rect normalized();
};
}

53
libui/src/Canvas.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <ui/Canvas.h>
namespace ui
{
Canvas Canvas::create(u8* ptr, int width, int height)
{
return Canvas { .width = width, .height = height, .stride = width, .ptr = ptr };
}
Canvas Canvas::subcanvas(Rect rect)
{
if (rect.pos.x < 0) rect.pos.x = 0;
if (rect.pos.y < 0) rect.pos.y = 0;
if (rect.pos.x + rect.width > width) rect.width = width - rect.pos.x;
if (rect.pos.y + rect.height > height) rect.height = height - rect.pos.y;
u8* p = ptr + rect.pos.x * sizeof(Color) + (rect.pos.y * sizeof(Color) * stride);
return Canvas { .width = rect.width, .height = rect.height, .stride = stride, .ptr = p };
}
void Canvas::fill(Color color)
{
u8* p = ptr;
for (int i = 0; i < height; i++)
{
u32* colorp = (u32*)p;
for (int j = 0; j < width; j++)
{
*colorp = color.raw;
colorp++;
}
p += stride * sizeof(Color);
}
}
void Canvas::fill(u32* pixels, int _stride)
{
u8* p = ptr;
for (int i = 0; i < height; i++)
{
u32* colorp = (u32*)p;
for (int j = 0; j < width; j++)
{
u32 pix = pixels[j];
if (Color::from_u32(pix).alpha() == 0xff) *colorp = pix;
colorp++;
}
pixels += _stride;
p += stride * sizeof(Color);
}
}
}

112
libui/src/Font.cpp Normal file
View File

@ -0,0 +1,112 @@
#include <luna/String.h>
#include <os/File.h>
#include <ui/Font.h>
constexpr static int BYTES_PER_PIXEL = (int)sizeof(ui::Color);
namespace ui
{
Result<SharedPtr<Font>> Font::load(const os::Path& path)
{
auto font = TRY(make_shared<Font>());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
TRY(file->read_typed(font->m_psf_header));
if (font->m_psf_header.magic != PSF_FONT_MAGIC)
{
os::eprintln("ui::Font::load(%s) failed: font magic does not match PSF2 magic", path.name().chars());
return err(ENOTSUP);
}
if (font->m_psf_header.version != 0)
{
os::eprintln("ui::Font::load(%s) failed: font version is unsupported", path.name().chars());
return err(ENOTSUP);
}
if (font->m_psf_header.flags)
{
os::eprintln("ui::Font::load(%s) warning: font has a unicode table, which we're ignoring",
path.name().chars());
// todo(); // Font has a unicode table, oh no!
}
font->m_font_data = TRY(file->read_all()); // Read the rest of the file into the font data buffer.
return font;
}
Result<SharedPtr<Font>> Font::load_builtin(StringView name, FontWeight weight)
{
auto path = TRY(String::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(),
weight == FontWeight::Bold ? "Bold" : "Regular"));
return load(path.view());
}
SharedPtr<Font> Font::default_font()
{
static SharedPtr<ui::Font> s_default_font = {};
if (!s_default_font) s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value();
return s_default_font;
}
SharedPtr<Font> Font::default_bold_font()
{
static SharedPtr<ui::Font> s_default_bold_font = {};
if (!s_default_bold_font) s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value();
return s_default_bold_font;
}
void Font::render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas)
{
const wchar_t str[] = { codepoint, 0 };
render(str, color, canvas);
}
void Font::render(const wchar_t* text, ui::Color color, ui::Canvas& canvas)
{
usize len = wcslen(text);
int height = m_psf_header.height;
int width = m_psf_header.width;
int last_char_width = width;
if (canvas.width < (m_psf_header.width * static_cast<int>(len)))
{
len = (canvas.width / width) + 1;
last_char_width = canvas.width % width;
}
if (canvas.height < height) height = canvas.height;
const int bytes_per_line = (m_psf_header.width + 7) / 8;
for (usize i = 0; i < len; i++)
{
if (i + 1 == len) width = last_char_width;
wchar_t codepoint = text[i];
u8* glyph =
m_font_data.data() + (codepoint > 0 && codepoint < (wchar_t)m_psf_header.numglyph ? codepoint : 0) *
m_psf_header.bytesperglyph;
u32 offset = (u32)i * m_psf_header.width * BYTES_PER_PIXEL;
for (int y = 0; y < height; y++)
{
u32 line = offset;
int mask = 1 << (m_psf_header.width - 1);
for (int x = 0; x < width; x++)
{
if (*((u32*)glyph) & mask) *(u32*)(canvas.ptr + line) = color.raw;
mask >>= 1;
line += BYTES_PER_PIXEL;
}
glyph += bytes_per_line;
offset += canvas.stride * BYTES_PER_PIXEL;
}
}
}
}

24
libui/src/Image.cpp Normal file
View File

@ -0,0 +1,24 @@
#include <os/File.h>
#include <ui/Image.h>
namespace ui
{
Result<SharedPtr<Image>> Image::load(const os::Path& path)
{
auto image = TRY(make_shared<Image>());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
TRY(file->read_typed(image->m_tga_header));
if (image->m_tga_header.encoding != 2) todo();
if (image->m_tga_header.bpp != 32) todo();
Buffer image_id;
TRY(file->read(image_id, image->m_tga_header.idlen));
TRY(file->read(image->m_image_data,
image->m_tga_header.w * image->m_tga_header.h * (image->m_tga_header.bpp / 8)));
return image;
}
}

53
libui/src/Rect.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <ui/Rect.h>
namespace ui
{
bool Rect::contains(Point point)
{
return (point.x >= pos.x) && (point.y >= pos.y) && (point.x <= (pos.x + width)) &&
(point.y <= (pos.y + height));
}
bool Rect::contains(Rect rect)
{
if (!contains(rect.pos)) return false;
Point rel = relative(rect.pos);
if ((rel.x + rect.width) > width) return false;
if ((rel.y + rect.height) > height) return false;
return true;
}
Point Rect::normalize(Point point)
{
if (point.x < pos.x) point.x = pos.x;
if (point.y < pos.y) point.y = pos.y;
if (point.x > pos.x + width) point.x = pos.x + width;
if (point.y > pos.y + height) point.y = pos.y + height;
return point;
}
Point Rect::relative(Point point)
{
point = normalize(point);
point.x -= pos.x;
point.y -= pos.y;
return point;
}
Point Rect::absolute(Point point)
{
point.x += pos.x;
point.y += pos.y;
return point;
}
Rect Rect::absolute(Rect rect)
{
return Rect { absolute(rect.pos), rect.width, rect.height };
}
Rect Rect::normalized()
{
return Rect { ui::Point { pos.x < 0 ? 0 : pos.x, pos.y < 0 ? 0 : pos.y }, width, height };
}
};

View File

@ -8,10 +8,12 @@ cd $LUNA_ROOT
mkdir -p $LUNA_BASE
mkdir -p $LUNA_BASE/usr/include
mkdir -p $LUNA_BASE/usr/include/luna
mkdir -p $LUNA_BASE/usr/include/ui
mkdir -p $LUNA_BASE/usr/include/os
mkdir -p $LUNA_BASE/usr/include/moon
cp --preserve=timestamps -RT libc/include/ $LUNA_BASE/usr/include
cp --preserve=timestamps -RT libluna/include/luna/ $LUNA_BASE/usr/include/luna
cp --preserve=timestamps -RT libui/include/ui/ $LUNA_BASE/usr/include/ui
cp --preserve=timestamps -RT libos/include/os/ $LUNA_BASE/usr/include/os
cp --preserve=timestamps -RT kernel/src/api/ $LUNA_BASE/usr/include/moon

View File

@ -4,7 +4,7 @@ source $(dirname $0)/env.sh
cd $LUNA_ROOT
FOLDERS=(kernel libc libos libluna apps shell tests)
FOLDERS=(kernel libc libos libui libluna apps shell tests)
SOURCES=($(find ${FOLDERS[@]} -type f -name "*.cpp"))
SOURCES+=($(find ${FOLDERS[@]} -type f -name "*.h"))

16
wind/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
set(SOURCES
main.cpp
Screen.h
Screen.cpp
Mouse.h
Mouse.cpp
Window.h
Window.cpp
)
add_executable(wind ${SOURCES})
target_compile_options(wind PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings -fno-threadsafe-statics)
add_dependencies(wind libc)
target_include_directories(wind PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(wind PRIVATE os ui)
install(TARGETS wind DESTINATION ${LUNA_BASE}/usr/bin)

77
wind/Mouse.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "Mouse.h"
#include <os/File.h>
#include <ui/Image.h>
static SharedPtr<ui::Image> g_mouse_cursor;
Mouse::Mouse(ui::Canvas& screen)
{
m_position.x = screen.width / 2;
m_position.y = screen.height / 2;
m_screen_rect = screen.rect();
g_mouse_cursor = ui::Image::load("/usr/share/cursors/default.tga").value_or({});
}
void Mouse::draw(ui::Canvas& screen)
{
if (!g_mouse_cursor) return;
auto canvas = screen.subcanvas(ui::Rect { m_position, g_mouse_cursor->width(), g_mouse_cursor->height() });
canvas.fill(g_mouse_cursor->pixels(), g_mouse_cursor->width());
}
void Mouse::update(const moon::MousePacket& packet)
{
m_position.x += packet.xdelta;
m_position.y -= packet.ydelta;
m_position = m_screen_rect.normalize(m_position);
if (m_dragging_window && !(packet.buttons & moon::MouseButton::Left))
{
os::println("Stopped drag: window at (%d,%d,%d,%d) with offset (%d,%d)", m_dragging_window->surface.pos.x,
m_dragging_window->surface.pos.y, m_dragging_window->surface.width,
m_dragging_window->surface.height, this->m_initial_drag_position.x,
this->m_initial_drag_position.y);
m_dragging_window = nullptr;
}
if (m_dragging_window)
{
m_dragging_window->surface.pos =
ui::Point { m_position.x - m_initial_drag_position.x, m_position.y - m_initial_drag_position.y };
m_dragging_window->surface = m_dragging_window->surface.normalized();
}
else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window)
{
// Iterate from the end of the list, since windows at the beginning are stacked at the bottom and windows at the
// top are at the end.
for (Window* window = g_windows.last().value_or(nullptr); window;
window = g_windows.previous(window).value_or(nullptr))
{
if (window->surface.absolute(window->close_button).contains(m_position))
{
// Close button pressed
g_windows.remove(window);
delete window;
break;
}
else if (window->surface.absolute(window->titlebar).contains(m_position))
{
m_dragging_window = window;
m_initial_drag_position = window->surface.relative(m_position);
os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x,
window->surface.pos.y, window->surface.width, window->surface.height,
m_initial_drag_position.x, m_initial_drag_position.y);
window->focus();
break;
}
else if (window->surface.absolute(window->contents).contains(m_position))
{
window->focus();
break; // We don't want to continue iterating, otherwise this would take into account windows whose
// titlebar is underneath another window's contents!
}
}
}
}

22
wind/Mouse.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "Screen.h"
#include "Window.h"
#include <moon/Mouse.h>
#include <ui/Canvas.h>
class Mouse
{
public:
Mouse(ui::Canvas& screen);
void update(const moon::MousePacket& packet);
void draw(ui::Canvas& screen);
private:
ui::Point m_position;
ui::Rect m_screen_rect;
Window* m_dragging_window = nullptr;
ui::Point m_initial_drag_position;
};

32
wind/Screen.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "Screen.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
Result<Screen> Screen::open()
{
int fd = ::open("/dev/fb0", O_RDWR);
if (fd < 0) return err(errno);
int width = ioctl(fd, FB_GET_WIDTH);
int height = ioctl(fd, FB_GET_HEIGHT);
void* p = mmap(nullptr, width * height * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
if (p == MAP_FAILED) { return err(errno); }
Screen screen;
screen.m_canvas = ui::Canvas::create((u8*)p, width, height);
screen.m_size = width * height * BYTES_PER_PIXEL;
return screen;
}
void Screen::sync()
{
msync(m_canvas.ptr, size(), MS_SYNC);
}

27
wind/Screen.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <ui/Canvas.h>
constexpr int BYTES_PER_PIXEL = 4;
class Screen
{
public:
static Result<Screen> open();
ui::Canvas& canvas()
{
return m_canvas;
}
int size() const
{
return m_size;
}
void sync();
private:
ui::Canvas m_canvas;
int m_size;
};

50
wind/Window.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "Window.h"
#include <luna/Utf8.h>
#include <os/File.h>
#include <ui/Font.h>
#include <ui/Image.h>
LinkedList<Window> g_windows;
void Window::draw(ui::Canvas& screen)
{
auto window = screen.subcanvas(surface);
window.subcanvas(contents).fill(color);
wchar_t buffer[4096];
Utf8StringDecoder decoder(name.chars());
decoder.decode(buffer, sizeof(buffer)).release_value();
auto font = ui::Font::default_font();
auto titlebar_canvas = window.subcanvas(titlebar);
titlebar_canvas.fill(ui::GRAY);
auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height });
font->render(buffer, ui::BLACK, textarea);
static SharedPtr<ui::Image> g_close_icon;
if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").release_value();
auto close_area = window.subcanvas(close_button);
close_area.fill(g_close_icon->pixels(), g_close_icon->width());
}
void Window::focus()
{
// Bring the window to the front of the list.
g_windows.remove(this);
g_windows.append(this);
}
Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), name(n)
{
auto font = ui::Font::default_font();
if (surface.width < 36) surface.width = 36;
if (surface.height < (font->height() + 20)) surface.height = font->height() + 20;
titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 };
close_button = ui::Rect { surface.width - 26, 10, 16, 16 };
contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) };
g_windows.append(this);
}

24
wind/Window.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <luna/LinkedList.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Color.h>
#include <ui/Rect.h>
struct Window : public LinkedListNode<Window>
{
ui::Rect surface;
ui::Rect titlebar;
ui::Rect close_button;
ui::Rect contents;
ui::Color color;
StringView name;
Window(ui::Rect, ui::Color, StringView);
void focus();
void draw(ui::Canvas& screen);
};
extern LinkedList<Window> g_windows;

131
wind/main.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "Mouse.h"
#include "Screen.h"
#include "Window.h"
#include <errno.h>
#include <moon/Keyboard.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/LocalServer.h>
#include <os/Process.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <time.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
{
srand((unsigned)time(NULL));
StringView socket_path = "/tmp/wind.sock";
StringView user;
os::ArgumentParser parser;
parser.add_description("The display server for Luna's graphical user interface."_sv);
parser.add_system_program_info("wind"_sv);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.add_value_argument(user, 'u', "user"_sv, "the user to run as"_sv);
parser.parse(argc, argv);
if (geteuid() != 0)
{
os::eprintln("error: wind must be run as root to initialize resources, run with --user=<USERNAME> to drop "
"privileges afterwards");
return 1;
}
auto mouse = TRY(os::File::open("/dev/mouse", os::File::ReadOnly));
mouse->set_buffer(os::File::NotBuffered);
mouse->set_close_on_exec();
auto keyboard = TRY(os::File::open("/dev/kbd", os::File::ReadOnly));
keyboard->set_buffer(os::File::NotBuffered);
keyboard->set_close_on_exec();
auto screen = TRY(Screen::open());
Mouse mouse_pointer { screen.canvas() };
ioctl(STDIN_FILENO, TTYSETGFX, 1);
setpgid(0, 0);
int fd = open("/dev/null", O_RDONLY);
if (fd >= 0)
{
dup2(fd, STDIN_FILENO);
close(fd);
}
clearenv();
if (!user.is_empty())
{
auto* pwd = getpwnam(user.chars());
if (pwd)
{
setgid(pwd->pw_gid);
setuid(pwd->pw_uid);
}
}
auto server = TRY(os::LocalServer::create(socket_path, false));
TRY(server->listen(20));
StringView args[] = { "/usr/bin/gclient"_sv };
TRY(os::Process::spawn("/usr/bin/gclient"_sv, Slice<StringView> { args, 1 }, false));
ui::Color background = ui::BLACK;
TRY(make<Window>(ui::Rect { 200, 200, 600, 400 }, ui::GREEN, "Calculator"_sv));
TRY(make<Window>(ui::Rect { 100, 100, 300, 200 }, ui::RED, "Settings"_sv));
TRY(make<Window>(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv));
Vector<os::LocalServer::Client> clients;
while (1)
{
screen.canvas().fill(background);
for (auto* window : g_windows) window->draw(screen.canvas());
mouse_pointer.draw(screen.canvas());
screen.sync();
struct pollfd fds[] = {
{ .fd = mouse->fd(), .events = POLLIN, .revents = 0 },
{ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 },
{ .fd = server->fd(), .events = POLLIN, .revents = 0 },
};
int rc = poll(fds, 3, 1000);
if (!rc) continue;
if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); }
if (fds[0].revents & POLLIN)
{
moon::MousePacket packet;
TRY(mouse->read_typed(packet));
mouse_pointer.update(packet);
}
if (fds[1].revents & POLLIN)
{
moon::KeyboardPacket packet;
TRY(keyboard->read_typed(packet));
if (!packet.released)
{
TRY(make<Window>(ui::Rect { rand() % screen.canvas().width, rand() % screen.canvas().height,
rand() % screen.canvas().width, rand() % screen.canvas().height },
ui::Color::from_rgb(static_cast<u8>(rand() % 256), static_cast<u8>(rand() % 256),
static_cast<u8>(rand() % 256)),
strerror(packet.key)));
}
}
if (fds[2].revents & POLLIN)
{
auto client = TRY(server->accept());
os::println("wind: New client connected!");
TRY(clients.try_append(move(client)));
}
}
}