alecAbstraction Layer for Escape Codes |
git clone git://git.dimitrijedobrota.com/alec.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | 970f2676dd7c05d9a8b34e278b2778c2a393d1eb |
parent | 99d2f8a13cdc0cbfc0fc8b9d77c4590be3b73bb4 |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Tue, 4 Feb 2025 15:07:40 +0100 |
Started working on keypress detection * Error types * Input buffer * Event class * Enter raw-mode
Diffstat:M | CMakeLists.txt | | | +- |
M | source/alec.rules.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
2 files changed, 212 insertions(+), 2 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)
project(
alec
VERSION 0.1.12
VERSION 0.1.13
DESCRIPTION "Abstraction Layer for Escape Codes"
HOMEPAGE_URL "git://git.dimitrijedobrota.com/alec.git"
LANGUAGES CXX
diff --git a/source/alec.rules.hpp b/source/alec.rules.hpp
@@ -4,8 +4,13 @@
#include <array>
#include <cassert>
#include <cinttypes>
#include <optional>
#include <stdexcept>
#include <string>
#include <termios.h>
#include <unistd.h>
namespace alec
{
@@ -365,6 +370,211 @@ static constexpr bool limit_256(int n)
// NOLINTEND (*cast*)
*//*%%*/
// Keyboard string TODO
class runtime_error : public std::runtime_error
{
public:
explicit runtime_error(const std::string& err)
: std::runtime_error(err)
{
}
};
enum error_code_t // NOLINT
{
FDNTERM,
TERMIOSRD,
TERMIOSWR,
BUFFULL,
CHARRD,
};
template<error_code_t e>
class error : public runtime_error
{
public:
explicit error()
: runtime_error(error_get_message(e))
{
}
private:
static std::string error_get_message(error_code_t error)
{
switch (error) {
case error_code_t::FDNTERM:
return "File descriptor is not associated with a terminal";
case error_code_t::TERMIOSRD:
return "Can't read termios";
case error_code_t::TERMIOSWR:
return "Can't write termios";
case error_code_t::BUFFULL:
return "Buffer is full";
case error_code_t::CHARRD:
return "Can't read character";
}
return "alec error, should not happen...";
}
};
class buffer
{
public:
explicit buffer(int fdsc)
: m_fd(fdsc)
, m_orig_termios()
{
if (isatty(m_fd) == 0) {
throw error<error_code_t::FDNTERM>();
}
if (tcgetattr(m_fd, &m_orig_termios) == -1) {
throw error<error_code_t::TERMIOSRD>();
}
struct termios raw = m_orig_termios;
// NOLINTBEGIN
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // | ISIG
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;
// NOLINTEND
/* put terminal in raw mode after flushing */
if (tcsetattr(m_fd, TCSAFLUSH, &raw) < 0) {
throw error<error_code_t::TERMIOSWR>();
}
}
buffer(const buffer&) = delete;
buffer& operator=(const buffer&) = delete;
buffer(buffer&&) = default;
buffer& operator=(buffer&&) = default;
~buffer() { tcsetattr(m_fd, TCSAFLUSH, &m_orig_termios); }
uint8_t read()
{
if (m_start == m_end && get() == 0) {
return 0;
}
uint8_t chr = m_buffer[m_start]; // NOLINT
m_start = (m_start + 1) % m_buffer.size();
return chr;
}
uint8_t read_blocking()
{
while (m_start == m_end) {
get();
}
uint8_t chr = m_buffer[m_start]; // NOLINT
m_start = (m_start + 1) % m_buffer.size();
return chr;
}
void flush()
{
while (get() != 0) {
}
}
private:
size_t get()
{
ssize_t scnt = -1;
if ((m_end + 1) % m_buffer.size() == m_start) {
throw error<error_code_t::BUFFULL>();
}
if (m_start <= m_end) {
scnt = ::read(m_fd, m_buffer.data() + m_end, m_buffer.size() - m_end);
} else {
scnt = ::read(m_fd, m_buffer.data() + m_end, m_start - m_end);
}
if (scnt == -1) {
throw error<error_code_t::CHARRD>();
}
const auto cnt = static_cast<size_t>(scnt);
m_end = (m_end + cnt) % m_buffer.size();
return cnt;
}
std::array<uint8_t, 1024> m_buffer = {0};
int m_fd = 0;
uint64_t m_start = 0;
uint64_t m_end = 0;
struct termios m_orig_termios;
};
inline auto& get_buffer()
{
static std::optional<buffer> ibuf;
return ibuf;
}
inline void init_buffer(int fdsc)
{
get_buffer().emplace(fdsc);
}
inline void dest_buffer()
{
get_buffer().reset();
}
class event
{
public:
enum class Type : std::uint8_t
{
NONE = 0,
KEY = 1,
RESIZE = 2,
MOUSE = 3,
};
enum class Mod : std::uint8_t
{
ALT = 1,
CTRL = 2,
SHIFT = 4,
MOTION = 8,
};
event(Type type, uint8_t mod_mask, uint8_t key)
: m_type(type)
, m_mod_mask(mod_mask)
, m_key(key)
{
}
auto type() const { return m_type; }
auto key() const { return m_key; }
auto mod_mask() const { return m_mod_mask; }
bool is_set(uint8_t mask) const { return mask == (m_mod_mask & mask); }
private:
Type m_type = Type::NONE;
uint8_t m_mod_mask = 0;
uint8_t m_key = 0;
};
inline event get_event()
{
const auto chr = get_buffer().value().read();
return {chr != 0 ? event::Type::KEY : event::Type::NONE, 0, chr};
}
} // namespace alec