Compare commits

...

82 Commits

Author SHA1 Message Date
6eea3bda06
wind: Move more fallible operations before window creation
All checks were successful
continuous-integration/drone/pr Build is passing
2023-09-04 12:16:55 +02:00
2f608d46ac
wind: Make sure stdin is always a TTY 2023-09-04 12:16:55 +02:00
1edf10380f
libui+wind+libos: Move shared memory handling code to os::SharedMemory 2023-09-04 12:16:55 +02:00
5867195168
libui: Add default handlers for events in Widget 2023-09-04 12:16:55 +02:00
5e1d70819a
libui: Propagate Container events only if they are in the child widget's rect 2023-09-04 12:16:54 +02:00
07e992f61a
libui+wind: Handle mouse leave events when the mouse leaves a window 2023-09-04 12:16:54 +02:00
d47c25a2fb
wind: Stop using the removed 'signal' pledge 2023-09-04 12:16:54 +02:00
9a7cb66c34
libui: Install the built library into the system root 2023-09-04 12:16:54 +02:00
7a93b15a02
wind: Show memory usage in debug output 2023-09-04 12:16:54 +02:00
de15730db5
wind: Handle ftruncate() and mmap() errors properly 2023-09-04 12:16:54 +02:00
1774eadd60
wind: Fix client references being out-of-date in windows when disconnecting other clients
Classic "keeping a pointer to an element inside a vector after the vector is updated" bug, ah yes.
2023-09-04 12:16:54 +02:00
ea34f782e8
taskbar: Wait for terminated child windows 2023-09-04 12:16:53 +02:00
09ac7b0730
wind: Add debug keybind 2023-09-04 12:16:53 +02:00
077a42c15c
wind+libos+libui: Handle interrupted reads properly 2023-09-04 12:16:53 +02:00
2278a0b947
base: Actually add the start icon to source control 2023-09-04 12:16:53 +02:00
1214d1711d
libui: Add Buttons 2023-09-04 12:16:53 +02:00
6d92eefd5f
libui: Handle other mouse events 2023-09-04 12:16:53 +02:00
56a2284b39
libui: Add aligned items using Containers, ImageWidget 2023-09-04 12:16:52 +02:00
5e3b9fc950
libui: Add VerticalLayout 2023-09-04 12:16:52 +02:00
c8edb55eb2
wind+libui+taskbar: Add GetScreenRect IPC, non-decorated windows, taskbar 2023-09-04 12:16:52 +02:00
ca92ff9d96
libui: Actually fill window backgrounds with the correct color 2023-09-04 12:16:52 +02:00
b2de0d2b84
libui: Add basic widget and layout system =D 2023-09-04 12:16:52 +02:00
ccd5551450
ui+wind: Send mouse move events through IPC 2023-09-04 12:16:52 +02:00
51b9acac31
wind+libui: Add protocol for window close requests 2023-09-04 12:16:51 +02:00
d7d18c7c31
libos+libui+wind: Use uppercase for static struct IDs to avoid confusion with fields 2023-09-04 12:16:51 +02:00
51ef9536d3
libui+gclient: Add basic OOP wrappers around the IPC protocol 2023-09-04 12:16:51 +02:00
87a54a6187
wind+gclient: Add SetWindowTitle and support shm buffers 2023-09-04 12:16:51 +02:00
35b645c5c9
gclient: Create two example windows 2023-09-04 12:16:51 +02:00
5b875bc12a
wind: Handle CreateWindow IPC messages 2023-09-04 12:16:51 +02:00
e35c940bd9
libui: Add CreateWindow IPC message definitions 2023-09-04 12:16:50 +02:00
63098bb5af
libos: Add basic IPC message framework 2023-09-04 12:16:50 +02:00
133541b7e4
kernel: Fix poll syscall 2023-09-04 12:16:50 +02:00
0629719975
wind: Monitor data on client connections 2023-09-04 12:16:50 +02:00
39278465ac
kernel: Add POLLHUP and store it when a polled socket's peer disconnects 2023-09-04 12:16:50 +02:00
210d322886
libui: Add copyright/author text 2023-09-04 12:16:50 +02:00
4011aa0467
libos: Add copyright/author comments to LocalServer and LocalClient 2023-09-04 12:16:50 +02:00
0aaac192d8
wind: Use init --user and pledge() 2023-09-04 12:16:49 +02:00
a703238ff9
Update .gitignore 2023-09-04 12:16:49 +02:00
8e2def7d88
libos: Remove some shared pointers and change them to owned/live on the stack 2023-09-04 12:16:49 +02:00
bfa5ce4c00
wind: Spawn a new client process after startup
Also, create the socket after dropping privileges.
2023-09-04 12:16:49 +02:00
1aa0406649
apps: Add gclient 2023-09-04 12:16:49 +02:00
8ebc5e88a6
libos: Add os::LocalClient 2023-09-04 12:16:49 +02:00
b380d84347
libui: Change 'into' to 'onto' 2023-09-04 12:16:48 +02:00
2d7ece4e9b
libui: Document ui::Font 2023-09-04 12:16:48 +02:00
1c4f2dfb15
libui+wind: Move some static variables inside functions 2023-09-04 12:16:48 +02:00
7066a976c3
wind: Generate random windows on keypresses 2023-09-04 12:16:48 +02:00
3442ae0bdb
wind: Make sure windows have a minimum size to fit the titlebar 2023-09-04 12:16:48 +02:00
d6b7a4a921
libui: Properly cut off the last drawn character if necessary 2023-09-04 12:16:48 +02:00
feb84ac3fa
libui: Add Rect::contains(Rect) 2023-09-04 12:16:47 +02:00
80c8a80301
libui: Render font characters properly with no spacing, matching the width calculations 2023-09-04 12:16:47 +02:00
0fcd984c21
wind: Render an actual TGA mouse cursor 2023-09-04 12:16:47 +02:00
3755d4c0a3
wind: Add a close button to windows using a TGA icon 2023-09-04 12:16:47 +02:00
218278fb22
libui: Add support for TGA image loading 2023-09-04 12:16:47 +02:00
117edb3e90
libui: Add an interface to fill a Canvas with an array of pixels 2023-09-04 12:16:47 +02:00
739b05ab67
wind: Add window titlebars using ui::Font 2023-09-04 12:16:47 +02:00
dd3611cb25
libui: Add PSF font loading and rendering 2023-09-04 12:16:46 +02:00
e932efd72c
libui: Add Color::GRAY 2023-09-04 12:16:46 +02:00
35d07206d7
libui: Rename Rect::absolute to normalized and add a new absolute function 2023-09-04 12:16:46 +02:00
40143597d8
libluna: Add assignment operators to Buffer 2023-09-04 12:16:46 +02:00
f90634e1b1
wind: Reorder drag sequence 2023-09-04 12:16:46 +02:00
f61dd9ba99
libui: Add Rect::relative 2023-09-04 12:16:46 +02:00
eef6483192
libui: Remove redundant statement 2023-09-04 12:16:45 +02:00
88fb4be5ed
libui: Add getters for separate color values 2023-09-04 12:16:45 +02:00
94c06379ea
libui: Remove unnecessary stuff 2023-09-04 12:16:45 +02:00
156092cbbb
base: Remove startup items not necessary for GUI startup 2023-09-04 12:16:45 +02:00
4933e1e32c
libui+wind: (Draggable) windows 2023-09-04 12:16:45 +02:00
2a22b751e9
wind: Create a local server object 2023-09-04 12:16:45 +02:00
ea4e374718
libos: Add a new LocalServer class for local domain sockets 2023-09-04 12:16:44 +02:00
6191039bfb
kernel: Support listening sockets in poll() 2023-09-04 12:16:44 +02:00
b222221517
base: Start wind on startup instead of the shell 2023-09-04 12:16:44 +02:00
c17f6d0e57
wind: Add a simple display server skeleton using libui
No client functionality yet, but it's a start.
2023-09-04 12:16:44 +02:00
4502073b7d
libui: Add a GUI and graphics library 2023-09-04 12:16:44 +02:00
6fe2343233
kernel: Fix negative movement in the PS/2 mouse driver 2023-09-04 12:16:40 +02:00
27eacac19c
kernel: Add a blinking cursor to the terminal
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-04 11:44:35 +02:00
c5e11bb6cf
apps+base+libc: Use /usr/bin paths instead of /bin everywhere 2023-09-04 11:44:10 +02:00
3c9b2c49aa
init: Fix wrong log message 2023-09-04 11:43:36 +02:00
1528c772fd
kernel: Store the full command line of a process 2023-09-04 11:43:11 +02:00
39ba4c9087
ls: Add colors to output
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-02 20:01:10 +02:00
c524dc8d58
libluna+kernel: Basic ANSI escape sequences 2023-09-02 19:35:42 +02:00
e76a91d5d0
libc+libluna: Move the scanf implementation from libc to libluna 2023-09-02 15:48:58 +02:00
cb0d5cb6a1
rm: Add the -v flag
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-02 14:46:14 +02:00
06aa7725a1
libos: Stop using syscalls directly and proxy to libc 2023-09-02 14:27:04 +02:00
101 changed files with 4575 additions and 367 deletions

9
.gitignore vendored
View File

