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(shell)
add_subdirectory(wind) add_subdirectory(wind)
add_subdirectory(terminal) add_subdirectory(terminal)
add_subdirectory(editor)

View File

@ -1,5 +1,5 @@
# Luna # 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? ## Another UNIX clone?
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_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. `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 images
Prebuilt ISO images (numbered) for every version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases). Prebuilt ISO images for every release 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.
## Is there third-party software I can use on Luna? ## 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) target_link_libraries(clock PUBLIC ui)
luna_app(startui.cpp startui) luna_app(startui.cpp startui)
luna_app(launch.cpp launch) 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() }; StringView args[] = { path.view() };
os::Process::spawn(args[0], { args, 1 }, false); os::Process::spawn(args[0], { args, 1 }, request.search_in_path);
return {}; 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 & POLLIN) clients[i]->check_for_messages();
if (fds[i + 1].revents & POLLHUP) 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); fds.remove_at(i + 1);
auto client = clients.remove_at(i); auto client = clients.remove_at(i);
client->disconnect(); client->disconnect();

View File

@ -212,14 +212,15 @@ Result<int> luna_main(int argc, char** argv)
{ {
if (colors) 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, formatted_mode, st.st_nlink, owner.chars(), group.chars(), st.st_size,
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
} }
else else
{ {
os::println("%s %u %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(), os::println("%s %lu %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(),
st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); group.chars(), st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ",
link.chars());
} }
} }
else 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)); auto size = TRY(to_dynamic_unit(st.st_size, 10, false, si ? Unit::SI : Unit::Binary, false));
if (colors) 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(), formatted_mode, st.st_nlink, owner.chars(), group.chars(), size.chars(),
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
} }
else 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()); 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); close(fd);
} }
os::println("%s"_sv, str.chars()); os::println("%s", str.chars());
return 0; 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, " ")); 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); usage.ru_utime.tv_usec / 10000, usage.ru_stime.tv_sec, usage.ru_stime.tv_usec / 10000);
return 0; 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/Format.h>
#include <luna/SourceLocation.h> #include <luna/SourceLocation.h>
#include <luna/Spinlock.h> #include <luna/Spinlock.h>
#include <luna/StringBuilder.h>
static bool g_debug_enabled = true; static bool g_debug_enabled = true;
static bool g_serial_enabled = true; static bool g_serial_enabled = true;
@ -174,3 +175,23 @@ static bool g_check_already_failed = false;
} }
CPU::efficient_halt(); 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 kwarnln(...) log(LogLevel::Warn, __VA_ARGS__)
#define kerrorln(...) log(LogLevel::Error, __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, [[noreturn]] extern void __critical_error_handler(SourceLocation location, const char* expr, const char* failmsg,
const char* errmsg); const char* errmsg);

View File

@ -157,13 +157,27 @@ namespace ATA
void Channel::select(u8 drive) void Channel::select(u8 drive)
{ {
if (drive == m_current_drive) return;
u8 value = (u8)(drive << 4) | 0xa0; u8 value = (u8)(drive << 4) | 0xa0;
if (value == m_current_select_value) return;
write_register(Register::DriveSelect, value); write_register(Register::DriveSelect, value);
delay_400ns(); 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; m_current_drive = drive;
} }
@ -543,10 +557,61 @@ namespace ATA
return true; 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; 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); m_channel->select(m_drive_index);
// We use PIO here. // We use PIO here.
@ -559,30 +624,11 @@ namespace ATA
m_channel->delay_400ns(); m_channel->delay_400ns();
usize i = 0;
TRY(m_channel->wait_until_ready()); TRY(m_channel->wait_until_ready());
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]); for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
while (i < response_size) TRY(read_pio_bytes(out, 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;
}
return {}; return {};
} }
@ -704,6 +750,26 @@ namespace ATA
return send_packet_atapi_pio(&read_packet, out, (u16)size); 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) Result<void> Drive::read_lba(u64 lba, void* out, usize nblocks)
{ {
const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size; 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); return atapi_read_pio(lba, out, nblocks * m_block_size);
} }
else 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() void Drive::irq_handler()

View File

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

View File

@ -9,6 +9,7 @@
#pragma once #pragma once
#include <fcntl.h> #include <fcntl.h>
#include <luna/Attributes.h>
#include <luna/Buffer.h> #include <luna/Buffer.h>
#include <luna/Result.h> #include <luna/Result.h>
#include <luna/SharedPtr.h> #include <luna/SharedPtr.h>
@ -113,7 +114,7 @@ namespace os
/** /**
* @brief Write a string to this File. * @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. * @return Result<void> Whether the operation succeeded.
*/ */
Result<void> write(StringView str); Result<void> write(StringView str);
@ -126,6 +127,24 @@ namespace os
*/ */
Result<void> write(const Buffer& buf); 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. * @brief Read a line from this File.
* *
@ -247,36 +266,36 @@ namespace os
/** /**
* @brief Print a formatted string to standard output. * @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. * @param ... The format arguments.
* @return Result<void> Whether the operation succeeded. * @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. * @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. * @param ... The format arguments.
* @return Result<void> Whether the operation succeeded. * @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. * @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. * @param ... The format arguments.
* @return Result<void> Whether the operation succeeded. * @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. * @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. * @param ... The format arguments.
* @return Result<void> Whether the operation succeeded. * @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; static constexpr u8 ID = LAUNCH_DETACHED_ID;
IPC_STRING(command); IPC_STRING(command);
bool search_in_path { false };
}; };
} }
} }

View File

@ -8,6 +8,7 @@
*/ */
#include <errno.h> #include <errno.h>
#include <luna/Format.h>
#include <luna/StringBuilder.h> #include <luna/StringBuilder.h>
#include <os/File.h> #include <os/File.h>
#include <unistd.h> #include <unistd.h>
@ -141,6 +142,33 @@ namespace os
return {}; 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() Result<String> File::read_line()
{ {
Vector<char> data; Vector<char> data;
@ -246,64 +274,57 @@ namespace os
setvbuf(m_file, NULL, mode, 0); setvbuf(m_file, NULL, mode, 0);
} }
// FIXME: Do not allocate memory for printing. Result<void> print(const char* format, ...)
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, ...)
{ {
va_list ap; 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); va_end(ap);
return rc; return {};
} }
Result<void> println(StringView fmt, ...) Result<void> println(const char* format, ...)
{ {
va_list ap; 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); va_end(ap);
TRY(rc); return {};
return File::standard_output()->write("\n"_sv);
} }
Result<void> eprint(StringView fmt, ...) Result<void> eprint(const char* format, ...)
{ {
va_list ap; 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); va_end(ap);
return rc; return {};
} }
Result<void> eprintln(StringView fmt, ...) Result<void> eprintln(const char* format, ...)
{ {
va_list ap; 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); va_end(ap);
TRY(rc); return {};
return File::standard_error()->write("\n"_sv);
} }
} }

View File

@ -23,7 +23,7 @@ namespace os::SharedMemory
if (fd < 0) if (fd < 0)
{ {
int olderr = errno; 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)); strerror(olderr));
return err(olderr); return err(olderr);
} }

View File

@ -17,6 +17,8 @@ set(SOURCES
src/Container.cpp src/Container.cpp
src/Button.cpp src/Button.cpp
src/Label.cpp src/Label.cpp
src/InputField.cpp
src/TextInput.cpp
) )
add_library(ui ${SOURCES}) 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) if (maybe_cmd.error() == EINTR)
{ {
os::println(""); os::print("\n");
continue; continue;
} }
return maybe_cmd.release_error(); return maybe_cmd.release_error();
@ -200,7 +200,7 @@ Result<int> luna_main(int argc, char** argv)
int sig = WTERMSIG(status); int sig = WTERMSIG(status);
if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig)); if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig));
else 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_font = ui::Font::default_font();
m_bold_font = ui::Font::default_bold_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_terminal_canvas.fill(ui::BLACK);
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); })); 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 (did_draw) drawn++;
} }
if (drawn > 0) ui::App::the().main_window()->draw(); if (drawn > 0) window()->draw();
return {}; return {};
} }
@ -117,7 +117,7 @@ void TerminalWidget::tick_cursor()
else else
erase_current_char(); erase_current_char();
ui::App::the().main_window()->draw(); window()->draw();
} }
void TerminalWidget::draw_glyph(wchar_t c, int x, int y) 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 (fds[i + 4].revents & POLLHUP) clients[i]->should_be_disconnected = true;
if (clients[i]->should_be_disconnected) 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); fds.remove_at(i + 4);
auto client = clients.remove_at(i); auto client = clients.remove_at(i);
client->conn->disconnect(); client->conn->disconnect();