Skip to content

Commit

Permalink
Yaft2: Unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
timower committed Oct 19, 2023
1 parent 73437f4 commit 6f6bacd
Show file tree
Hide file tree
Showing 21 changed files with 585 additions and 261 deletions.
25 changes: 13 additions & 12 deletions apps/yaft2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
project(yaft2)

add_executable(${PROJECT_NAME}
main.cpp
keyboard.cpp
screen.cpp
layout.cpp
keymap.cpp
config.cpp)
add_library(yaft_app_lib OBJECT
YaftWidget.cpp keyboard.cpp screen.cpp layout.cpp keymap.cpp config.cpp)
add_library(Yaft::app_lib ALIAS yaft_app_lib)

target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
target_include_directories(yaft_app_lib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

target_link_libraries(${PROJECT_NAME}
PRIVATE
target_link_libraries(yaft_app_lib PUBLIC
rMlib
libYaft
util
tomlplusplus::tomlplusplus)

if (APPLE)
target_link_libraries(${PROJECT_NAME} PRIVATE linux::mxcfb)
target_link_libraries(yaft_app_lib PUBLIC linux::mxcfb)
endif()

install(TARGETS ${PROJECT_NAME} DESTINATION opt/bin)
add_executable(${PROJECT_NAME} main.cpp)

target_link_libraries(${PROJECT_NAME}
PRIVATE
yaft_app_lib
rMlib)

install(TARGETS ${PROJECT_NAME} DESTINATION opt/bin)
install(FILES draft/yaft2.draft DESTINATION opt/etc/draft)
158 changes: 158 additions & 0 deletions apps/yaft2/YaftWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#include "YaftWidget.h"

// libYaft
#include "conf.h"
#include "parse.h"
#include "terminal.h"
#include "yaft.h"

#include "util.h"

#include <Device.h>

#include <sstream>

using namespace rmlib;

namespace {
const char* term_name = "yaft-256color";

AppContext* globalCtx = nullptr;

void
sig_handler(int signo) {
if (signo == SIGCHLD) {
if (globalCtx != nullptr) {
globalCtx->stop();
}
wait(NULL);
}
}

void
initSignalHandler(AppContext& ctx) {
globalCtx = &ctx;

struct sigaction sigact;
memset(&sigact, 0, sizeof(struct sigaction));
sigact.sa_handler = sig_handler;
sigact.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sigact, NULL);
}

bool
fork_and_exec(int* master,
const char* cmd,
char* const argv[],
int lines,
int cols) {
pid_t pid;
struct winsize ws;
ws.ws_row = lines;
ws.ws_col = cols;
/* XXX: this variables are UNUSED (man tty_ioctl),
but useful for calculating terminal cell size */
ws.ws_ypixel = CELL_HEIGHT * lines;
ws.ws_xpixel = CELL_WIDTH * cols;

pid = eforkpty(master, NULL, NULL, &ws);
if (pid < 0) {
return false;
} else if (pid == 0) { /* child */
setenv("TERM", term_name, 1);
execvp(cmd, argv);
/* never reach here */
exit(EXIT_FAILURE);
}
return true;
}
} // namespace

YaftState::~YaftState() {
if (term) {
term_die(term.get());
}
}

void
YaftState::logTerm(std::string_view str) {
parse(term.get(), reinterpret_cast<const uint8_t*>(str.data()), str.size());
}

void
YaftState::init(rmlib::AppContext& ctx, const rmlib::BuildContext&) {
term = std::make_unique<terminal_t>();

// term_init needs the maximum size of the terminal.
int maxSize = std::max(ctx.getFbCanvas().width(), ctx.getFbCanvas().height());
if (!term_init(term.get(), maxSize, maxSize)) {
std::cout << "Error init term\n";
ctx.stop();
return;
}

if (const auto& err = getWidget().configError; err.has_value()) {
logTerm(err->msg);
}

initSignalHandler(ctx);

if (!fork_and_exec(&term->fd,
getWidget().cmd,
getWidget().argv,
term->lines,
term->cols)) {
puts("Failed to fork!");
std::exit(EXIT_FAILURE);
}

ctx.listenFd(term->fd, [this] {
std::array<char, 512> buf;
auto size = read(term->fd, &buf[0], buf.size());

// Only update if the buffer isn't full. Otherwise more data is comming
// probably.
if (size != buf.size()) {
setState([&](auto& self) {
parse(self.term.get(), reinterpret_cast<uint8_t*>(&buf[0]), size);
});
} else {
parse(term.get(), reinterpret_cast<uint8_t*>(&buf[0]), size);
}
});

// listen to stdin in debug.
if constexpr (USE_STDIN) {
ctx.listenFd(STDIN_FILENO, [this] {
std::array<char, 512> buf;
auto size = read(STDIN_FILENO, &buf[0], buf.size());
if (size > 0) {
write(term->fd, &buf[0], size);
}
});
}

checkLandscape(ctx);
ctx.onDeviceUpdate([this, &ctx] { modify().checkLandscape(ctx); });
}

void
YaftState::checkLandscape(rmlib::AppContext& ctx) {
const auto& config = getWidget().config;

if (config.orientation == YaftConfig::Orientation::Auto) {

// The pogo state updates after a delay, so wait 100 ms before checking.
pogoTimer = ctx.addTimer(std::chrono::milliseconds(100), [this] {
setState(
[](auto& self) { self.isLandscape = device::IsPogoConnected(); });
});
} else {
isLandscape = config.orientation == YaftConfig::Orientation::Landscape;
}
}

YaftState
Yaft::createState() const {
return {};
}
75 changes: 75 additions & 0 deletions apps/yaft2/YaftWidget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#pragma once

#include "config.h"
#include "keyboard.h"
#include "layout.h"
#include "screen.h"

#include <yaft.h>

#include <UI/StatefulWidget.h>

class YaftState;

class Yaft : public rmlib::StatefulWidget<Yaft> {
public:
Yaft(const char* cmd, char* const argv[], YaftConfigAndError config)
: config(std::move(config.config))
, configError(std::move(config.err))
, cmd(cmd)
, argv(argv) {}

YaftState createState() const;

private:
friend class YaftState;

YaftConfig config;
std::optional<YaftConfigError> configError;

const char* cmd;
char* const* argv;
};

class YaftState : public rmlib::StateBase<Yaft> {
public:
~YaftState();

/// Logs the given string to the terminal console.
void logTerm(std::string_view str);

void init(rmlib::AppContext& ctx, const rmlib::BuildContext&);

void checkLandscape(rmlib::AppContext& ctx);

auto build(rmlib::AppContext& ctx,
const rmlib::BuildContext& buildCtx) const {
using namespace rmlib;

const auto& layout = [this]() -> const Layout& {
if (isLandscape) {
return empty_layout;
}

if (hidden) {
return hidden_layout;
}

return *getWidget().config.layout;
}();

return Column(
Expanded(Screen(term.get(), isLandscape, getWidget().config.autoRefresh)),
Keyboard(
term.get(), { layout, *getWidget().config.keymap }, [this](int num) {
setState([](auto& self) { self.hidden = !self.hidden; });
}));
}

private:
std::unique_ptr<terminal_t> term;
rmlib::TimerHandle pogoTimer;

bool hidden = false;
bool isLandscape = false;
};
46 changes: 40 additions & 6 deletions apps/yaft2/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@ auto-refresh = 1024

std::filesystem::path
getConfigPath() {
const auto* home = getenv("HOME");
if (home == nullptr || home[0] == 0) {
home = "/home/root";
}
return std::filesystem::path(home) / ".config" / "yaft" / "config.toml";
const auto configDir = [] {
if (const auto* xdgCfg = getenv("XDG_CONFIG_HOME");
xdgCfg != nullptr && xdgCfg[0] != 0) {
return std::filesystem::path(xdgCfg);
}

const auto* home = getenv("HOME");
if (home == nullptr || home[0] == 0) {
home = "/home/root";
}

return std::filesystem::path(home) / ".config" / "yaft";
}();

return configDir / "config.toml";
}

template<typename Value>
Expand Down Expand Up @@ -84,7 +94,7 @@ getConfig(const toml::table& tbl) {
YaftConfig
YaftConfig::getDefault() {
auto tbl = toml::parse(default_config);
return *getConfig(tbl);
return getConfig(tbl).value();
}

ErrorOr<YaftConfig, YaftConfigError>
Expand Down Expand Up @@ -123,3 +133,27 @@ saveDefaultConfig() {

return {};
}

YaftConfigAndError
loadConfigOrMakeDefault() {
auto cfgOrErr = loadConfig().transform([](auto val) {
return YaftConfigAndError{ std::move(val), {} };
});
if (cfgOrErr.has_value()) {
return *cfgOrErr;
}

auto err = cfgOrErr.error();
if (err.type == YaftConfigError::Missing) {
err.msg = "No config, creating new one\r\n";
if (const auto optErr = saveDefaultConfig(); !optErr.has_value()) {
err.msg += optErr.error().msg + "\r\n";
}
} else {
std::stringstream ss;
ss << "Config syntax error: " << err.msg << "\r\n";
err.msg = ss.str();
}

return { YaftConfig::getDefault(), std::move(err) };
}
15 changes: 15 additions & 0 deletions apps/yaft2/config.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include "Error.h"

#include <optional>
#include <string>

#include "keymap.h"
Expand Down Expand Up @@ -29,9 +31,22 @@ struct YaftConfigError {
std::string msg;
};

struct YaftConfigAndError {
YaftConfig config;

std::optional<YaftConfigError> err;
};

/// Load the config from the `~/.config/yaft/config.toml` location.
ErrorOr<YaftConfig, YaftConfigError>
loadConfig();

OptError<>
saveDefaultConfig();

/// Always returns a config, either the default one or the one on the file
/// system. Will also make a new config file if it didn't exist.
///
/// If any error occured during the loading of the config, it's also returned.
YaftConfigAndError
loadConfigOrMakeDefault();
Loading

0 comments on commit 6f6bacd

Please sign in to comment.