Compare commits

...

18 Commits

Author SHA1 Message Date
ce1e9abc39
Remove the build status badge
All checks were successful
Build and test / build (push) Successful in 1m39s
It links to drone, which we have phased out in favor of Gitea Actions.
2024-03-29 20:50:14 +01:00
b803358ea3
editor: Fix creation of new files
The editor is supposed to create files if they don't exist, however before this commit stat() would fail and exit load_file() before we even got to File::open_or_create().
2024-03-29 20:50:14 +01:00
2e53f9999f
editor: Display only the basename of the current file in the window title 2024-03-29 20:50:14 +01:00
bd5a231936
terminal: Use widget->window() instead of the App's main window 2024-03-29 20:50:14 +01:00
cc3be43633
editor: Use TextInput as a base class 2024-03-29 20:50:14 +01:00
677bcb861e
libui: Add a TextInput base class to handle most input fields and add an InputField class for single-line inputs 2024-03-29 20:50:14 +01:00
5dc23d1a09
editor: Refuse to load non-regular file types 2024-03-29 20:50:13 +01:00
47538b16fd
editor: Remove insert mode and use the arrow keys to navigate, plus Ctrl+S to save 2024-03-29 20:50:13 +01:00
77737c2cd8
editor: Add basic loading and saving 2024-03-29 20:50:13 +01:00
622a044244
editor: Add a basic text editor 2024-03-29 20:50:13 +01:00
7d69ac56e2
apps+libos+shell+wind: Correct a bunch of format strings
All checks were successful
Build and test / build (push) Successful in 1m41s
2024-03-29 14:42:38 +01:00
f9b39c5ff3
libos: Add ways to format output to a File 2024-03-29 14:41:45 +01:00
d70effd1db
libos: Clarify requirements for File::write 2024-03-29 14:25:14 +01:00
59713279a0
kernel: Add a hexdump() method to log binary data for debugging
All checks were successful
Build and test / build (push) Successful in 1m33s
2024-03-29 12:12:56 +01:00
6443ec77f8
kernel/ATA: Add support for regular ATA drives (non-ATAPI)
Don't know why this took so long to figure out, I just had to pass the right value to select().
2024-03-29 12:11:39 +01:00
86372a3893
Update README.md 2024-03-28 22:37:16 +01:00
3dc2c24ec5
apps: Add run
Some checks failed
Build and test / build (push) Failing after 1m43s
This utility lets you run a process detached from the shell, using the magic of the launch server.
2024-03-20 19:58:45 +01:00
06b8a41d2f
launch: Add support for PATH searching 2024-03-20 19:57:43 +01:00
28 changed files with 860 additions and 87 deletions

View File

@ -53,3 +53,4 @@ add_subdirectory(tests)
add_subdirectory(shell)
add_subdirectory(wind)
add_subdirectory(terminal)
add_subdirectory(editor)

View File