@ -4,7 +4,14 @@ build/
initrd/boot/moon initrd/boot/moon
env-local.sh env-local.sh
initrd/bin/** initrd/bin/**
base/usr/** base/usr/bin/**
base/usr/include/**
base/usr/lib/**
base/usr/share/pkgdb/**
!base/usr/share/fonts/*
!base/usr/share/icons/*
base/usr/share/**
base/usr/x86_64-luna/**
base/etc/skel/LICENSE base/etc/skel/LICENSE
.fakeroot .fakeroot
kernel/config.cmake kernel/config.cmake

View File

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

View File

@ -41,3 +41,7 @@ luna_app(kill.cpp kill)
luna_app(gol.cpp gol) luna_app(gol.cpp gol)
luna_app(touch.cpp touch) luna_app(touch.cpp touch)
luna_app(free.cpp free) luna_app(free.cpp free)
luna_app(gclient.cpp gclient)
target_link_libraries(gclient PUBLIC ui)
luna_app(taskbar.cpp taskbar)
target_link_libraries(taskbar PUBLIC ui)

65
apps/gclient.cpp Normal file
View File

@ -0,0 +1,65 @@
#include <ui/App.h>
#include <ui/Layout.h>
struct ColorWidget : public ui::Widget
{
public:
ColorWidget(ui::Color first, ui::Color second) : m_color(first), m_first_color(first), m_second_color(second)
{
}
Result<ui::EventResult> handle_mouse_move(ui::Point) override
{
m_color = m_second_color;
return ui::EventResult::DidHandle;
}
Result<ui::EventResult> handle_mouse_leave() override
{
m_color = m_first_color;
return ui::EventResult::DidHandle;
}
Result<void> draw(ui::Canvas& canvas) override
{
canvas.fill(m_color);
return {};
}
private:
ui::Color m_color;
ui::Color m_first_color;
ui::Color m_second_color;
};
Result<int> luna_main(int argc, char** argv)
{
ui::App app;
TRY(app.init(argc, argv));
auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 }));
app.set_main_window(window);
window->set_title("Main Window");
window->set_background(ui::CYAN);
ui::HorizontalLayout layout;
window->set_main_widget(layout);
ColorWidget green(ui::GREEN, ui::WHITE);
layout.add_widget(green);
ColorWidget blue(ui::BLUE, ui::GRAY);
layout.add_widget(blue);
ui::VerticalLayout sublayout;
layout.add_widget(sublayout);
ColorWidget red(ui::RED, ui::CYAN);
sublayout.add_widget(red);
ColorWidget white(ui::WHITE, ui::GREEN);
sublayout.add_widget(white);
window->draw();
return app.run();
}

View File

@ -261,7 +261,7 @@ static Result<void> load_service(const os::Path& path)
if (service.command.is_empty()) if (service.command.is_empty())
{ {
do_log("[init] service file is missing 'Command' or 'Script' entry, aborting!\n"); do_log("[init] service file is missing 'Command' entry, aborting!\n");
return {}; return {};
} }

View File

@ -45,7 +45,7 @@ Result<int> luna_main(int argc, char** argv)
username = name.view(); username = name.view();
} }
execl("/bin/su", "login", "-lp", "--", username.chars(), nullptr); execl("/usr/bin/su", "login", "-lp", "--", username.chars(), nullptr);
perror("su"); perror("su");
return 1; return 1;

View File

@ -8,6 +8,7 @@
#include <os/FileSystem.h> #include <os/FileSystem.h>
#include <os/Mode.h> #include <os/Mode.h>
#include <pwd.h> #include <pwd.h>
#include <unistd.h>
void find_user_and_group(struct stat& st, StringView& owner, StringView& group) void find_user_and_group(struct stat& st, StringView& owner, StringView& group)
{ {
@ -47,18 +48,46 @@ int sort_reverse(const os::Directory::Entry* a, const os::Directory::Entry* b)
return 0; return 0;
} }
static Result<String> entry_join(const Vector<os::Directory::Entry>& vec, StringView delim) #define RESET_COLORS "\x1b[m"
#define SYMLINK_COLOR "\x1b[36m"
#define FILE_COLOR "\x1b[1;32m"
#define DIR_COLOR "\x1b[1;34m"
#define SOCKET_COLOR "\x1b[33m"
#define SPECIAL_COLOR "\x1b[35m"
#define STICKY_COLOR "\x1b[30;1;42m"
#define SETUID_COLOR "\x1b[30;1;41m"
#define EXEC_COLOR "\x1b[1;31m"
static const char* file_type_color(const os::Directory::Entry& entry)
{
if (entry.mode & S_ISVTX) return STICKY_COLOR;
if (entry.mode & S_ISUID || entry.mode & S_ISGID) return SETUID_COLOR;
switch (entry.mode & S_IFMT)
{
case S_IFREG: return entry.mode & S_IXUSR ? EXEC_COLOR : FILE_COLOR;
case S_IFDIR: return DIR_COLOR;
case S_IFLNK: return SYMLINK_COLOR;
case S_IFSOCK: return SOCKET_COLOR;
default: return SPECIAL_COLOR;
}
}
static Result<String> entry_join(const Vector<os::Directory::Entry>& vec, StringView delim, bool colors)
{ {
if (vec.size() == 0) return String {}; if (vec.size() == 0) return String {};
if (vec.size() == 1) return vec[0].name.clone();
StringBuilder sb; StringBuilder sb;
if (colors) TRY(sb.add(StringView { file_type_color(vec[0]) }));
TRY(sb.add(vec[0].name)); TRY(sb.add(vec[0].name));
if (colors) TRY(sb.add(StringView { RESET_COLORS }));
for (usize i = 1; i < vec.size(); i++) for (usize i = 1; i < vec.size(); i++)
{ {
TRY(sb.add(delim)); TRY(sb.add(delim));
if (colors) TRY(sb.add(StringView { file_type_color(vec[i]) }));
TRY(sb.add(vec[i].name)); TRY(sb.add(vec[i].name));
if (colors) TRY(sb.add(StringView { RESET_COLORS }));
} }
return sb.string(); return sb.string();
@ -75,6 +104,7 @@ Result<int> luna_main(int argc, char** argv)
bool follow_symlink_args { false }; bool follow_symlink_args { false };
bool one_per_line { false }; bool one_per_line { false };
bool list_directories { false }; bool list_directories { false };
bool no_colors { false };
StringView sort_type { "name" }; StringView sort_type { "name" };
@ -98,6 +128,8 @@ Result<int> luna_main(int argc, char** argv)
parser.add_switch_argument(list_directories, 'd', "directory"_sv, "list directories instead of their contents"_sv); parser.add_switch_argument(list_directories, 'd', "directory"_sv, "list directories instead of their contents"_sv);
parser.add_value_argument(sort_type, ' ', "sort"_sv, "sort by name, size or time"_sv); parser.add_value_argument(sort_type, ' ', "sort"_sv, "sort by name, size or time"_sv);
parser.add_switch_argument(reverse_sort, 'r', "reverse"_sv, "reverse order while sorting"_sv); parser.add_switch_argument(reverse_sort, 'r', "reverse"_sv, "reverse order while sorting"_sv);
parser.add_switch_argument(no_colors, ' ', "no-colors"_sv,
"disable coloring of output (defaults to true when not in a TTY)"_sv);
parser.parse(argc, argv); parser.parse(argc, argv);
Vector<os::Directory::Entry> files; Vector<os::Directory::Entry> files;
@ -154,11 +186,13 @@ Result<int> luna_main(int argc, char** argv)
if (!long_list) if (!long_list)
{ {
auto list = TRY(entry_join(files, one_per_line ? "\n"_sv : " "_sv)); auto list = TRY(entry_join(files, one_per_line ? "\n"_sv : " "_sv, !no_colors && isatty(STDIN_FILENO)));
if (!list.is_empty()) os::println("%s", list.chars()); if (!list.is_empty()) os::println("%s", list.chars());
} }
else else
{ {
bool colors = !no_colors && isatty(STDIN_FILENO);
for (const auto& file : files) for (const auto& file : files)
{ {
struct stat st; struct stat st;
@ -176,14 +210,32 @@ Result<int> luna_main(int argc, char** argv)
if (!human_readable && !si) if (!human_readable && !si)
{ {
os::println("%s %u %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(), if (colors)
st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); {
os::println("%s %u %4s %4s %10lu %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
formatted_mode, st.st_nlink, owner.chars(), group.chars(), st.st_size,
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
else
{
os::println("%s %u %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
} }
else else
{ {
auto size = TRY(to_dynamic_unit(st.st_size, 10, false, si ? Unit::SI : Unit::Binary, false)); auto size = TRY(to_dynamic_unit(st.st_size, 10, false, si ? Unit::SI : Unit::Binary, false));
os::println("%s %u %4s %4s %6s %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(), if (colors)
size.chars(), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); {
os::println("%s %u %4s %4s %6s %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
formatted_mode, st.st_nlink, owner.chars(), group.chars(), size.chars(),
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
else
{
os::println("%s %u %4s %4s %6s %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
size.chars(), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
} }
} }
} }

View File

@ -72,10 +72,9 @@ int main()
// Now, mount the /dev file system on the new root. // Now, mount the /dev file system on the new root.
mount_devfs(); mount_devfs();
setenv("PATH", "/sbin:/usr/bin", 1); char* argv[] = { "/usr/bin/init", nullptr };
char* argv[] = { "init", nullptr };
char* envp[] = { nullptr }; char* envp[] = { nullptr };
execvpe(argv[0], argv, envp); execve(argv[0], argv, envp);
fail_errno("Failed to execute init"); fail_errno("Failed to execute init");

View File

@ -1,10 +1,35 @@
#include <os/ArgumentParser.h> #include <os/ArgumentParser.h>
#include <os/Directory.h>
#include <os/File.h>
#include <os/FileSystem.h> #include <os/FileSystem.h>
Result<void> remove_wrapper(const os::Path& path, bool verbose)
{
TRY(os::FileSystem::remove(path));
if (verbose) os::println("removed '%s'", path.name().chars());
return {};
}
Result<void> remove_tree(const os::Path& path, bool verbose)
{
auto rc = remove_wrapper(path, verbose);
if (!rc.has_error()) return {};
if (rc.error() != ENOTEMPTY) return rc.release_error();
auto dir = TRY(os::Directory::open(path));
Vector<String> entries = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));
for (const auto& entry : entries) { TRY(remove_tree({ dir->fd(), entry.view() }, verbose)); }
return remove_wrapper(path, verbose);
}
Result<int> luna_main(int argc, char** argv) Result<int> luna_main(int argc, char** argv)
{ {
StringView path; StringView path;
bool recursive; bool recursive;
bool verbose;
os::ArgumentParser parser; os::ArgumentParser parser;
parser.add_description("Remove a path from the file system."_sv); parser.add_description("Remove a path from the file system."_sv);
@ -12,11 +37,12 @@ Result<int> luna_main(int argc, char** argv)
parser.add_positional_argument(path, "path"_sv, true); parser.add_positional_argument(path, "path"_sv, true);
parser.add_switch_argument(recursive, 'r', "recursive"_sv, parser.add_switch_argument(recursive, 'r', "recursive"_sv,
"remove a directory recursively (by default, rm removes only empty directories)"_sv); "remove a directory recursively (by default, rm removes only empty directories)"_sv);
parser.add_switch_argument(verbose, 'v', "verbose"_sv, "log every removed file and directory"_sv);
parser.parse(argc, argv); parser.parse(argc, argv);
if (!recursive) TRY(os::FileSystem::remove(path)); if (!recursive) TRY(remove_wrapper(path, verbose));
else else
TRY(os::FileSystem::remove_tree(path)); TRY(remove_tree(path, verbose));
return 0; return 0;
} }

View File

@ -127,7 +127,7 @@ Result<int> luna_main(int argc, char** argv)
{ {
chdir(entry->pw_dir); chdir(entry->pw_dir);
clearenv(); clearenv();
setenv("PATH", "/bin:/sbin", 1); setenv("PATH", "/usr/bin:/usr/local/bin", 1);
setpgid(0, 0); setpgid(0, 0);
} }

49
apps/taskbar.cpp Normal file
View File

@ -0,0 +1,49 @@
#include <os/Process.h>
#include <signal.h>
#include <sys/wait.h>
#include <ui/App.h>
#include <ui/Button.h>
#include <ui/Container.h>
#include <ui/Image.h>
#include <ui/Layout.h>
void sigchld_handler(int)
{
wait(nullptr);
}
Result<int> luna_main(int argc, char** argv)
{
ui::App app;
TRY(app.init(argc, argv));
signal(SIGCHLD, sigchld_handler);
ui::Rect screen = app.screen_rect();
ui::Rect bar = ui::Rect { ui::Point { 0, screen.height - 50 }, screen.width, 50 };
auto window = TRY(ui::Window::create(bar, false));
app.set_main_window(window);
window->set_background(ui::GRAY);
ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No);
window->set_main_widget(layout);
ui::Button button({ 0, 0, 50, 50 });
layout.add_widget(button);
ui::Container container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
button.set_widget(container);
button.set_action([] {
StringView args[] = { "/usr/bin/gclient" };
os::Process::spawn("/usr/bin/gclient", Slice<StringView> { args, 1 }, false);
});
auto image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga"));
container.set_widget(*image);
window->draw();
return app.run();
}

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

View File

@ -1,2 +1,2 @@
root:toor:0:0:Administrator:/:/bin/sh root:toor:0:0:Administrator:/:/usr/bin/sh
selene:moon:1000:1000:User:/home/selene:/bin/sh selene:moon:1000:1000:User:/home/selene:/usr/bin/sh

4
base/etc/user/00-taskbar Normal file
View File

@ -0,0 +1,4 @@
Name=taskbar
Description=Start the taskbar.
Command=/usr/bin/taskbar
Restart=true

3
base/etc/user/01-gclient Normal file
View File

@ -0,0 +1,3 @@
Name=gclient
Description=Sample user application.
Command=/usr/bin/gclient

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -161,7 +161,11 @@ void io_thread()
static void timer_interrupt(Registers* regs, void*) static void timer_interrupt(Registers* regs, void*)
{ {
Timer::tick(); Timer::tick();
if (should_invoke_scheduler()) Scheduler::invoke(regs); if (should_invoke_scheduler())
{
Scheduler::invoke(regs);
TextConsole::tick_cursor();
}
} }
static void keyboard_interrupt(Registers*, void*) static void keyboard_interrupt(Registers*, void*)

View File

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

View File

@ -246,6 +246,9 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
} }
case TTYSETGFX: { case TTYSETGFX: {
s_is_in_graphical_mode = (bool)arg; s_is_in_graphical_mode = (bool)arg;
if (!s_is_in_graphical_mode) TextConsole::enable_cursor();
else
TextConsole::disable_cursor();
return 0; return 0;
} }
default: return err(EINVAL); default: return err(EINVAL);

View File

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

View File

@ -17,6 +17,21 @@ class UnixSocket : public Socket
return (m_state == Connected || m_state == Reset) && !m_data.size(); 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();
}
bool peer_disconnected() const override
{
return m_state == Reset;
}
Result<usize> send(const u8*, usize, int) override; Result<usize> send(const u8*, usize, int) override;
Result<usize> recv(u8*, usize, int) const override; Result<usize> recv(u8*, usize, int) const override;

View File

@ -59,6 +59,8 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
Vector<String> envp; Vector<String> envp;
if (args[2]) envp = TRY(copy_string_vector_from_userspace(args[2])); if (args[2]) envp = TRY(copy_string_vector_from_userspace(args[2]));
String cmdline = TRY(String::join(argv, " "));
if ((calculate_userspace_stack_size(argv) + calculate_userspace_stack_size(envp)) > MAX_ARGV_STACK_SIZE) if ((calculate_userspace_stack_size(argv) + calculate_userspace_stack_size(envp)) > MAX_ARGV_STACK_SIZE)
return err(E2BIG); return err(E2BIG);
@ -115,7 +117,7 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
if (is_setuid) current->auth.euid = current->auth.suid = inode->metadata().uid; if (is_setuid) current->auth.euid = current->auth.suid = inode->metadata().uid;
if (is_setgid) current->auth.egid = current->auth.sgid = inode->metadata().gid; if (is_setgid) current->auth.egid = current->auth.sgid = inode->metadata().gid;
current->name = path.chars(); current->cmdline = cmdline.chars();
image->apply(current); image->apply(current);
@ -159,7 +161,7 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
thread->state = ThreadState::Runnable; thread->state = ThreadState::Runnable;
thread->is_kernel = false; thread->is_kernel = false;
thread->fp_data.save(); thread->fp_data.save();
thread->name = current->name; thread->cmdline = current->cmdline;
thread->auth = current->auth; thread->auth = current->auth;
thread->current_directory = current->current_directory; thread->current_directory = current->current_directory;
thread->current_directory_path = move(current_directory_path); thread->current_directory_path = move(current_directory_path);

View File

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

View File

@ -35,7 +35,7 @@ Result<u64> sys_pstat(Registers*, SyscallArgs args)
set_timespec(proc.ps_time, thread->user_ticks_self + thread->kernel_ticks_self); set_timespec(proc.ps_time, thread->user_ticks_self + thread->kernel_ticks_self);
set_timespec(proc.ps_ktime, thread->kernel_ticks_self); set_timespec(proc.ps_ktime, thread->kernel_ticks_self);
set_timespec(proc.ps_utime, thread->kernel_ticks_children); set_timespec(proc.ps_utime, thread->kernel_ticks_children);
strlcpy(proc.ps_name, thread->name.chars(), sizeof(proc.ps_name)); strlcpy(proc.ps_name, thread->cmdline.chars(), sizeof(proc.ps_name));
strlcpy(proc.ps_cwd, thread->current_directory_path.is_empty() ? "/" : thread->current_directory_path.chars(), strlcpy(proc.ps_cwd, thread->current_directory_path.is_empty() ? "/" : thread->current_directory_path.chars(),
sizeof(proc.ps_cwd)); sizeof(proc.ps_cwd));

View File

@ -27,7 +27,7 @@ namespace Scheduler
g_idle.state = ThreadState::Idle; g_idle.state = ThreadState::Idle;
g_idle.is_kernel = true; g_idle.is_kernel = true;
g_idle.parent = nullptr; g_idle.parent = nullptr;
g_idle.name = "[idle]"; g_idle.cmdline = "[idle]";
g_idle.active_directory = nullptr; g_idle.active_directory = nullptr;
g_idle.ticks_left = 1; g_idle.ticks_left = 1;
@ -96,7 +96,7 @@ namespace Scheduler
thread->stack = thread_stack; thread->stack = thread_stack;
thread->name = name; thread->cmdline = name;
thread->is_kernel = true; thread->is_kernel = true;
thread->active_directory = MMU::kernel_page_directory(); thread->active_directory = MMU::kernel_page_directory();
@ -148,7 +148,7 @@ namespace Scheduler
thread->is_kernel = false; thread->is_kernel = false;
thread->id = 1; thread->id = 1;
thread->pgid = 1; thread->pgid = 1;
thread->name = name; thread->cmdline = name;
thread->auth = Credentials { .uid = 0, .euid = 0, .suid = 0, .gid = 0, .egid = 0, .sgid = 0 }; thread->auth = Credentials { .uid = 0, .euid = 0, .suid = 0, .gid = 0, .egid = 0, .sgid = 0 };
Vector<String> args; Vector<String> args;
@ -374,7 +374,7 @@ namespace Scheduler
{ {
kdbgln("%p %c [%-20s] %4d, parent = (%-18p,%d), state = %d, ticks: (k:%04zu,u:%04zu), status = " kdbgln("%p %c [%-20s] %4d, parent = (%-18p,%d), state = %d, ticks: (k:%04zu,u:%04zu), status = "
"%d, cwd = %s", "%d, cwd = %s",
thread, thread->is_kernel ? 'k' : 'u', thread->name.chars(), thread->id, thread->parent, thread, thread->is_kernel ? 'k' : 'u', thread->cmdline.chars(), thread->id, thread->parent,
thread->parent ? thread->parent->id : 0, (int)thread->state, thread->kernel_ticks_self, thread->parent ? thread->parent->id : 0, (int)thread->state, thread->kernel_ticks_self,
thread->user_ticks_self, thread->status, thread->user_ticks_self, thread->status,
thread->current_directory_path.is_empty() ? "/" : thread->current_directory_path.chars()); thread->current_directory_path.is_empty() ? "/" : thread->current_directory_path.chars());

View File

@ -123,7 +123,7 @@ struct Thread : public LinkedListNode<Thread>
mode_t umask { 0 }; mode_t umask { 0 };
StaticString<128> name; StaticString<128> cmdline;
String current_directory_path = {}; String current_directory_path = {};
SharedPtr<VFS::Inode> current_directory = {}; SharedPtr<VFS::Inode> current_directory = {};

View File

@ -3,6 +3,7 @@
#include "video/Framebuffer.h" #include "video/Framebuffer.h"
#include <luna/CString.h> #include <luna/CString.h>
#include <luna/CType.h> #include <luna/CType.h>
#include <luna/EscapeSequence.h>
#include <luna/Format.h> #include <luna/Format.h>
#include <luna/Result.h> #include <luna/Result.h>
#include <luna/ScopeGuard.h> #include <luna/ScopeGuard.h>
@ -13,20 +14,48 @@ extern const BOOTBOOT bootboot;
#include "video/BuiltinFont.h" #include "video/BuiltinFont.h"
static constexpr u32 BLACK = 0xff000000; // Default text color.
static constexpr u32 WHITE = 0xffffffff; static constexpr u32 WHITE = 0xffffffff;
// xterm color palette.
static constexpr u32 BLACK = 0xff000000;
static constexpr u32 RED = 0xffcd0000;
static constexpr u32 GREEN = 0xff00cd00;
static constexpr u32 YELLOW = 0xffcdcd00;
static constexpr u32 BLUE = 0xff0000ee;
static constexpr u32 MAGENTA = 0xffcd00cd;
static constexpr u32 CYAN = 0xff00cdcd;
static constexpr u32 GRAY = 0xffe5e5e5;
static constexpr u32 BRIGHT_BLACK = 0xff7f7f7f;
static constexpr u32 BRIGHT_RED = 0xffff0000;
static constexpr u32 BRIGHT_GREEN = 0xff00ff00;
static constexpr u32 BRIGHT_YELLOW = 0xffffff00;
static constexpr u32 BRIGHT_BLUE = 0xff5c5cff;
static constexpr u32 BRIGHT_MAGENTA = 0xffff00ff;
static constexpr u32 BRIGHT_CYAN = 0xff00ffff;
static constexpr u32 BRIGHT_GRAY = 0xffffffff;
static u32 g_background_color = BLACK; static u32 g_background_color = BLACK;
static u32 g_foreground_color = WHITE; static u32 g_foreground_color = WHITE;
static constexpr u32 FONT_HEIGHT = 16; static constexpr u32 FONT_HEIGHT = 16;
static constexpr u32 FONT_WIDTH = 8; static constexpr u32 FONT_WIDTH = 8;
static bool bold = false;
static u32 g_x_position = 0; static u32 g_x_position = 0;
static u32 g_y_position = 0; static u32 g_y_position = 0;
static constexpr int CURSOR_TIMEOUT = 500;
static int current_cursor_timeout = CURSOR_TIMEOUT;
static bool cursor_activated = true;
static bool cursor_enabled = true;
static Utf8StateDecoder utf8_decoder; static Utf8StateDecoder utf8_decoder;
static Option<EscapeSequenceParser> escape_sequence_parser;
static void putwchar_at(wchar_t c, u32 x, u32 y) static void putwchar_at(wchar_t c, u32 x, u32 y)
{ {
const u8* glyph = &font[c * 16]; const u8* glyph = &font[c * 16];
@ -58,7 +87,7 @@ static void scroll()
static bool should_scroll() static bool should_scroll()
{ {
return (g_y_position + FONT_HEIGHT) >= Framebuffer::height(); return g_y_position >= Framebuffer::height();
} }
static void next_line() static void next_line()
@ -82,11 +111,216 @@ static void erase_current_char()
Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, BLACK); Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, BLACK);
} }
static void draw_cursor()
{
Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, WHITE);
}
static bool at_end_of_screen() static bool at_end_of_screen()
{ {
return (g_x_position + FONT_WIDTH) > Framebuffer::width(); return (g_x_position + FONT_WIDTH) > Framebuffer::width();
} }
static bool handle_escape_sequence(wchar_t c)
{
auto rc = escape_sequence_parser->advance(static_cast<u8>(c));
if (rc.has_error())
{
escape_sequence_parser = Option<EscapeSequenceParser> {};
return false;
}
if (!rc.value()) return true;
if (!escape_sequence_parser->valid())
{
escape_sequence_parser = Option<EscapeSequenceParser> {};
return false;
}
const auto& params = escape_sequence_parser->parameters();
switch (escape_sequence_parser->code())
{
case EscapeCode::CursorUp: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if ((u32)pixels > g_y_position) g_y_position = 0;
else
g_y_position -= pixels;
};
break;
case EscapeCode::CursorDown: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
else
g_y_position += pixels;
};
break;
case EscapeCode::CursorBack: {
int chars = params.size() ? params[0] : 1;
int pixels = chars * FONT_WIDTH;
if ((u32)pixels > g_x_position) g_x_position = 0;
else
g_x_position -= pixels;
};
break;
case EscapeCode::CursorForward: {
int chars = params.size() ? params[0] : 1;
int pixels = chars * FONT_WIDTH;
if (pixels + g_x_position >= Framebuffer::width()) g_x_position = Framebuffer::width() - FONT_WIDTH;
else
g_x_position += pixels;
};
break;
case EscapeCode::CursorNextLine: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if ((u32)pixels > g_y_position) g_y_position = 0;
else
g_y_position -= pixels;
g_x_position = 0;
};
break;
case EscapeCode::CursorPreviousLine: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
else
g_y_position += pixels;
g_x_position = 0;
};
break;
case EscapeCode::CursorHorizontalAbsolute: {
int line = (params.size() ? params[0] : 1) - 1;
if (line < 0) break;
u32 position = line * FONT_HEIGHT;
if (position >= Framebuffer::height()) position = Framebuffer::height() - FONT_HEIGHT;
g_y_position = position;
};
break;
case EscapeCode::SetCursorPosition: {
int x = (params.size() ? params[0] : 1) - 1;
int y = (params.size() > 1 ? params[1] : 1) - 1;
if (x < 0 || y < 0) break;
u32 x_position = x * FONT_WIDTH;
if (x_position >= Framebuffer::width()) x_position = Framebuffer::width() - FONT_HEIGHT;
g_x_position = x_position;
u32 y_position = y * FONT_HEIGHT;
if (y_position >= Framebuffer::height()) y_position = Framebuffer::height() - FONT_HEIGHT;
g_y_position = y_position;
};
break;
case EscapeCode::SelectGraphicRendition: {
if (!params.size())
{
g_foreground_color = WHITE;
g_background_color = BLACK;
bold = false;
break;
}
for (usize i = 0; i < params.size(); i++)
{
int arg = params[i];
switch (arg)
{
case 0: {
g_foreground_color = BLACK;
g_background_color = WHITE;
bold = false;
break;
}
case 1: {
bold = true;
break;
}
case 22: {
bold = false;
break;
}
case 30: {
g_foreground_color = bold ? BRIGHT_BLACK : BLACK;
break;
}
case 31: {
g_foreground_color = bold ? BRIGHT_RED : RED;
break;
}
case 32: {
g_foreground_color = bold ? BRIGHT_GREEN : GREEN;
break;
}
case 33: {
g_foreground_color = bold ? BRIGHT_YELLOW : YELLOW;
break;
}
case 34: {
g_foreground_color = bold ? BRIGHT_BLUE : BLUE;
break;
}
case 35: {
g_foreground_color = bold ? BRIGHT_MAGENTA : MAGENTA;
break;
}
case 36: {
g_foreground_color = bold ? BRIGHT_CYAN : CYAN;
break;
}
case 37: {
g_foreground_color = bold ? BRIGHT_GRAY : GRAY;
break;
}
case 39: {
g_foreground_color = WHITE;
break;
}
case 40: {
g_background_color = bold ? BRIGHT_BLACK : BLACK;
break;
}
case 41: {
g_background_color = bold ? BRIGHT_RED : RED;
break;
}
case 42: {
g_background_color = bold ? BRIGHT_GREEN : GREEN;
break;
}
case 43: {
g_background_color = bold ? BRIGHT_YELLOW : YELLOW;
break;
}
case 44: {
g_background_color = bold ? BRIGHT_BLUE : BLUE;
break;
}
case 45: {
g_background_color = bold ? BRIGHT_MAGENTA : MAGENTA;
break;
}
case 46: {
g_background_color = bold ? BRIGHT_CYAN : CYAN;
break;
}
case 47: {
g_background_color = bold ? BRIGHT_GRAY : GRAY;
break;
}
case 49: {
g_background_color = BLACK;
break;
}
default: break;
}
}
}
break;
default: break;
}
escape_sequence_parser = Option<EscapeSequenceParser> {};
return true;
}
namespace TextConsole namespace TextConsole
{ {
void putwchar(wchar_t c) void putwchar(wchar_t c)
@ -94,6 +328,16 @@ namespace TextConsole
// Unprintable (not in the built-in font) characters get represented as a box // Unprintable (not in the built-in font) characters get represented as a box
if (c > (wchar_t)255) c = (wchar_t)256; if (c > (wchar_t)255) c = (wchar_t)256;
if (escape_sequence_parser.has_value())
{
if (handle_escape_sequence(c)) return;
}
// Erase the current cursor.
if (cursor_enabled) erase_current_char();
bool should_draw_cursor = cursor_enabled;
switch (c) switch (c)
{ {
case L'\n': { case L'\n': {
@ -113,6 +357,13 @@ namespace TextConsole
erase_current_char(); erase_current_char();
} }
break; break;
case L'\x1b':
case L'\x9b':
case L'\x90':
case L'\x9d':
escape_sequence_parser = EscapeSequenceParser { (u8)c };
should_draw_cursor = false;
break;
default: { default: {
if (_iscntrl(c)) return; if (_iscntrl(c)) return;
putwchar_at(c, g_x_position, g_y_position); putwchar_at(c, g_x_position, g_y_position);
@ -125,6 +376,42 @@ namespace TextConsole
break; break;
} }
} }
if (should_draw_cursor)
{
current_cursor_timeout = CURSOR_TIMEOUT;
cursor_activated = true;
draw_cursor();
}
}
void tick_cursor()
{
if (!cursor_enabled) return;
current_cursor_timeout--;
if (current_cursor_timeout == 0)
{
current_cursor_timeout = CURSOR_TIMEOUT;
cursor_activated = !cursor_activated;
if (cursor_activated) draw_cursor();
else
erase_current_char();
}
}
void disable_cursor()
{
cursor_enabled = false;
}
void enable_cursor()
{
cursor_enabled = true;
cursor_activated = true;
current_cursor_timeout = CURSOR_TIMEOUT;
draw_cursor();
} }
Result<void> putchar(char c) Result<void> putchar(char c)

View File

@ -19,6 +19,9 @@ namespace TextConsole
Result<void> println(const char* str); Result<void> println(const char* str);
void wprintln(const wchar_t* str); void wprintln(const wchar_t* str);
Result<usize> printf(const char* format, ...) _format(1, 2); Result<usize> printf(const char* format, ...) _format(1, 2);
void tick_cursor();
void disable_cursor();
void enable_cursor();
u16 rows(); u16 rows();
u16 cols(); u16 cols();

View File

@ -20,7 +20,6 @@ set(SOURCES
src/pwd.cpp src/pwd.cpp
src/grp.cpp src/grp.cpp
src/locale.cpp src/locale.cpp
src/scanf.cpp
src/signal.cpp src/signal.cpp
src/termios.cpp src/termios.cpp
src/utime.cpp src/utime.cpp

View File

@ -8,6 +8,7 @@
#define POLLIN (1 << 0) #define POLLIN (1 << 0)
#define POLLERR (1 << 1) #define POLLERR (1 << 1)
#define POLLNVAL (1 << 2) #define POLLNVAL (1 << 2)
#define POLLHUP (1 << 3)
typedef __u64_t nfds_t; typedef __u64_t nfds_t;

View File

@ -1,263 +0,0 @@
#include <errno.h>
#include <luna/CType.h>
#include <luna/NumberParsing.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FLAG_DISCARD (1 << 0)
#define FLAG_ALLOC (1 << 1)
#define FLAG_WIDTH (1 << 2)
#define FLAG_LONG (1 << 3)
#define FLAG_LONG_LONG (1 << 4)
#define FLAG_SHORT (1 << 5)
#define FLAG_CHAR (1 << 6)
static int parse_flags(const char** format)
{
int result = 0;
while (true)
{
switch (**format)
{
case '*':
result |= FLAG_DISCARD;
(*format)++;
break;
case 'm':
result |= FLAG_ALLOC;
(*format)++;
break;
default: return result;
}
}
}
static size_t parse_width(const char** format, int& flags)
{
size_t result = 0;
if (_isdigit(**format))
{
result = scan_unsigned_integer(format);
flags |= FLAG_WIDTH;
}
return result;
}
static void parse_type(const char** format, int& flags)
{
// FIXME: Support %j (intmax_t/uintmax_t)
switch (**format)
{
case 'h':
flags |= FLAG_SHORT;
(*format)++;
if (**format == 'h')
{
flags |= FLAG_CHAR;
(*format)++;
}
break;
case 'l':
flags |= FLAG_LONG;
(*format)++;
if (**format == 'l')
{
flags |= FLAG_LONG_LONG;
(*format)++;
}
break;
case 't':
flags |= (sizeof(ptrdiff_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
case 'z':
flags |= (sizeof(size_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
default: break;
}
}
static void write_parsed_signed_integer(ssize_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, signed long long*) = (signed long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, signed long*) = (signed long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, signed int*) = (signed short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, signed int*) = (signed char)value;
else
*va_arg(ap, signed int*) = (signed int)value;
}
static void write_parsed_unsigned_integer(size_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, unsigned long long*) = (unsigned long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, unsigned long*) = (unsigned long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, unsigned int*) = (unsigned short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, unsigned int*) = (unsigned char)value;
else
*va_arg(ap, unsigned int*) = (unsigned int)value;
}
#define WHITESPACE_CHARACTERS " \t\f\r\n\v"
static void skip_whitespace(const char** str)
{
*str += strspn(*str, WHITESPACE_CHARACTERS);
}
extern "C"
{
int vsscanf(const char* str, const char* format, va_list ap)
{
int parsed = 0;
const char* s = str; // Keep a pointer to the beginning of the string for %n
if (*str == 0) return EOF;
while (*format)
{
if (*format != '%')
{
normal:
if (!_isspace(*format))
{
if (*str != *format) return parsed;
str++;
format++;
if (*str == 0) return parsed;
continue;
}
skip_whitespace(&format);
skip_whitespace(&str);
if (*str == 0) return parsed;
continue;
}
else
{
format++;
if (*format == '%')
{
skip_whitespace(&str);
goto normal;
}
int flags = parse_flags(&format);
size_t width = parse_width(&format, flags);
parse_type(&format, flags);
char specifier = *format++;
if (!specifier) return parsed;
switch (specifier)
{
case 's': {
skip_whitespace(&str);
size_t chars = strcspn(str, WHITESPACE_CHARACTERS);
if (!chars) return parsed;
if ((flags & FLAG_WIDTH) && chars > width) chars = width;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc(chars + 1);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, chars);
ptr[chars] = 0;
}
str += chars;
parsed++;
break;
}
case 'c': {
if (strlen(str) < width) return parsed;
if (!(flags & FLAG_WIDTH)) width = 1;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc(width);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, width);
}
str += width;
parsed++;
break;
}
case 'd': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'i': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 0);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'o': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 8);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'u': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'X':
case 'x': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'p': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) *va_arg(ap, void**) = (void*)value;
parsed++;
break;
}
case 'n': {
if (!(flags & FLAG_DISCARD)) *va_arg(ap, int*) = (int)(str - s);
break;
}
default: {
fprintf(stderr, "vsscanf: unknown conversion specifier: %%%c\n", specifier);
return parsed;
}
}
}
}
return parsed;
}
}

View File

@ -2,6 +2,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <luna/Common.h> #include <luna/Common.h>
#include <luna/Format.h> #include <luna/Format.h>
#include <luna/Scanf.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -618,12 +619,17 @@ extern "C"
return rc; return rc;
} }
int vsscanf(const char* str, const char* format, va_list ap)
{
return scanf_impl(str, format, ap);
}
int sscanf(const char* str, const char* format, ...) int sscanf(const char* str, const char* format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
int rc = vsscanf(str, format, ap); int rc = scanf_impl(str, format, ap);
va_end(ap); va_end(ap);
@ -634,7 +640,7 @@ extern "C"
{ {
char buf[BUFSIZ]; char buf[BUFSIZ];
if (!fgets(buf, sizeof(buf), stream)) return EOF; if (!fgets(buf, sizeof(buf), stream)) return EOF;
return vsscanf(buf, format, ap); return scanf_impl(buf, format, ap);
} }
int fscanf(FILE* stream, const char* format, ...) int fscanf(FILE* stream, const char* format, ...)

View File

@ -23,7 +23,7 @@ static Result<int> try_execvpe(const char* name, char* const* argv, char* const*
if (strchr(name, '/')) return execve(name, argv, envp); if (strchr(name, '/')) return execve(name, argv, envp);
char* path = getenv("PATH"); char* path = getenv("PATH");
if (!path) path = const_cast<char*>("/bin:/sbin"); if (!path) path = const_cast<char*>("/usr/bin:/usr/local/bin");
Vector<String> paths = TRY(StringView { path }.split(":")); Vector<String> paths = TRY(StringView { path }.split(":"));

View File

@ -5,6 +5,7 @@ file(GLOB HEADERS include/luna/*.h)
set(FREESTANDING_SOURCES set(FREESTANDING_SOURCES
${HEADERS} ${HEADERS}
src/CRC32.cpp src/CRC32.cpp
src/EscapeSequence.cpp
src/Format.cpp src/Format.cpp
src/Sort.cpp src/Sort.cpp
src/NumberParsing.cpp src/NumberParsing.cpp
@ -14,6 +15,7 @@ set(FREESTANDING_SOURCES
src/SystemError.cpp src/SystemError.cpp
src/Bitmap.cpp src/Bitmap.cpp
src/Buffer.cpp src/Buffer.cpp
src/Scanf.cpp
src/Stack.cpp src/Stack.cpp
src/String.cpp src/String.cpp
src/StringBuilder.cpp src/StringBuilder.cpp

View File

@ -23,6 +23,9 @@ class Buffer
Buffer(Buffer&& other); Buffer(Buffer&& other);
Buffer(const Buffer& other) = delete; // For now. Buffer(const Buffer& other) = delete; // For now.
Buffer& operator=(Buffer&&);
Buffer& operator=(const Buffer&) = delete;
/** /**
* @brief Create a Buffer object, allocating a specific amount of memory for it. * @brief Create a Buffer object, allocating a specific amount of memory for it.
* *

View File

@ -37,7 +37,7 @@ template <typename T, usize Size> class CircularQueue
* @return true The queue is empty. * @return true The queue is empty.
* @return false The queue is not empty. * @return false The queue is not empty.
*/ */
bool is_empty() bool is_empty() const
{ {
return m_tail.load() == m_head.load(); return m_tail.load() == m_head.load();
} }
@ -124,7 +124,7 @@ template <typename T> class DynamicCircularQueue
* @return true The queue is empty. * @return true The queue is empty.
* @return false The queue is not empty. * @return false The queue is not empty.
*/ */
bool is_empty() bool is_empty() const
{ {
return m_tail.load() == m_head.load(); return m_tail.load() == m_head.load();
} }

