Add a display server and graphical user interface #38
9
.gitignore
vendored
9
.gitignore
vendored
@ -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
|
||||||
|
@ -45,8 +45,11 @@ 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)
|
||||||
|
add_subdirectory(terminal)
|
||||||
|
@ -38,5 +38,10 @@ luna_app(sysfuzz.cpp sysfuzz)
|
|||||||
luna_app(cp.cpp cp)
|
luna_app(cp.cpp cp)
|
||||||
luna_app(kill.cpp kill)
|
luna_app(kill.cpp kill)
|
||||||
luna_app(gol.cpp gol)
|
luna_app(gol.cpp gol)
|
||||||
|
target_link_libraries(gol PUBLIC ui)
|
||||||
luna_app(touch.cpp touch)
|
luna_app(touch.cpp touch)
|
||||||
luna_app(free.cpp free)
|
luna_app(free.cpp free)
|
||||||
|
luna_app(about.cpp about)
|
||||||
|
target_link_libraries(about PUBLIC ui)
|
||||||
|
luna_app(taskbar.cpp taskbar)
|
||||||
|
target_link_libraries(taskbar PUBLIC ui)
|
||||||
|
48
apps/about.cpp
Normal file
48
apps/about.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include <luna/String.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
#include <ui/App.h>
|
||||||
|
#include <ui/Button.h>
|
||||||
|
#include <ui/Label.h>
|
||||||
|
#include <ui/Layout.h>
|
||||||
|
|
||||||
|
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89);
|
||||||
|
|
||||||
|
Result<int> luna_main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ui::App app;
|
||||||
|
TRY(app.init(argc, argv));
|
||||||
|
|
||||||
|
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 }));
|
||||||
|
app.set_main_window(window);
|
||||||
|
|
||||||
|
window->set_title("About");
|
||||||
|
window->set_background(BACKGROUND_COLOR);
|
||||||
|
|
||||||
|
utsname info;
|
||||||
|
uname(&info);
|
||||||
|
|
||||||
|
ui::VerticalLayout main_layout;
|
||||||
|
window->set_main_widget(main_layout);
|
||||||
|
|
||||||
|
ui::Label title("About Luna", ui::WHITE, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center,
|
||||||
|
ui::Font::default_bold_font());
|
||||||
|
main_layout.add_widget(title);
|
||||||
|
|
||||||
|
ui::VerticalLayout version_info;
|
||||||
|
main_layout.add_widget(version_info);
|
||||||
|
|
||||||
|
ui::Label license("Licensed under the BSD-2-Clause license.");
|
||||||
|
main_layout.add_widget(license);
|
||||||
|
|
||||||
|
String os_release_text = TRY(String::format("OS release: %s"_sv, info.release));
|
||||||
|
ui::Label os_release(os_release_text.view());
|
||||||
|
version_info.add_widget(os_release);
|
||||||
|
|
||||||
|
String kernel_version_text = TRY(String::format("Kernel version: %s"_sv, info.version));
|
||||||
|
ui::Label kernel_version(kernel_version_text.view());
|
||||||
|
version_info.add_widget(kernel_version);
|
||||||
|
|
||||||
|
window->draw();
|
||||||
|
|
||||||
|
return app.run();
|
||||||
|
}
|
117
apps/gol.cpp
117
apps/gol.cpp
@ -1,13 +1,12 @@
|
|||||||
#include <alloca.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <luna/Heap.h>
|
#include <luna/Heap.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <ui/App.h>
|
||||||
|
#include <ui/Window.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
struct Cell
|
struct Cell
|
||||||
@ -16,16 +15,12 @@ struct Cell
|
|||||||
bool new_state;
|
bool new_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int g_num_rows = 76;
|
static int g_num_rows = 40;
|
||||||
static int g_num_columns = 102;
|
static int g_num_columns = 60;
|
||||||
|
|
||||||
static int g_fb_width;
|
|
||||||
static int g_fb_height;
|
|
||||||
|
|
||||||
static int g_fd;
|
|
||||||
|
|
||||||
static Cell* g_cells;
|
static Cell* g_cells;
|
||||||
static char* g_fb;
|
|
||||||
|
static ui::Window* g_window;
|
||||||
|
|
||||||
static Result<void> fill_cells()
|
static Result<void> fill_cells()
|
||||||
{
|
{
|
||||||
@ -47,31 +42,30 @@ static Cell& find_cell(int row, int column)
|
|||||||
return g_cells[row * g_num_columns + column];
|
return g_cells[row * g_num_columns + column];
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr int BYTES_PER_PIXEL = 4;
|
static constexpr int BYTES_PER_PIXEL = sizeof(u32);
|
||||||
|
static constexpr ui::Color activated_cell_color = ui::CYAN;
|
||||||
|
static constexpr ui::Color deactivated_cell_color = ui::Color::from_rgb(40, 40, 40);
|
||||||
|
|
||||||
static void draw_cells()
|
static void draw_cells()
|
||||||
{
|
{
|
||||||
const int CELL_WIDTH = g_fb_width / g_num_columns;
|
const int CELL_WIDTH = g_window->canvas().width / g_num_columns;
|
||||||
const int CELL_HEIGHT = g_fb_height / g_num_rows;
|
const int CELL_HEIGHT = g_window->canvas().height / g_num_rows;
|
||||||
|
|
||||||
|
auto canvas = g_window->canvas();
|
||||||
|
|
||||||
for (int i = 0; i < g_num_rows; i++)
|
for (int i = 0; i < g_num_rows; i++)
|
||||||
{
|
{
|
||||||
|
|
||||||
for (int j = 0; j < g_num_columns; j++)
|
for (int j = 0; j < g_num_columns; j++)
|
||||||
{
|
{
|
||||||
char* buf = g_fb + (i * g_fb_width * CELL_HEIGHT * BYTES_PER_PIXEL);
|
auto subcanvas = canvas.subcanvas(ui::Rect { j * CELL_WIDTH, i * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT });
|
||||||
|
|
||||||
auto& cell = find_cell(i, j);
|
auto& cell = find_cell(i, j);
|
||||||
u8 color = cell.state ? 0xff : 0x00;
|
ui::Color color = cell.state ? activated_cell_color : deactivated_cell_color;
|
||||||
for (int k = 0; k < CELL_HEIGHT; k++)
|
subcanvas.fill(color);
|
||||||
{
|
|
||||||
memset(buf + (j * CELL_WIDTH * BYTES_PER_PIXEL), color, CELL_WIDTH * BYTES_PER_PIXEL);
|
|
||||||
buf += g_fb_width * BYTES_PER_PIXEL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msync(g_fb, g_fb_height * g_fb_width * BYTES_PER_PIXEL, MS_SYNC);
|
g_window->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int find_neighbors(int row, int column)
|
static int find_neighbors(int row, int column)
|
||||||
@ -109,71 +103,30 @@ static void next_generation()
|
|||||||
|
|
||||||
Result<int> luna_main(int argc, char** argv)
|
Result<int> luna_main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
u64 delay_between_iterations = 250;
|
ui::App app;
|
||||||
u64 delay_at_end = 3000;
|
TRY(app.init(argc, argv));
|
||||||
u64 num_iterations = 100;
|
app.set_nonblocking();
|
||||||
|
|
||||||
StringView columns;
|
g_window = TRY(ui::Window::create(ui::Rect { 200, 200, 600, 400 }));
|
||||||
StringView rows;
|
g_window->set_title("Game of Life");
|
||||||
StringView delay;
|
app.set_main_window(g_window);
|
||||||
StringView end_delay;
|
|
||||||
StringView iterations;
|
|
||||||
StringView seed;
|
|
||||||
|
|
||||||
os::ArgumentParser parser;
|
|
||||||
parser.add_description("A framebuffer-based implementation for Conway's Game of Life.");
|
|
||||||
parser.add_system_program_info("gol"_sv);
|
|
||||||
parser.add_positional_argument(rows, "rows"_sv, "76"_sv);
|
|
||||||
parser.add_positional_argument(columns, "columns"_sv, "102"_sv);
|
|
||||||
parser.add_value_argument(delay, 'd', "delay"_sv, "the delay between generations (in ms)");
|
|
||||||
parser.add_value_argument(end_delay, 'e', "end-delay"_sv,
|
|
||||||
"after finishing, how much to wait before returning to the shell (in ms)");
|
|
||||||
parser.add_value_argument(iterations, 'i', "iterations"_sv, "how many generations to show (default: 100)");
|
|
||||||
parser.add_value_argument(seed, 's', "seed"_sv, "the seed for the random number generator");
|
|
||||||
parser.parse(argc, argv);
|
|
||||||
|
|
||||||
g_num_columns = (int)TRY(columns.to_uint());
|
|
||||||
g_num_rows = (int)TRY(rows.to_uint());
|
|
||||||
if (!delay.is_empty()) delay_between_iterations = TRY(delay.to_uint());
|
|
||||||
if (!end_delay.is_empty()) delay_at_end = TRY(end_delay.to_uint());
|
|
||||||
if (!iterations.is_empty()) num_iterations = TRY(iterations.to_uint());
|
|
||||||
if (!seed.is_empty()) srand((unsigned)TRY(seed.to_uint()));
|
|
||||||
else
|
|
||||||
srand((unsigned)time(NULL));
|
|
||||||
|
|
||||||
g_fd = open("/dev/fb0", O_RDWR);
|
|
||||||
if (g_fd < 0)
|
|
||||||
{
|
|
||||||
perror("gol: cannot open framebuffer for writing");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_fb_height = ioctl(g_fd, FB_GET_HEIGHT);
|
|
||||||
g_fb_width = ioctl(g_fd, FB_GET_WIDTH);
|
|
||||||
|
|
||||||
TRY(fill_cells());
|
TRY(fill_cells());
|
||||||
|
|
||||||
g_fb =
|
int counter = 0;
|
||||||
(char*)mmap(nullptr, g_fb_height * g_fb_width * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, 0);
|
|
||||||
if (g_fb == MAP_FAILED)
|
while (app.process_events())
|
||||||
{
|
{
|
||||||
perror("gol: cannot map framebuffer into memory");
|
if (counter >= 10)
|
||||||
return 1;
|
{
|
||||||
|
next_generation();
|
||||||
|
draw_cells();
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
counter++;
|
||||||
|
usleep(10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_cells();
|
|
||||||
|
|
||||||
while (num_iterations--)
|
|
||||||
{
|
|
||||||
usleep(delay_between_iterations * 1000);
|
|
||||||
next_generation();
|
|
||||||
draw_cells();
|
|
||||||
}
|
|
||||||
|
|
||||||
usleep(delay_at_end * 1000);
|
|
||||||
|
|
||||||
munmap(g_fb, g_fb_height * g_fb_width * BYTES_PER_PIXEL);
|
|
||||||
free(g_cells);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -339,6 +339,13 @@ static void mount_shmfs()
|
|||||||
if (chmod("/dev/shm", 01777) < 0) exit(255);
|
if (chmod("/dev/shm", 01777) < 0) exit(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mount_devpts()
|
||||||
|
{
|
||||||
|
if (mkdir("/dev/pts", 0755) < 0) exit(255);
|
||||||
|
|
||||||
|
if (mount("/dev/pts", "devpts", "devpts") < 0) exit(255);
|
||||||
|
}
|
||||||
|
|
||||||
Result<int> sysinit(StringView path)
|
Result<int> sysinit(StringView path)
|
||||||
{
|
{
|
||||||
if (getpid() != 1)
|
if (getpid() != 1)
|
||||||
@ -359,6 +366,7 @@ Result<int> sysinit(StringView path)
|
|||||||
|
|
||||||
mount_tmpfs();
|
mount_tmpfs();
|
||||||
mount_shmfs();
|
mount_shmfs();
|
||||||
|
mount_devpts();
|
||||||
|
|
||||||
umask(022);
|
umask(022);
|
||||||
|
|
||||||
|
65
apps/taskbar.cpp
Normal file
65
apps/taskbar.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include <os/File.h>
|
||||||
|
#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>
|
||||||
|
|
||||||
|
static constexpr ui::Color TASKBAR_COLOR = ui::Color::from_rgb(83, 83, 83);
|
||||||
|
|
||||||
|
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(TASKBAR_COLOR);
|
||||||
|
|
||||||
|
ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No);
|
||||||
|
window->set_main_widget(layout);
|
||||||
|
|
||||||
|
ui::Button term_button({ 0, 0, 50, 50 });
|
||||||
|
layout.add_widget(term_button);
|
||||||
|
|
||||||
|
ui::Container term_container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
|
||||||
|
term_button.set_widget(term_container);
|
||||||
|
term_button.set_action([] {
|
||||||
|
StringView args[] = { "/usr/bin/terminal" };
|
||||||
|
os::Process::spawn("/usr/bin/terminal", Slice<StringView> { args, 1 }, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto term_image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/app-terminal.tga"));
|
||||||
|
term_container.set_widget(*term_image);
|
||||||
|
|
||||||
|
ui::Button about_button({ 0, 0, 50, 50 });
|
||||||
|
layout.add_widget(about_button);
|
||||||
|
|
||||||
|
ui::Container about_container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
|
||||||
|
about_button.set_widget(about_container);
|
||||||
|
about_button.set_action([] {
|
||||||
|
StringView args[] = { "/usr/bin/about" };
|
||||||
|
os::Process::spawn("/usr/bin/about", Slice<StringView> { args, 1 }, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto about_image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/app-about.tga"));
|
||||||
|
about_container.set_widget(*about_image);
|
||||||
|
|
||||||
|
window->draw();
|
||||||
|
|
||||||
|
return app.run();
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
Name=motd
|
|
||||||
Description=Show the message of the day to the user.
|
|
||||||
Command=/usr/bin/cat /etc/motd
|
|
||||||
Wait=true
|
|
@ -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
|
||||||
|
4
base/etc/user/00-taskbar
Normal file
4
base/etc/user/00-taskbar
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Name=taskbar
|
||||||
|
Description=Start the taskbar.
|
||||||
|
Command=/usr/bin/taskbar
|
||||||
|
Restart=true
|
3
base/etc/user/01-terminal
Normal file
3
base/etc/user/01-terminal
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Name=terminal
|
||||||
|
Description=Start the terminal.
|
||||||
|
Command=/usr/bin/terminal
|
BIN
base/usr/share/cursors/default.tga
Normal file
BIN
base/usr/share/cursors/default.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 1004 B |
BIN
base/usr/share/fonts/Tamsyn-Bold.psf
Normal file
BIN
base/usr/share/fonts/Tamsyn-Bold.psf
Normal file
Binary file not shown.
BIN
base/usr/share/fonts/Tamsyn-Regular.psf
Normal file
BIN
base/usr/share/fonts/Tamsyn-Regular.psf
Normal file
Binary file not shown.
BIN
base/usr/share/icons/16x16/app-close.tga
Normal file
BIN
base/usr/share/icons/16x16/app-close.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
base/usr/share/icons/32x32/app-terminal.tga
Normal file
BIN
base/usr/share/icons/32x32/app-terminal.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
@ -58,6 +58,8 @@ set(SOURCES
|
|||||||
src/net/UnixSocket.cpp
|
src/net/UnixSocket.cpp
|
||||||
src/fs/tmpfs/FileSystem.cpp
|
src/fs/tmpfs/FileSystem.cpp
|
||||||
src/fs/tmpfs/Inode.cpp
|
src/fs/tmpfs/Inode.cpp
|
||||||
|
src/fs/devpts/FileSystem.cpp
|
||||||
|
src/fs/devpts/Inode.cpp
|
||||||
src/fs/ext2/FileSystem.cpp
|
src/fs/ext2/FileSystem.cpp
|
||||||
src/fs/ext2/Inode.cpp
|
src/fs/ext2/Inode.cpp
|
||||||
src/fs/devices/DeviceRegistry.cpp
|
src/fs/devices/DeviceRegistry.cpp
|
||||||
@ -70,6 +72,9 @@ set(SOURCES
|
|||||||
src/fs/devices/UARTDevice.cpp
|
src/fs/devices/UARTDevice.cpp
|
||||||
src/fs/devices/MouseDevice.cpp
|
src/fs/devices/MouseDevice.cpp
|
||||||
src/fs/devices/KeyboardDevice.cpp
|
src/fs/devices/KeyboardDevice.cpp
|
||||||
|
src/fs/devices/PTYMultiplexer.cpp
|
||||||
|
src/fs/devices/MasterPTY.cpp
|
||||||
|
src/fs/devices/SlavePTY.cpp
|
||||||
src/fs/InitRD.cpp
|
src/fs/InitRD.cpp
|
||||||
src/binfmt/ELF.cpp
|
src/binfmt/ELF.cpp
|
||||||
src/binfmt/BinaryFormat.cpp
|
src/binfmt/BinaryFormat.cpp
|
||||||
|
@ -118,6 +118,11 @@ namespace VFS
|
|||||||
return err(EACCES);
|
return err(EACCES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual Result<SharedPtr<Inode>> open()
|
||||||
|
{
|
||||||
|
return SharedPtr<Inode> { this };
|
||||||
|
}
|
||||||
|
|
||||||
// Directory-specific methods
|
// Directory-specific methods
|
||||||
virtual Result<SharedPtr<Inode>> find(const char* name) const = 0;
|
virtual Result<SharedPtr<Inode>> find(const char* name) const = 0;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "fs/devices/KeyboardDevice.h"
|
#include "fs/devices/KeyboardDevice.h"
|
||||||
#include "fs/devices/MouseDevice.h"
|
#include "fs/devices/MouseDevice.h"
|
||||||
#include "fs/devices/NullDevice.h"
|
#include "fs/devices/NullDevice.h"
|
||||||
|
#include "fs/devices/PTYMultiplexer.h"
|
||||||
#include "fs/devices/UARTDevice.h"
|
#include "fs/devices/UARTDevice.h"
|
||||||
#include "fs/devices/ZeroDevice.h"
|
#include "fs/devices/ZeroDevice.h"
|
||||||
#include "fs/tmpfs/FileSystem.h"
|
#include "fs/tmpfs/FileSystem.h"
|
||||||
@ -92,6 +93,12 @@ namespace DeviceRegistry
|
|||||||
|
|
||||||
for (const auto& descriptor : g_available_devices) TRY(create_special_device_inode(fs, descriptor));
|
for (const auto& descriptor : g_available_devices) TRY(create_special_device_inode(fs, descriptor));
|
||||||
|
|
||||||
|
auto multiplexer = TRY(make_shared<PTYMultiplexer>());
|
||||||
|
multiplexer->set_fs(*fs);
|
||||||
|
multiplexer->set_inode_number(TRY(fs->allocate_inode_number()));
|
||||||
|
|
||||||
|
TRY(fs->root_inode()->add_entry(multiplexer, "ptmx"));
|
||||||
|
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ namespace DeviceRegistry
|
|||||||
DiskPartition = 5,
|
DiskPartition = 5,
|
||||||
Serial = 6,
|
Serial = 6,
|
||||||
Input = 7,
|
Input = 7,
|
||||||
|
Terminal = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);
|
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);
|
||||||
|
125
kernel/src/fs/devices/MasterPTY.cpp
Normal file
125
kernel/src/fs/devices/MasterPTY.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#include "fs/devices/MasterPTY.h"
|
||||||
|
#include "Pledge.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include "fs/devices/PTYMultiplexer.h"
|
||||||
|
#include "fs/devpts/FileSystem.h"
|
||||||
|
#include "memory/MemoryManager.h"
|
||||||
|
#include "thread/Scheduler.h"
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> MasterPTY::create_pair(int index)
|
||||||
|
{
|
||||||
|
auto master = TRY(make_shared<MasterPTY>());
|
||||||
|
auto slave = TRY(make_shared<SlavePTY>());
|
||||||
|
|
||||||
|
auto name = TRY(String::format("%d"_sv, index));
|
||||||
|
for (auto& fs : g_devpts_instances) { fs->root_inode()->add_entry(slave, name.chars()); }
|
||||||
|
slave->m_name = move(name);
|
||||||
|
|
||||||
|
master->m_metadata.mode = 0666;
|
||||||
|
master->m_index = index;
|
||||||
|
master->m_slave = slave;
|
||||||
|
master->m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, 0);
|
||||||
|
master->m_settings.c_lflag = ECHO | ECHOE | ECHOCTL | ISIG | ICANON;
|
||||||
|
master->m_settings.c_cc[VEOF] = '\4';
|
||||||
|
master->m_settings.c_cc[VERASE] = '\b';
|
||||||
|
master->m_settings.c_cc[VINTR] = '\3';
|
||||||
|
master->m_settings.c_cc[VQUIT] = '\x1c';
|
||||||
|
master->m_window.ws_col = 80;
|
||||||
|
master->m_window.ws_row = 25;
|
||||||
|
|
||||||
|
slave->m_master = master.ptr();
|
||||||
|
slave->m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, index + 1);
|
||||||
|
slave->m_metadata.uid = Scheduler::current()->auth.euid;
|
||||||
|
slave->m_metadata.gid = Scheduler::current()->auth.egid;
|
||||||
|
slave->m_metadata.mode = 0620;
|
||||||
|
|
||||||
|
return (SharedPtr<VFS::Inode>)master;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> MasterPTY::handle_background_process_group(bool can_succeed, int signo) const
|
||||||
|
{
|
||||||
|
if (!m_foreground_process_group.has_value()) return {};
|
||||||
|
|
||||||
|
auto foreground_pgrp = m_foreground_process_group.value();
|
||||||
|
|
||||||
|
auto* current = Scheduler::current();
|
||||||
|
if (current->pgid == foreground_pgrp) return {};
|
||||||
|
|
||||||
|
if ((current->signal_mask.get(signo - 1)) || (current->signal_handlers[signo - 1].sa_handler == SIG_IGN))
|
||||||
|
{
|
||||||
|
if (can_succeed) return {};
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
current->send_signal(signo);
|
||||||
|
|
||||||
|
if (can_succeed) return err(EINTR);
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> MasterPTY::read(u8* buf, usize, usize length) const
|
||||||
|
{
|
||||||
|
length = m_buffer.dequeue_data(buf, length);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> MasterPTY::write(const u8* buf, usize, usize length)
|
||||||
|
{
|
||||||
|
TRY(m_slave->m_buffer.append_data(buf, length));
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> MasterPTY::ioctl(int request, void* arg)
|
||||||
|
{
|
||||||
|
auto* current = Scheduler::current();
|
||||||
|
TRY(check_pledge(current, Promise::p_tty));
|
||||||
|
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
case TCGETS: {
|
||||||
|
return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT);
|
||||||
|
}
|
||||||
|
case TCSETS: {
|
||||||
|
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCSPGRP: {
|
||||||
|
pid_t pgid;
|
||||||
|
if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT);
|
||||||
|
|
||||||
|
bool pgid_exists = false;
|
||||||
|
Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) {
|
||||||
|
pgid_exists = true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (!pgid_exists) return err(EPERM);
|
||||||
|
|
||||||
|
m_foreground_process_group = pgid;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCGPGRP: {
|
||||||
|
pid_t pgid = m_foreground_process_group.value_or((pid_t)next_thread_id());
|
||||||
|
if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCGWINSZ: {
|
||||||
|
if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &m_window)) return err(EFAULT);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCGPTN: {
|
||||||
|
if (!MemoryManager::copy_to_user_typed((int*)arg, &m_index)) return err(EFAULT);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
default: return err(EINVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MasterPTY::~MasterPTY()
|
||||||
|
{
|
||||||
|
m_slave->m_master = nullptr;
|
||||||
|
for (auto& fs : g_devpts_instances) { fs->root_inode()->remove_entry(m_slave->m_name.chars()); }
|
||||||
|
PTYMultiplexer::did_remove_pty(m_index);
|
||||||
|
}
|
75
kernel/src/fs/devices/MasterPTY.h
Normal file
75
kernel/src/fs/devices/MasterPTY.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/devices/SlavePTY.h"
|
||||||
|
#include <bits/termios.h>
|
||||||
|
#include <luna/Buffer.h>
|
||||||
|
|
||||||
|
class MasterPTY : public VFS::DeviceInode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MasterPTY() = default;
|
||||||
|
|
||||||
|
static Result<SharedPtr<VFS::Inode>> create_pair(int index);
|
||||||
|
|
||||||
|
VFS::InodeType type() const override
|
||||||
|
{
|
||||||
|
return VFS::InodeType::CharacterDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> query_shared_memory(off_t, usize) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
VFS::FileSystem* fs() const override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> read(u8* buf, usize offset, usize length) const override;
|
||||||
|
|
||||||
|
Result<usize> write(const u8* buf, usize offset, usize length) override;
|
||||||
|
|
||||||
|
Result<u64> ioctl(int request, void* arg) override;
|
||||||
|
|
||||||
|
Result<u64> isatty() const override
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> truncate(usize) override
|
||||||
|
{
|
||||||
|
// POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files.
|
||||||
|
return err(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool will_block_if_read() const override
|
||||||
|
{
|
||||||
|
return m_buffer.is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_link() override
|
||||||
|
{
|
||||||
|
m_metadata.nlinks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_unlink() override
|
||||||
|
{
|
||||||
|
m_metadata.nlinks--;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~MasterPTY();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct termios m_settings;
|
||||||
|
mutable Buffer m_buffer;
|
||||||
|
SharedPtr<SlavePTY> m_slave;
|
||||||
|
mutable Option<pid_t> m_foreground_process_group;
|
||||||
|
struct winsize m_window;
|
||||||
|
|
||||||
|
Result<void> handle_background_process_group(bool can_succeed, int signo) const;
|
||||||
|
|
||||||
|
int m_index;
|
||||||
|
|
||||||
|
friend class SlavePTY;
|
||||||
|
};
|
36
kernel/src/fs/devices/PTYMultiplexer.cpp
Normal file
36
kernel/src/fs/devices/PTYMultiplexer.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "fs/devices/PTYMultiplexer.h"
|
||||||
|
|
||||||
|
Bitset<u64> PTYMultiplexer::m_available_indexes = 0;
|
||||||
|
|
||||||
|
PTYMultiplexer::PTYMultiplexer()
|
||||||
|
{
|
||||||
|
m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, 0);
|
||||||
|
m_metadata.mode = 0666;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> PTYMultiplexer::open()
|
||||||
|
{
|
||||||
|
int index = -1;
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
if (!m_available_indexes.get(i))
|
||||||
|
{
|
||||||
|
index = i;
|
||||||
|
m_available_indexes.set(i, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index == -1) return err(ENOSPC);
|
||||||
|
|
||||||
|
return MasterPTY::create_pair(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PTYMultiplexer::init()
|
||||||
|
{
|
||||||
|
m_available_indexes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PTYMultiplexer::did_remove_pty(int index)
|
||||||
|
{
|
||||||
|
m_available_indexes.set(index, false);
|
||||||
|
}
|
80
kernel/src/fs/devices/PTYMultiplexer.h
Normal file
80
kernel/src/fs/devices/PTYMultiplexer.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include "fs/devices/MasterPTY.h"
|
||||||
|
#include <luna/Bitset.h>
|
||||||
|
|
||||||
|
class PTYMultiplexer : public VFS::DeviceInode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PTYMultiplexer();
|
||||||
|
|
||||||
|
VFS::InodeType type() const override
|
||||||
|
{
|
||||||
|
return VFS::InodeType::CharacterDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_fs(VFS::FileSystem& fs)
|
||||||
|
{
|
||||||
|
m_fs = &fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_inode_number(usize inum)
|
||||||
|
{
|
||||||
|
m_metadata.inum = inum;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> query_shared_memory(off_t, usize) override
|
||||||
|
{
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> open() override;
|
||||||
|
|
||||||
|
VFS::FileSystem* fs() const override
|
||||||
|
{
|
||||||
|
return m_fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> read(u8*, usize, usize) const override
|
||||||
|
{
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> write(const u8*, usize, usize) override
|
||||||
|
{
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> truncate(usize) override
|
||||||
|
{
|
||||||
|
// POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files.
|
||||||
|
return err(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool will_block_if_read() const override
|
||||||
|
{
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_link() override
|
||||||
|
{
|
||||||
|
m_metadata.nlinks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_unlink() override
|
||||||
|
{
|
||||||
|
m_metadata.nlinks--;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init();
|
||||||
|
|
||||||
|
static void did_remove_pty(int index);
|
||||||
|
|
||||||
|
virtual ~PTYMultiplexer() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VFS::FileSystem* m_fs;
|
||||||
|
|
||||||
|
static Bitset<u64> m_available_indexes;
|
||||||
|
};
|
71
kernel/src/fs/devices/SlavePTY.cpp
Normal file
71
kernel/src/fs/devices/SlavePTY.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include "fs/devices/SlavePTY.h"
|
||||||
|
#include "Pledge.h"
|
||||||
|
#include "fs/devices/MasterPTY.h"
|
||||||
|
#include "memory/MemoryManager.h"
|
||||||
|
#include "thread/Scheduler.h"
|
||||||
|
|
||||||
|
Result<usize> SlavePTY::read(u8* buf, usize, usize length) const
|
||||||
|
{
|
||||||
|
if (!m_master) return err(EIO);
|
||||||
|
|
||||||
|
TRY(m_master->handle_background_process_group(false, SIGTTIN));
|
||||||
|
|
||||||
|
length = m_buffer.dequeue_data(buf, length);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> SlavePTY::write(const u8* buf, usize, usize length)
|
||||||
|
{
|
||||||
|
if (!m_master) return err(EIO);
|
||||||
|
|
||||||
|
if (m_master->m_settings.c_lflag & TOSTOP) TRY(m_master->handle_background_process_group(true, SIGTTOU));
|
||||||
|
|
||||||
|
TRY(m_master->m_buffer.append_data(buf, length));
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> SlavePTY::ioctl(int request, void* arg)
|
||||||
|
{
|
||||||
|
auto* current = Scheduler::current();
|
||||||
|
TRY(check_pledge(current, Promise::p_tty));
|
||||||
|
|
||||||
|
if (!m_master) return err(EIO);
|
||||||
|
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
case TCGETS: {
|
||||||
|
return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_master->m_settings) ? 0 : err(EFAULT);
|
||||||
|
}
|
||||||
|
case TCSETS: {
|
||||||
|
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_master->m_settings)) return err(EFAULT);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCSPGRP: {
|
||||||
|
pid_t pgid;
|
||||||
|
if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT);
|
||||||
|
|
||||||
|
bool pgid_exists = false;
|
||||||
|
Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) {
|
||||||
|
pgid_exists = true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (!pgid_exists) return err(EPERM);
|
||||||
|
|
||||||
|
m_master->m_foreground_process_group = pgid;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCGPGRP: {
|
||||||
|
pid_t pgid = m_master->m_foreground_process_group.value_or((pid_t)next_thread_id());
|
||||||
|
if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case TIOCGWINSZ: {
|
||||||
|
if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &m_master->m_window)) return err(EFAULT);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
default: return err(EINVAL);
|
||||||
|
}
|
||||||
|
}
|
69
kernel/src/fs/devices/SlavePTY.h
Normal file
69
kernel/src/fs/devices/SlavePTY.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "fs/VFS.h"
|
||||||
|
#include <luna/Buffer.h>
|
||||||
|
#include <luna/String.h>
|
||||||
|
|
||||||
|
class MasterPTY;
|
||||||
|
|
||||||
|
class SlavePTY : public VFS::DeviceInode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SlavePTY() = default;
|
||||||
|
|
||||||
|
VFS::InodeType type() const override
|
||||||
|
{
|
||||||
|
return VFS::InodeType::CharacterDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> query_shared_memory(off_t, usize) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
VFS::FileSystem* fs() const override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> read(u8* buf, usize offset, usize length) const override;
|
||||||
|
|
||||||
|
Result<usize> write(const u8* buf, usize offset, usize length) override;
|
||||||
|
|
||||||
|
Result<u64> ioctl(int request, void* arg) override;
|
||||||
|
|
||||||
|
Result<u64> isatty() const override
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> truncate(usize) override
|
||||||
|
{
|
||||||
|
// POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files.
|
||||||
|
return err(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool will_block_if_read() const override
|
||||||
|
{
|
||||||
|
return m_buffer.is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_link() override
|
||||||
|
{
|
||||||
|
m_metadata.nlinks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_unlink() override
|
||||||
|
{
|
||||||
|
m_metadata.nlinks--;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~SlavePTY() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable Buffer m_buffer;
|
||||||
|
|
||||||
|
MasterPTY* m_master;
|
||||||
|
String m_name;
|
||||||
|
|
||||||
|
friend class MasterPTY;
|
||||||
|
};
|
62
kernel/src/fs/devpts/FileSystem.cpp
Normal file
62
kernel/src/fs/devpts/FileSystem.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include "fs/devpts/FileSystem.h"
|
||||||
|
#include "arch/Timer.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include "fs/devpts/Inode.h"
|
||||||
|
#include <luna/Alloc.h>
|
||||||
|
#include <luna/CString.h>
|
||||||
|
#include <luna/Ignore.h>
|
||||||
|
|
||||||
|
Vector<VFS::FileSystem*> g_devpts_instances;
|
||||||
|
|
||||||
|
namespace DevPTS
|
||||||
|
{
|
||||||
|
Result<SharedPtr<VFS::FileSystem>> FileSystem::create()
|
||||||
|
{
|
||||||
|
SharedPtr<FileSystem> fs = TRY(adopt_shared_if_nonnull(new (std::nothrow) FileSystem()));
|
||||||
|
SharedPtr<RootInode> root = TRY(make_shared<RootInode>());
|
||||||
|
|
||||||
|
TRY(root->add_entry(root, "."));
|
||||||
|
TRY(root->add_entry(root, ".."));
|
||||||
|
|
||||||
|
root->set_self(root, {});
|
||||||
|
root->set_fs(*fs, {});
|
||||||
|
root->set_inode_number();
|
||||||
|
root->m_metadata.mode = 0755;
|
||||||
|
root->m_metadata.atime = root->m_metadata.ctime = root->m_metadata.mtime = *Timer::realtime_clock();
|
||||||
|
fs->set_root(root);
|
||||||
|
|
||||||
|
TRY(g_devpts_instances.try_append(fs.ptr()));
|
||||||
|
|
||||||
|
return (SharedPtr<VFS::FileSystem>)fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> FileSystem::allocate_inode_number()
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystem::FileSystem()
|
||||||
|
{
|
||||||
|
m_host_device_id = DeviceRegistry::next_null_device_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> FileSystem::set_mount_dir(SharedPtr<VFS::Inode> parent)
|
||||||
|
{
|
||||||
|
return m_root_inode->replace_entry(parent, "..");
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> FileSystem::reset_mount_dir()
|
||||||
|
{
|
||||||
|
return m_root_inode->replace_entry(m_root_inode, "..");
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSystem::set_root(SharedPtr<VFS::Inode> root)
|
||||||
|
{
|
||||||
|
m_root_inode = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystem::~FileSystem()
|
||||||
|
{
|
||||||
|
g_devpts_instances.remove_first_matching([this](VFS::FileSystem* ptr) { return ptr == this; });
|
||||||
|
}
|
||||||
|
}
|
61
kernel/src/fs/devpts/FileSystem.h
Normal file
61
kernel/src/fs/devpts/FileSystem.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
|
||||||
|
namespace DevPTS
|
||||||
|
{
|
||||||
|
class FileSystem : public VFS::FileSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SharedPtr<VFS::Inode> root_inode() const override
|
||||||
|
{
|
||||||
|
return m_root_inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_file_inode(mode_t) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_dir_inode(SharedPtr<VFS::Inode>, mode_t) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_device_inode(u32, u32, mode_t) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_symlink_inode(StringView) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> allocate_inode_number() override;
|
||||||
|
|
||||||
|
Result<void> set_mount_dir(SharedPtr<VFS::Inode> parent) override;
|
||||||
|
|
||||||
|
Result<void> reset_mount_dir() override;
|
||||||
|
|
||||||
|
static Result<SharedPtr<VFS::FileSystem>> create();
|
||||||
|
|
||||||
|
dev_t host_device_id() const override
|
||||||
|
{
|
||||||
|
return m_host_device_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~FileSystem();
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileSystem();
|
||||||
|
|
||||||
|
void set_root(SharedPtr<VFS::Inode> root);
|
||||||
|
|
||||||
|
SharedPtr<VFS::Inode> m_root_inode;
|
||||||
|
|
||||||
|
dev_t m_host_device_id;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extern Vector<VFS::FileSystem*> g_devpts_instances;
|
78
kernel/src/fs/devpts/Inode.cpp
Normal file
78
kernel/src/fs/devpts/Inode.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#include "fs/devpts/Inode.h"
|
||||||
|
|
||||||
|
namespace DevPTS
|
||||||
|
{
|
||||||
|
Result<SharedPtr<VFS::Inode>> RootInode::find(const char* name) const
|
||||||
|
{
|
||||||
|
for (const auto& entry : m_entries)
|
||||||
|
{
|
||||||
|
if (!strcmp(name, entry.name.chars())) return entry.inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> RootInode::replace_entry(SharedPtr<VFS::Inode> inode, const char* name)
|
||||||
|
{
|
||||||
|
for (auto& entry : m_entries)
|
||||||
|
{
|
||||||
|
if (!strcmp(name, entry.name.chars()))
|
||||||
|
{
|
||||||
|
entry.inode = inode;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<VFS::DirectoryEntry> RootInode::get(usize index) const
|
||||||
|
{
|
||||||
|
if (index >= m_entries.size()) return {};
|
||||||
|
|
||||||
|
return m_entries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> RootInode::add_entry(SharedPtr<VFS::Inode> inode, const char* name)
|
||||||
|
{
|
||||||
|
if (find(name).has_value()) return err(EEXIST);
|
||||||
|
|
||||||
|
VFS::DirectoryEntry entry { inode, name };
|
||||||
|
|
||||||
|
TRY(m_entries.try_append(move(entry)));
|
||||||
|
|
||||||
|
inode->did_link();
|
||||||
|
|
||||||
|
m_metadata.mtime = *Timer::realtime_clock();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> RootInode::remove_entry(const char* name)
|
||||||
|
{
|
||||||
|
SharedPtr<VFS::Inode> inode = TRY(find(name));
|
||||||
|
|
||||||
|
if (inode->type() == VFS::InodeType::Directory && inode->entries() != 2) return err(ENOTEMPTY);
|
||||||
|
|
||||||
|
if (inode->is_mountpoint()) return err(EBUSY);
|
||||||
|
|
||||||
|
m_entries.remove_first_matching(
|
||||||
|
[&](const VFS::DirectoryEntry& entry) { return !strcmp(entry.name.chars(), name); });
|
||||||
|
|
||||||
|
inode->did_unlink();
|
||||||
|
|
||||||
|
m_metadata.mtime = *Timer::realtime_clock();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> RootInode::create_file(const char*, mode_t)
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> RootInode::create_subdirectory(const char*, mode_t)
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
}
|
93
kernel/src/fs/devpts/Inode.h
Normal file
93
kernel/src/fs/devpts/Inode.h
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include "fs/devpts/FileSystem.h"
|
||||||
|
|
||||||
|
namespace DevPTS
|
||||||
|
{
|
||||||
|
class RootInode : public VFS::Inode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RootInode() = default;
|
||||||
|
|
||||||
|
void set_fs(FileSystem& fs, Badge<FileSystem>)
|
||||||
|
{
|
||||||
|
m_fs = &fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_inode_number()
|
||||||
|
{
|
||||||
|
m_metadata.inum = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_self(SharedPtr<VFS::Inode> self, Badge<FileSystem>)
|
||||||
|
{
|
||||||
|
m_self = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> find(const char* name) const override;
|
||||||
|
Option<VFS::DirectoryEntry> get(usize index) const override;
|
||||||
|
|
||||||
|
Result<usize> read(u8*, usize, usize) const override
|
||||||
|
{
|
||||||
|
return err(EISDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<usize> write(const u8*, usize, usize) override
|
||||||
|
{
|
||||||
|
return err(EISDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> truncate(usize) override
|
||||||
|
{
|
||||||
|
return err(EISDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool will_block_if_read() const override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VFS::FileSystem* fs() const override
|
||||||
|
{
|
||||||
|
return m_fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
VFS::InodeType type() const override
|
||||||
|
{
|
||||||
|
return VFS::InodeType::Directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_link() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void did_unlink() override
|
||||||
|
{
|
||||||
|
m_self = {};
|
||||||
|
m_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
usize entries() const override
|
||||||
|
{
|
||||||
|
return m_entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> remove_entry(const char* name) override;
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_file(const char* name, mode_t mode) override;
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_subdirectory(const char* name, mode_t mode) override;
|
||||||
|
|
||||||
|
Result<void> add_entry(SharedPtr<VFS::Inode> inode, const char* name);
|
||||||
|
Result<void> replace_entry(SharedPtr<VFS::Inode> inode, const char* name);
|
||||||
|
|
||||||
|
virtual ~RootInode() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VFS::FileSystem* m_fs;
|
||||||
|
SharedPtr<VFS::Inode> m_self;
|
||||||
|
Vector<VFS::DirectoryEntry> m_entries;
|
||||||
|
|
||||||
|
friend class FileSystem;
|
||||||
|
};
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "fs/InitRD.h"
|
#include "fs/InitRD.h"
|
||||||
#include "fs/devices/DeviceRegistry.h"
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include "fs/devices/PTYMultiplexer.h"
|
||||||
#include "fs/tmpfs/FileSystem.h"
|
#include "fs/tmpfs/FileSystem.h"
|
||||||
#include "memory/MemoryManager.h"
|
#include "memory/MemoryManager.h"
|
||||||
#include "thread/Scheduler.h"
|
#include "thread/Scheduler.h"
|
||||||
@ -94,6 +95,7 @@ extern "C" [[noreturn]] void _start()
|
|||||||
|
|
||||||
Thread::init();
|
Thread::init();
|
||||||
Scheduler::init();
|
Scheduler::init();
|
||||||
|
PTYMultiplexer::init();
|
||||||
|
|
||||||
Scheduler::new_kernel_thread(init, "[kinit]");
|
Scheduler::new_kernel_thread(init, "[kinit]");
|
||||||
|
|
||||||
|
@ -157,6 +157,13 @@ namespace MemoryManager
|
|||||||
used_mem += ARCH_PAGE_SIZE;
|
used_mem += ARCH_PAGE_SIZE;
|
||||||
free_mem -= ARCH_PAGE_SIZE;
|
free_mem -= ARCH_PAGE_SIZE;
|
||||||
|
|
||||||
|
if (free_mem < 4 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
// Less than 4 MiB of free memory! Let's start clearing caches...
|
||||||
|
kwarnln("Less than 4 MiB of free memory, clearing caches to try to gain extra memory");
|
||||||
|
Scheduler::signal_oom_thread();
|
||||||
|
}
|
||||||
|
|
||||||
return index * ARCH_PAGE_SIZE;
|
return index * ARCH_PAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "Pledge.h"
|
#include "Pledge.h"
|
||||||
#include "fs/VFS.h"
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/devpts/FileSystem.h"
|
||||||
#include "fs/ext2/FileSystem.h"
|
#include "fs/ext2/FileSystem.h"
|
||||||
#include "fs/tmpfs/FileSystem.h"
|
#include "fs/tmpfs/FileSystem.h"
|
||||||
#include "memory/MemoryManager.h"
|
#include "memory/MemoryManager.h"
|
||||||
@ -26,6 +27,8 @@ Result<u64> sys_mount(Registers*, SyscallArgs args)
|
|||||||
SharedPtr<VFS::FileSystem> fs;
|
SharedPtr<VFS::FileSystem> fs;
|
||||||
|
|
||||||
if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create());
|
if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create());
|
||||||
|
else if (fstype.view() == "devpts")
|
||||||
|
fs = TRY(DevPTS::FileSystem::create());
|
||||||
else if (fstype.view() == "devfs")
|
else if (fstype.view() == "devfs")
|
||||||
fs = TRY(DeviceRegistry::create_devfs_instance());
|
fs = TRY(DeviceRegistry::create_devfs_instance());
|
||||||
else if (fstype.view() == "ext2")
|
else if (fstype.view() == "ext2")
|
||||||
|
@ -62,6 +62,8 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
|
|||||||
if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth)) return err(EACCES);
|
if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth)) return err(EACCES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inode = TRY(inode->open());
|
||||||
|
|
||||||
// This should only be possible if O_NOFOLLOW was in flags.
|
// This should only be possible if O_NOFOLLOW was in flags.
|
||||||
if (inode->type() == VFS::InodeType::Symlink) return err(ELOOP);
|
if (inode->type() == VFS::InodeType::Symlink) return err(ELOOP);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,16 +54,65 @@ Result<u64> sys_kill(Registers*, SyscallArgs args)
|
|||||||
pid_t pid = (pid_t)args[0];
|
pid_t pid = (pid_t)args[0];
|
||||||
int signo = (int)args[1];
|
int signo = (int)args[1];
|
||||||
|
|
||||||
// FIXME: Support this case.
|
auto send_signal = [&](Thread* target) -> Result<void> {
|
||||||
if (pid <= 0) return err(ENOTSUP);
|
if (current->auth.euid != 0 && current->auth.euid != target->auth.euid &&
|
||||||
|
current->auth.egid != target->auth.egid)
|
||||||
|
return err(EPERM);
|
||||||
|
if (target->is_kernel) return {};
|
||||||
|
if (signo == 0) return {};
|
||||||
|
|
||||||
auto* target = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
|
target->send_signal(signo);
|
||||||
if (current->auth.euid != 0 && current->auth.euid != target->auth.euid && current->auth.egid != target->auth.egid)
|
|
||||||
return err(EPERM);
|
|
||||||
if (target->is_kernel) return 0;
|
|
||||||
if (signo == 0) return 0;
|
|
||||||
|
|
||||||
target->send_signal(signo);
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
auto* target = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
|
||||||
|
TRY(send_signal(target));
|
||||||
|
}
|
||||||
|
else if (pid == 0)
|
||||||
|
{
|
||||||
|
int errno = -1;
|
||||||
|
bool pgid_exists = false;
|
||||||
|
Scheduler::for_each_in_process_group(current->pgid, [&](Thread* target) {
|
||||||
|
pgid_exists = true;
|
||||||
|
auto rc = send_signal(target);
|
||||||
|
if (rc.has_error())
|
||||||
|
{
|
||||||
|
errno = rc.error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (errno > 0) return err(errno);
|
||||||
|
if (!pgid_exists) return err(ESRCH);
|
||||||
|
}
|
||||||
|
else if (pid == -1)
|
||||||
|
{
|
||||||
|
for (auto* thread : g_threads)
|
||||||
|
{
|
||||||
|
// We ignore permission errors here.
|
||||||
|
if (thread != current && thread->id != 1) send_signal(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pid < -1)
|
||||||
|
{
|
||||||
|
int errno = -1;
|
||||||
|
bool pgid_exists = false;
|
||||||
|
Scheduler::for_each_in_process_group(-pid, [&](Thread* target) {
|
||||||
|
pgid_exists = true;
|
||||||
|
auto rc = send_signal(target);
|
||||||
|
if (rc.has_error())
|
||||||
|
{
|
||||||
|
errno = rc.error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (errno > 0) return err(errno);
|
||||||
|
if (!pgid_exists) return err(ESRCH);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ void Thread::send_signal(int signo)
|
|||||||
check(signo > 0 && signo <= NSIG);
|
check(signo > 0 && signo <= NSIG);
|
||||||
pending_signals.set(signo - 1, true);
|
pending_signals.set(signo - 1, true);
|
||||||
|
|
||||||
if (state == ThreadState::Waiting || state == ThreadState::Sleeping)
|
if (state == ThreadState::Waiting || state == ThreadState::Sleeping || is_in_kernel(®s))
|
||||||
{
|
{
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
wake_up();
|
wake_up();
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -45,5 +45,6 @@ struct winsize
|
|||||||
#define TIOCSPGRP 2
|
#define TIOCSPGRP 2
|
||||||
#define TIOCGPGRP 3
|
#define TIOCGPGRP 3
|
||||||
#define TIOCGWINSZ 4
|
#define TIOCGWINSZ 4
|
||||||
|
#define TIOCGPTN 5
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -148,6 +148,18 @@ extern "C"
|
|||||||
/* Create a unique file from a template string whose last 6 bytes must be XXXXXX. */
|
/* Create a unique file from a template string whose last 6 bytes must be XXXXXX. */
|
||||||
int mkstemp(char* _template);
|
int mkstemp(char* _template);
|
||||||
|
|
||||||
|
/* Create a new pseudoterminal pair. */
|
||||||
|
int posix_openpt(int flags);
|
||||||
|
|
||||||
|
/* Set the credentials of a pseudoterminal master. */
|
||||||
|
int grantpt(int fd);
|
||||||
|
|
||||||
|
/* Unlock a pseudoterminal master. */
|
||||||
|
int unlockpt(int fd);
|
||||||
|
|
||||||
|
/* Return the name of the slave associated with a pseudoterminal master. */
|
||||||
|
char* ptsname(int fd);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <bits/errno-return.h>
|
#include <bits/errno-return.h>
|
||||||
|
#include <bits/termios.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
@ -7,8 +8,10 @@
|
|||||||
#include <luna/Sort.h>
|
#include <luna/Sort.h>
|
||||||
#include <luna/Utf8.h>
|
#include <luna/Utf8.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
@ -300,4 +303,31 @@ extern "C"
|
|||||||
{
|
{
|
||||||
return strtod(str, nullptr);
|
return strtod(str, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int posix_openpt(int flags)
|
||||||
|
{
|
||||||
|
return open("/dev/ptmx", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
int grantpt(int)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int unlockpt(int)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* ptsname(int fd)
|
||||||
|
{
|
||||||
|
static char buffer[4096];
|
||||||
|
|
||||||
|
int index;
|
||||||
|
if (ioctl(fd, TIOCGPTN, &index) < 0) return nullptr;
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "/dev/pts/%d", index);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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})
|
||||||
|
157
libos/include/os/IPC.h
Normal file
157
libos/include/os/IPC.h
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
99
libos/include/os/LocalClient.h
Normal file
99
libos/include/os/LocalClient.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
142
libos/include/os/LocalServer.h
Normal file
142
libos/include/os/LocalServer.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
36
libos/include/os/SharedMemory.h
Normal file
36
libos/include/os/SharedMemory.h
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
43
libos/src/IPC.cpp
Normal file
43
libos/src/IPC.cpp
Normal 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
68
libos/src/LocalClient.cpp
Normal 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
101
libos/src/LocalServer.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
77
libos/src/SharedMemory.cpp
Normal file
77
libos/src/SharedMemory.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
31
libui/CMakeLists.txt
Normal file
31
libui/CMakeLists.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# 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
|
||||||
|
src/Label.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
|
||||||
|
)
|
30
libui/include/ui/Alignment.h
Normal file
30
libui/include/ui/Alignment.h
Normal 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);
|
||||||
|
}
|
69
libui/include/ui/App.h
Normal file
69
libui/include/ui/App.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @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_nonblocking();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
bool process_events();
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
36
libui/include/ui/Button.h
Normal file
36
libui/include/ui/Button.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @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<EventResult> handle_key_event(const ui::KeyEventRequest& request) 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
73
libui/include/ui/Canvas.h
Normal 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
113
libui/include/ui/Color.h
Normal 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);
|
||||||
|
};
|
35
libui/include/ui/Container.h
Normal file
35
libui/include/ui/Container.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @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<EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
|
||||||
|
Result<void> draw(Canvas& canvas) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Widget* m_widget;
|
||||||
|
VerticalAlignment m_valign;
|
||||||
|
HorizontalAlignment m_halign;
|
||||||
|
};
|
||||||
|
}
|
123
libui/include/ui/Font.h
Normal file
123
libui/include/ui/Font.h
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @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 low-level direct rendering of glyphs into a canvas.
|
||||||
|
*
|
||||||
|
* This class does not handle special characters such as tabs or newlines. For those, you should be using a more
|
||||||
|
* high-level component such as ui::Label instead.
|
||||||
|
*/
|
||||||
|
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
92
libui/include/ui/Image.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
14
libui/include/ui/Key.h
Normal file
14
libui/include/ui/Key.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <moon/Keyboard.h>
|
||||||
|
|
||||||
|
namespace ui
|
||||||
|
{
|
||||||
|
enum Modifier
|
||||||
|
{
|
||||||
|
Mod_Shift = (1 << 0),
|
||||||
|
Mod_Alt = (1 << 1),
|
||||||
|
Mod_Super = (1 << 2),
|
||||||
|
Mod_AltGr = (1 << 3),
|
||||||
|
Mod_Ctrl = (1 << 4)
|
||||||
|
};
|
||||||
|
}
|
42
libui/include/ui/Label.h
Normal file
42
libui/include/ui/Label.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* @file Label.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief A simple one-line text widget.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <ui/Alignment.h>
|
||||||
|
#include <ui/Font.h>
|
||||||
|
#include <ui/Widget.h>
|
||||||
|
|
||||||
|
namespace ui
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Displays one line of text.
|
||||||
|
*
|
||||||
|
* This component does not handle newlines.
|
||||||
|
*/
|
||||||
|
class Label final : public Widget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Label(StringView text, ui::Color color = ui::WHITE, VerticalAlignment valign = VerticalAlignment::Center,
|
||||||
|
HorizontalAlignment halign = HorizontalAlignment::Center, SharedPtr<Font> font = Font::default_font());
|
||||||
|
|
||||||
|
void set_text(StringView text)
|
||||||
|
{
|
||||||
|
m_text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> draw(Canvas& canvas) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
StringView m_text;
|
||||||
|
VerticalAlignment m_valign;
|
||||||
|
HorizontalAlignment m_halign;
|
||||||
|
ui::Color m_color;
|
||||||
|
SharedPtr<Font> m_font;
|
||||||
|
};
|
||||||
|
}
|
71
libui/include/ui/Layout.h
Normal file
71
libui/include/ui/Layout.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* @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<EventResult> handle_key_event(const ui::KeyEventRequest& request) 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 { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
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<EventResult> handle_key_event(const ui::KeyEventRequest& request) 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 { 0 };
|
||||||
|
};
|
||||||
|
}
|
21
libui/include/ui/Mouse.h
Normal file
21
libui/include/ui/Mouse.h
Normal 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
22
libui/include/ui/Point.h
Normal 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
81
libui/include/ui/Rect.h
Normal 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();
|
||||||
|
};
|
||||||
|
}
|
94
libui/include/ui/Widget.h
Normal file
94
libui/include/ui/Widget.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* @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>
|
||||||
|
#include <ui/ipc/Client.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<EventResult> handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
ignore(request);
|
||||||
|
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 };
|
||||||
|
};
|
||||||
|
}
|
68
libui/include/ui/Window.h
Normal file
68
libui/include/ui/Window.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* @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<ui::EventResult> handle_mouse_leave();
|
||||||
|
Result<ui::EventResult> handle_mouse_move(ui::Point position);
|
||||||
|
Result<ui::EventResult> handle_mouse_buttons(ui::Point position, int buttons);
|
||||||
|
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request);
|
||||||
|
|
||||||
|
int id() const
|
||||||
|
{
|
||||||
|
return m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Window();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_id;
|
||||||
|
Canvas m_canvas;
|
||||||
|
Widget* m_main_widget { nullptr };
|
||||||
|
Option<Color> m_background {};
|
||||||
|
Option<int> m_old_mouse_buttons;
|
||||||
|
};
|
||||||
|
}
|
80
libui/include/ui/ipc/Client.h
Normal file
80
libui/include/ui/ipc/Client.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* @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/Key.h>
|
||||||
|
#include <ui/Point.h>
|
||||||
|
#include <ui/Rect.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,
|
||||||
|
KEY_EVENT_REQUEST_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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyEventRequest
|
||||||
|
{
|
||||||
|
static constexpr u8 ID = KEY_EVENT_REQUEST_ID;
|
||||||
|
|
||||||
|
int window;
|
||||||
|
|
||||||
|
bool pressed;
|
||||||
|
|
||||||
|
char letter;
|
||||||
|
char key;
|
||||||
|
moon::KeyCode code;
|
||||||
|
int modifiers;
|
||||||
|
};
|
||||||
|
}
|
66
libui/include/ui/ipc/Server.h
Normal file
66
libui/include/ui/ipc/Server.h
Normal 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
40
libui/src/Alignment.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
158
libui/src/App.cpp
Normal file
158
libui/src/App.cpp
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* @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()
|
||||||
|
{
|
||||||
|
while (process_events())
|
||||||
|
;
|
||||||
|
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);
|
||||||
|
auto move_result = window->handle_mouse_move(request.position).value_or(ui::EventResult::DidNotHandle);
|
||||||
|
auto button_result =
|
||||||
|
window->handle_mouse_buttons(request.position, request.buttons).value_or(ui::EventResult::DidNotHandle);
|
||||||
|
if (move_result == ui::EventResult::DidHandle || button_result == ui::EventResult::DidHandle)
|
||||||
|
window->draw();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case MOUSE_LEAVE_REQUEST_ID: {
|
||||||
|
MouseLeaveRequest request;
|
||||||
|
READ_MESSAGE(request);
|
||||||
|
auto* window = find_window(request.window);
|
||||||
|
if (window->handle_mouse_leave().value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
|
||||||
|
window->draw();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case KEY_EVENT_REQUEST_ID: {
|
||||||
|
KeyEventRequest request;
|
||||||
|
READ_MESSAGE(request);
|
||||||
|
auto* window = find_window(request.window);
|
||||||
|
if (window->handle_key_event(request).value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
|
||||||
|
window->draw();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
default: fail("Unexpected IPC request from server!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::set_nonblocking()
|
||||||
|
{
|
||||||
|
fcntl(m_client->fd(), F_SETFL, O_NONBLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool App::process_events()
|
||||||
|
{
|
||||||
|
check(m_main_window);
|
||||||
|
os::IPC::check_for_messages(*m_client).release_value();
|
||||||
|
return !m_should_close;
|
||||||
|
}
|
||||||
|
}
|
73
libui/src/Button.cpp
Normal file
73
libui/src/Button.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* @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<EventResult> Button::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
return m_child->handle_key_event(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Button::draw(Canvas& canvas)
|
||||||
|
{
|
||||||
|
return m_child->draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
62
libui/src/Canvas.cpp
Normal file
62
libui/src/Canvas.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
libui/src/Container.cpp
Normal file
63
libui/src/Container.cpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* @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<EventResult> Container::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
return m_widget->handle_key_event(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
121
libui/src/Font.cpp
Normal 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
48
libui/src/Image.cpp
Normal 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 {};
|
||||||
|
}
|
||||||
|
}
|
37
libui/src/Label.cpp
Normal file
37
libui/src/Label.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @file Label.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief A simple one-line text widget.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <luna/Utf8.h>
|
||||||
|
#include <ui/Label.h>
|
||||||
|
|
||||||
|
namespace ui
|
||||||
|
{
|
||||||
|
Label::Label(StringView text, ui::Color color, VerticalAlignment valign, HorizontalAlignment halign,
|
||||||
|
SharedPtr<Font> font)
|
||||||
|
: m_text(text), m_valign(valign), m_halign(halign), m_color(color), m_font(font)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Label::draw(Canvas& canvas)
|
||||||
|
{
|
||||||
|
ui::Rect contained;
|
||||||
|
contained.pos = { 0, 0 };
|
||||||
|
contained.width = static_cast<int>(m_text.length() * m_font->width());
|
||||||
|
contained.height = m_font->height();
|
||||||
|
auto subcanvas =
|
||||||
|
canvas.subcanvas(ui::align({ 0, 0, m_rect.width, m_rect.height }, contained, m_valign, m_halign));
|
||||||
|
|
||||||
|
Utf8StringDecoder decoder(m_text.chars());
|
||||||
|
wchar_t buf[4096];
|
||||||
|
TRY(decoder.decode(buf, sizeof(buf)));
|
||||||
|
|
||||||
|
m_font->render(buf, m_color, subcanvas);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
230
libui/src/Layout.cpp
Normal file
230
libui/src/Layout.cpp
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* @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()
|
||||||
|
{
|
||||||
|
EventResult result = ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
|
for (auto widget : m_widgets)
|
||||||
|
{
|
||||||
|
auto rc = TRY(widget->handle_mouse_leave());
|
||||||
|
if (rc == ui::EventResult::DidHandle) result = rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<EventResult> HorizontalLayout::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
EventResult result = ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
|
for (auto widget : m_widgets)
|
||||||
|
{
|
||||||
|
auto rc = TRY(widget->handle_key_event(request));
|
||||||
|
if (rc == ui::EventResult::DidHandle) result = rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
EventResult result = ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
|
for (auto widget : m_widgets)
|
||||||
|
{
|
||||||
|
auto rc = TRY(widget->handle_mouse_leave());
|
||||||
|
if (rc == ui::EventResult::DidHandle) result = rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<EventResult> VerticalLayout::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
EventResult result = ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
|
for (auto widget : m_widgets)
|
||||||
|
{
|
||||||
|
auto rc = TRY(widget->handle_key_event(request));
|
||||||
|
if (rc == ui::EventResult::DidHandle) result = rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
62
libui/src/Rect.cpp
Normal 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 };
|
||||||
|
}
|
||||||
|
};
|
123
libui/src/Window.cpp
Normal file
123
libui/src/Window.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @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()
|
||||||
|
{
|
||||||
|
if (m_background.has_value()) m_canvas.fill(*m_background);
|
||||||
|
if (m_main_widget) TRY(m_main_widget->draw(m_canvas));
|
||||||
|
update();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ui::EventResult> Window::handle_mouse_leave()
|
||||||
|
{
|
||||||
|
if (!m_main_widget) return ui::EventResult::DidNotHandle;
|
||||||
|
return m_main_widget->handle_mouse_leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ui::EventResult> Window::handle_mouse_move(ui::Point position)
|
||||||
|
{
|
||||||
|
if (!m_main_widget) return ui::EventResult::DidNotHandle;
|
||||||
|
return m_main_widget->handle_mouse_move(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ui::EventResult> Window::handle_mouse_buttons(ui::Point position, int buttons)
|
||||||
|
{
|
||||||
|
if (!m_main_widget) return ui::EventResult::DidNotHandle;
|
||||||
|
auto result = ui::EventResult::DidNotHandle;
|
||||||
|
if (buttons)
|
||||||
|
{
|
||||||
|
auto rc = TRY(m_main_widget->handle_mouse_down(position, buttons));
|
||||||
|
if (rc == ui::EventResult::DidHandle) result = rc;
|
||||||
|
}
|
||||||
|
if (m_old_mouse_buttons.has_value())
|
||||||
|
{
|
||||||
|
int old_buttons = m_old_mouse_buttons.value();
|
||||||
|
int diff = old_buttons & ~buttons;
|
||||||
|
if (diff)
|
||||||
|
{
|
||||||
|
auto rc = TRY(m_main_widget->handle_mouse_up(position, diff));
|
||||||
|
if (rc == ui::EventResult::DidHandle) result = rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_old_mouse_buttons = buttons;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ui::EventResult> Window::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
if (!m_main_widget) return ui::EventResult::DidNotHandle;
|
||||||
|
return m_main_widget->handle_key_event(request);
|
||||||
|
}
|
||||||
|
}
|
@ -85,6 +85,7 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
{
|
{
|
||||||
StringView path;
|
StringView path;
|
||||||
StringView command;
|
StringView command;
|
||||||
|
bool interactive { false };
|
||||||
|
|
||||||
SharedPtr<File> input_file;
|
SharedPtr<File> input_file;
|
||||||
|
|
||||||
@ -93,6 +94,7 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
parser.add_system_program_info("sh"_sv);
|
parser.add_system_program_info("sh"_sv);
|
||||||
parser.add_positional_argument(path, "path"_sv, "-"_sv);
|
parser.add_positional_argument(path, "path"_sv, "-"_sv);
|
||||||
parser.add_value_argument(command, 'c', "command"_sv, "execute a single command and then exit"_sv);
|
parser.add_value_argument(command, 'c', "command"_sv, "execute a single command and then exit"_sv);
|
||||||
|
parser.add_switch_argument(interactive, 'i', "interactive"_sv, "run an interactive shell"_sv);
|
||||||
parser.parse(argc, argv);
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
// TODO: This does not properly handle builtins.
|
// TODO: This does not properly handle builtins.
|
||||||
@ -105,7 +107,7 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
input_file->set_close_on_exec();
|
input_file->set_close_on_exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool interactive = isatty(input_file->fd());
|
if (isatty(input_file->fd())) interactive = true;
|
||||||
|
|
||||||
if (interactive)
|
if (interactive)
|
||||||
{
|
{
|
||||||
|
12
terminal/CMakeLists.txt
Normal file
12
terminal/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
set(SOURCES
|
||||||
|
main.cpp
|
||||||
|
TerminalWidget.h
|
||||||
|
TerminalWidget.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(terminal ${SOURCES})
|
||||||
|
target_compile_options(terminal PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||||
|
add_dependencies(terminal libc)
|
||||||
|
target_include_directories(terminal PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
target_link_libraries(terminal PRIVATE os ui)
|
||||||
|
install(TARGETS terminal DESTINATION ${LUNA_BASE}/usr/bin)
|
596
terminal/TerminalWidget.cpp
Normal file
596
terminal/TerminalWidget.cpp
Normal file
@ -0,0 +1,596 @@
|
|||||||
|
#include "TerminalWidget.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <luna/CType.h>
|
||||||
|
#include <os/File.h>
|
||||||
|
#include <os/Process.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <ui/App.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static constexpr auto RED = ui::Color::from_u32(0xffcd0000);
|
||||||
|
static constexpr auto GREEN = ui::Color::from_u32(0xff00cd00);
|
||||||
|
static constexpr auto YELLOW = ui::Color::from_u32(0xffcdcd00);
|
||||||
|
static constexpr auto BLUE = ui::Color::from_u32(0xff0000ee);
|
||||||
|
static constexpr auto MAGENTA = ui::Color::from_u32(0xffcd00cd);
|
||||||
|
static constexpr auto CYAN = ui::Color::from_u32(0xff00cdcd);
|
||||||
|
static constexpr auto GRAY = ui::Color::from_u32(0xffe5e5e5);
|
||||||
|
|
||||||
|
static constexpr auto BRIGHT_BLACK = ui::Color::from_u32(0xff7f7f7f);
|
||||||
|
static constexpr auto BRIGHT_RED = ui::Color::from_u32(0xffff0000);
|
||||||
|
static constexpr auto BRIGHT_GREEN = ui::Color::from_u32(0xff00ff00);
|
||||||
|
static constexpr auto BRIGHT_YELLOW = ui::Color::from_u32(0xffffff00);
|
||||||
|
static constexpr auto BRIGHT_BLUE = ui::Color::from_u32(0xff5c5cff);
|
||||||
|
static constexpr auto BRIGHT_MAGENTA = ui::Color::from_u32(0xffff00ff);
|
||||||
|
static constexpr auto BRIGHT_CYAN = ui::Color::from_u32(0xff00ffff);
|
||||||
|
static constexpr auto BRIGHT_GRAY = ui::Color::from_u32(0xffffffff);
|
||||||
|
|
||||||
|
static long get_time_in_milliseconds()
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
check(clock_gettime(CLOCK_REALTIME, &ts) == 0);
|
||||||
|
|
||||||
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sigchld_handler(int)
|
||||||
|
{
|
||||||
|
wait(NULL);
|
||||||
|
ui::App::the().set_should_close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> TerminalWidget::init(char* const* args)
|
||||||
|
{
|
||||||
|
m_font = ui::Font::default_font();
|
||||||
|
m_bold_font = ui::Font::default_bold_font();
|
||||||
|
|
||||||
|
m_terminal_canvas = ui::App::the().main_window()->canvas();
|
||||||
|
m_terminal_canvas.fill(ui::BLACK);
|
||||||
|
|
||||||
|
signal(SIGCHLD, sigchld_handler);
|
||||||
|
|
||||||
|
int fd = posix_openpt(O_RDWR | O_CLOEXEC);
|
||||||
|
if (fd < 0) return err(errno);
|
||||||
|
|
||||||
|
grantpt(fd);
|
||||||
|
unlockpt(fd);
|
||||||
|
|
||||||
|
pid_t child = TRY(os::Process::fork());
|
||||||
|
if (child == 0)
|
||||||
|
{
|
||||||
|
int ptsfd = open(ptsname(fd), O_RDWR);
|
||||||
|
|
||||||
|
dup2(ptsfd, STDIN_FILENO);
|
||||||
|
dup2(ptsfd, STDOUT_FILENO);
|
||||||
|
dup2(ptsfd, STDERR_FILENO);
|
||||||
|
|
||||||
|
setpgid(0, 0);
|
||||||
|
tcsetpgrp(ptsfd, getpid());
|
||||||
|
|
||||||
|
close(ptsfd);
|
||||||
|
|
||||||
|
execv(args[0], args);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pty = fd;
|
||||||
|
|
||||||
|
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||||
|
|
||||||
|
m_child_pid = child;
|
||||||
|
|
||||||
|
m_last_cursor_tick = get_time_in_milliseconds();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
// Avoid handling "key released" events
|
||||||
|
if (!request.pressed) return ui::EventResult::DidNotHandle;
|
||||||
|
// Non-printable key or key that has no special character (unlike Tab or Enter). We exit early to avoid inserting an
|
||||||
|
// invalid zero byte into the terminal input (this would also happen on Shift or Ctrl keypresses).
|
||||||
|
if (request.letter == '\0') return ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
|
query_termios();
|
||||||
|
|
||||||
|
bool is_special_character { false };
|
||||||
|
|
||||||
|
if (/*is_canonical()*/ true)
|
||||||
|
{
|
||||||
|
if (request.letter == m_settings.c_cc[VERASE])
|
||||||
|
{
|
||||||
|
auto maybe_char = m_line_buffer.try_pop();
|
||||||
|
if (maybe_char.has_value() && maybe_char.value())
|
||||||
|
{
|
||||||
|
if ((m_settings.c_lflag & ECHO) && (m_settings.c_lflag & ECHOE))
|
||||||
|
{
|
||||||
|
put_code_point(L'\b');
|
||||||
|
if (_iscntrl(maybe_char.value())) put_code_point(L'\b');
|
||||||
|
if (maybe_char.value() == '\t')
|
||||||
|
{
|
||||||
|
put_code_point(L'\b');
|
||||||
|
put_code_point(L'\b');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui::App::the().main_window()->draw();
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((m_settings.c_lflag & ECHOE)) return ui::EventResult::DidHandle;
|
||||||
|
else
|
||||||
|
is_special_character = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.letter == m_settings.c_cc[VEOF])
|
||||||
|
{
|
||||||
|
write(m_pty, m_line_buffer.data(), m_line_buffer.size());
|
||||||
|
m_line_buffer.clear();
|
||||||
|
|
||||||
|
// FIXME: tell the kernel that process may read without blocking.
|
||||||
|
is_special_character = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_settings.c_lflag & ISIG)
|
||||||
|
{
|
||||||
|
if (request.letter == m_settings.c_cc[VINTR])
|
||||||
|
{
|
||||||
|
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
|
||||||
|
|
||||||
|
pid_t group = tcgetpgrp(m_pty);
|
||||||
|
TRY(os::Process::kill(-group, SIGINT));
|
||||||
|
|
||||||
|
is_special_character = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.letter == m_settings.c_cc[VQUIT])
|
||||||
|
{
|
||||||
|
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
|
||||||
|
|
||||||
|
pid_t group = tcgetpgrp(m_pty);
|
||||||
|
TRY(os::Process::kill(-group, SIGQUIT));
|
||||||
|
|
||||||
|
is_special_character = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_special_character)
|
||||||
|
{
|
||||||
|
if (/*is_canonical()*/ true)
|
||||||
|
{
|
||||||
|
m_line_buffer.try_append((u8)request.letter);
|
||||||
|
|
||||||
|
if (request.letter == '\n')
|
||||||
|
{
|
||||||
|
write(m_pty, m_line_buffer.data(), m_line_buffer.size());
|
||||||
|
m_line_buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
write(m_pty, &request.letter, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(m_settings.c_lflag & ECHO)) return ui::EventResult::DidHandle;
|
||||||
|
|
||||||
|
if (_iscntrl(request.letter))
|
||||||
|
{
|
||||||
|
if (m_settings.c_lflag & ECHOCTL)
|
||||||
|
{
|
||||||
|
bool should_echo = true;
|
||||||
|
if (request.letter == '\n' || request.letter == '\t' || request.letter == '\0' ||
|
||||||
|
request.letter == m_settings.c_cc[VEOF])
|
||||||
|
should_echo = false;
|
||||||
|
|
||||||
|
if (should_echo)
|
||||||
|
{
|
||||||
|
char caret_notation[3] = { '^', '\0', '\0' };
|
||||||
|
if (request.letter == 0x7f) caret_notation[1] = '?';
|
||||||
|
else
|
||||||
|
caret_notation[1] = request.letter + 0x40;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) { putchar(caret_notation[i]); }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
putchar(request.letter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
putchar(request.letter);
|
||||||
|
|
||||||
|
ui::App::the().main_window()->draw();
|
||||||
|
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> TerminalWidget::draw(ui::Canvas&)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> TerminalWidget::process()
|
||||||
|
{
|
||||||
|
char buffer[BUFSIZ];
|
||||||
|
ssize_t nread = read(m_pty, buffer, BUFSIZ);
|
||||||
|
if (nread < 0)
|
||||||
|
{
|
||||||
|
if (errno == EAGAIN) nread = 0;
|
||||||
|
else
|
||||||
|
return err(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
query_termios();
|
||||||
|
|
||||||
|
bool should_update_cursor = tick_cursor();
|
||||||
|
|
||||||
|
for (ssize_t i = 0; i < nread; i++) TRY(putchar(buffer[i]));
|
||||||
|
|
||||||
|
if (should_update_cursor || nread > 0) ui::App::the().main_window()->draw();
|
||||||
|
|
||||||
|
return nread == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TerminalWidget::tick_cursor()
|
||||||
|
{
|
||||||
|
if (!m_cursor_enabled) return false;
|
||||||
|
|
||||||
|
long now = get_time_in_milliseconds();
|
||||||
|
long diff = now - m_last_cursor_tick;
|
||||||
|
m_last_cursor_tick = now;
|
||||||
|
|
||||||
|
m_current_cursor_timeout -= (int)diff;
|
||||||
|
if (m_current_cursor_timeout <= 0)
|
||||||
|
{
|
||||||
|
m_current_cursor_timeout = CURSOR_TIMEOUT;
|
||||||
|
m_cursor_activated = !m_cursor_activated;
|
||||||
|
|
||||||
|
if (m_cursor_activated) draw_cursor();
|
||||||
|
else
|
||||||
|
erase_current_char();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::query_termios()
|
||||||
|
{
|
||||||
|
tcgetattr(m_pty, &m_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)
|
||||||
|
{
|
||||||
|
auto subcanvas = m_terminal_canvas.subcanvas({ x, y, m_font->width(), m_font->height() });
|
||||||
|
subcanvas.fill(m_background_color);
|
||||||
|
(m_bold ? m_bold_font : m_font)->render(c, m_foreground_color, subcanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::erase_current_line()
|
||||||
|
{
|
||||||
|
m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }).fill(ui::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::scroll()
|
||||||
|
{
|
||||||
|
memcpy(m_terminal_canvas.ptr, m_terminal_canvas.ptr + (m_rect.width * sizeof(u32) * m_font->height()),
|
||||||
|
(m_rect.width * m_rect.height * sizeof(u32)) - (m_rect.width * sizeof(u32) * m_font->height()));
|
||||||
|
m_y_position -= m_font->height();
|
||||||
|
erase_current_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TerminalWidget::should_scroll()
|
||||||
|
{
|
||||||
|
return m_y_position >= m_rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::next_line()
|
||||||
|
{
|
||||||
|
m_x_position = 0;
|
||||||
|
m_y_position += m_font->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::next_char()
|
||||||
|
{
|
||||||
|
m_x_position += m_font->width();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::prev_char()
|
||||||
|
{
|
||||||
|
m_x_position -= m_font->width();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::erase_current_char()
|
||||||
|
{
|
||||||
|
m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::draw_cursor()
|
||||||
|
{
|
||||||
|
m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TerminalWidget::at_end_of_screen()
|
||||||
|
{
|
||||||
|
return (m_x_position + m_font->width()) > m_rect.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TerminalWidget::handle_escape_sequence(wchar_t c)
|
||||||
|
{
|
||||||
|
auto rc = m_escape_parser->advance(static_cast<u8>(c));
|
||||||
|
if (rc.has_error())
|
||||||
|
{
|
||||||
|
m_escape_parser = Option<EscapeSequenceParser> {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!rc.value()) return true;
|
||||||
|
if (!m_escape_parser->valid())
|
||||||
|
{
|
||||||
|
m_escape_parser = Option<EscapeSequenceParser> {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& params = m_escape_parser->parameters();
|
||||||
|
switch (m_escape_parser->code())
|
||||||
|
{
|
||||||
|
case EscapeCode::CursorUp: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * m_font->height();
|
||||||
|
if (pixels > m_y_position) m_y_position = 0;
|
||||||
|
else
|
||||||
|
m_y_position -= pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorDown: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * m_font->height();
|
||||||
|
if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height();
|
||||||
|
else
|
||||||
|
m_y_position += pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorBack: {
|
||||||
|
int chars = params.size() ? params[0] : 1;
|
||||||
|
int pixels = chars * m_font->width();
|
||||||
|
if (pixels > m_x_position) m_x_position = 0;
|
||||||
|
else
|
||||||
|
m_x_position -= pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorForward: {
|
||||||
|
int chars = params.size() ? params[0] : 1;
|
||||||
|
int pixels = chars * m_font->width();
|
||||||
|
if (pixels + m_x_position >= m_rect.width) m_x_position = m_rect.width - m_font->width();
|
||||||
|
else
|
||||||
|
m_x_position += pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorNextLine: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * m_font->height();
|
||||||
|
if (pixels > m_y_position) m_y_position = 0;
|
||||||
|
else
|
||||||
|
m_y_position -= pixels;
|
||||||
|
m_x_position = 0;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorPreviousLine: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * m_font->height();
|
||||||
|
if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height();
|
||||||
|
else
|
||||||
|
m_y_position += pixels;
|
||||||
|
m_x_position = 0;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorHorizontalAbsolute: {
|
||||||
|
int line = (params.size() ? params[0] : 1) - 1;
|
||||||
|
if (line < 0) break;
|
||||||
|
int position = line * m_font->height();
|
||||||
|
if (position >= m_rect.height) position = m_rect.height - m_font->height();
|
||||||
|
m_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;
|
||||||
|
int x_position = x * m_font->width();
|
||||||
|
if (x_position >= m_rect.width) x_position = m_rect.width - m_font->height();
|
||||||
|
m_x_position = x_position;
|
||||||
|
int y_position = y * m_font->height();
|
||||||
|
if (y_position >= m_rect.height) y_position = m_rect.height - m_font->height();
|
||||||
|
m_y_position = y_position;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::SelectGraphicRendition: {
|
||||||
|
if (!params.size())
|
||||||
|
{
|
||||||
|
m_foreground_color = ui::WHITE;
|
||||||
|
m_background_color = ui::BLACK;
|
||||||
|
m_bold = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (usize i = 0; i < params.size(); i++)
|
||||||
|
{
|
||||||
|
int arg = params[i];
|
||||||
|
switch (arg)
|
||||||
|
{
|
||||||
|
case 0: {
|
||||||
|
m_foreground_color = ui::BLACK;
|
||||||
|
m_background_color = ui::WHITE;
|
||||||
|
m_bold = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
m_bold = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 22: {
|
||||||
|
m_bold = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 30: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_BLACK : ui::BLACK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 31: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_RED : RED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 32: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_GREEN : GREEN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 33: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_YELLOW : YELLOW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 34: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_BLUE : BLUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 35: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_MAGENTA : MAGENTA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 36: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_CYAN : CYAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 37: {
|
||||||
|
m_foreground_color = m_bold ? BRIGHT_GRAY : GRAY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 39: {
|
||||||
|
m_foreground_color = ui::WHITE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 40: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_BLACK : ui::BLACK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 41: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_RED : RED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 42: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_GREEN : GREEN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 43: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_YELLOW : YELLOW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 44: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_BLUE : BLUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 45: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_MAGENTA : MAGENTA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 46: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_CYAN : CYAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 47: {
|
||||||
|
m_background_color = m_bold ? BRIGHT_GRAY : GRAY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 49: {
|
||||||
|
m_background_color = ui::BLACK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_escape_parser = Option<EscapeSequenceParser> {};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> TerminalWidget::putchar(char c)
|
||||||
|
{
|
||||||
|
auto guard = make_scope_guard([this] { m_decoder.reset(); });
|
||||||
|
|
||||||
|
bool is_ready = TRY(m_decoder.feed(c));
|
||||||
|
|
||||||
|
if (is_ready) put_code_point(TRY(m_decoder.extract()));
|
||||||
|
|
||||||
|
guard.deactivate();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::put_code_point(wchar_t c)
|
||||||
|
{
|
||||||
|
if (c > (wchar_t)255) c = (wchar_t)256;
|
||||||
|
|
||||||
|
if (m_escape_parser.has_value())
|
||||||
|
{
|
||||||
|
if (handle_escape_sequence(c)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase the current cursor.
|
||||||
|
if (m_cursor_activated) erase_current_char();
|
||||||
|
|
||||||
|
bool should_draw_cursor = m_cursor_enabled;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case L'\n': {
|
||||||
|
next_line();
|
||||||
|
if (should_scroll()) scroll();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case L'\t': {
|
||||||
|
for (int i = 0; i < 4; i++) { put_code_point(L' '); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case L'\r': m_x_position = 0; break;
|
||||||
|
case L'\b':
|
||||||
|
if (m_x_position != 0)
|
||||||
|
{
|
||||||
|
prev_char();
|
||||||
|
erase_current_char();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case L'\x1b':
|
||||||
|
case L'\x9b':
|
||||||
|
case L'\x90':
|
||||||
|
case L'\x9d':
|
||||||
|
m_escape_parser = EscapeSequenceParser { (u8)c };
|
||||||
|
should_draw_cursor = false;
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
if (iscntrl(c)) return;
|
||||||
|
draw_glyph(c, m_x_position, m_y_position);
|
||||||
|
next_char();
|
||||||
|
if (at_end_of_screen())
|
||||||
|
{
|
||||||
|
next_line();
|
||||||
|
if (should_scroll()) scroll();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_draw_cursor)
|
||||||
|
{
|
||||||
|
m_current_cursor_timeout = CURSOR_TIMEOUT;
|
||||||
|
m_cursor_activated = true;
|
||||||
|
draw_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::quit()
|
||||||
|
{
|
||||||
|
kill(-tcgetpgrp(m_pty), SIGHUP);
|
||||||
|
}
|
70
terminal/TerminalWidget.h
Normal file
70
terminal/TerminalWidget.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <luna/EscapeSequence.h>
|
||||||
|
#include <luna/Utf8.h>
|
||||||
|
#include <luna/Vector.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <ui/Font.h>
|
||||||
|
#include <ui/Widget.h>
|
||||||
|
|
||||||
|
class TerminalWidget : public ui::Widget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Result<void> init(char* const* args);
|
||||||
|
|
||||||
|
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
|
||||||
|
|
||||||
|
Result<void> draw(ui::Canvas& canvas) override;
|
||||||
|
|
||||||
|
Result<bool> process();
|
||||||
|
|
||||||
|
void quit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ui::Canvas m_terminal_canvas;
|
||||||
|
Vector<u8> m_line_buffer;
|
||||||
|
int m_pty;
|
||||||
|
pid_t m_child_pid;
|
||||||
|
|
||||||
|
struct termios m_settings;
|
||||||
|
|
||||||
|
SharedPtr<ui::Font> m_font;
|
||||||
|
SharedPtr<ui::Font> m_bold_font;
|
||||||
|
|
||||||
|
static constexpr int CURSOR_TIMEOUT = 500;
|
||||||
|
|
||||||
|
int m_current_cursor_timeout = CURSOR_TIMEOUT;
|
||||||
|
bool m_cursor_activated = false;
|
||||||
|
bool m_cursor_enabled = true;
|
||||||
|
|
||||||
|
long m_last_cursor_tick;
|
||||||
|
|
||||||
|
int m_x_position { 0 };
|
||||||
|
int m_y_position { 0 };
|
||||||
|
|
||||||
|
bool m_bold { false };
|
||||||
|
|
||||||
|
ui::Color m_foreground_color { ui::WHITE };
|
||||||
|
ui::Color m_background_color { ui::BLACK };
|
||||||
|
|
||||||
|
void query_termios();
|
||||||
|
|
||||||
|
bool tick_cursor();
|
||||||
|
|
||||||
|
Utf8StateDecoder m_decoder;
|
||||||
|
Option<EscapeSequenceParser> m_escape_parser;
|
||||||
|
|
||||||
|
void draw_glyph(wchar_t c, int x, int y);
|
||||||
|
void erase_current_line();
|
||||||
|
void scroll();
|
||||||
|
bool should_scroll();
|
||||||
|
void next_line();
|
||||||
|
void next_char();
|
||||||
|
void prev_char();
|
||||||
|
void erase_current_char();
|
||||||
|
void draw_cursor();
|
||||||
|
bool at_end_of_screen();
|
||||||
|
bool handle_escape_sequence(wchar_t c);
|
||||||
|
Result<void> putchar(char c);
|
||||||
|
void put_code_point(wchar_t c);
|
||||||
|
};
|
33
terminal/main.cpp
Normal file
33
terminal/main.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "TerminalWidget.h"
|
||||||
|
#include <os/ArgumentParser.h>
|
||||||
|
#include <ui/App.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
Result<int> luna_main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ui::App app;
|
||||||
|
TRY(app.init(argc, argv));
|
||||||
|
app.set_nonblocking();
|
||||||
|
|
||||||
|
auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 }));
|
||||||
|
app.set_main_window(window);
|
||||||
|
window->set_title("Terminal");
|
||||||
|
|
||||||
|
TerminalWidget terminal;
|
||||||
|
window->set_main_widget(terminal);
|
||||||
|
|
||||||
|
char* args[] = { "/bin/sh", nullptr };
|
||||||
|
TRY(terminal.init(args));
|
||||||
|
|
||||||
|
window->draw();
|
||||||
|
|
||||||
|
while (app.process_events())
|
||||||
|
{
|
||||||
|
bool should_sleep = TRY(terminal.process());
|
||||||
|
if (should_sleep) usleep(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.quit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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"))
|
||||||
|
21
wind/CMakeLists.txt
Normal file
21
wind/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
set(SOURCES
|
||||||
|
main.cpp
|
||||||
|
Screen.h
|
||||||
|
Screen.cpp
|
||||||
|
Mouse.h
|
||||||
|
Mouse.cpp
|
||||||
|
Window.h
|
||||||
|
Window.cpp
|
||||||
|
IPC.cpp
|
||||||
|
IPC.h
|
||||||
|
Keyboard.cpp
|
||||||
|
Keyboard.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
18
wind/Client.h
Normal 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
|
||||||
|
};
|
183
wind/IPC.cpp
Normal file
183
wind/IPC.cpp
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#include "IPC.h"
|
||||||
|
#include "Mouse.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);
|
||||||
|
Mouse::the().window_did_close(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
10
wind/IPC.h
Normal 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);
|
||||||
|
}
|
305
wind/Keyboard.cpp
Normal file
305
wind/Keyboard.cpp
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
#include "Keyboard.h"
|
||||||
|
#include <luna/CType.h>
|
||||||
|
|
||||||
|
static const char table[] = {
|
||||||
|
// Function keys
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// System keys
|
||||||
|
'\x1b',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Modifier keys
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0', // or AltGr on some keyboards
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Navigation keys
|
||||||
|
'\t',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Editing keys
|
||||||
|
'\b',
|
||||||
|
'\n',
|
||||||
|
'\0',
|
||||||
|
'\x7f',
|
||||||
|
'\n',
|
||||||
|
// Lock keys
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Keypad keys
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'.',
|
||||||
|
'+',
|
||||||
|
'-',
|
||||||
|
'*',
|
||||||
|
'/',
|
||||||
|
// Character keys (depending on keyboard layout), examples in US QWERTY
|
||||||
|
'`', // `
|
||||||
|
'1', // 1
|
||||||
|
'2', // 2
|
||||||
|
'3', // 3
|
||||||
|
'4', // 4
|
||||||
|
'5', // 5
|
||||||
|
'6', // 6
|
||||||
|
'7', // 7
|
||||||
|
'8', // 8
|
||||||
|
'9', // 9
|
||||||
|
'0', // 0
|
||||||
|
'-', // -
|
||||||
|
'=', // =
|
||||||
|
'q', // Q
|
||||||
|
'w', // W
|
||||||
|
'e', // E
|
||||||
|
'r', // R
|
||||||
|
't', // T
|
||||||
|
'y', // Y
|
||||||
|
'u', // U
|
||||||
|
'i', // I
|
||||||
|
'o', // O
|
||||||
|
'p', // P
|
||||||
|
'[', // [
|
||||||
|
']', // ]
|
||||||
|
'a', // A
|
||||||
|
's', // S
|
||||||
|
'd', // D
|
||||||
|
'f', // F
|
||||||
|
'g', // G
|
||||||
|
'h', // H
|
||||||
|
'j', // J
|
||||||
|
'k', // K
|
||||||
|
'l', // L
|
||||||
|
';', // ;
|
||||||
|
'\'', // '
|
||||||
|
'#', // #
|
||||||
|
'\\', // Backslash
|
||||||
|
'z', // Z
|
||||||
|
'x', // X
|
||||||
|
'c', // C
|
||||||
|
'v', // V
|
||||||
|
'b', // B
|
||||||
|
'n', // N
|
||||||
|
'm', // M
|
||||||
|
',', // ,
|
||||||
|
'.', // .
|
||||||
|
'/', // /
|
||||||
|
' ', // Space
|
||||||
|
// Unknown key
|
||||||
|
'\0',
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char shift_table[] = {
|
||||||
|
// Function keys
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// System keys
|
||||||
|
'\x1b',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Modifier keys
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0', // or AltGr on some keyboards
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Navigation keys
|
||||||
|
'\t',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Editing keys
|
||||||
|
'\b',
|
||||||
|
'\n',
|
||||||
|
'\0',
|
||||||
|
'\x7f',
|
||||||
|
'\n',
|
||||||
|
// Lock keys
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
'\0',
|
||||||
|
// Keypad keys
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'.',
|
||||||
|
'+',
|
||||||
|
'-',
|
||||||
|
'*',
|
||||||
|
'/',
|
||||||
|
// Character keys (depending on keyboard layout), examples in US QWERTY
|
||||||
|
'~', // `
|
||||||
|
'!',
|
||||||
|
'@',
|
||||||
|
'#',
|
||||||
|
'$',
|
||||||
|
'%',
|
||||||
|
'^',
|
||||||
|
'&',
|
||||||
|
'*',
|
||||||
|
'(',
|
||||||
|
')',
|
||||||
|
'_',
|
||||||
|
'+',
|
||||||
|
'Q',
|
||||||
|
'W',
|
||||||
|
'E',
|
||||||
|
'R',
|
||||||
|
'T',
|
||||||
|
'Y',
|
||||||
|
'U',
|
||||||
|
'I',
|
||||||
|
'O',
|
||||||
|
'P',
|
||||||
|
'{',
|
||||||
|
'}',
|
||||||
|
'A',
|
||||||
|
'S',
|
||||||
|
'D',
|
||||||
|
'F',
|
||||||
|
'G',
|
||||||
|
'H',
|
||||||
|
'J',
|
||||||
|
'K',
|
||||||
|
'L',
|
||||||
|
':',
|
||||||
|
'"',
|
||||||
|
' ', // #
|
||||||
|
'|', // Backslash
|
||||||
|
'Z',
|
||||||
|
'X',
|
||||||
|
'C',
|
||||||
|
'V',
|
||||||
|
'B',
|
||||||
|
'N',
|
||||||
|
'M',
|
||||||
|
'<',
|
||||||
|
'>',
|
||||||
|
'?',
|
||||||
|
' ', // Space
|
||||||
|
// Unknown key
|
||||||
|
'\0',
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace wind::Keyboard
|
||||||
|
{
|
||||||
|
static bool g_caps_lock = false;
|
||||||
|
static bool g_right_shift = false;
|
||||||
|
static bool g_left_shift = false;
|
||||||
|
static bool g_right_control = false;
|
||||||
|
static bool g_left_control = false;
|
||||||
|
static bool g_altgr = false;
|
||||||
|
static bool g_alt = false;
|
||||||
|
static bool g_super = false;
|
||||||
|
|
||||||
|
ui::KeyEventRequest decode_keyboard_event(moon::KeyCode code, bool released)
|
||||||
|
{
|
||||||
|
ui::KeyEventRequest request;
|
||||||
|
request.code = code;
|
||||||
|
request.pressed = !released;
|
||||||
|
request.modifiers = 0;
|
||||||
|
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case moon::K_CapsLock:
|
||||||
|
if (!released) { g_caps_lock = !g_caps_lock; }
|
||||||
|
break;
|
||||||
|
case moon::K_RightShift: g_right_shift = !released; break;
|
||||||
|
case moon::K_LeftShift: g_left_shift = !released; break;
|
||||||
|
case moon::K_RightControl: g_right_control = !released; break;
|
||||||
|
case moon::K_LeftControl: g_left_control = !released; break;
|
||||||
|
case moon::K_RightAlt: g_altgr = !released; break;
|
||||||
|
case moon::K_LeftAlt: g_alt = !released; break;
|
||||||
|
case moon::K_Super: g_super = !released; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((g_caps_lock && !(g_left_shift || g_right_shift)) || (g_left_shift || g_right_shift))
|
||||||
|
{
|
||||||
|
request.modifiers |= ui::Mod_Shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_right_control || g_left_control) request.modifiers |= ui::Mod_Ctrl;
|
||||||
|
|
||||||
|
if (g_alt) request.modifiers |= ui::Mod_Alt;
|
||||||
|
if (g_altgr) request.modifiers |= ui::Mod_AltGr;
|
||||||
|
if (g_super) request.modifiers |= ui::Mod_Super;
|
||||||
|
|
||||||
|
request.key = table[code];
|
||||||
|
|
||||||
|
if (request.modifiers & ui::Mod_Ctrl)
|
||||||
|
{
|
||||||
|
char letter;
|
||||||
|
if (request.modifiers & ui::Mod_Shift) letter = shift_table[code];
|
||||||
|
else
|
||||||
|
letter = table[code];
|
||||||
|
if (_islower(letter)) letter = (char)_toupper(letter);
|
||||||
|
if (_isupper(letter)) letter = letter - 0x40;
|
||||||
|
if (letter == '@') letter = letter - 0x40;
|
||||||
|
if (letter > 'Z' && letter < '`') letter = letter - 0x40;
|
||||||
|
if (letter == '?') letter = 0x7f;
|
||||||
|
request.letter = letter;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.modifiers & ui::Mod_Shift) request.letter = shift_table[code];
|
||||||
|
else
|
||||||
|
request.letter = table[code];
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
10
wind/Keyboard.h
Normal file
10
wind/Keyboard.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ui/ipc/Client.h>
|
||||||
|
|
||||||
|
namespace wind
|
||||||
|
{
|
||||||
|
namespace Keyboard
|
||||||
|
{
|
||||||
|
ui::KeyEventRequest decode_keyboard_event(moon::KeyCode code, bool released);
|
||||||
|
}
|
||||||
|
}
|
129
wind/Mouse.cpp
Normal file
129
wind/Mouse.cpp
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
static Mouse* s_mouse;
|
||||||
|
|
||||||
|
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({});
|
||||||
|
|
||||||
|
s_mouse = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mouse& Mouse::the()
|
||||||
|
{
|
||||||
|
check(s_mouse);
|
||||||
|
return *s_mouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::window_did_close(Window* window)
|
||||||
|
{
|
||||||
|
if (m_dragging_window == window) { m_dragging_window = nullptr; }
|
||||||
|
|
||||||
|
if (m_active_window == window) { m_active_window = nullptr; }
|
||||||
|
}
|
28
wind/Mouse.h
Normal file
28
wind/Mouse.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
void window_did_close(Window* window);
|
||||||
|
|
||||||
|
static Mouse& the();
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user