@ -1,5 +1,5 @@
# Luna
A simple POSIX-based operating system for personal computers, written in C++. [![Build Status](https://drone.cloudapio.eu/api/badges/apio/Luna/status.svg)](https://drone.cloudapio.eu/apio/Luna)
A simple POSIX-based operating system for personal computers, written in C++.
## Another UNIX clone?
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone).
@ -38,17 +38,11 @@ Additionally, the build process needs some extra dependencies to run: `cmake`, `
`tools/run.sh` is the script you should use in most cases. It will build changed files, install, make an ISO image, and run Luna in QEMU.
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` since `run.sh` does it for you.
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` manually since `run.sh` does it for you.
## Prebuilt images
Prebuilt ISO images (numbered) for every version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
These images are built manually whenever a new release is created, and thus don't reflect the latest changes on the `main` branch.
Every hour, this server pulls the latest commits on `main` and builds an hourly ISO image. The ten most recent ones can be found in the [hourly](https://pub.cloudapio.eu/luna/hourly) directory, and [Luna-latest.iso](https://pub.cloudapio.eu/luna/Luna-latest.iso) should always be symlinked to the newest one.
These images do reflect the latest changes on the `main` branch, but are obviously less stable. Additionally, an hourly image will be skipped if building the latest commit of the project fails.
Prebuilt ISO images for every release version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
## Is there third-party software I can use on Luna?

View File

@ -51,3 +51,4 @@ luna_app(clock.cpp clock)
target_link_libraries(clock PUBLIC ui)
luna_app(startui.cpp startui)
luna_app(launch.cpp launch)
luna_app(run.cpp run)

View File

@ -31,7 +31,7 @@ Result<void> handle_launch_detached_message(os::IPC::ClientConnection& client)
StringView args[] = { path.view() };
os::Process::spawn(args[0], { args, 1 }, false);
os::Process::spawn(args[0], { args, 1 }, request.search_in_path);
return {};
}
@ -95,7 +95,7 @@ Result<int> luna_main(int argc, char** argv)
if (fds[i + 1].revents & POLLIN) clients[i]->check_for_messages();
if (fds[i + 1].revents & POLLHUP)
{
os::println("launch: Client %d disconnected", i);
os::println("launch: Client %zu disconnected", i);
fds.remove_at(i + 1);
auto client = clients.remove_at(i);
client->disconnect();

View File

@ -212,14 +212,15 @@ Result<int> luna_main(int argc, char** argv)
{
if (colors)
{
os::println("%s %u %4s %4s %10lu %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
os::println("%s %lu %4s %4s %10lu %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
formatted_mode, st.st_nlink, owner.chars(), group.chars(), st.st_size,
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
else
{
os::println("%s %u %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
os::println("%s %lu %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(),
group.chars(), st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ",
link.chars());
}
}
else
@ -227,13 +228,13 @@ Result<int> luna_main(int argc, char** argv)
auto size = TRY(to_dynamic_unit(st.st_size, 10, false, si ? Unit::SI : Unit::Binary, false));
if (colors)
{
os::println("%s %u %4s %4s %6s %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
os::println("%s %lu %4s %4s %6s %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
formatted_mode, st.st_nlink, owner.chars(), group.chars(), size.chars(),
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
else
{
os::println("%s %u %4s %4s %6s %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
os::println("%s %lu %4s %4s %6s %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
size.chars(), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
}

View File

@ -34,7 +34,7 @@ Result<int> luna_main(int argc, char** argv)
close(fd);
}
os::println("%s"_sv, str.chars());
os::println("%s", str.chars());
return 0;
}

35
apps/run.cpp Normal file
View File

@ -0,0 +1,35 @@
/**
* @file run.cpp
* @author apio (cloudapio.eu)
* @brief Tiny command-line utility to start a detached program in the current GUI session.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/LocalClient.h>
#include <os/ipc/Launcher.h>
Result<int> luna_main(int argc, char** argv)
{
StringView program;
os::ArgumentParser parser;
parser.add_description("Start a detached program in the current GUI session."_sv);
parser.add_system_program_info("run"_sv);
parser.add_positional_argument(program, "program", true);
parser.parse(argc, argv);
OwnedPtr<os::IPC::Client> launcher_client = TRY(os::IPC::Client::connect("/tmp/launch.sock", false));
os::println("Requesting to start program '%s'...", program.chars());
os::Launcher::LaunchDetachedRequest request;
SET_IPC_STRING(request.command, program.chars());
request.search_in_path = true;
launcher_client->send_async(request);
return 0;
}

View File

@ -33,7 +33,7 @@ Result<int> luna_main(int argc, char** argv)
auto cmdline = TRY(String::join(command, " "));
os::println("%s %d.%.2ds user %d.%.2ds system"_sv, cmdline.chars(), usage.ru_utime.tv_sec,
os::println("%s %ld.%.2lds user %ld.%.2lds system", cmdline.chars(), usage.ru_utime.tv_sec,
usage.ru_utime.tv_usec / 10000, usage.ru_stime.tv_sec, usage.ru_stime.tv_usec / 10000);
return 0;

12
editor/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
set(SOURCES
main.cpp
EditorWidget.h
EditorWidget.cpp
)
add_executable(editor ${SOURCES})
target_compile_options(editor PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
add_dependencies(editor libc)
target_include_directories(editor PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(editor PRIVATE os ui)
install(TARGETS editor DESTINATION ${LUNA_BASE}/usr/bin)

251
editor/EditorWidget.cpp Normal file
View File

@ -0,0 +1,251 @@
/**
* @file EditorWidget.cpp
* @author apio (cloudapio.eu)
* @brief Multiline text editing widget.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include "EditorWidget.h"
#include <ctype.h>
#include <luna/PathParser.h>
#include <luna/Utf8.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <ui/App.h>
EditorWidget::EditorWidget(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
{
recalculate_lines();
}
Result<void> EditorWidget::load_file(const os::Path& path)
{
struct stat st;
auto rc = os::FileSystem::stat(path, st, true);
if (!rc.has_error() && !S_ISREG(st.st_mode))
{
os::eprintln("editor: not loading %s as it is not a regular file", path.name().chars());
return {};
}
os::eprintln("Loading file: %s", path.name().chars());
auto file = TRY(os::File::open_or_create(path, os::File::ReadOnly));
m_data = TRY(file->read_all());
os::eprintln("Read %zu bytes.", m_data.size());
m_cursor = m_data.size();
m_path = path;
auto basename = TRY(PathParser::basename(m_path.name()));
String title = TRY(String::format("Text Editor - %s"_sv, basename.chars()));
window()->set_title(title.view());
TRY(recalculate_lines());
return {};
}
Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest& request)
{
// Avoid handling "key released" events
if (!request.pressed) return ui::EventResult::DidNotHandle;
if (request.code == moon::K_UpArrow)
{
if (m_cursor_position.y > 0) m_cursor_position.y--;
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_index();
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_DownArrow)
{
if (m_cursor_position.y + 1 < (int)m_lines.size()) m_cursor_position.y++;
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_index();
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_LeftArrow)
{
if (m_cursor > 0) m_cursor--;
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_position();
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_RightArrow)
{
if (m_cursor < m_data.size()) m_cursor++;
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_position();
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.modifiers & ui::Mod_Ctrl)
{
switch (request.key)
{
case 's': {
auto result = save_file();
if (result.has_error())
{
os::eprintln("editor: failed to save file: %s", result.error_string());
return ui::EventResult::DidNotHandle;
}
os::println("editor: buffer saved to %s successfully", m_path.name().chars());
return ui::EventResult::DidNotHandle;
}
default: return ui::EventResult::DidNotHandle;
}
}
if (request.code == moon::K_Backspace)
{
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
m_cursor--;
delete_current_character();
TRY(recalculate_lines());
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.letter != '\n' && iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
else
TRY(insert_character(request.letter));
m_cursor++;
TRY(recalculate_lines());
update_cursor();
return ui::EventResult::DidHandle;
}
Result<void> EditorWidget::save_file()
{
if (m_path.is_empty_path())
{
os::eprintln("editor: no file to save buffer to!");
return err(ENOENT);
}
auto file = TRY(os::File::open(m_path, os::File::WriteOnly));
return file->write(m_data);
}
Result<void> EditorWidget::draw(ui::Canvas& canvas)
{
int visible_lines = canvas.height / m_font->height();
int visible_columns = canvas.width / m_font->width();
if ((usize)visible_lines > m_lines.size()) visible_lines = static_cast<int>(m_lines.size());
for (int i = 0; i < visible_lines; i++)
{
auto line = m_lines[i];
if (line.begin == line.end) continue;
auto slice = TRY(m_data.slice(line.begin, line.end - line.begin));
auto string = TRY(
String::from_string_view(StringView::from_fixed_size_cstring((const char*)slice, line.end - line.begin)));
Utf8StringDecoder decoder(string.chars());
wchar_t buf[4096];
decoder.decode(buf, sizeof(buf)).release_value();
int characters_to_render = (int)wcslen(buf);
for (int j = 0; j < visible_columns && j < characters_to_render; j++)
{
auto subcanvas =
canvas.subcanvas({ j * m_font->width(), i * m_font->height(), m_font->width(), m_font->height() });
m_font->render(buf[j], ui::WHITE, subcanvas);
}
}
// Draw the cursor
if (m_cursor_position.x < visible_columns && m_cursor_position.y < visible_lines && m_cursor_activated)
{
canvas
.subcanvas(
{ m_cursor_position.x * m_font->width(), m_cursor_position.y * m_font->height(), 1, m_font->height() })
.fill(ui::WHITE);
}
return {};
}
Result<void> EditorWidget::recalculate_lines()
{
m_lines.clear();
Line l;
l.begin = 0;
for (usize i = 0; i < m_data.size(); i++)
{
if (m_data.data()[i] == '\n')
{
l.end = i;
TRY(m_lines.try_append(l));
l.begin = i + 1;
}
}
l.end = m_data.size();
TRY(m_lines.try_append(l));
recalculate_cursor_position();
return {};
}
void EditorWidget::recalculate_cursor_position()
{
if (m_cursor == 0) m_cursor_position = { 0, 0 };
for (int i = 0; i < (int)m_lines.size(); i++)
{
auto line = m_lines[i];
if (m_cursor >= line.begin && m_cursor <= line.end)
{
m_cursor_position.x = (int)(m_cursor - line.begin);
m_cursor_position.y = i;
return;
}
}
unreachable();
}
void EditorWidget::recalculate_cursor_index()
{
m_cursor = m_lines[m_cursor_position.y].begin + m_cursor_position.x;
if (m_cursor > m_lines[m_cursor_position.y].end)
{
m_cursor = m_lines[m_cursor_position.y].end;
recalculate_cursor_position();
}
}

44
editor/EditorWidget.h Normal file
View File

@ -0,0 +1,44 @@
/**
* @file EditorWidget.h
* @author apio (cloudapio.eu)
* @brief Multiline text editing widget.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <luna/String.h>
#include <os/Timer.h>
#include <ui/Font.h>
#include <ui/TextInput.h>
#include <ui/Widget.h>
class EditorWidget : public ui::TextInput
{
public:
EditorWidget(SharedPtr<ui::Font> font);
Result<void> load_file(const os::Path& path);
Result<void> save_file();
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(ui::Canvas& canvas) override;
private:
SharedPtr<ui::Font> m_font;
struct Line
{
usize begin;
usize end;
};
Vector<Line> m_lines;
os::Path m_path { AT_FDCWD };
Result<void> recalculate_lines();
void recalculate_cursor_position();
void recalculate_cursor_index();
};

39
editor/main.cpp Normal file
View File

@ -0,0 +1,39 @@
/**
* @file main.cpp
* @author apio (cloudapio.eu)
* @brief Graphical text editor.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include "EditorWidget.h"
#include <os/ArgumentParser.h>
#include <ui/App.h>
Result<int> luna_main(int argc, char** argv)
{
StringView path;
os::ArgumentParser parser;
parser.add_description("A graphical text editor"_sv);
parser.add_system_program_info("editor"_sv);
parser.add_positional_argument(path, "path", false);
parser.parse(argc, argv);
ui::App app;
TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 200, 300, 600, 600 }));
window->set_background(ui::Color::from_rgb(40, 40, 40));
window->set_title("Text Editor");
app.set_main_window(window);
auto* editor = TRY(make<EditorWidget>(ui::Font::default_font()));
window->set_main_widget(*editor);
if (!path.is_empty()) TRY(editor->load_file(path));
window->draw();
return app.run();
}

View File

@ -6,6 +6,7 @@
#include <luna/Format.h>
#include <luna/SourceLocation.h>
#include <luna/Spinlock.h>
#include <luna/StringBuilder.h>
static bool g_debug_enabled = true;
static bool g_serial_enabled = true;
@ -174,3 +175,23 @@ static bool g_check_already_failed = false;
}
CPU::efficient_halt();
}
Result<void> hexdump(void* data, usize size)
{
StringBuilder sb;
u8* ptr = (u8*)data;
while (size)
{
TRY(sb.format("%#2x ", *ptr));
ptr++;
size--;
}
auto message = TRY(sb.string());
kdbgln("hexdump: %s", message.chars());
return {};
}

View File

@ -26,6 +26,8 @@ void set_text_console_initialized();
#define kwarnln(...) log(LogLevel::Warn, __VA_ARGS__)
#define kerrorln(...) log(LogLevel::Error, __VA_ARGS__)
Result<void> hexdump(void* data, usize size);
[[noreturn]] extern void __critical_error_handler(SourceLocation location, const char* expr, const char* failmsg,
const char* errmsg);

View File

@ -157,13 +157,27 @@ namespace ATA
void Channel::select(u8 drive)
{
if (drive == m_current_drive) return;
u8 value = (u8)(drive << 4) | 0xa0;
if (value == m_current_select_value) return;
write_register(Register::DriveSelect, value);
delay_400ns();
m_current_select_value = value;
m_current_drive = drive;
}
void Channel::select(u8 base, u8 drive)
{
u8 value = (u8)(drive << 4) | base;
if (value == m_current_select_value) return;
write_register(Register::DriveSelect, value);
delay_400ns();
m_current_select_value = value;
m_current_drive = drive;
}
@ -543,10 +557,61 @@ namespace ATA
return true;
}
Result<void> Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size)
void Drive::select_lba(u64 lba, usize count)
{
if (m_is_lba48)
{
m_channel->write_register(Register::SectorCount, (u8)((count >> 8) & 0xff));
m_channel->write_register(Register::LBALow, (u8)((lba >> 24) & 0xff));
m_channel->write_register(Register::LBAMiddle, (u8)((lba >> 32) & 0xff));
m_channel->write_register(Register::LBAHigh, (u8)((lba >> 40) & 0xff));
}
m_channel->write_register(Register::SectorCount, (u8)(count & 0xff));
m_channel->write_register(Register::LBALow, (u8)(lba & 0xff));
m_channel->write_register(Register::LBAMiddle, (u8)((lba >> 8) & 0xff));
m_channel->write_register(Register::LBAHigh, (u8)((lba >> 16) & 0xff));
}
Result<void> Drive::read_pio_bytes(void* out, usize size)
{
u8* ptr = (u8*)out;
usize i = 0;
while (i < size)
{
TRY(m_channel->wait_until_ready());
usize byte_count;
if (m_is_atapi)
{
byte_count =
m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle);
}
else
byte_count = min(512ul, size - i);
usize word_count = byte_count / 2;
while (word_count--)
{
u16 value = m_channel->read_data();
ptr[0] = (u8)(value & 0xff);
ptr[1] = (u8)(value >> 8);
ptr += 2;
}
i += byte_count;
m_channel->delay_400ns();
}
return {};
}
Result<void> Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size)
{
m_channel->select(m_drive_index);
// We use PIO here.
@ -559,30 +624,11 @@ namespace ATA
m_channel->delay_400ns();
usize i = 0;
TRY(m_channel->wait_until_ready());
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
while (i < response_size)
{
TRY(m_channel->wait_until_ready());
usize byte_count =
m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle);
usize word_count = byte_count / 2;
while (word_count--)
{
u16 value = m_channel->read_data();
ptr[0] = (u8)(value & 0xff);
ptr[1] = (u8)(value >> 8);
ptr += 2;
}
i += byte_count;
}
TRY(read_pio_bytes(out, response_size));
return {};
}
@ -704,6 +750,26 @@ namespace ATA
return send_packet_atapi_pio(&read_packet, out, (u16)size);
}
Result<void> Drive::ata_read_pio(u64 lba, void* out, usize size)
{
check(lba < m_block_count);
check(size <= ARCH_PAGE_SIZE);
usize count = ceil_div(size, m_block_size);
m_channel->select(m_is_lba48 ? 0x40 : (0xe0 | ((lba >> 24) & 0xf)), m_drive_index);
select_lba(lba, count);
m_channel->write_register(Register::Command, m_is_lba48 ? CMD_ReadSectorsExt : CMD_ReadSectors);
m_channel->delay_400ns();
TRY(read_pio_bytes(out, size));
return {};
}
Result<void> Drive::read_lba(u64 lba, void* out, usize nblocks)
{
const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size;
@ -719,7 +785,16 @@ namespace ATA
return atapi_read_pio(lba, out, nblocks * m_block_size);
}
else
todo();
{
while (nblocks > blocks_per_page)
{
TRY(ata_read_pio(lba, out, ARCH_PAGE_SIZE));
lba += blocks_per_page;
nblocks -= blocks_per_page;
out = offset_ptr(out, ARCH_PAGE_SIZE);
}
return ata_read_pio(lba, out, nblocks * m_block_size);
}
}
void Drive::irq_handler()

View File

@ -54,6 +54,8 @@ namespace ATA
enum CommandRegister : u8
{
CMD_ReadSectors = 0x20,
CMD_ReadSectorsExt = 0x24,
CMD_Identify = 0xec,
CMD_Packet = 0xa0,
CMD_Identify_Packet = 0xa1
@ -180,6 +182,11 @@ namespace ATA
Result<void> atapi_read_pio(u64 lba, void* out, usize size);
Result<void> ata_read_pio(u64 lba, void* out, usize size);
void select_lba(u64 lba, usize count);
Result<void> read_pio_bytes(void* out, usize size);
Channel* m_channel;
u8 m_drive_index;
@ -250,6 +257,7 @@ namespace ATA
}
void select(u8 drive);
void select(u8 base, u8 drive);
bool initialize();
@ -270,6 +278,7 @@ namespace ATA
bool m_irq_called { false };
u8 m_current_drive = (u8)-1;
u8 m_current_select_value = 0xff;
Option<Drive> m_drives[2];
};

View File

@ -9,6 +9,7 @@
#pragma once
#include <fcntl.h>
#include <luna/Attributes.h>
#include <luna/Buffer.h>
#include <luna/Result.h>
#include <luna/SharedPtr.h>
@ -113,7 +114,7 @@ namespace os
/**
* @brief Write a string to this File.
*
* @param str The string to write (can be not null-terminated).
* @param str The string to write (doesn't have to be null-terminated).
* @return Result<void> Whether the operation succeeded.
*/
Result<void> write(StringView str);
@ -126,6 +127,24 @@ namespace os
*/
Result<void> write(const Buffer& buf);
/**
* @brief Write a formatted string to this File.
*
* @param format The format string.
* @param ... The format arguments.
* @return Result<void> Whether the operation succeeded.
*/
Result<usize> write_formatted(const char* format, ...) _format(2, 3);
/**
* @brief Write a formatted string to this File.
*
* @param format The format string.
* @param args The format arguments.
* @return Result<void> Whether the operation succeeded.
*/
Result<usize> write_vformatted(const char* format, va_list args);
/**
* @brief Read a line from this File.
*
@ -247,36 +266,36 @@ namespace os
/**
* @brief Print a formatted string to standard output.
*
* @param fmt The format string (in the same format as printf(3)).
* @param format The format string (in the same format as printf(3)).
* @param ... The format arguments.
* @return Result<void> Whether the operation succeeded.
*/
Result<void> print(StringView fmt, ...);
Result<void> print(const char* format, ...) _format(1, 2);
/**
* @brief Print a newline-terminated formatted string to standard output.
*
* @param fmt The format string (in the same format as printf(3)).
* @param format The format string (in the same format as printf(3)).
* @param ... The format arguments.
* @return Result<void> Whether the operation succeeded.
*/
Result<void> println(StringView fmt, ...);
Result<void> println(const char* format, ...) _format(1, 2);
/**
* @brief Print a formatted string to standard error.
*
* @param fmt The format string (in the same format as printf(3)).
* @param format The format string (in the same format as printf(3)).
* @param ... The format arguments.
* @return Result<void> Whether the operation succeeded.
*/
Result<void> eprint(StringView fmt, ...);
Result<void> eprint(const char* format, ...) _format(1, 2);
/**
* @brief Print a newline-terminated formatted string to standard error.
*
* @param fmt The format string (in the same format as printf(3)).
* @param format The format string (in the same format as printf(3)).
* @param ... The format arguments.
* @return Result<void> Whether the operation succeeded.
*/
Result<void> eprintln(StringView fmt, ...);
Result<void> eprintln(const char* format, ...) _format(1, 2);
}

View File

@ -25,6 +25,7 @@ namespace os
static constexpr u8 ID = LAUNCH_DETACHED_ID;
IPC_STRING(command);
bool search_in_path { false };
};
}
}

View File

@ -8,6 +8,7 @@
*/
#include <errno.h>
#include <luna/Format.h>
#include <luna/StringBuilder.h>
#include <os/File.h>
#include <unistd.h>
@ -141,6 +142,33 @@ namespace os
return {};
}
Result<usize> File::write_formatted(const char* format, ...)
{
va_list ap;
va_start(ap, format);
auto result = write_vformatted(format, ap);
va_end(ap);
return result;
}
Result<usize> File::write_vformatted(const char* format, va_list args)
{
auto rc = TRY(cstyle_format(
format,
[](char c, void* f) -> Result<void> {
TRY(((File*)f)->raw_write((const u8*)&c, 1));
return {};
},
this, args));
flush();
return rc;
}
Result<String> File::read_line()
{
Vector<char> data;
@ -246,64 +274,57 @@ namespace os
setvbuf(m_file, NULL, mode, 0);
}
// FIXME: Do not allocate memory for printing.
Result<void> print_impl(SharedPtr<File> f, StringView fmt, va_list ap)
{
auto str = TRY(String::vformat(fmt, ap));
auto rc = f->write(str.view());
f->flush();
return rc;
}
Result<void> print(StringView fmt, ...)
Result<void> print(const char* format, ...)
{
va_list ap;
va_start(ap, fmt);
va_start(ap, format);
auto rc = print_impl(File::standard_output(), fmt, ap);
TRY(File::standard_output()->write_vformatted(format, ap));
va_end(ap);
return rc;
return {};
}
Result<void> println(StringView fmt, ...)
Result<void> println(const char* format, ...)
{
va_list ap;
va_start(ap, fmt);
va_start(ap, format);
auto rc = print_impl(File::standard_output(), fmt, ap);
auto file = File::standard_output();
TRY(file->write_vformatted(format, ap));
TRY(file->write("\n"_sv));
va_end(ap);
TRY(rc);
return File::standard_output()->write("\n"_sv);
return {};
}
Result<void> eprint(StringView fmt, ...)
Result<void> eprint(const char* format, ...)
{
va_list ap;
va_start(ap, fmt);
va_start(ap, format);
auto rc = print_impl(File::standard_error(), fmt, ap);
TRY(File::standard_error()->write_vformatted(format, ap));
va_end(ap);
return rc;
return {};
}
Result<void> eprintln(StringView fmt, ...)
Result<void> eprintln(const char* format, ...)
{
va_list ap;
va_start(ap, fmt);
va_start(ap, format);
auto rc = print_impl(File::standard_error(), fmt, ap);
auto file = File::standard_error();
TRY(file->write_vformatted(format, ap));
TRY(file->write("\n"_sv));
va_end(ap);
TRY(rc);
return File::standard_error()->write("\n"_sv);
return {};
}
}

View File

@ -23,7 +23,7 @@ namespace os::SharedMemory
if (fd < 0)
{
int olderr = errno;
os::eprintln("os: could not create shared memory region: shm_open failed (%s) - %s", path,
os::eprintln("os: could not create shared memory region: shm_open failed (%s) - %s", path.chars(),
strerror(olderr));
return err(olderr);
}

View File

@ -17,6 +17,8 @@ set(SOURCES
src/Container.cpp
src/Button.cpp
src/Label.cpp
src/InputField.cpp
src/TextInput.cpp
)
add_library(ui ${SOURCES})

View File

@ -0,0 +1,40 @@
/**
* @file InputField.h
* @author apio (cloudapio.eu)
* @brief Single line text input widget.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#pragma once
#include <os/Action.h>
#include <ui/Font.h>
#include <ui/TextInput.h>
namespace ui
{
class InputField : public ui::TextInput
{
public:
InputField(SharedPtr<ui::Font> font);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(ui::Canvas& canvas) override;
StringView data();
void on_submit(os::Function<StringView>&& action)
{
m_on_submit_action = move(action);
m_has_on_submit_action = true;
}
private:
SharedPtr<ui::Font> m_font;
os::Function<StringView> m_on_submit_action;
bool m_has_on_submit_action { false };
};
}

View File

@ -0,0 +1,41 @@
/**
* @file TextInput.h
* @author apio (cloudapio.eu)
* @brief Base class for text inputs.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#pragma once
#include <luna/Buffer.h>
#include <os/Timer.h>
#include <ui/Widget.h>
namespace ui
{
class TextInput : public Widget
{
public:
TextInput();
virtual Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) = 0;
virtual Result<void> draw(ui::Canvas& canvas) = 0;
protected:
Buffer m_data;
usize m_cursor { 0 };
ui::Point m_cursor_position { 0, 0 };
OwnedPtr<os::Timer> m_cursor_timer;
bool m_cursor_activated = true;
void tick_cursor();
void update_cursor();
Result<void> delete_current_character();
Result<void> insert_character(char c);
};
}

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

@ -0,0 +1,112 @@
/**
* @file InputField.cpp
* @author apio (cloudapio.eu)
* @brief Single line text input widget.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <ctype.h>
#include <luna/String.h>
#include <luna/StringView.h>
#include <luna/Utf8.h>
#include <ui/InputField.h>
namespace ui
{
InputField::InputField(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
{
}
Result<ui::EventResult> InputField::handle_key_event(const ui::KeyEventRequest& request)
{
// Avoid handling "key released" events
if (!request.pressed) return ui::EventResult::DidNotHandle;
if (request.code == moon::K_LeftArrow)
{
if (m_cursor > 0) m_cursor--;
else
return ui::EventResult::DidNotHandle;
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_RightArrow)
{
if (m_cursor < m_data.size()) m_cursor++;
else
return ui::EventResult::DidNotHandle;
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_Backspace)
{
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
m_cursor--;
delete_current_character();
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.letter == '\n')
{
if (m_has_on_submit_action)
{
m_on_submit_action(data());
return ui::EventResult::DidHandle;
}
return ui::EventResult::DidNotHandle;
}
if (iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
else
TRY(insert_character(request.letter));
m_cursor++;
update_cursor();
return ui::EventResult::DidHandle;
}
Result<void> InputField::draw(ui::Canvas& canvas)
{
int visible_characters = canvas.width / m_font->width();
auto string = data();
Utf8StringDecoder decoder(string.chars());
wchar_t buf[4096];
decoder.decode(buf, sizeof(buf)).release_value();
int characters_to_render = (int)wcslen(buf);
for (int j = 0; j < visible_characters && j < characters_to_render; j++)
{
auto subcanvas = canvas.subcanvas({ j * m_font->width(), 0, m_font->width(), m_font->height() });
m_font->render(buf[j], ui::WHITE, subcanvas);
}
// Draw the cursor
if ((int)m_cursor < visible_characters && m_cursor_activated)
{
canvas.subcanvas({ (int)m_cursor * m_font->width(), 0, 1, m_font->height() }).fill(ui::WHITE);
}
return {};
}
StringView InputField::data()
{
return StringView { (const char*)m_data.data(), m_data.size() };
}
}

52
libui/src/TextInput.cpp Normal file
View File

@ -0,0 +1,52 @@
/**
* @file TextInput.cpp
* @author apio (cloudapio.eu)
* @brief Base class for text inputs.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <ui/App.h>
#include <ui/TextInput.h>
namespace ui
{
TextInput::TextInput() : Widget()
{
m_cursor_timer = os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }).release_value();
}
void TextInput::update_cursor()
{
m_cursor_timer->restart();
m_cursor_activated = true;
}
Result<void> TextInput::delete_current_character()
{
usize size = m_data.size() - m_cursor;
u8* slice = TRY(m_data.slice(m_cursor, size));
memmove(slice, slice + 1, size - 1);
TRY(m_data.try_resize(m_data.size() - 1));
return {};
}
Result<void> TextInput::insert_character(char c)
{
usize size = m_data.size() - m_cursor;
u8* slice = TRY(m_data.slice(m_cursor, size + 1));
memmove(slice + 1, slice, size);
*slice = (u8)c;
return {};
}
void TextInput::tick_cursor()
{
m_cursor_activated = !m_cursor_activated;
window()->draw();
}
}

View File

@ -144,7 +144,7 @@ Result<int> luna_main(int argc, char** argv)
{
if (maybe_cmd.error() == EINTR)
{
os::println("");
os::print("\n");
continue;
}
return maybe_cmd.release_error();
@ -200,7 +200,7 @@ Result<int> luna_main(int argc, char** argv)
int sig = WTERMSIG(status);
if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig));
else
os::println("");
os::print("\n");
}
}

View File

@ -40,7 +40,7 @@ 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 = window()->canvas();
m_terminal_canvas.fill(ui::BLACK);
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));
@ -102,7 +102,7 @@ Result<void> TerminalWidget::process()
if (did_draw) drawn++;
}
if (drawn > 0) ui::App::the().main_window()->draw();
if (drawn > 0) window()->draw();
return {};
}
@ -117,7 +117,7 @@ void TerminalWidget::tick_cursor()
else
erase_current_char();
ui::App::the().main_window()->draw();
window()->draw();
}
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)

View File

@ -186,7 +186,7 @@ Result<int> luna_main(int argc, char** argv)
if (fds[i + 4].revents & POLLHUP) clients[i]->should_be_disconnected = true;
if (clients[i]->should_be_disconnected)
{
os::println("wind: Client %d disconnected", i);
os::println("wind: Client %zu disconnected", i);
fds.remove_at(i + 4);
auto client = clients.remove_at(i);
client->conn->disconnect();