View File

@ -0,0 +1,67 @@
/**
* @file EscapeSequence.h
* @author apio (cloudapio.eu)
* @brief ANSI escape sequence parsing.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Vector.h>
enum class EscapeCode
{
SaveCursor,
RestoreCursor,
CursorUp,
CursorDown,
CursorForward,
CursorBack,
CursorNextLine,
CursorPreviousLine,
CursorHorizontalAbsolute,
SetCursorPosition,
SelectGraphicRendition,
};
class EscapeSequenceParser
{
public:
EscapeSequenceParser(u8 begin);
Result<bool> advance(u8 byte);
bool valid() const
{
return m_valid;
}
const Vector<int>& parameters() const
{
return m_parameters;
}
EscapeCode code() const
{
return m_escape_code;
}
private:
enum class SequenceType
{
ESC,
CSI,
DCS,
OSC,
};
Vector<u8> m_parameter;
Vector<int> m_parameters;
SequenceType m_sequence_type;
bool m_parsing_parameter { false };
bool m_valid { false };
EscapeCode m_escape_code;
};

View File

@ -0,0 +1,21 @@
/**
* @file Scanf.h
* @author apio (cloudapio.eu)
* @brief Scanf implementation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <stdarg.h>
/**
* @brief scanf() implementation.
*
* @param str The string to read input from.
* @param format The format string.
* @param ap The variadic argument list.
* @return int The number of arguments read, or -1 if the string was empty.
*/
int scanf_impl(const char* str, const char* format, va_list ap);

View File

@ -24,6 +24,16 @@ Buffer::Buffer(Buffer&& other) : m_data(other.data()), m_size(other.size())
other.m_data = nullptr; 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() Buffer::~Buffer()
{ {
if (m_data) free_impl(m_data); if (m_data) free_impl(m_data);

View File

@ -0,0 +1,146 @@
/**
* @file EscapeSequence.cpp
* @author apio (cloudapio.eu)
* @brief ANSI escape sequence parsing.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/CType.h>
#include <luna/Check.h>
#include <luna/EscapeSequence.h>
#include <luna/NumberParsing.h>
EscapeSequenceParser::EscapeSequenceParser(u8 begin)
{
switch (begin)
{
case 0x1b: m_sequence_type = SequenceType::ESC; break;
case 0x9b: m_sequence_type = SequenceType::CSI; break;
case 0x90: m_sequence_type = SequenceType::DCS; break;
case 0x9d: m_sequence_type = SequenceType::OSC; break;
default: fail("Unrecognized escape sequence type");
}
}
Result<bool> EscapeSequenceParser::advance(u8 byte)
{
switch (m_sequence_type)
{
case SequenceType::ESC: {
switch (byte)
{
case '[': {
m_sequence_type = SequenceType::CSI;
return false;
};
case 'P': {
m_sequence_type = SequenceType::DCS;
return false;
};
case ']': {
m_sequence_type = SequenceType::OSC;
return false;
};
case '7': {
m_escape_code = EscapeCode::SaveCursor;
m_valid = true;
return true;
};
case '8': {
m_escape_code = EscapeCode::RestoreCursor;
m_valid = true;
return true;
};
default: {
m_valid = false;
return true;
}
}
};
break;
case SequenceType::CSI: {
if (_isdigit(byte))
{
m_parsing_parameter = true;
TRY(m_parameter.try_append(byte));
return false;
}
if (!m_parsing_parameter && byte == ';')
{
TRY(m_parameters.try_append(0));
return false;
}
if (m_parsing_parameter)
{
TRY(m_parameter.try_append(0));
int value = static_cast<int>(parse_unsigned_integer((const char*)m_parameter.data(), nullptr, 10));
m_parameter.clear();
TRY(m_parameters.try_append(value));
}
switch (byte)
{
case 'A': {
m_escape_code = EscapeCode::CursorUp;
m_valid = true;
return true;
};
case 'B': {
m_escape_code = EscapeCode::CursorDown;
m_valid = true;
return true;
};
case 'C': {
m_escape_code = EscapeCode::CursorForward;
m_valid = true;
return true;
};
case 'D': {
m_escape_code = EscapeCode::CursorBack;
m_valid = true;
return true;
};
case 'E': {
m_escape_code = EscapeCode::CursorNextLine;
m_valid = true;
return true;
};
case 'F': {
m_escape_code = EscapeCode::CursorPreviousLine;
m_valid = true;
return true;
};
case 'G': {
m_escape_code = EscapeCode::CursorHorizontalAbsolute;
m_valid = true;
return true;
};
case 'H': {
m_escape_code = EscapeCode::SetCursorPosition;
m_valid = true;
return true;
};
case 'm': {
m_escape_code = EscapeCode::SelectGraphicRendition;
m_valid = true;
return true;
};
case ';': {
return false;
};
default: {
m_valid = false;
return true;
}
}
};
break;
case SequenceType::DCS: todo();
case SequenceType::OSC: todo();
default: todo();
}
}

270
libluna/src/Scanf.cpp Normal file
View File

@ -0,0 +1,270 @@
/**
* @file Scanf.cpp
* @author apio (cloudapio.eu)
* @brief Scanf implementation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/CString.h>
#include <luna/CType.h>
#include <luna/DebugLog.h>
#include <luna/Heap.h>
#include <luna/NumberParsing.h>
#include <luna/SystemError.h>
#include <sys/types.h>
#define FLAG_DISCARD (1 << 0)
#define FLAG_ALLOC (1 << 1)
#define FLAG_WIDTH (1 << 2)
#define FLAG_LONG (1 << 3)
#define FLAG_LONG_LONG (1 << 4)
#define FLAG_SHORT (1 << 5)
#define FLAG_CHAR (1 << 6)
static int parse_flags(const char** format)
{
int result = 0;
while (true)
{
switch (**format)
{
case '*':
result |= FLAG_DISCARD;
(*format)++;
break;
case 'm':
result |= FLAG_ALLOC;
(*format)++;
break;
default: return result;
}
}
}
static size_t parse_width(const char** format, int& flags)
{
size_t result = 0;
if (_isdigit(**format))
{
result = scan_unsigned_integer(format);
flags |= FLAG_WIDTH;
}
return result;
}
static void parse_type(const char** format, int& flags)
{
// FIXME: Support %j (intmax_t/uintmax_t)
switch (**format)
{
case 'h':
flags |= FLAG_SHORT;
(*format)++;
if (**format == 'h')
{
flags |= FLAG_CHAR;
(*format)++;
}
break;
case 'l':
flags |= FLAG_LONG;
(*format)++;
if (**format == 'l')
{
flags |= FLAG_LONG_LONG;
(*format)++;
}
break;
case 't':
flags |= (sizeof(ptrdiff_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
case 'z':
flags |= (sizeof(size_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
default: break;
}
}
static void write_parsed_signed_integer(ssize_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, signed long long*) = (signed long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, signed long*) = (signed long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, signed int*) = (signed short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, signed int*) = (signed char)value;
else
*va_arg(ap, signed int*) = (signed int)value;
}
static void write_parsed_unsigned_integer(size_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, unsigned long long*) = (unsigned long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, unsigned long*) = (unsigned long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, unsigned int*) = (unsigned short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, unsigned int*) = (unsigned char)value;
else
*va_arg(ap, unsigned int*) = (unsigned int)value;
}
#define WHITESPACE_CHARACTERS " \t\f\r\n\v"
static void skip_whitespace(const char** str)
{
*str += strspn(*str, WHITESPACE_CHARACTERS);
}
int scanf_impl(const char* str, const char* format, va_list ap)
{
int parsed = 0;
const char* s = str; // Keep a pointer to the beginning of the string for %n
if (*str == 0) return -1;
while (*format)
{
if (*format != '%')
{
normal:
if (!_isspace(*format))
{
if (*str != *format) return parsed;
str++;
format++;
if (*str == 0) return parsed;
continue;
}
skip_whitespace(&format);
skip_whitespace(&str);
if (*str == 0) return parsed;
continue;
}
else
{
format++;
if (*format == '%')
{
skip_whitespace(&str);
goto normal;
}
int flags = parse_flags(&format);
size_t width = parse_width(&format, flags);
parse_type(&format, flags);
char specifier = *format++;
if (!specifier) return parsed;
switch (specifier)
{
case 's': {
skip_whitespace(&str);
size_t chars = strcspn(str, WHITESPACE_CHARACTERS);
if (!chars) return parsed;
if ((flags & FLAG_WIDTH) && chars > width) chars = width;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc_impl(chars + 1).value_or(nullptr);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, chars);
ptr[chars] = 0;
}
str += chars;
parsed++;
break;
}
case 'c': {
if (strlen(str) < width) return parsed;
if (!(flags & FLAG_WIDTH)) width = 1;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc_impl(width).value_or(nullptr);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, width);
}
str += width;
parsed++;
break;
}
case 'd': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'i': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 0);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'o': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 8);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'u': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'X':
case 'x': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'p': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) *va_arg(ap, void**) = (void*)value;
parsed++;
break;
}
case 'n': {
if (!(flags & FLAG_DISCARD)) *va_arg(ap, int*) = (int)(str - s);
break;
}
default: {
dbgln("vsscanf: unknown conversion specifier: %%%c\n", specifier);
return parsed;
}
}
}
}
return parsed;
}

View File

@ -14,6 +14,10 @@ set(SOURCES
src/Mode.cpp src/Mode.cpp
src/Prompt.cpp src/Prompt.cpp
src/Security.cpp src/Security.cpp
src/LocalServer.cpp
src/LocalClient.cpp
src/IPC.cpp
src/SharedMemory.cpp
) )
add_library(os ${SOURCES}) add_library(os ${SOURCES})

View File

@ -68,15 +68,6 @@ namespace os
*/ */
Result<void> remove(const Path& path); Result<void> remove(const Path& path);
/**
* @brief Remove a directory tree from the file system recursively, deleting subfiles and subdirectories as
* well.
*
* @param path The path to remove.
* @return Result<void> Whether the operation succeeded.
*/
Result<void> remove_tree(const Path& path);
/** /**
* @brief Read the target of a symbolic link. * @brief Read the target of a symbolic link.
* *

157
libos/include/os/IPC.h Normal file
View File

@ -0,0 +1,157 @@
/**
* @file IPC.h
* @author apio (cloudapio.eu)
* @brief Inter-process communication primitives.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <os/LocalClient.h>
#include <os/LocalServer.h>
#define IPC_ENUM_SERVER(name) __##name##_SERVER_ERROR = 0
#define IPC_ENUM_CLIENT(name) __##name##_CLIENT_ERROR = 0
/**
* @brief Called to handle IPC events (client-side).
*
* @param conn The connection object being used.
* @param id The ID of the message.
* @return Result<void> Whether the operation succeded.
*/
extern Result<void> handle_ipc_client_event(os::LocalClient& conn, u8 id);
/**
* @brief Called to handle IPC events (server-side).
*
* @param conn The connection object being used.
* @param id The ID of the message.
* @return Result<void> Whether the operation succeded.
*/
extern Result<void> handle_ipc_server_event(os::LocalServer::Client& conn, u8 id);
namespace os
{
namespace IPC
{
static constexpr usize IPC_STRING_LENGTH = 256;
#define IPC_STRING(name) char name[os::IPC::IPC_STRING_LENGTH];
#define COPY_IPC_STRING(name) \
TRY(String::from_string_view(StringView::from_fixed_size_cstring(name, os::IPC::IPC_STRING_LENGTH)))
#define SET_IPC_STRING(name, value) strlcpy(name, value, os::IPC::IPC_STRING_LENGTH)
/**
* @brief Sends an IPC message without waiting for a reply.
*
* @tparam Client The type of the client interface being used to communicate.
* @tparam T The type of the message.
* @param client The connection object being used to communicate.
* @param message The IPC message.
* @return Result<void> Whether the operation succeded.
*/
template <typename Client, typename T> Result<void> send_async(Client& client, const T& message)
{
u8 id = T::ID;
TRY(client.send_typed(id));
TRY(client.send_typed(message));
return {};
}
/**
* @brief Sends an error result to the IPC connection, indicating that an operation could not be performed.
*
* @tparam Client The type of the client interface being used to communicate.
* @param client The connection object being used to communicate.
* @param error The error code.
* @return Result<void> Whether the operation succeded.
*/
template <typename Client> Result<void> send_error(Client& client, int error)
{
u8 id = 0;
TRY(client.send_typed(id));
TRY(client.send_typed(error));
return {};
}
/**
* @brief Sends an IPC message and waits for a reply (client-only).
*
* @tparam ResponseType The type of the response.
* @tparam T The type of the message.
* @param client The connection object being used to communicate.
* @param message The IPC message.
* @param handler The function used to handle messages that do not match the reply.
* @return Result<ResponseType> An error, or the response.
*/
template <typename ResponseType, typename T>
Result<ResponseType> send_sync(os::LocalClient& client, const T& message,
decltype(handle_ipc_client_event) handler = handle_ipc_client_event)
{
u8 id = T::ID;
TRY(client.send_typed(id));
TRY(client.send_typed(message));
// We allow receiving 5 messages of different types, but if those have passed and we still don't have a
// reply, fail with ENOMSG.
int max_other_messages = 5;
while (max_other_messages)
{
u8 response_id;
auto rc = client.recv_typed(response_id);
if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue;
if (response_id == 0) // Error result
{
while (1)
{
int code;
rc = client.recv_typed(code);
if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue;
return err(code);
}
}
if (response_id != ResponseType::ID)
{
TRY(handler(client, response_id));
max_other_messages--;
continue;
}
while (1)
{
ResponseType response;
rc = client.recv_typed(response);
if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue;
return response;
}
}
return err(ENOMSG);
}
/**
* @brief Check for new IPC messages on a connection and handle them appropriately.
*
* @param client The client connection.
* @param handler The function used to handle messages.
* @return Result<void> Whether the operation succeded.
*/
Result<void> check_for_messages(os::LocalClient& client,
decltype(handle_ipc_client_event) handler = handle_ipc_client_event);
/**
* @brief Check for new IPC messages on a connection and handle them appropriately.
*
* @param server The server connection.
* @param handler The function used to handle messages.
* @return Result<void> Whether the operation succeded.
*/
Result<void> check_for_messages(os::LocalServer::Client& server,
decltype(handle_ipc_server_event) handler = handle_ipc_server_event);
}
}

View File

@ -0,0 +1,99 @@
/**
* @file LocalClient.h
* @author apio (cloudapio.eu)
* @brief UNIX local domain client class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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,142 @@
/**
* @file LocalServer.h
* @author apio (cloudapio.eu)
* @brief UNIX local domain server class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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;
};
}

View File

@ -0,0 +1,36 @@
/**
* @file SharedMemory.h
* @author apio (cloudapio.eu)
* @brief Create and map areas of memory shared between processes.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/Result.h>
#include <luna/StringView.h>
namespace os
{
namespace SharedMemory
{
/**
* @brief Create a new shared memory region and map it.
*
* @param path The shared memory path to use. It must be of the same format as shm_open().
* @param size The amount of bytes to use for the shared memory region.
* @return Result<u8*> An error, or a pointer to the shared memory region.
*/
Result<u8*> create(StringView path, usize size);
/**
* @brief Map an existing shared memory region, possibly created by another process.
*
* @param path The shared memory path to use. It must be of the same format as shm_open().
* @param size The amount of bytes to map from the shared memory region.
* @param delete_fs Whether to delete the region from the file system so no other processes can open it.
* @return Result<u8*> An error, or a pointer to the shared memory region.
*/
Result<u8*> adopt(StringView path, usize size, bool delete_fs = true);
};
}

View File

@ -12,7 +12,6 @@
#include <luna/CString.h> #include <luna/CString.h>
#include <os/Directory.h> #include <os/Directory.h>
#include <os/FileSystem.h> #include <os/FileSystem.h>
#include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
static bool should_skip_entry(const char* name, os::Directory::Filter filter) static bool should_skip_entry(const char* name, os::Directory::Filter filter)
@ -33,8 +32,8 @@ namespace os
{ {
auto dir = TRY(adopt_shared_if_nonnull(new (std::nothrow) Directory({}))); auto dir = TRY(adopt_shared_if_nonnull(new (std::nothrow) Directory({})));
long rc = syscall(SYS_openat, path.dirfd(), path.name().chars(), O_RDONLY | O_DIRECTORY, 0); int fd = openat(path.dirfd(), path.name().chars(), O_RDONLY | O_DIRECTORY, 0);
int fd = TRY(Result<int>::from_syscall(rc)); if (fd < 0) return err(errno);
DIR* dp = fdopendir(fd); DIR* dp = fdopendir(fd);
if (!dp) if (!dp)

View File

@ -10,7 +10,6 @@
#include <errno.h> #include <errno.h>
#include <luna/StringBuilder.h> #include <luna/StringBuilder.h>
#include <os/File.h> #include <os/File.h>
#include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
static SharedPtr<os::File> g_stdin = {}; static SharedPtr<os::File> g_stdin = {};
@ -83,8 +82,8 @@ namespace os
{ {
auto file = TRY(adopt_shared_if_nonnull(new (std::nothrow) File({}))); auto file = TRY(adopt_shared_if_nonnull(new (std::nothrow) File({})));
long rc = syscall(SYS_openat, path.dirfd(), path.name().chars(), flags, mode); int fd = openat(path.dirfd(), path.name().chars(), flags, mode);
int fd = TRY(Result<int>::from_syscall(rc)); if (fd < 0) return err(errno);
file->m_file = fdopen(fd, stdio_mode_from_openmode(flags)); file->m_file = fdopen(fd, stdio_mode_from_openmode(flags));
if (!file->m_file) return err(errno); if (!file->m_file) return err(errno);

View File

@ -10,13 +10,13 @@
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <luna/Buffer.h>
#include <luna/PathParser.h> #include <luna/PathParser.h>
#include <luna/String.h> #include <luna/String.h>
#include <os/Directory.h> #include <os/Directory.h>
#include <os/FileSystem.h> #include <os/FileSystem.h>
#include <pwd.h> #include <pwd.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
namespace os::FileSystem namespace os::FileSystem
@ -41,39 +41,26 @@ namespace os::FileSystem
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks) Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks)
{ {
long rc = syscall(SYS_fstatat, path.dirfd(), path.name().chars(), &st, int rc = fstatat(path.dirfd(), path.name().chars(), &st,
(int)(path.is_empty_path() | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW))); (int)(path.is_empty_path() | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW)));
return Result<void>::from_syscall(rc); if (rc < 0) return err(errno);
return {};
} }
Result<void> create_directory(StringView path, mode_t mode) Result<void> create_directory(StringView path, mode_t mode)
{ {
long rc = syscall(SYS_mkdir, path.chars(), mode); int rc = mkdir(path.chars(), mode);
if (rc < 0) return err(errno);
return Result<void>::from_syscall(rc); return {};
} }
Result<void> remove(const Path& path) Result<void> remove(const Path& path)
{ {
long rc = syscall(SYS_unlinkat, path.dirfd(), path.name().chars(), 0); // FIXME: This will not work on many operating systems that require rmdir() for directories.
int rc = unlinkat(path.dirfd(), path.name().chars(), 0);
return Result<void>::from_syscall(rc); if (rc < 0) return err(errno);
} return {};
Result<void> remove_tree(const Path& path)
{
auto rc = remove(path);
if (!rc.has_error()) return {};
if (rc.error() != ENOTEMPTY) return rc.release_error();
auto dir = TRY(os::Directory::open(path));
Vector<String> entries = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));
for (const auto& entry : entries) { TRY(remove_tree({ dir->fd(), entry.view() })); }
return remove(path);
} }
Result<String> readlink(const Path& path) Result<String> readlink(const Path& path)
@ -82,14 +69,13 @@ namespace os::FileSystem
TRY(stat(path, st, false)); TRY(stat(path, st, false));
if (!S_ISLNK(st.st_mode)) return String {}; if (!S_ISLNK(st.st_mode)) return String {};
char* buf = (char*)TRY(calloc_impl(st.st_size + 1, 1)); Buffer buf = TRY(Buffer::create_sized(st.st_size + 1));
auto guard = make_scope_guard([buf] { free_impl(buf); }); memset(buf.data(), 0, buf.size());
usize nread = TRY(
Result<usize>::from_syscall(syscall(SYS_readlinkat, path.dirfd(), path.name().chars(), buf, st.st_size)));
guard.deactivate(); ssize_t nread = readlinkat(path.dirfd(), path.name().chars(), (char*)buf.data(), st.st_size);
if (nread < 0) return err(errno);
return String { buf, nread }; return String { (char*)buf.release_data(), (usize)nread };
} }
Result<String> working_directory() Result<String> working_directory()
@ -112,8 +98,8 @@ namespace os::FileSystem
Result<void> change_directory(StringView path) Result<void> change_directory(StringView path)
{ {
long rc = syscall(SYS_chdir, path.chars()); int rc = chdir(path.chars());
if (rc < 0) return err(errno);
return Result<void>::from_syscall(rc); return {};
} }
} }

43
libos/src/IPC.cpp Normal file
View File

@ -0,0 +1,43 @@
/**
* @file IPC.cpp
* @author apio (cloudapio.eu)
* @brief Inter-process communication primitives.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/IPC.h>
namespace os::IPC
{
Result<void> check_for_messages(os::LocalClient& client, decltype(handle_ipc_client_event) handler)
{
u8 id;
auto rc = client.recv_typed(id);
if (rc.has_error())
{
if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block.
if (rc.error() == EINTR)
return {}; // Let the caller check for anything having happened because a signal handler ran.
return rc.release_error();
}
return handler(client, id);
}
Result<void> check_for_messages(os::LocalServer::Client& client, decltype(handle_ipc_server_event) handler)
{
u8 id;
auto rc = client.recv_typed(id);
if (rc.has_error())
{
if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block.
if (rc.error() == EINTR)
return {}; // Let the caller check for anything having happened because a signal handler ran.
return rc.release_error();
}
return handler(client, id);
}
}

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

@ -0,0 +1,68 @@
/**
* @file LocalClient.cpp
* @author apio (cloudapio.eu)
* @brief UNIX local domain client class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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;
}
}

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

@ -0,0 +1,101 @@
/**
* @file LocalServer.cpp
* @author apio (cloudapio.eu)
* @brief UNIX local domain server class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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;
}
}

View File

@ -9,8 +9,8 @@
#include <errno.h> #include <errno.h>
#include <os/Process.h> #include <os/Process.h>
#include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
extern char** environ; extern char** environ;
@ -19,8 +19,9 @@ namespace os
{ {
Result<pid_t> Process::fork() Result<pid_t> Process::fork()
{ {
long rc = syscall(SYS_fork); pid_t pid = ::fork();
return Result<pid_t>::from_syscall(rc); if (pid < 0) return err(errno);
return pid;
} }
Result<void> Process::exec(StringView path, Slice<String> args, bool search_in_path) Result<void> Process::exec(StringView path, Slice<String> args, bool search_in_path)
@ -121,14 +122,16 @@ namespace os
Result<pid_t> Process::wait(pid_t child, int* status, int options) Result<pid_t> Process::wait(pid_t child, int* status, int options)
{ {
long rc = syscall(SYS_waitpid, child, status, options); pid_t pid = waitpid(child, status, options);
return Result<pid_t>::from_syscall(rc); if (pid < 0) return err(errno);
return pid;
} }
Result<void> Process::kill(pid_t pid, int signo) Result<void> Process::kill(pid_t pid, int signo)
{ {
long rc = syscall(SYS_kill, pid, signo); int rc = ::kill(pid, signo);
return Result<void>::from_syscall(rc); if (rc < 0) return err(errno);
return {};
} }
[[noreturn]] void Process::exit(int status) [[noreturn]] void Process::exit(int status)

View File

@ -0,0 +1,77 @@
/**
* @file SharedMemory.cpp
* @author apio (cloudapio.eu)
* @brief Create and map areas of memory shared between processes.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <errno.h>
#include <luna/Alignment.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
namespace os::SharedMemory
{
Result<u8*> create(StringView path, usize size)
{
int fd = shm_open(path.chars(), O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd < 0)
{
int olderr = errno;
os::eprintln("os: could not create shared memory region: shm_open failed (%s) - %s", path,
strerror(olderr));
return err(olderr);
}
size = align_up<PAGE_SIZE>(size);
if (ftruncate(fd, size) < 0)
{
int olderr = errno;
os::eprintln("os: could not create shared memory region: ftruncate failed (%d, %zu) - %s", fd, size,
strerror(olderr));
shm_unlink(path.chars());
close(fd);
return err(olderr);
}
void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
int olderr = errno;
os::eprintln("os: could not create shared memory region: mmap failed (%zu, %d) - %s", size, fd,
strerror(olderr));
shm_unlink(path.chars());
close(fd);
return err(olderr);
}
close(fd);
return (u8*)p;
}
Result<u8*> adopt(StringView path, usize size, bool delete_fs)
{
int fd = shm_open(path.chars(), O_RDWR, 0600);
if (delete_fs) shm_unlink(path.chars());
if (fd < 0) return err(errno);
void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
int olderr = errno;
os::eprintln("os: could not adopt shared memory region: mmap failed (%zu, %d) - %s", size, fd,
strerror(olderr));
close(fd);
return 0;
}
close(fd);
return (u8*)p;
}
}

30
libui/CMakeLists.txt Normal file
View File

@ -0,0 +1,30 @@
# The UI and graphics library for Luna.
file(GLOB HEADERS include/ui/*.h)
set(SOURCES
${HEADERS}
include/ui/ipc/Server.h
include/ui/ipc/Client.h
src/Canvas.cpp
src/Rect.cpp
src/Font.cpp
src/Image.cpp
src/App.cpp
src/Window.cpp
src/Layout.cpp
src/Alignment.cpp
src/Container.cpp
src/Button.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)
add_custom_command(
TARGET ui
COMMAND "${CMAKE_COMMAND}" -E copy ${CMAKE_CURRENT_BINARY_DIR}/libui.a ${LUNA_BASE}/usr/lib/libui.a
)

View File

@ -0,0 +1,30 @@
/**
* @file Alignment.h
* @author apio (cloudapio.eu)
* @brief UI component alignment.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Rect.h>
namespace ui
{
enum class VerticalAlignment
{
Top,
Center,
Bottom
};
enum class HorizontalAlignment
{
Left,
Center,
Right
};
Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign);
}

65
libui/include/ui/App.h Normal file
View File

@ -0,0 +1,65 @@
/**
* @file App.h
* @author apio (cloudapio.eu)
* @brief UI application event loop.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/HashMap.h>
#include <os/LocalClient.h>
#include <ui/Window.h>
namespace ui
{
class App
{
public:
App();
~App();
Result<void> init(int, char**);
Result<int> run();
Rect screen_rect();
os::LocalClient& client()
{
return *m_client;
}
void set_should_close(bool b)
{
m_should_close = b;
}
void set_main_window(Window* window)
{
check(!m_main_window);
m_main_window = window;
}
Window* main_window()
{
return m_main_window;
}
Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>);
void unregister_window(Window* window, Badge<Window>);
Result<void> handle_ipc_event(u8 id);
static App& the();
private:
static App* s_app;
OwnedPtr<os::LocalClient> m_client;
Window* m_main_window { nullptr };
HashMap<int, OwnedPtr<Window>> m_windows;
bool m_should_close { false };
Window* find_window(int id);
};
}

35
libui/include/ui/Button.h Normal file
View File

@ -0,0 +1,35 @@
/**
* @file Button.h
* @author apio (cloudapio.eu)
* @brief A clickable component that triggers an action when pressed.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Widget.h>
namespace ui
{
class Button : public Widget
{
public:
Button(Rect rect);
void set_widget(Widget& widget);
void set_action(void (*action)(void));
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
private:
bool m_hovered { false };
bool m_clicked { false };
Widget* m_child;
void (*m_action)(void);
};
}

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

@ -0,0 +1,73 @@
/**
* @file Canvas.h
* @author apio (cloudapio.eu)
* @brief Drawable surfaces.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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);
};
};

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

@ -0,0 +1,113 @@
/**
* @file Color.h
* @author apio (cloudapio.eu)
* @brief RGBA colors.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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);
};

View File

@ -0,0 +1,34 @@
/**
* @file Container.h
* @author apio (cloudapio.eu)
* @brief A container widget to pad and align objects inside it.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Alignment.h>
#include <ui/Widget.h>
namespace ui
{
class Container : public Widget
{
public:
Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign);
void set_widget(Widget& widget);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
private:
Widget* m_widget;
VerticalAlignment m_valign;
HorizontalAlignment m_halign;
};
}

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

@ -0,0 +1,120 @@
/**
* @file Font.h
* @author apio (cloudapio.eu)
* @brief PSF font loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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;
};
};

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

@ -0,0 +1,92 @@
/**
* @file Image.h
* @author apio (cloudapio.eu)
* @brief TGA image loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.h>
#include <ui/Widget.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;
};
class ImageWidget final : public Widget
{
public:
static Result<OwnedPtr<ImageWidget>> load(const os::Path& path);
Result<void> draw(Canvas& canvas) override;
private:
SharedPtr<Image> m_image;
};
}

69
libui/include/ui/Layout.h Normal file
View File

@ -0,0 +1,69 @@
/**
* @file Layout.h
* @author apio (cloudapio.eu)
* @brief Layout widgets to organize content.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Vector.h>
#include <ui/Widget.h>
namespace ui
{
enum class AdjustHeight
{
No,
Yes
};
enum class AdjustWidth
{
No,
Yes
};
class HorizontalLayout final : public Widget
{
public:
HorizontalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
Result<void> add_widget(Widget& widget);
private:
Vector<Widget*> m_widgets;
AdjustHeight m_adjust_height;
AdjustWidth m_adjust_width;
int m_used_width;
};
class VerticalLayout final : public Widget
{
public:
VerticalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
Result<void> add_widget(Widget& widget);
private:
Vector<Widget*> m_widgets;
AdjustHeight m_adjust_height;
AdjustWidth m_adjust_width;
int m_used_height;
};
}

21
libui/include/ui/Mouse.h Normal file
View File

@ -0,0 +1,21 @@
/**
* @file Mouse.h
* @author apio (cloudapio.eu)
* @brief Mouse buttons.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <moon/Mouse.h>
namespace ui
{
enum MouseButtons
{
LEFT = moon::Left,
MIDDLE = moon::Middle,
RIGHT = moon::Right,
};
}

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

@ -0,0 +1,22 @@
/**
* @file Point.h
* @author apio (cloudapio.eu)
* @brief 2D space points.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
namespace ui
{
/**
* @brief A point in 2D space.
*/
struct Point
{
int x { 0 };
int y { 0 };
};
}

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

@ -0,0 +1,81 @@
/**
* @file Rect.h
* @author apio (cloudapio.eu)
* @brief A simple 2D rectangle representation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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();
};
}

87
libui/include/ui/Widget.h Normal file
View File

@ -0,0 +1,87 @@
/**
* @file Widget.h
* @author apio (cloudapio.eu)
* @brief Abstract widget class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Ignore.h>
#include <luna/Result.h>
#include <ui/Canvas.h>
#include <ui/Point.h>
#include <ui/Rect.h>
namespace ui
{
class Window;
enum class EventResult
{
DidHandle,
DidNotHandle,
};
class Widget
{
public:
virtual Result<EventResult> handle_mouse_move(Point position)
{
ignore(position);
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_mouse_down(Point position, int buttons)
{
ignore(position, buttons);
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_mouse_up(Point position, int buttons)
{
ignore(position, buttons);
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_mouse_leave()
{
return EventResult::DidNotHandle;
}
virtual Result<void> draw(Canvas& canvas);
void set_window(Window* window, Rect rect, Badge<Window>)
{
m_window = window;
m_rect = rect;
}
void set_parent(Widget* parent)
{
m_parent = parent;
m_window = parent->m_window;
}
Widget* parent()
{
return m_parent;
}
Window* window()
{
return m_window;
}
Rect& rect()
{
return m_rect;
}
protected:
Widget* m_parent { nullptr };
Window* m_window;
Rect m_rect { 0, 0, 50, 50 };
};
}

67
libui/include/ui/Window.h Normal file
View File

@ -0,0 +1,67 @@
/**
* @file Window.h
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Mouse.h>
#include <ui/Rect.h>
#include <ui/Widget.h>
namespace ui
{
class Window
{
public:
static Result<Window*> create(Rect rect, bool decorated = true);
void set_title(StringView title);
void set_background(Color color)
{
m_background = color;
}
void set_main_widget(Widget& widget)
{
check(!m_main_widget);
widget.set_window(this, m_canvas.rect(), {});
m_main_widget = &widget;
}
Canvas& canvas()
{
return m_canvas;
}
void update();
void close();
Result<void> draw();
Result<void> handle_mouse_leave();
Result<void> handle_mouse_move(ui::Point position);
Result<void> handle_mouse_buttons(ui::Point position, int buttons);
int id() const
{
return m_id;
}
~Window();
private:
int m_id;
Canvas m_canvas;
Widget* m_main_widget { nullptr };
Color m_background { ui::BLACK };
Option<int> m_old_mouse_buttons;
};
}

View File

@ -0,0 +1,63 @@
/**
* @file ipc/Client.h
* @author apio (cloudapio.eu)
* @brief IPC message definitions for UI messages sent to the client.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <os/IPC.h>
#include <ui/Point.h>
namespace ui
{
enum ClientMessages : u8
{
IPC_ENUM_CLIENT(ui),
CREATE_WINDOW_RESPONSE_ID,
WINDOW_CLOSE_REQUEST_ID,
MOUSE_EVENT_REQUEST_ID,
MOUSE_LEAVE_REQUEST_ID,
GET_SCREEN_RECT_RESPONSE_ID
};
struct CreateWindowResponse
{
static constexpr u8 ID = CREATE_WINDOW_RESPONSE_ID;
int window;
IPC_STRING(shm_path);
};
struct WindowCloseRequest
{
static constexpr u8 ID = WINDOW_CLOSE_REQUEST_ID;
int window;
};
struct MouseEventRequest
{
static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID;
int window;
Point position;
int buttons;
};
struct MouseLeaveRequest
{
static constexpr u8 ID = MOUSE_LEAVE_REQUEST_ID;
int window;
};
struct GetScreenRectResponse
{
static constexpr u8 ID = GET_SCREEN_RECT_RESPONSE_ID;
Rect rect;
};
}

View File

@ -0,0 +1,66 @@
/**
* @file ipc/Server.h
* @author apio (cloudapio.eu)
* @brief IPC message definitions for UI messages sent to the server.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <os/IPC.h>
#include <ui/Color.h>
#include <ui/Rect.h>
#include <ui/ipc/Client.h>
namespace ui
{
enum ServerMessages : u8
{
IPC_ENUM_SERVER(ui),
CREATE_WINDOW_ID,
SET_WINDOW_TITLE_ID,
INVALIDATE_ID,
CLOSE_WINDOW_ID,
GET_SCREEN_RECT_ID,
};
struct CreateWindowRequest
{
using ResponseType = CreateWindowResponse;
static constexpr u8 ID = CREATE_WINDOW_ID;
ui::Rect rect;
bool decorated;
};
struct SetWindowTitleRequest
{
static constexpr u8 ID = SET_WINDOW_TITLE_ID;
int window;
IPC_STRING(title);
};
struct InvalidateRequest
{
static constexpr u8 ID = INVALIDATE_ID;
int window;
};
struct CloseWindowRequest
{
static constexpr u8 ID = CLOSE_WINDOW_ID;
int window;
};
struct GetScreenRectRequest
{
using ResponseType = GetScreenRectResponse;
static constexpr u8 ID = GET_SCREEN_RECT_ID;
int _shadow; // Unused.
};
}

40
libui/src/Alignment.cpp Normal file
View File

@ -0,0 +1,40 @@
/**
* @file Alignment.cpp
* @author apio (cloudapio.eu)
* @brief UI component alignment.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Alignment.h>
namespace ui
{
Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign)
{
Rect result;
result.width = contained.width;
result.height = contained.height;
result.pos.y = container.pos.y;
result.pos.x = container.pos.x;
switch (valign)
{
case VerticalAlignment::Top: break;
case VerticalAlignment::Center: result.pos.y += (container.height - contained.height) / 2; break;
case VerticalAlignment::Bottom: result.pos.y += container.height - contained.height; break;
default: break;
}
switch (halign)
{
case HorizontalAlignment::Left: break;
case HorizontalAlignment::Center: result.pos.x += (container.width - contained.width) / 2; break;
case HorizontalAlignment::Right: result.pos.x += container.width - contained.width; break;
default: break;
}
return result;
}
}

136
libui/src/App.cpp Normal file
View File

@ -0,0 +1,136 @@
/**
* @file App.cpp
* @author apio (cloudapio.eu)
* @brief UI application event loop.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/IPC.h>
#include <ui/App.h>
#include <ui/ipc/Client.h>
#include <ui/ipc/Server.h>
Result<void> handle_ipc_client_event(os::LocalClient&, u8 id)
{
return ui::App::the().handle_ipc_event(id);
}
namespace ui
{
App* App::s_app { nullptr };
App::App()
{
s_app = this;
}
App::~App()
{
s_app = nullptr;
}
Result<void> App::init(int argc, char** argv)
{
StringView socket_path = "/tmp/wind.sock";
os::ArgumentParser parser;
parser.add_description("A UI application."_sv);
parser.add_system_program_info(argv[0]);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.parse(argc, argv);
m_client = TRY(os::LocalClient::connect(socket_path, true));
return {};
}
Result<int> App::run()
{
check(m_main_window);
while (!m_should_close) { TRY(os::IPC::check_for_messages(*m_client)); }
return 0;
}
App& App::the()
{
check(s_app);
return *s_app;
}
Rect App::screen_rect()
{
ui::GetScreenRectRequest request {};
auto response = os::IPC::send_sync<ui::GetScreenRectResponse>(*m_client, request).release_value();
return response.rect;
}
Result<void> App::register_window(OwnedPtr<Window>&& window, Badge<Window>)
{
int id = window->id();
check(TRY(m_windows.try_set(id, move(window))));
return {};
}
void App::unregister_window(Window* window, Badge<Window>)
{
int id = window->id();
check(m_windows.try_remove(id));
}
Window* App::find_window(int id)
{
auto* window = m_windows.try_get_ref(id);
check(window);
return window->ptr();
}
#define READ_MESSAGE(request) \
do { \
auto rc = m_client->recv_typed(request); \
if (rc.has_error()) \
{ \
if (rc.error() == EAGAIN) { continue; } \
if (rc.error() == EINTR) { continue; } \
else \
return rc.release_error(); \
} \
break; \
} while (true)
Result<void> App::handle_ipc_event(u8 id)
{
switch (id)
{
case WINDOW_CLOSE_REQUEST_ID: {
WindowCloseRequest request;
READ_MESSAGE(request);
os::eprintln("ui: Window close request from server! Shall comply.");
auto* window = find_window(request.window);
window->close();
return {};
}
case MOUSE_EVENT_REQUEST_ID: {
MouseEventRequest request;
READ_MESSAGE(request);
auto* window = find_window(request.window);
window->handle_mouse_move(request.position);
window->handle_mouse_buttons(request.position, request.buttons);
window->draw();
return {};
}
case MOUSE_LEAVE_REQUEST_ID: {
MouseLeaveRequest request;
READ_MESSAGE(request);
auto* window = find_window(request.window);
window->handle_mouse_leave();
window->draw();
return {};
}
default: fail("Unexpected IPC request from server!");
}
}
}

68
libui/src/Button.cpp Normal file
View File

@ -0,0 +1,68 @@
/**
* @file Button.cpp
* @author apio (cloudapio.eu)
* @brief A clickable component that triggers an action when pressed.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Button.h>
#include <ui/Mouse.h>
namespace ui
{
Button::Button(Rect rect)
{
m_rect = rect;
}
void Button::set_widget(Widget& widget)
{
widget.rect() = m_rect;
m_child = &widget;
widget.set_parent(this);
}
void Button::set_action(void (*action)(void))
{
m_action = action;
}
Result<EventResult> Button::handle_mouse_move(Point position)
{
m_hovered = true;
return m_child->handle_mouse_move(position);
}
Result<EventResult> Button::handle_mouse_leave()
{
m_hovered = m_clicked = false;
return m_child->handle_mouse_leave();
}
Result<EventResult> Button::handle_mouse_down(Point position, int buttons)
{
auto result = TRY(m_child->handle_mouse_down(position, buttons));
if (result == EventResult::DidNotHandle)
{
if (!m_clicked && (buttons == ui::MouseButtons::LEFT))
{
m_clicked = true;
m_action();
}
}
return EventResult::DidHandle;
}
Result<EventResult> Button::handle_mouse_up(Point position, int buttons)
{
if (buttons & ui::MouseButtons::LEFT) m_clicked = false;
return m_child->handle_mouse_up(position, buttons);
}
Result<void> Button::draw(Canvas& canvas)
{
return m_child->draw(canvas);
}
}

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

@ -0,0 +1,62 @@
/**
* @file Canvas.cpp
* @author apio (cloudapio.eu)
* @brief Drawable surfaces.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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);
}
}
}

58
libui/src/Container.cpp Normal file
View File

@ -0,0 +1,58 @@
/**
* @file Container.cpp
* @author apio (cloudapio.eu)
* @brief A container widget to pad and align objects inside it.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Container.h>
namespace ui
{
Container::Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign)
: m_valign(valign), m_halign(halign)
{
m_rect = rect;
}
void Container::set_widget(Widget& widget)
{
m_widget = &widget;
widget.rect() = ui::align(m_rect, widget.rect(), m_valign, m_halign);
widget.set_parent(this);
}
Result<EventResult> Container::handle_mouse_move(Point position)
{
if (m_widget->rect().contains(position)) return m_widget->handle_mouse_move(position);
return ui::EventResult::DidNotHandle;
}
Result<EventResult> Container::handle_mouse_leave()
{
return m_widget->handle_mouse_leave();
}
Result<EventResult> Container::handle_mouse_down(Point position, int buttons)
{
if (m_widget->rect().contains(position)) return m_widget->handle_mouse_down(position, buttons);
return ui::EventResult::DidNotHandle;
}
Result<EventResult> Container::handle_mouse_up(Point position, int buttons)
{
if (m_widget->rect().contains(position)) return m_widget->handle_mouse_up(position, buttons);
return ui::EventResult::DidNotHandle;
}
Result<void> Container::draw(Canvas& canvas)
{
auto rect = ui::Rect { m_widget->rect().pos.x - m_rect.pos.x, m_widget->rect().pos.y - m_rect.pos.y,
m_widget->rect().width, m_widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
return m_widget->draw(subcanvas);
}
}

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

@ -0,0 +1,121 @@
/**
* @file Font.cpp
* @author apio (cloudapio.eu)
* @brief PSF font loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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;
}
}
}
}

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

@ -0,0 +1,48 @@
/**
* @file Image.cpp
* @author apio (cloudapio.eu)
* @brief TGA image loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/File.h>
#include <ui/Alignment.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;
}
Result<OwnedPtr<ImageWidget>> ImageWidget::load(const os::Path& path)
{
auto widget = TRY(make_owned<ImageWidget>());
widget->m_image = TRY(Image::load(path));
widget->m_rect = { 0, 0, widget->m_image->width(), widget->m_image->height() };
return widget;
}
Result<void> ImageWidget::draw(Canvas& canvas)
{
canvas.subcanvas({ 0, 0, m_image->width(), m_image->height() }).fill(m_image->pixels(), m_image->width());
return {};
}
}

192
libui/src/Layout.cpp Normal file
View File

@ -0,0 +1,192 @@
/**
* @file Layout.cpp
* @author apio (cloudapio.eu)
* @brief Layout widgets to organize content.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <stdlib.h>
#include <ui/Layout.h>
namespace ui
{
HorizontalLayout::HorizontalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width)
: m_adjust_height(adjust_height), m_adjust_width(adjust_width)
{
}
Result<EventResult> HorizontalLayout::handle_mouse_move(Point position)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position));
else
TRY(widget->handle_mouse_leave());
}
return result;
}
Result<EventResult> HorizontalLayout::handle_mouse_leave()
{
for (auto widget : m_widgets) TRY(widget->handle_mouse_leave());
return ui::EventResult::DidNotHandle;
}
Result<EventResult> HorizontalLayout::handle_mouse_up(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> HorizontalLayout::handle_mouse_down(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<void> HorizontalLayout::draw(Canvas& canvas)
{
for (auto widget : m_widgets)
{
ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
TRY(widget->draw(subcanvas));
}
return {};
}
Result<void> HorizontalLayout::add_widget(Widget& widget)
{
TRY(m_widgets.try_append(&widget));
if (m_adjust_width == AdjustWidth::No)
{
widget.rect().pos.x = m_rect.pos.x + m_used_width;
m_used_width += widget.rect().width;
}
else
{
int used_width = 0;
div_t result = div(m_rect.width, (int)m_widgets.size());
for (auto w : m_widgets)
{
w->rect().pos.x = m_rect.pos.x + used_width;
w->rect().width = result.quot;
used_width += result.quot;
}
m_widgets[m_widgets.size() - 1]->rect().width += result.rem;
}
widget.rect().pos.y = m_rect.pos.y;
if (m_adjust_height == AdjustHeight::Yes) { widget.rect().height = m_rect.height; }
widget.set_parent(this);
return {};
}
VerticalLayout::VerticalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width)
: m_adjust_height(adjust_height), m_adjust_width(adjust_width)
{
}
Result<EventResult> VerticalLayout::handle_mouse_move(Point position)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position));
else
TRY(widget->handle_mouse_leave());
}
return result;
}
Result<EventResult> VerticalLayout::handle_mouse_leave()
{
for (auto widget : m_widgets) TRY(widget->handle_mouse_leave());
return ui::EventResult::DidNotHandle;
}
Result<EventResult> VerticalLayout::handle_mouse_up(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> VerticalLayout::handle_mouse_down(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<void> VerticalLayout::draw(Canvas& canvas)
{
for (auto widget : m_widgets)
{
ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
TRY(widget->draw(subcanvas));
}
return {};
}
Result<void> VerticalLayout::add_widget(Widget& widget)
{
TRY(m_widgets.try_append(&widget));
if (m_adjust_height == AdjustHeight::No)
{
widget.rect().pos.y = m_rect.pos.y + m_used_height;
m_used_height += widget.rect().height;
}
else
{
int used_height = 0;
div_t result = div(m_rect.height, (int)m_widgets.size());
for (auto w : m_widgets)
{
w->rect().pos.y = m_rect.pos.y + used_height;
w->rect().height = result.quot;
used_height += result.quot;
}
m_widgets[m_widgets.size() - 1]->rect().height += result.rem;
}
widget.rect().pos.x = m_rect.pos.x;
if (m_adjust_width == AdjustWidth::Yes) { widget.rect().width = m_rect.width; }
widget.set_parent(this);
return {};
}
}

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

@ -0,0 +1,62 @@
/**
* @file Rect.cpp
* @author apio (cloudapio.eu)
* @brief A simple 2D rectangle representation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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 };
}
};

110
libui/src/Window.cpp Normal file
View File

@ -0,0 +1,110 @@
/**
* @file Window.cpp
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/String.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
#include <ui/App.h>
#include <ui/Window.h>
#include <ui/ipc/Server.h>
namespace ui
{
Result<Window*> Window::create(Rect rect, bool decorated)
{
auto window = TRY(make_owned<Window>());
ui::CreateWindowRequest request;
request.rect = rect;
request.decorated = decorated;
auto response = TRY(os::IPC::send_sync<ui::CreateWindowResponse>(App::the().client(), request));
auto path = COPY_IPC_STRING(response.shm_path);
u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4));
window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels };
window->m_id = response.window;
Window* p = window.ptr();
App::the().register_window(move(window), {});
return p;
}
Window::~Window()
{
if (m_canvas.ptr) munmap(m_canvas.ptr, ((usize)m_canvas.width) * ((usize)m_canvas.height) * 4);
}
void Window::set_title(StringView title)
{
ui::SetWindowTitleRequest request;
request.window = m_id;
SET_IPC_STRING(request.title, title.chars());
os::IPC::send_async(App::the().client(), request);
}
void Window::update()
{
ui::InvalidateRequest request;
request.window = m_id;
os::IPC::send_async(App::the().client(), request);
}
void Window::close()
{
App& app = App::the();
ui::CloseWindowRequest request;
request.window = m_id;
os::IPC::send_async(app.client(), request);
if (this == app.main_window()) app.set_should_close(true);
app.unregister_window(this, {});
}
Result<void> Window::draw()
{
m_canvas.fill(m_background);
if (m_main_widget) return m_main_widget->draw(m_canvas);
update();
return {};
}
Result<void> Window::handle_mouse_leave()
{
if (!m_main_widget) return {};
TRY(m_main_widget->handle_mouse_leave());
return {};
}
Result<void> Window::handle_mouse_move(ui::Point position)
{
if (!m_main_widget) return {};
TRY(m_main_widget->handle_mouse_move(position));
return {};
}
Result<void> Window::handle_mouse_buttons(ui::Point position, int buttons)
{
if (!m_main_widget) return {};
if (buttons) TRY(m_main_widget->handle_mouse_down(position, buttons));
if (m_old_mouse_buttons.has_value())
{
int old_buttons = m_old_mouse_buttons.value();
int diff = old_buttons & ~buttons;
if (diff) TRY(m_main_widget->handle_mouse_up(position, diff));
}
m_old_mouse_buttons = buttons;
return {};
}
}

View File

@ -133,7 +133,8 @@ Result<int> luna_main(int argc, char** argv)
if (interactive) if (interactive)
{ {
auto cwd = TRY(os::FileSystem::working_directory()); auto cwd = TRY(os::FileSystem::working_directory());
os::print("%s@%s:%s%c ", username, hostname, cwd.chars(), prompt_end); os::print("\x1b[%dm%s\x1b[m@\x1b[36m%s\x1b[m:\x1b[1;34m%s\x1b[m%c ", getuid() == 0 ? 31 : 35, username,
hostname, cwd.chars(), prompt_end);
} }
auto maybe_cmd = input_file->read_line(); auto maybe_cmd = input_file->read_line();

View File

@ -8,10 +8,12 @@ cd $LUNA_ROOT
mkdir -p $LUNA_BASE mkdir -p $LUNA_BASE
mkdir -p $LUNA_BASE/usr/include mkdir -p $LUNA_BASE/usr/include
mkdir -p $LUNA_BASE/usr/include/luna 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/os
mkdir -p $LUNA_BASE/usr/include/moon mkdir -p $LUNA_BASE/usr/include/moon
cp --preserve=timestamps -RT libc/include/ $LUNA_BASE/usr/include 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 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 libos/include/os/ $LUNA_BASE/usr/include/os
cp --preserve=timestamps -RT kernel/src/api/ $LUNA_BASE/usr/include/moon 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 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 "*.cpp"))
SOURCES+=($(find ${FOLDERS[@]} -type f -name "*.h")) SOURCES+=($(find ${FOLDERS[@]} -type f -name "*.h"))

19
wind/CMakeLists.txt Normal file
View File

@ -0,0 +1,19 @@
set(SOURCES
main.cpp
Screen.h
Screen.cpp
Mouse.h
Mouse.cpp
Window.h
Window.cpp
IPC.cpp
IPC.h
Client.h
)
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)

18
wind/Client.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include "Window.h"
#include <os/LocalServer.h>
struct Client
{
os::LocalServer::Client conn;
Vector<Window*> windows;
bool rpc_in_progress { false };
u8 rpc_id { 0 };
Client(os::LocalServer::Client&& client)
#ifdef CLIENT_IMPLEMENTATION
: conn(move(client)), windows() {}
#else
;
#endif
};

181
wind/IPC.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "IPC.h"
#include "Screen.h"
#include <luna/Alignment.h>
#include <luna/String.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <time.h>
#define TRY_OR_IPC_ERROR(expr) \
({ \
auto _expr_rc = (expr); \
if (!_expr_rc.has_value()) \
{ \
g_windows.remove(window); \
delete window; \
os::IPC::send_error(client.conn, _expr_rc.error()); \
return {}; \
} \
_expr_rc.release_value(); \
})
#define READ_MESSAGE(request) \
do { \
auto rc = client.conn.recv_typed(request); \
if (rc.has_error()) \
{ \
if (rc.error() == EAGAIN) \
{ \
client.rpc_in_progress = true; \
client.rpc_id = decltype(request)::ID; \
return {}; \
} \
if (rc.error() == EINTR) \
{ \
client.rpc_in_progress = true; \
client.rpc_id = decltype(request)::ID; \
return {}; \
} \
else \
return rc.release_error(); \
} \
} while (0)
#define CHECK_WINDOW_ID(request) \
do { \
if ((usize)request.window >= client.windows.size() || !client.windows[request.window]) \
{ \
os::eprintln("wind: Window id is invalid!"); \
return {}; \
} \
} while (0)
static Result<void> handle_create_window_message(Client& client)
{
ui::CreateWindowRequest request;
READ_MESSAGE(request);
if (request.decorated)
{
request.rect.height +=
Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for.
request.rect.pos.y -= Window::titlebar_height(); // Adjust it so the contents begin at the expected coordinates.
}
request.rect = request.rect.normalized();
auto name = TRY(String::from_cstring("Window"));
auto shm_path = TRY(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL)));
auto* window = new (std::nothrow) Window(request.rect, move(name), request.decorated);
if (!window)
{
os::IPC::send_error(client.conn, ENOMEM);
return {};
}
window->pixels = (u32*)TRY_OR_IPC_ERROR(
os::SharedMemory::create(shm_path.view(), window->contents.height * window->contents.width * 4));
TRY_OR_IPC_ERROR(client.windows.try_append(window));
int id = static_cast<int>(client.windows.size() - 1);
window->client = &client;
window->id = id;
ui::CreateWindowResponse response;
response.window = id;
SET_IPC_STRING(response.shm_path, shm_path.chars());
os::IPC::send_async(client.conn, response);
return {};
}
static Result<void> handle_set_window_title_message(Client& client)
{
ui::SetWindowTitleRequest request;
READ_MESSAGE(request);
auto name = COPY_IPC_STRING(request.title);
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
CHECK_WINDOW_ID(request);
client.windows[request.window]->name = move(name);
return {};
}
static Result<void> handle_invalidate_message(Client& client)
{
ui::InvalidateRequest request;
READ_MESSAGE(request);
CHECK_WINDOW_ID(request);
client.windows[request.window]->dirty = true;
return {};
}
static Result<void> handle_close_window_message(Client& client)
{
ui::CloseWindowRequest request;
READ_MESSAGE(request);
CHECK_WINDOW_ID(request);
auto* window = client.windows[request.window];
client.windows[request.window] = nullptr;
g_windows.remove(window);
delete window;
return {};
}
static Result<void> handle_get_screen_rect_message(Client& client)
{
ui::GetScreenRectRequest request;
READ_MESSAGE(request); // Kinda pointless, but required.
ui::GetScreenRectResponse response;
response.rect = Screen::the().canvas().rect();
os::IPC::send_async(client.conn, response);
return {};
}
namespace wind
{
Result<void> handle_ipc_message(Client& client, u8 id)
{
client.rpc_in_progress = false;
switch (id)
{
case ui::CREATE_WINDOW_ID: return handle_create_window_message(client);
case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_message(client);
case ui::INVALIDATE_ID: return handle_invalidate_message(client);
case ui::CLOSE_WINDOW_ID: return handle_close_window_message(client);
case ui::GET_SCREEN_RECT_ID: return handle_get_screen_rect_message(client);
default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL);
}
}
Result<void> handle_ipc(Client& client)
{
if (client.rpc_in_progress) return handle_ipc_message(client, client.rpc_id);
u8 id;
auto rc = client.conn.recv_typed(id);
if (rc.has_error())
{
if (rc.error() == EAGAIN) { return {}; }
if (rc.error() == EINTR) { return {}; }
else
return rc.release_error();
}
return handle_ipc_message(client, id);
}
}

10
wind/IPC.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include "Client.h"
#include <ui/ipc/Server.h>
namespace wind
{
Result<void> handle_ipc_message(Client& client, u8 id);
Result<void> handle_ipc(Client& client);
}

112
wind/Mouse.cpp Normal file
View File

@ -0,0 +1,112 @@
#include "Mouse.h"
#include "Client.h"
#include <os/File.h>
#include <os/IPC.h>
#include <ui/Image.h>
#include <ui/ipc/Client.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))
{
ui::WindowCloseRequest request;
request.window = window->id;
auto& client = *window->client;
os::IPC::send_async(client.conn, request);
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!
}
}
}
Window* new_active_window = nullptr;
for (Window* window = g_windows.last().value_or(nullptr); window;
window = g_windows.previous(window).value_or(nullptr))
{
auto titlebar = window->surface.absolute(window->titlebar);
auto contents = window->surface.absolute(window->contents);
if (titlebar.contains(m_position)) break;
if (contents.contains(m_position))
{
ui::MouseEventRequest request;
request.window = window->id;
request.position = contents.relative(m_position);
request.buttons = packet.buttons;
os::IPC::send_async(window->client->conn, request);
new_active_window = window;
break;
}
}
if (m_active_window != new_active_window)
{
if (m_active_window)
{
ui::MouseLeaveRequest request;
request.window = m_active_window->id;
os::IPC::send_async(m_active_window->client->conn, request);
}
m_active_window = new_active_window;
}
}

24
wind/Mouse.h Normal file
View File

@ -0,0 +1,24 @@
#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;
Window* m_active_window = nullptr;
};

36
wind/Screen.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "Screen.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
Screen Screen::s_the;
Result<void> 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;
s_the = screen;
return {};
}
void Screen::sync()
{
msync(m_canvas.ptr, size(), MS_SYNC);
}

34
wind/Screen.h Normal file
View File

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

67
wind/Window.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "Window.h"
#include <luna/Utf8.h>
#include <os/File.h>
#include <sys/mman.h>
#include <ui/Font.h>
#include <ui/Image.h>
LinkedList<Window> g_windows;
void Window::draw(ui::Canvas& screen)
{
dirty = false;
auto window = screen.subcanvas(surface);
window.subcanvas(contents).fill(pixels, contents.width);
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, String&& n, bool d) : surface(r), name(move(n)), decorated(d)
{
auto font = ui::Font::default_font();
if (decorated && surface.width < 36) surface.width = 36;
if (decorated && surface.height < (font->height() + 20)) surface.height = font->height() + 20;
titlebar = decorated ? ui::Rect { 0, 0, surface.width, font->height() + 20 } : ui::Rect { 0, 0, 0, 0 };
close_button = decorated ? ui::Rect { surface.width - 26, 10, 16, 16 } : ui::Rect { 0, 0, 0, 0 };
contents = decorated ? ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }
: ui::Rect { 0, 0, surface.width, surface.height };
g_windows.append(this);
}
int Window::titlebar_height()
{
auto font = ui::Font::default_font();
return font->height() + 20;
}
Window::~Window()
{
usize size = contents.width * contents.height * 4;
munmap(pixels, size);
}

33
wind/Window.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <luna/LinkedList.h>
#include <luna/String.h>
#include <ui/Canvas.h>
#include <ui/Color.h>
#include <ui/Rect.h>
struct Client;
struct Window : public LinkedListNode<Window>
{
ui::Rect surface;
ui::Rect titlebar;
ui::Rect close_button;
ui::Rect contents;
u32* pixels;
String name;
bool dirty { false };
Client* client;
int id;
bool decorated;
static int titlebar_height();
Window(ui::Rect, String&&, bool);
~Window();
void focus();
void draw(ui::Canvas& screen);
};
extern LinkedList<Window> g_windows;

Some files were not shown because too many files have changed in this diff Show More