chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Arguments
|
||||
{
|
||||
public:
|
||||
Arguments();
|
||||
static std::size_t argc();
|
||||
static std::vector<std::string> argv();
|
||||
std::string operator[](const std::size_t& arg) const;
|
||||
|
||||
private:
|
||||
static void parse();
|
||||
static std::vector<std::string> m_args;
|
||||
static bool m_parsed;
|
||||
};
|
||||
|
||||
class Argc
|
||||
{
|
||||
public:
|
||||
Argc();
|
||||
operator unsigned int();
|
||||
operator unsigned int() const;
|
||||
};
|
||||
|
||||
static const Arguments argv;
|
||||
static const Argc argc;
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <streambuf>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Buffer final : public std::streambuf
|
||||
{
|
||||
public:
|
||||
enum class Type : std::uint8_t
|
||||
{
|
||||
Unbuffered,
|
||||
LineBuffered,
|
||||
FullBuffered
|
||||
};
|
||||
explicit Buffer(const Term::Buffer::Type& type = Term::Buffer::Type::LineBuffered, const std::streamsize& size = BUFSIZ);
|
||||
~Buffer() override;
|
||||
Buffer(const Buffer&) = delete;
|
||||
Buffer(Buffer&&) = delete;
|
||||
Buffer& operator=(Buffer&&) = delete;
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
protected:
|
||||
int_type underflow() override;
|
||||
int_type overflow(int c = std::char_traits<Term::Buffer::char_type>::eof()) override;
|
||||
int sync() override;
|
||||
|
||||
private:
|
||||
void setType(const Term::Buffer::Type& type);
|
||||
std::streambuf* setbuf(char* s, std::streamsize n) override;
|
||||
std::string m_buffer;
|
||||
Term::Buffer::Type m_type{Term::Buffer::Type::LineBuffered};
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/terminfo.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Color
|
||||
{
|
||||
public:
|
||||
enum class Type : std::uint8_t
|
||||
{
|
||||
Unset,
|
||||
NoColor,
|
||||
Bit3,
|
||||
Bit4,
|
||||
Bit8,
|
||||
Bit24
|
||||
};
|
||||
|
||||
///
|
||||
/// @brief The 3bit/4bit colors for the terminal.
|
||||
/// Get the foreground color: Color4 + 30, Background color: Color4 + 40
|
||||
/// See https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit.
|
||||
///
|
||||
enum class Name : std::uint8_t
|
||||
{
|
||||
Black = 0, ///< Black FG: 30, BG: 40
|
||||
Red = 1, ///< Red FG: 31, BG: 41
|
||||
Green = 2, ///< Green FG: 32, BG: 42
|
||||
Yellow = 3, ///< Yellow FG: 33, BG: 43
|
||||
Blue = 4, ///< Blue FG: 34, BG: 44
|
||||
Magenta = 5, ///< Magenta FG: 35, BG: 45
|
||||
Cyan = 6, ///< Cyan FG: 36, BG: 46
|
||||
White = 7, ///< White FG: 37, BG: 47
|
||||
Default = 9, ///< Use the default terminal color, FG: 39, BG: 49
|
||||
Gray = 60, ///< Gray FG: 90, BG: 100
|
||||
BrightBlack = 60, ///< BrightBlack FG: 90, BG: 100
|
||||
BrightRed = 61, ///< BrightRed FG: 91, BG: 101
|
||||
BrightGreen = 62, ///< BrightGreen FG: 92, BG: 102
|
||||
BrightYellow = 63, ///< BrightYellow FG: 93, BG: 103
|
||||
BrightBlue = 64, ///< BrightBlue FG: 94, BG: 104
|
||||
BrightMagenta = 65, ///< BrightMagenta FG: 95, BG: 105
|
||||
BrightCyan = 66, ///< BrightCyan FG: 96, BG: 106
|
||||
BrightWhite = 67 ///< BrightWhite FG: 97, BG: 107
|
||||
};
|
||||
bool operator==(const Color&) const;
|
||||
bool operator!=(const Color&) const;
|
||||
Color();
|
||||
Color(const Term::Color::Name& name);
|
||||
Color(const std::uint8_t& color);
|
||||
Color(const std::uint8_t& red, const std::uint8_t& green, const std::uint8_t& blue);
|
||||
Type getType() const;
|
||||
Name to3bits() const;
|
||||
Name to4bits() const;
|
||||
std::uint8_t to8bits() const;
|
||||
std::array<std::uint8_t, 3> to24bits() const;
|
||||
|
||||
private:
|
||||
Type m_Type{Type::Unset};
|
||||
union
|
||||
{
|
||||
std::uint8_t m_bit8;
|
||||
std::array<std::uint8_t, 3> m_bit24;
|
||||
};
|
||||
};
|
||||
|
||||
std::string color_bg(const Term::Color::Name& name);
|
||||
std::string color_bg(const std::uint8_t& value);
|
||||
std::string color_bg(const std::uint8_t& red, const std::uint8_t& green, const std::uint8_t& blue);
|
||||
std::string color_bg(const Color& color);
|
||||
|
||||
std::string color_fg(const Term::Color::Name& name);
|
||||
std::string color_fg(const std::uint8_t& value);
|
||||
std::string color_fg(const std::uint8_t& red, const std::uint8_t& green, const std::uint8_t& blue);
|
||||
std::string color_fg(const Color& color);
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/position.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Cursor
|
||||
{
|
||||
public:
|
||||
Cursor() = default;
|
||||
explicit Cursor(const Position& position);
|
||||
std::size_t row() const;
|
||||
std::size_t column() const;
|
||||
bool empty() const;
|
||||
bool operator==(const Term::Cursor& cursor) const;
|
||||
bool operator!=(const Term::Cursor& cursor) const;
|
||||
|
||||
private:
|
||||
Position m_position;
|
||||
};
|
||||
|
||||
// returns the current cursor position (row, column) (Y, X)
|
||||
Term::Cursor cursor_position();
|
||||
|
||||
// move the cursor to the given (row, column) / (Y, X)
|
||||
std::string cursor_move(const std::size_t& row, const std::size_t& column);
|
||||
// move the cursor the given rows up
|
||||
std::string cursor_up(const std::size_t& rows);
|
||||
// move the cursor the given rows down
|
||||
std::string cursor_down(const std::size_t& rows);
|
||||
// move the cursor the given columns left
|
||||
std::string cursor_left(const std::size_t& columns);
|
||||
// move the cursor the given columns right
|
||||
std::string cursor_right(const std::size_t& columns);
|
||||
// the ANSI code to generate a cursor position report
|
||||
std::string cursor_position_report();
|
||||
// turn off the cursor
|
||||
std::string cursor_off();
|
||||
// turn on the cursor
|
||||
std::string cursor_on();
|
||||
|
||||
// clears the screen from the current cursor position to the end of the screen
|
||||
std::string clear_eol();
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/cursor.hpp"
|
||||
#include "cpp-terminal/focus.hpp"
|
||||
#include "cpp-terminal/key.hpp"
|
||||
#include "cpp-terminal/mouse.hpp"
|
||||
#include "cpp-terminal/screen.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
Empty,
|
||||
Key,
|
||||
Screen,
|
||||
Cursor,
|
||||
Focus,
|
||||
Mouse,
|
||||
CopyPaste,
|
||||
};
|
||||
~Event();
|
||||
Event();
|
||||
Event(const std::string& str);
|
||||
Event(const Term::Key& key);
|
||||
Event(const Term::Screen& screen);
|
||||
Event(const Term::Cursor& cursor);
|
||||
Event(const Term::Focus& focus);
|
||||
Event(const Term::Mouse& mouse);
|
||||
Event(const Term::Event& event);
|
||||
Event(Term::Event&& event) noexcept;
|
||||
Event& operator=(Event&& other) noexcept;
|
||||
Event& operator=(const Term::Event& event);
|
||||
bool empty() const;
|
||||
Type type() const;
|
||||
operator Term::Key() const;
|
||||
operator Term::Screen() const;
|
||||
operator Term::Cursor() const;
|
||||
operator Term::Focus() const;
|
||||
operator Term::Mouse() const;
|
||||
operator std::string() const;
|
||||
|
||||
// getters
|
||||
Key* get_if_key();
|
||||
const Key* get_if_key() const;
|
||||
Screen* get_if_screen();
|
||||
const Screen* get_if_screen() const;
|
||||
Cursor* get_if_cursor();
|
||||
const Cursor* get_if_cursor() const;
|
||||
Focus* get_if_focus();
|
||||
const Focus* get_if_focus() const;
|
||||
Mouse* get_if_mouse();
|
||||
const Mouse* get_if_mouse() const;
|
||||
std::string* get_if_copy_paste();
|
||||
const std::string* get_if_copy_paste() const;
|
||||
|
||||
private:
|
||||
void parse(const std::string& str);
|
||||
union container
|
||||
{
|
||||
container();
|
||||
~container();
|
||||
container(const container&) = delete;
|
||||
container(container&&) = delete;
|
||||
container& operator=(const container&) = delete;
|
||||
container& operator=(container&&) = delete;
|
||||
Term::Key m_Key;
|
||||
Term::Cursor m_Cursor;
|
||||
Term::Screen m_Screen;
|
||||
Term::Focus m_Focus;
|
||||
Term::Mouse m_Mouse;
|
||||
std::string m_string;
|
||||
};
|
||||
Type m_Type{Type::Empty};
|
||||
container m_container;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Exception : public std::exception
|
||||
{
|
||||
public:
|
||||
explicit Exception(const std::string& message) noexcept;
|
||||
Exception(const std::int64_t& code, const std::string& message) noexcept;
|
||||
Exception(const Exception&) = default;
|
||||
Exception(Exception&&) = default;
|
||||
Exception& operator=(Exception&&) = default;
|
||||
Exception& operator=(const Exception&) = default;
|
||||
|
||||
const char* what() const noexcept override;
|
||||
std::int64_t code() const noexcept;
|
||||
std::string message() const noexcept;
|
||||
std::string context() const noexcept;
|
||||
~Exception() noexcept override = default;
|
||||
|
||||
protected:
|
||||
explicit Exception(const std::int64_t& code) noexcept;
|
||||
virtual void build_what() const noexcept;
|
||||
void setMessage(const std::string& message) noexcept;
|
||||
void setContext(const std::string& context) noexcept;
|
||||
void setWhat(const std::string& what) const noexcept;
|
||||
static const constexpr std::size_t m_maxSize{256};
|
||||
|
||||
private:
|
||||
std::int64_t m_code{0};
|
||||
std::string m_message;
|
||||
std::string m_context;
|
||||
mutable std::string m_what;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Class to return the focus of the terminal
|
||||
*
|
||||
*/
|
||||
class Focus
|
||||
{
|
||||
public:
|
||||
enum class Type : std::int8_t
|
||||
{
|
||||
Unknown = -1, ///< The terminal focus is \b unknown.
|
||||
Out = 0, ///< The terminal focus is \b out.
|
||||
In = 1, ///< The terminal focus is \b in.
|
||||
};
|
||||
|
||||
Focus() = default;
|
||||
|
||||
explicit Focus(const Term::Focus::Type& type);
|
||||
|
||||
/**
|
||||
* @brief Get the type of focus.
|
||||
*
|
||||
* @return Term::Focus::Type
|
||||
*/
|
||||
Term::Focus::Type type() const;
|
||||
|
||||
/**
|
||||
* @brief Check is the focus is \b in.
|
||||
*
|
||||
* @return true : The terminal has focus \b in.
|
||||
* @return false : The terminal has focus \b out.
|
||||
*/
|
||||
bool in() const;
|
||||
|
||||
/**
|
||||
* @brief Check is the focus is \b out.
|
||||
*
|
||||
* @return true : The terminal has focus \b out.
|
||||
* @return false : The terminal has focus \b in.
|
||||
*/
|
||||
bool out() const;
|
||||
|
||||
bool operator==(const Term::Focus& focus) const;
|
||||
bool operator!=(const Term::Focus& focus) const;
|
||||
|
||||
private:
|
||||
Term::Focus::Type m_focus{Term::Focus::Type::Unknown};
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/event.hpp"
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
Term::Event read_event();
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/iostream_initializer.hpp"
|
||||
#include "cpp-terminal/stream.hpp"
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
extern TIstream& cin;
|
||||
extern TOstream& cout;
|
||||
extern TOstream& cerr;
|
||||
extern TOstream& clog;
|
||||
|
||||
static const IOStreamInitializer IO_stream_initializer; //NOLINT(cert-err58-cpp,fuchsia-statically-constructed-objects)
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class IOStreamInitializer
|
||||
{
|
||||
public:
|
||||
~IOStreamInitializer() noexcept;
|
||||
IOStreamInitializer() noexcept;
|
||||
IOStreamInitializer(const IOStreamInitializer&) = delete;
|
||||
IOStreamInitializer(IOStreamInitializer&&) = delete;
|
||||
IOStreamInitializer& operator=(IOStreamInitializer&&) = delete;
|
||||
IOStreamInitializer& operator=(const IOStreamInitializer&) = delete;
|
||||
|
||||
private:
|
||||
static std::size_t m_counter;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class MetaKey
|
||||
{
|
||||
public:
|
||||
enum class Value : std::int32_t
|
||||
{
|
||||
// Last utf8 codepoint is U+10FFFF (000100001111111111111111) So:
|
||||
None = 0,
|
||||
Alt = (1UL << 22UL),
|
||||
Ctrl = (1UL << 23UL),
|
||||
};
|
||||
|
||||
constexpr MetaKey() : value(static_cast<std::int32_t>(Value::None)) {}
|
||||
constexpr MetaKey(const MetaKey& key) = default;
|
||||
inline MetaKey& operator=(const MetaKey& key) = default;
|
||||
|
||||
constexpr MetaKey(const Value& v) : value(static_cast<std::int32_t>(v)) {}
|
||||
inline MetaKey& operator=(const Value& v)
|
||||
{
|
||||
this->value = static_cast<std::int32_t>(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr MetaKey(std::int32_t val) : value(val) {}
|
||||
inline MetaKey& operator=(std::int32_t val)
|
||||
{
|
||||
this->value = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr operator std::int32_t() const { return this->value; }
|
||||
|
||||
constexpr bool hasAlt() const { return (this->value & static_cast<std::int32_t>(MetaKey::Value::Alt)) == static_cast<std::int32_t>(MetaKey::Value::Alt); }
|
||||
constexpr bool hasCtrl() const { return (this->value & static_cast<std::int32_t>(MetaKey::Value::Ctrl)) == static_cast<std::int32_t>(MetaKey::Value::Ctrl); }
|
||||
|
||||
friend constexpr MetaKey operator+(MetaKey l, MetaKey r) { return MetaKey(l.value | r.value); }
|
||||
friend constexpr MetaKey operator+(MetaKey::Value l, MetaKey::Value r) { return MetaKey(l) + MetaKey(r); }
|
||||
friend constexpr MetaKey operator+(MetaKey l, MetaKey::Value r) { return l + MetaKey(r); }
|
||||
friend constexpr MetaKey operator+(MetaKey::Value l, MetaKey r) { return MetaKey(l) + r; }
|
||||
|
||||
MetaKey& operator+=(MetaKey r) { return *this = *this + r; }
|
||||
MetaKey& operator+=(MetaKey::Value r) { return *this = *this + r; }
|
||||
|
||||
friend constexpr bool operator==(MetaKey l, MetaKey r) { return l.value == r.value; }
|
||||
friend constexpr bool operator==(MetaKey l, MetaKey::Value r) { return l == MetaKey(r); }
|
||||
friend constexpr bool operator==(MetaKey::Value l, MetaKey r) { return MetaKey(l) == r; }
|
||||
|
||||
friend constexpr bool operator!=(MetaKey l, MetaKey r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(MetaKey l, MetaKey::Value r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(MetaKey::Value l, MetaKey r) { return !(l == r); }
|
||||
|
||||
friend constexpr bool operator<(MetaKey l, MetaKey r) { return l.value < r.value; }
|
||||
|
||||
friend constexpr bool operator>=(MetaKey l, MetaKey r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(MetaKey l, MetaKey::Value r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(MetaKey::Value l, MetaKey r) { return !(l < r); }
|
||||
|
||||
friend constexpr bool operator>(MetaKey l, MetaKey r) { return r < l; }
|
||||
friend constexpr bool operator>(MetaKey l, MetaKey::Value r) { return r < l; }
|
||||
friend constexpr bool operator>(MetaKey::Value l, MetaKey r) { return r < l; }
|
||||
|
||||
friend constexpr bool operator<=(MetaKey l, MetaKey r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(MetaKey l, MetaKey::Value r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(MetaKey::Value l, MetaKey r) { return !(l > r); }
|
||||
|
||||
std::int32_t value;
|
||||
};
|
||||
|
||||
class Key
|
||||
{
|
||||
public:
|
||||
using value_type = std::int32_t;
|
||||
|
||||
enum Value : std::int32_t
|
||||
{
|
||||
NoKey = -1,
|
||||
// Now use << to for detecting special key + key press
|
||||
|
||||
// Begin ASCII (some ASCII names has been change to their Ctrl_+key part) the value is different to be able to do
|
||||
Ctrl_Arobase = 0,
|
||||
Ctrl_A = 1,
|
||||
Ctrl_B = 2,
|
||||
Ctrl_C = 3,
|
||||
Ctrl_D = 4,
|
||||
Ctrl_E = 5,
|
||||
Ctrl_F = 6,
|
||||
Ctrl_G = 7,
|
||||
Ctrl_H = 8, /*corresponds to Backspace*/
|
||||
Ctrl_I = 9, /*corresponds to Tab*/
|
||||
Ctrl_J = 10,
|
||||
Ctrl_K = 11,
|
||||
Ctrl_L = 12,
|
||||
Ctrl_M = 13, /*corresponds to Enter*/
|
||||
Ctrl_N = 14,
|
||||
Ctrl_O = 15,
|
||||
Ctrl_P = 16,
|
||||
Ctrl_Q = 17,
|
||||
Ctrl_R = 18,
|
||||
Ctrl_S = 19,
|
||||
Ctrl_T = 20,
|
||||
Ctrl_U = 21,
|
||||
Ctrl_V = 22,
|
||||
Ctrl_W = 23,
|
||||
Ctrl_X = 24,
|
||||
Ctrl_Y = 25,
|
||||
Ctrl_Z = 26,
|
||||
Ctrl_OpenBracket = 27, /*corresponds to Escape*/
|
||||
Ctrl_BackSlash = 28,
|
||||
Ctrl_CloseBracket = 29,
|
||||
Ctrl_Caret = 30,
|
||||
Ctrl_Underscore = 31,
|
||||
Space = 32,
|
||||
ExclamationMark = 33,
|
||||
Quote = 34,
|
||||
Hash = 35,
|
||||
Dollar = 36,
|
||||
Percent = 37,
|
||||
Ampersand = 38,
|
||||
Apostrophe = 39,
|
||||
OpenParenthesis = 40,
|
||||
CloseParenthesis = 41,
|
||||
Asterisk = 42,
|
||||
Plus = 43,
|
||||
Comma = 44,
|
||||
Hyphen = 45,
|
||||
Minus = 45,
|
||||
Period = 46,
|
||||
Slash = 47,
|
||||
Zero = 48,
|
||||
One = 49,
|
||||
Two = 50,
|
||||
Three = 51,
|
||||
Four = 52,
|
||||
Five = 53,
|
||||
Six = 54,
|
||||
Seven = 55,
|
||||
Eight = 56,
|
||||
Nine = 57,
|
||||
Colon = 58,
|
||||
Semicolon = 59,
|
||||
LessThan = 60,
|
||||
OpenChevron = 60,
|
||||
Equal = 61,
|
||||
GreaterThan = 62,
|
||||
CloseChevron = 62,
|
||||
QuestionMark = 63,
|
||||
Arobase = 64,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
OpenBracket = 91,
|
||||
Backslash = 92,
|
||||
CloseBracket = 93,
|
||||
Caret = 94,
|
||||
Underscore = 95,
|
||||
GraveAccent = 96,
|
||||
a = 97,
|
||||
b = 98,
|
||||
c = 99,
|
||||
d = 100,
|
||||
e = 101,
|
||||
f = 102,
|
||||
g = 103,
|
||||
h = 104,
|
||||
i = 105,
|
||||
j = 106,
|
||||
k = 107,
|
||||
l = 108,
|
||||
m = 109,
|
||||
n = 110,
|
||||
o = 111,
|
||||
p = 112,
|
||||
q = 113,
|
||||
r = 114,
|
||||
s = 115,
|
||||
t = 116,
|
||||
u = 117,
|
||||
v = 118,
|
||||
w = 119,
|
||||
x = 120,
|
||||
y = 121,
|
||||
z = 122,
|
||||
OpenBrace = 123,
|
||||
VerticalBar = 124,
|
||||
Close_Brace = 125,
|
||||
Tilde = 126,
|
||||
CTRL_QuestionMark = 127, /*corresponds to DEL*/
|
||||
// Very useful CTRL_* alternative names
|
||||
Null = 0,
|
||||
Backspace = 8,
|
||||
Tab = 9,
|
||||
Enter = 13,
|
||||
Esc = 27,
|
||||
Del = 127,
|
||||
//
|
||||
// End ASCII
|
||||
// Extended ASCII goes up to 255
|
||||
// Last Unicode codepage 0x10FFFF
|
||||
ArrowLeft = 0x10FFFF + 1,
|
||||
ArrowRight = 0x10FFFF + 2,
|
||||
ArrowUp = 0x10FFFF + 3,
|
||||
ArrowDown = 0x10FFFF + 4,
|
||||
Numeric5 = 0x10FFFF + 5,
|
||||
Home = 0x10FFFF + 6,
|
||||
Insert = 0x10FFFF + 7,
|
||||
End = 0x10FFFF + 8,
|
||||
PageUp = 0x10FFFF + 9,
|
||||
PageDown = 0x10FFFF + 10,
|
||||
F1 = 0x10FFFF + 11,
|
||||
F2 = 0x10FFFF + 12,
|
||||
F3 = 0x10FFFF + 13,
|
||||
F4 = 0x10FFFF + 14,
|
||||
F5 = 0x10FFFF + 15,
|
||||
F6 = 0x10FFFF + 16,
|
||||
F7 = 0x10FFFF + 17,
|
||||
F8 = 0x10FFFF + 18,
|
||||
F9 = 0x10FFFF + 19,
|
||||
F10 = 0x10FFFF + 20,
|
||||
F11 = 0x10FFFF + 21,
|
||||
F12 = 0x10FFFF + 22,
|
||||
F13 = 0x10FFFF + 23,
|
||||
F14 = 0x10FFFF + 24,
|
||||
F15 = 0x10FFFF + 25,
|
||||
F16 = 0x10FFFF + 26,
|
||||
F17 = 0x10FFFF + 27,
|
||||
F18 = 0x10FFFF + 28,
|
||||
F19 = 0x10FFFF + 29,
|
||||
F20 = 0x10FFFF + 30,
|
||||
F21 = 0x10FFFF + 31,
|
||||
F22 = 0x10FFFF + 32,
|
||||
F23 = 0x10FFFF + 33,
|
||||
F24 = 0x10FFFF + 34,
|
||||
PrintScreen = 0x10FFFF + 35,
|
||||
Menu = 0x10FFFF + 36,
|
||||
};
|
||||
|
||||
constexpr Key() : value(NoKey) {}
|
||||
constexpr Key(const Key& key) = default;
|
||||
inline Key& operator=(const Key& key) = default;
|
||||
|
||||
constexpr Key(const Value& v) : value(static_cast<std::int32_t>(v)) {}
|
||||
inline Key& operator=(const Value& v)
|
||||
{
|
||||
this->value = static_cast<std::int32_t>(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr Key(char val) : value(static_cast<std::int32_t>(val)) {}
|
||||
inline Key& operator=(char val)
|
||||
{
|
||||
value = static_cast<std::int32_t>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Key(std::int32_t val) : value(val) {}
|
||||
inline Key& operator=(std::int32_t val)
|
||||
{
|
||||
value = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr Key(std::size_t val) : value(static_cast<std::int32_t>(val)) {}
|
||||
inline Key& operator=(std::size_t val)
|
||||
{
|
||||
value = static_cast<std::int32_t>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr Key(char32_t val) : value(static_cast<std::int32_t>(val)) {}
|
||||
inline Key& operator=(char32_t val)
|
||||
{
|
||||
value = static_cast<std::int32_t>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr operator std::int32_t() const { return this->value; }
|
||||
|
||||
friend constexpr bool operator==(Key l, Key r) { return l.value == r.value; }
|
||||
friend constexpr bool operator==(Key l, char r) { return l == Key(r); }
|
||||
friend constexpr bool operator==(char l, Key r) { return Key(l) == r; }
|
||||
friend constexpr bool operator==(Key l, char32_t r) { return l == Key(r); }
|
||||
friend constexpr bool operator==(char32_t l, Key r) { return Key(l) == r; }
|
||||
friend constexpr bool operator==(Key l, std::int32_t r) { return l == Key(r); }
|
||||
friend constexpr bool operator==(std::int32_t l, Key r) { return Key(l) == r; }
|
||||
friend constexpr bool operator==(Key l, std::size_t r) { return static_cast<std::size_t>(l.value) == r; }
|
||||
friend constexpr bool operator==(std::size_t l, Key r) { return l == static_cast<std::size_t>(r.value); }
|
||||
|
||||
friend constexpr bool operator!=(Key l, Key r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(Key l, char r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(char l, Key r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(Key l, char32_t r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(char32_t l, Key r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(Key l, std::int32_t r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(std::int32_t l, Key r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(Key l, std::size_t r) { return !(l == r); }
|
||||
friend constexpr bool operator!=(std::size_t l, Key r) { return !(l == r); }
|
||||
|
||||
friend constexpr bool operator<(Key l, Key r) { return l.value < r.value; }
|
||||
friend constexpr bool operator<(Key l, char r) { return l < Key(r); }
|
||||
friend constexpr bool operator<(char l, Key r) { return Key(l) < r; }
|
||||
friend constexpr bool operator<(Key l, char32_t r) { return l < Key(r); }
|
||||
friend constexpr bool operator<(char32_t l, Key r) { return Key(l) < r; }
|
||||
friend constexpr bool operator<(Key l, std::int32_t r) { return l < Key(r); }
|
||||
friend constexpr bool operator<(std::int32_t l, Key r) { return Key(l) < r; }
|
||||
friend constexpr bool operator<(Key l, std::size_t r) { return static_cast<std::size_t>(l.value) < r; }
|
||||
friend constexpr bool operator<(std::size_t l, Key r) { return l < static_cast<std::size_t>(r.value); }
|
||||
|
||||
friend constexpr bool operator>=(Key l, Key r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(Key l, char r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(char l, Key r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(Key l, char32_t r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(char32_t l, Key r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(Key l, std::int32_t r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(std::int32_t l, Key r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(Key l, std::size_t r) { return !(l < r); }
|
||||
friend constexpr bool operator>=(std::size_t l, Key r) { return !(l < r); }
|
||||
|
||||
friend constexpr bool operator>(Key l, Key r) { return r < l; }
|
||||
friend constexpr bool operator>(Key l, char r) { return r < l; }
|
||||
friend constexpr bool operator>(char l, Key r) { return r < l; }
|
||||
friend constexpr bool operator>(Key l, char32_t r) { return r < l; }
|
||||
friend constexpr bool operator>(char32_t l, Key r) { return r < l; }
|
||||
friend constexpr bool operator>(Key l, std::int32_t r) { return r < l; }
|
||||
friend constexpr bool operator>(std::int32_t l, Key r) { return r < l; }
|
||||
friend constexpr bool operator>(Key l, std::size_t r) { return r < l; }
|
||||
friend constexpr bool operator>(std::size_t l, Key r) { return r < l; }
|
||||
|
||||
friend constexpr bool operator<=(Key l, Key r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(Key l, char r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(char l, Key r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(Key l, char32_t r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(char32_t l, Key r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(Key l, std::int32_t r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(std::int32_t l, Key r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(Key l, std::size_t r) { return !(l > r); }
|
||||
friend constexpr bool operator<=(std::size_t l, Key r) { return !(l > r); }
|
||||
|
||||
constexpr bool iscntrl() const { return (*this >= Key::Null && *this <= Key::Ctrl_Underscore) || *this == Key::Del; }
|
||||
constexpr bool isblank() const { return *this == Key::Tab || *this == Key::Space; }
|
||||
constexpr bool isspace() const { return this->isblank() || (*this >= Key::Ctrl_J && *this <= Key::Enter); }
|
||||
constexpr bool isupper() const { return *this >= Key::A && *this <= Key::Z; }
|
||||
constexpr bool islower() const { return *this >= Key::a && *this <= Key::z; }
|
||||
constexpr bool isalpha() const { return (this->isupper() || this->islower()); }
|
||||
constexpr bool isdigit() const { return *this >= Key::Zero && *this <= Key::Nine; }
|
||||
constexpr bool isxdigit() const { return this->isdigit() || (*this >= Key::A && *this <= Key::F) || (*this >= Key::a && *this <= Key::f); }
|
||||
constexpr bool isalnum() const { return (this->isdigit() || this->isalpha()); }
|
||||
constexpr bool ispunct() const { return (*this >= Key::ExclamationMark && *this <= Key::Slash) || (*this >= Key::Colon && *this <= Key::Arobase) || (*this >= Key::OpenBracket && *this <= Key::GraveAccent) || (*this >= Key::OpenBrace && *this <= Key::Tilde); }
|
||||
constexpr bool isgraph() const { return (this->isalnum() || this->ispunct()); }
|
||||
constexpr bool isprint() const { return (this->isgraph() || *this == Key::Space); }
|
||||
constexpr bool isunicode() const { return *this >= Key::Null && this->value <= 0x10FFFFL; }
|
||||
constexpr Key tolower() const { return (this->isalpha() && this->isupper()) ? Key(this->value + 32) : *this; }
|
||||
constexpr Key toupper() const { return (this->isalpha() && this->islower()) ? Key(this->value - 32) : *this; }
|
||||
|
||||
// Detect if *this is convertible to ANSII
|
||||
constexpr bool isASCII() const { return *this >= Key::Null && *this <= Key::Del; }
|
||||
|
||||
// Detect if *this is convertible to Extended ANSII
|
||||
constexpr bool isExtendedASCII() const { return *this >= Key::Null && this->value <= 255L; }
|
||||
|
||||
// Detect if *this has CTRL+*
|
||||
constexpr bool hasCtrlAll() const { return this->iscntrl() || ((this->value & static_cast<std::int32_t>(MetaKey::Value::Ctrl)) == static_cast<std::int32_t>(MetaKey::Value::Ctrl)); }
|
||||
|
||||
// Detect if *this has CTRL+* (excluding the CTRL+* that can be access with standard *this Tab Backspace Enter...)
|
||||
constexpr bool hasCtrl() const
|
||||
{
|
||||
// Need to suppress the TAB etc...
|
||||
return ((this->iscntrl() || this->hasCtrlAll()) && *this != Key::Backspace && *this != Key::Tab && *this != Key::Esc && *this != Key::Enter && *this != Key::Del);
|
||||
}
|
||||
|
||||
// Detect if key has ALT+*
|
||||
constexpr bool hasAlt() const { return (this->value & static_cast<std::int32_t>(MetaKey::Value::Alt)) == static_cast<std::int32_t>(MetaKey::Value::Alt); }
|
||||
|
||||
constexpr bool empty() const { return (this->value == Key::NoKey); }
|
||||
|
||||
void append_name(std::string& strOut) const;
|
||||
std::string name() const;
|
||||
std::string str() const;
|
||||
|
||||
// member variable value
|
||||
// cannot be Key::Value and has to be std::int32_t because it can also have numbers
|
||||
// that are not named within the enum. Otherwise it would be undefined behaviour
|
||||
std::int32_t value;
|
||||
};
|
||||
|
||||
constexpr bool operator==(Key l, MetaKey r) { return static_cast<std::int32_t>(l) == static_cast<std::int32_t>(r); }
|
||||
constexpr bool operator==(MetaKey l, Key r) { return static_cast<std::int32_t>(l) == static_cast<std::int32_t>(r); }
|
||||
|
||||
constexpr bool operator<(MetaKey l, Key r) { return static_cast<std::int32_t>(l) < static_cast<std::int32_t>(r); }
|
||||
constexpr bool operator<(Key l, MetaKey r) { return static_cast<std::int32_t>(l) < static_cast<std::int32_t>(r); }
|
||||
|
||||
constexpr bool operator!=(Key l, MetaKey r) { return static_cast<std::int32_t>(l) != static_cast<std::int32_t>(r); }
|
||||
constexpr bool operator!=(MetaKey l, Key r) { return static_cast<std::int32_t>(l) != static_cast<std::int32_t>(r); }
|
||||
|
||||
constexpr bool operator>=(MetaKey l, Key r) { return static_cast<std::int32_t>(l) >= static_cast<std::int32_t>(r); }
|
||||
constexpr bool operator>=(Key l, MetaKey r) { return static_cast<std::int32_t>(l) >= static_cast<std::int32_t>(r); }
|
||||
|
||||
constexpr bool operator>(MetaKey l, Key r) { return static_cast<std::int32_t>(l) > static_cast<std::int32_t>(r); }
|
||||
constexpr bool operator>(Key l, MetaKey r) { return static_cast<std::int32_t>(l) > static_cast<std::int32_t>(r); }
|
||||
|
||||
constexpr bool operator<=(MetaKey l, Key r) { return static_cast<std::int32_t>(l) <= static_cast<std::int32_t>(r); }
|
||||
constexpr bool operator<=(Key l, MetaKey r) { return static_cast<std::int32_t>(l) <= static_cast<std::int32_t>(r); }
|
||||
|
||||
constexpr Key operator+(MetaKey metakey, Key key) { return Key(key.value + ((metakey == MetaKey::Value::Ctrl && !key.hasCtrlAll() && !key.empty()) ? static_cast<std::int32_t>(MetaKey::Value::Ctrl) : 0) + ((metakey == MetaKey::Value::Alt && !key.hasAlt() && !key.empty()) ? static_cast<std::int32_t>(MetaKey::Value::Alt) : 0)); }
|
||||
constexpr Key operator+(Key key, MetaKey meta) { return meta + key; }
|
||||
|
||||
constexpr Key operator+(MetaKey::Value l, Key r) { return MetaKey(l) + r; }
|
||||
constexpr Key operator+(Key l, MetaKey::Value r) { return l + MetaKey(r); }
|
||||
constexpr Key operator+(MetaKey::Value l, Key::value_type r) { return MetaKey(l) + Key(r); }
|
||||
constexpr Key operator+(Key::value_type l, MetaKey::Value r) { return Key(l) + MetaKey(r); }
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Button
|
||||
{
|
||||
public:
|
||||
enum class Type : std::int8_t
|
||||
{
|
||||
None = -1,
|
||||
Left,
|
||||
Wheel,
|
||||
Right,
|
||||
Button1,
|
||||
Button2,
|
||||
Button3,
|
||||
Button4,
|
||||
Button5,
|
||||
Button6,
|
||||
Button7,
|
||||
Button8,
|
||||
};
|
||||
enum class Action : std::int8_t
|
||||
{
|
||||
None = 0,
|
||||
Pressed,
|
||||
Released,
|
||||
DoubleClicked,
|
||||
RolledUp,
|
||||
RolledDown,
|
||||
ToRight,
|
||||
ToLeft,
|
||||
};
|
||||
Button() = default;
|
||||
Button(const Term::Button::Type& type, const Term::Button::Action& action) : m_type(type), m_action(action) {}
|
||||
Term::Button::Action action() const noexcept;
|
||||
Term::Button::Type type() const noexcept;
|
||||
bool operator==(const Term::Button& button) const;
|
||||
bool operator!=(const Term::Button& button) const;
|
||||
|
||||
private:
|
||||
Term::Button::Type m_type{Term::Button::Type::None};
|
||||
Term::Button::Action m_action{Term::Button::Action::None};
|
||||
};
|
||||
|
||||
class Mouse
|
||||
{
|
||||
public:
|
||||
Mouse() = default;
|
||||
Mouse(const Term::Button& buttons, const std::uint16_t& row, const std::uint16_t& column) : m_buttons(buttons), m_row(row), m_column(column) {}
|
||||
std::size_t row() const noexcept;
|
||||
std::size_t column() const noexcept;
|
||||
Term::Button getButton() const noexcept;
|
||||
bool is(const Term::Button::Type& type, const Term::Button::Action& action) const noexcept;
|
||||
bool is(const Term::Button::Type& type) const noexcept;
|
||||
bool operator==(const Term::Mouse& mouse) const;
|
||||
bool operator!=(const Term::Mouse& mouse) const;
|
||||
|
||||
private:
|
||||
Term::Button m_buttons;
|
||||
std::uint16_t m_row{0};
|
||||
std::uint16_t m_column{0};
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
///
|
||||
/// @brief Option to set-up the terminal.
|
||||
///
|
||||
enum class Option : std::int16_t
|
||||
{
|
||||
Raw = 1, ///< Set terminal in \b raw mode.
|
||||
Cooked = -1, ///< Set terminal in \b cooked mode.
|
||||
ClearScreen = 2, ///< Clear the screen (and restore its states when the program stops).
|
||||
NoClearScreen = -2, ///< Doesn't clear the screen.
|
||||
SignalKeys = 3, ///< Enable the signal keys (Ctrl+C, etc...), if activated these keys will have their default OS behaviour.
|
||||
NoSignalKeys = -3, ///< Disable the signal keys (Ctrl+C, etc...) will not be processed by the OS and will appears has standard combination keys.
|
||||
Cursor = 4, ///< Show the cursor.
|
||||
NoCursor = -4 ///< Hide the cursor (and restore its states when the program stops).
|
||||
};
|
||||
|
||||
class Options
|
||||
{
|
||||
public:
|
||||
Options() = default;
|
||||
Options(const std::initializer_list<Term::Option>& option);
|
||||
template<typename... Args> explicit Options(const Args&&... args) : m_Options(std::initializer_list<Term::Option>{args...}) { clean(); }
|
||||
|
||||
bool operator==(const Options& options) const;
|
||||
bool operator!=(const Options& options) const;
|
||||
bool has(const Option& option) const noexcept;
|
||||
|
||||
private:
|
||||
void clean();
|
||||
std::vector<Term::Option> m_Options;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Row
|
||||
{
|
||||
public:
|
||||
Row() = default;
|
||||
explicit Row(const std::uint16_t& row) : m_row(row) {}
|
||||
operator std::size_t() const noexcept { return m_row; }
|
||||
|
||||
private:
|
||||
std::uint16_t m_row{0};
|
||||
};
|
||||
|
||||
class Column
|
||||
{
|
||||
public:
|
||||
Column() = default;
|
||||
explicit Column(const std::uint16_t& column) : m_column(column) {}
|
||||
operator std::size_t() const noexcept { return m_column; }
|
||||
|
||||
private:
|
||||
std::uint16_t m_column{0};
|
||||
};
|
||||
|
||||
class Position
|
||||
{
|
||||
public:
|
||||
Position() = default;
|
||||
Position(const Row& row, const Column& column) : m_row(row), m_column(column) {};
|
||||
Position(const Column& column, const Row& row) : m_row(row), m_column(column) {};
|
||||
const Row& row() const noexcept { return m_row; }
|
||||
const Column& column() const noexcept { return m_column; }
|
||||
|
||||
private:
|
||||
Row m_row{1};
|
||||
Column m_column{1};
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,30 @@
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED MODULE)
|
||||
|
||||
# configure version information
|
||||
configure_file(version.cpp.in version.cpp)
|
||||
add_library(cpp-terminal-private INTERFACE)
|
||||
target_sources(cpp-terminal-private INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/version.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/return_code.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/file_initializer.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/unicode.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/conversion.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/args.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/terminal_impl.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tty.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/terminfo.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/input.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/screen.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cursor.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/file.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/env.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/blocking_queue.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/sigwinch.cpp>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/signals.cpp>
|
||||
)
|
||||
target_link_libraries(cpp-terminal-private INTERFACE Warnings::Warnings Threads::Threads)
|
||||
target_compile_options(cpp-terminal-private INTERFACE $<$<CXX_COMPILER_ID:MSVC>:/utf-8>)
|
||||
target_include_directories(cpp-terminal-private INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
add_library(cpp-terminal::cpp-terminal-private ALIAS cpp-terminal-private)
|
||||
@@ -0,0 +1 @@
|
||||
# Private folder for cpp-terminal
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/args.hpp"
|
||||
|
||||
#include <ios>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <memory>
|
||||
// clang-format off
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4668)
|
||||
#include <windows.h>
|
||||
#include <processenv.h>
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
// clang-format on
|
||||
#elif defined(__APPLE__)
|
||||
#include <crt_externs.h>
|
||||
#else
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#endif
|
||||
|
||||
void Term::Arguments::parse()
|
||||
{
|
||||
if(m_parsed) { return; }
|
||||
#if defined(_WIN32)
|
||||
int argc{0};
|
||||
std::unique_ptr<LPWSTR[], void (*)(wchar_t**)> wargv = std::unique_ptr<LPWSTR[], void (*)(wchar_t**)>(CommandLineToArgvW(GetCommandLineW(), &argc), [](wchar_t** ptr) { LocalFree(ptr); });
|
||||
if(wargv == nullptr)
|
||||
{
|
||||
m_parsed = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_args.reserve(static_cast<std::size_t>(argc));
|
||||
for(std::size_t i = 0; i != static_cast<std::size_t>(argc); ++i) { m_args.push_back(Term::Private::to_narrow(&wargv.get()[i][0])); }
|
||||
m_parsed = true;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
std::size_t argc{static_cast<std::size_t>(*_NSGetArgc())};
|
||||
m_args.reserve(argc);
|
||||
char** argv{*_NSGetArgv()};
|
||||
for(std::size_t i = 0; i != argc; ++i) { m_args.push_back(argv[i]); }
|
||||
m_parsed = true;
|
||||
#else
|
||||
std::string cmdline;
|
||||
std::fstream file_stream;
|
||||
const std::fstream::iostate old_iostate{file_stream.exceptions()};
|
||||
try
|
||||
{
|
||||
file_stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
file_stream.open("/proc/self/cmdline", std::fstream::in | std::fstream::binary);
|
||||
file_stream.ignore(std::numeric_limits<std::streamsize>::max());
|
||||
cmdline.resize(static_cast<std::size_t>(file_stream.gcount()));
|
||||
file_stream.seekg(0, std::ios_base::beg);
|
||||
file_stream.get(&cmdline[0], static_cast<std::streamsize>(cmdline.size())); //NOLINT(readability-container-data-pointer)
|
||||
file_stream.exceptions(old_iostate);
|
||||
if(file_stream.is_open()) { file_stream.close(); }
|
||||
m_args.reserve(static_cast<std::size_t>(std::count(cmdline.begin(), cmdline.end(), '\0')));
|
||||
for(std::string::iterator it = cmdline.begin(); it != cmdline.end(); it = std::find(it, cmdline.end(), '\0') + 1) { m_args.emplace_back(cmdline.data() + (it - cmdline.begin())); }
|
||||
m_parsed = true;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
file_stream.exceptions(old_iostate);
|
||||
if(file_stream.is_open()) { file_stream.close(); }
|
||||
m_args.clear();
|
||||
m_parsed = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t Term::Arguments::argc()
|
||||
{
|
||||
parse();
|
||||
return m_args.size();
|
||||
}
|
||||
|
||||
std::vector<std::string> Term::Arguments::argv()
|
||||
{
|
||||
parse();
|
||||
return m_args;
|
||||
}
|
||||
|
||||
bool Term::Arguments::m_parsed = false;
|
||||
|
||||
std::vector<std::string> Term::Arguments::m_args = std::vector<std::string>(); //NOLINT(fuchsia-statically-constructed-objects)
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/blocking_queue.hpp"
|
||||
|
||||
Term::Event Term::Private::BlockingQueue::pop()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
Term::Event value = this->m_queue.front();
|
||||
m_queue.pop();
|
||||
return value;
|
||||
}
|
||||
|
||||
void Term::Private::BlockingQueue::push(const Term::Event& value, const std::size_t& occurrence)
|
||||
{
|
||||
for(std::size_t i = 0; i != occurrence; ++i)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_queue.push(value);
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void Term::Private::BlockingQueue::push(const Term::Event&& value, const std::size_t& occurrence)
|
||||
{
|
||||
for(std::size_t i = 0; i != occurrence; ++i)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_queue.push(value);
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool Term::Private::BlockingQueue::empty()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_queue.empty();
|
||||
}
|
||||
|
||||
std::size_t Term::Private::BlockingQueue::size()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
void Term::Private::BlockingQueue::wait_for_events(std::unique_lock<std::mutex>& lock) { m_cv.wait(lock); }
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/event.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
namespace Private
|
||||
{
|
||||
|
||||
class BlockingQueue
|
||||
{
|
||||
public:
|
||||
~BlockingQueue() = default;
|
||||
BlockingQueue() = default;
|
||||
BlockingQueue(const BlockingQueue& other) = delete;
|
||||
BlockingQueue(BlockingQueue&& other) = delete;
|
||||
BlockingQueue& operator=(const BlockingQueue& other) = delete;
|
||||
BlockingQueue& operator=(BlockingQueue&& other) = delete;
|
||||
Term::Event pop();
|
||||
void push(const Term::Event& value, const std::size_t& occurrence = 1);
|
||||
void push(const Term::Event&& value, const std::size_t& occurrence = 1);
|
||||
bool empty();
|
||||
std::size_t size();
|
||||
void wait_for_events(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::queue<Term::Event> m_queue;
|
||||
std::condition_variable m_cv;
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/conversion.hpp"
|
||||
|
||||
#include "cpp-terminal/exception.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
|
||||
static constexpr std::uint8_t UTF8_ACCEPT{0};
|
||||
static constexpr std::uint8_t UTF8_REJECT{0xf};
|
||||
|
||||
std::uint8_t utf8_decode_step(std::uint8_t state, std::uint8_t octet, std::uint32_t* cpp)
|
||||
{
|
||||
static const constexpr std::array<std::uint32_t, 0x10> utf8ClassTab{0x88888888UL, 0x88888888UL, 0x99999999UL, 0x99999999UL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0x222222ffUL, 0x22222222UL, 0x22222222UL, 0x22222222UL, 0x3333333bUL, 0x33433333UL, 0xfff5666cUL, 0xffffffffUL};
|
||||
|
||||
static const constexpr std::array<std::uint32_t, 0x10> utf8StateTab{0xfffffff0UL, 0xffffffffUL, 0xfffffff1UL, 0xfffffff3UL, 0xfffffff4UL, 0xfffffff7UL, 0xfffffff6UL, 0xffffffffUL, 0x33f11f0fUL, 0xf3311f0fUL, 0xf33f110fUL, 0xfffffff2UL, 0xfffffff5UL, 0xffffffffUL, 0xffffffffUL, 0xffffffffUL};
|
||||
|
||||
const std::uint8_t reject{static_cast<std::uint8_t>(state >> 3UL)};
|
||||
const std::uint8_t nonAscii{static_cast<std::uint8_t>(octet >> 7UL)};
|
||||
const std::uint8_t class_{static_cast<std::uint8_t>(!nonAscii ? 0 : (0xf & (utf8ClassTab[(octet >> 3) & 0xf] >> (4 * (octet & 7)))))};
|
||||
|
||||
*cpp = (state == UTF8_ACCEPT ? (octet & (0xffU >> class_)) : ((octet & 0x3fU) | (*cpp << 6)));
|
||||
|
||||
return (reject ? 0xf : (0xf & (utf8StateTab[class_] >> (4 * (state & 7)))));
|
||||
}
|
||||
|
||||
std::u32string utf8_to_utf32(const std::string& str)
|
||||
{
|
||||
std::uint32_t codepoint{0};
|
||||
std::uint8_t state{UTF8_ACCEPT};
|
||||
std::u32string ret;
|
||||
for(char idx: str)
|
||||
{
|
||||
state = utf8_decode_step(state, static_cast<std::uint8_t>(idx), &codepoint);
|
||||
if(state == UTF8_ACCEPT) { ret.push_back(codepoint); }
|
||||
else if(state == UTF8_REJECT) { throw Term::Exception("Invalid byte in UTF8 encoded string"); }
|
||||
}
|
||||
if(state != UTF8_ACCEPT) { throw Term::Exception("Expected more bytes in UTF8 encoded string"); }
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool is_valid_utf8_code_unit(const std::string& str)
|
||||
{
|
||||
static const constexpr std::uint8_t b1OOOOOOO{128};
|
||||
static const constexpr std::uint8_t b11OOOOOO{192};
|
||||
static const constexpr std::uint8_t b111OOOOO{224};
|
||||
static const constexpr std::uint8_t b1111OOOO{240};
|
||||
static const constexpr std::uint8_t b11111OOO{248};
|
||||
switch(str.size())
|
||||
{
|
||||
case 1: return (static_cast<std::uint8_t>(str[0]) & b1OOOOOOO) == 0;
|
||||
case 2: return ((static_cast<std::uint8_t>(str[0]) & b111OOOOO) == b11OOOOOO) && ((static_cast<std::uint8_t>(str[1]) & b11OOOOOO) == b1OOOOOOO);
|
||||
case 3: return ((static_cast<std::uint8_t>(str[0]) & b1111OOOO) == b111OOOOO) && ((static_cast<std::uint8_t>(str[1]) & b11OOOOOO) == b1OOOOOOO) && ((static_cast<std::uint8_t>(str[2]) & b11OOOOOO) == b1OOOOOOO);
|
||||
case 4: return ((static_cast<std::uint8_t>(str[0]) & b11111OOO) == b1111OOOO) && ((static_cast<std::uint8_t>(str[1]) & b11OOOOOO) == b1OOOOOOO) && ((static_cast<std::uint8_t>(str[2]) & b11OOOOOO) == b1OOOOOOO) && ((static_cast<std::uint8_t>(str[3]) & b11OOOOOO) == b1OOOOOOO);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
|
||||
std::uint8_t utf8_decode_step(std::uint8_t state, std::uint8_t octet, std::uint32_t* cpp);
|
||||
|
||||
std::u32string utf8_to_utf32(const std::string& str);
|
||||
|
||||
bool is_valid_utf8_code_unit(const std::string& str);
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/cursor.hpp"
|
||||
|
||||
#include "file_initializer.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#endif
|
||||
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
|
||||
Term::Cursor Term::cursor_position()
|
||||
{
|
||||
static const Term::Private::FileInitializer files_init;
|
||||
if(Term::Private::in.null()) { return {}; }
|
||||
#if defined(_WIN32)
|
||||
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||
if(GetConsoleScreenBufferInfo(Private::out.handle(), &inf)) return Term::Cursor({Row(inf.dwCursorPosition.Y + 1), Column(inf.dwCursorPosition.X + 1)});
|
||||
else
|
||||
return {};
|
||||
#else
|
||||
std::string ret;
|
||||
std::size_t nread{0};
|
||||
Term::Private::in.lockIO();
|
||||
// Hack to be sure we can do this all the time "Cooked" or "Raw" mode
|
||||
::termios actual;
|
||||
if(!Private::out.null())
|
||||
{
|
||||
if(tcgetattr(Private::out.fd(), &actual) == -1) { return {}; }
|
||||
}
|
||||
::termios raw = actual;
|
||||
// Put terminal in raw mode
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
// This disables output post-processing, requiring explicit \r\n. We
|
||||
// keep it enabled, so that in C++, one can still just use std::endl
|
||||
// for EOL instead of "\r\n".
|
||||
// raw.c_oflag &= ~(OPOST);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
|
||||
raw.c_lflag &= ~ISIG;
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
if(!Private::out.null()) tcsetattr(Private::out.fd(), TCSAFLUSH, &raw);
|
||||
Term::Private::out.write(Term::cursor_position_report());
|
||||
while(nread == 0) { ::ioctl(Private::in.fd(), FIONREAD, &nread); }
|
||||
ret = Term::Private::in.read();
|
||||
tcsetattr(Private::out.fd(), TCSAFLUSH, &actual);
|
||||
Term::Private::in.unlockIO();
|
||||
try
|
||||
{
|
||||
if(ret[0] == '\033' && ret[1] == '[' && ret[ret.size() - 1] == 'R')
|
||||
{
|
||||
std::size_t found = ret.find(';', 2);
|
||||
if(found != std::string::npos) { return Cursor({Row(std::stoi(ret.substr(2, found - 2))), Column(std::stoi(ret.substr(found + 1, ret.size() - (found + 2))))}); }
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/env.hpp"
|
||||
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
|
||||
std::pair<bool, std::string> Term::Private::getenv(const std::string& key)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
std::size_t size{0};
|
||||
_wgetenv_s(&size, nullptr, 0, Term::Private::to_wide(key).c_str());
|
||||
std::wstring ret;
|
||||
if(size == 0 || size > ret.max_size()) return {false, std::string()};
|
||||
ret.reserve(size);
|
||||
_wgetenv_s(&size, &ret[0], size, Term::Private::to_wide(key).c_str());
|
||||
return {true, Term::Private::to_narrow(ret)};
|
||||
#else
|
||||
if(std::getenv(key.c_str()) != nullptr) { return {true, static_cast<std::string>(std::getenv(key.c_str()))}; }
|
||||
return {false, std::string()};
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// This header should be used only in files contained inside platforms folder !!!
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
namespace Private
|
||||
{
|
||||
|
||||
///
|
||||
/// @brief Value of an environment variables.
|
||||
///
|
||||
/// @param env The environment variable.
|
||||
/// @return \b std::pair<bool,std::string> with \b bool set to \b true if the environment variable is set and \b std::string set to the value of environment variable.
|
||||
/// @warning Internal use only.
|
||||
///
|
||||
std::pair<bool, std::string> getenv(const std::string& env);
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/exception.hpp"
|
||||
|
||||
#include "cpp-terminal/exception.hpp"
|
||||
#include "cpp-terminal/version.hpp"
|
||||
#include "return_code.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
|
||||
#include <memory>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#if defined(MessageBox)
|
||||
#undef MessageBox
|
||||
#endif
|
||||
#else
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
Term::Exception::Exception(const std::string& message) noexcept : m_message(message) {}
|
||||
|
||||
Term::Exception::Exception(const std::int64_t& code, const std::string& message) noexcept : m_code(code), m_message(message) {}
|
||||
|
||||
const char* Term::Exception::what() const noexcept
|
||||
{
|
||||
build_what();
|
||||
return m_what.c_str();
|
||||
}
|
||||
|
||||
std::int64_t Term::Exception::code() const noexcept { return m_code; }
|
||||
|
||||
std::string Term::Exception::message() const noexcept { return m_message; }
|
||||
|
||||
std::string Term::Exception::context() const noexcept { return m_context; }
|
||||
|
||||
Term::Exception::Exception(const std::int64_t& code) noexcept : m_code(code) {}
|
||||
|
||||
void Term::Exception::build_what() const noexcept
|
||||
{
|
||||
if(0 == m_code) { m_what = m_message; }
|
||||
else { m_what = "error " + std::to_string(m_code) + ": " + m_message; }
|
||||
}
|
||||
|
||||
void Term::Exception::setMessage(const std::string& message) noexcept { m_message = message; }
|
||||
|
||||
void Term::Exception::setContext(const std::string& context) noexcept { m_context = context; }
|
||||
|
||||
void Term::Exception::setWhat(const std::string& what) const noexcept { m_what = what; }
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
std::int64_t Term::Private::WindowsError::error() const noexcept { return m_error; }
|
||||
|
||||
bool Term::Private::WindowsError::check_value() const noexcept { return m_check_value; }
|
||||
|
||||
Term::Private::WindowsError& Term::Private::WindowsError::check_if(const bool& ret) noexcept
|
||||
{
|
||||
m_error = static_cast<std::int64_t>(GetLastError());
|
||||
m_check_value = ret;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Term::Private::WindowsError::throw_exception(const std::string& str) const
|
||||
{
|
||||
if(m_check_value) { throw Term::Private::WindowsException(m_error, str); }
|
||||
}
|
||||
|
||||
Term::Private::WindowsException::WindowsException(const std::int64_t& error, const std::string& context) : Term::Exception(static_cast<std::int64_t>(error))
|
||||
{
|
||||
setContext(context);
|
||||
wchar_t* ptr{nullptr};
|
||||
const DWORD cchMsg{FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, static_cast<uint32_t>(code()), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), reinterpret_cast<wchar_t*>(&ptr), 0, nullptr)};
|
||||
if(cchMsg > 0)
|
||||
{
|
||||
auto deleter = [](void* p)
|
||||
{
|
||||
if(p != nullptr) { ::LocalFree(p); }
|
||||
};
|
||||
std::unique_ptr<wchar_t, decltype(deleter)> ptrBuffer(ptr, deleter);
|
||||
std::string ret{Term::Private::to_narrow(ptrBuffer.get())};
|
||||
if(ret.size() >= 2 && ret[ret.size() - 1] == '\n' && ret[ret.size() - 2] == '\r') ret.erase(ret.size() - 2);
|
||||
setMessage(ret);
|
||||
}
|
||||
else { throw Term::Exception(::GetLastError(), "Error in FormatMessageW"); }
|
||||
}
|
||||
|
||||
void Term::Private::WindowsException::build_what() const noexcept
|
||||
{
|
||||
std::string what{std::string("windows error ") + std::to_string(code()) + std::string(": ") + message().c_str()};
|
||||
if(!context().empty()) what += " [" + context() + "]";
|
||||
setWhat(what);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Term::Private::Errno::Errno() noexcept
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
_set_errno(0);
|
||||
#else
|
||||
errno = {0}; //NOSONAR
|
||||
#endif
|
||||
}
|
||||
|
||||
Term::Private::Errno::~Errno() noexcept
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
_set_errno(0);
|
||||
#else
|
||||
errno = {0}; //NOSONAR
|
||||
#endif
|
||||
}
|
||||
|
||||
std::int64_t Term::Private::Errno::error() const noexcept { return m_errno; }
|
||||
|
||||
bool Term::Private::Errno::check_value() const noexcept { return m_check_value; }
|
||||
|
||||
Term::Private::Errno& Term::Private::Errno::check_if(const bool& ret) noexcept
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
int err{static_cast<int>(m_errno)};
|
||||
_get_errno(&err);
|
||||
#else
|
||||
m_errno = static_cast<std::uint32_t>(errno); //NOSONAR
|
||||
#endif
|
||||
m_check_value = {ret};
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Term::Private::Errno::throw_exception(const std::string& str) const
|
||||
{
|
||||
if(m_check_value) { throw Term::Private::ErrnoException(m_errno, str); }
|
||||
}
|
||||
|
||||
Term::Private::ErrnoException::ErrnoException(const std::int64_t& error, const std::string& context) : Term::Exception(error)
|
||||
{
|
||||
setContext(context);
|
||||
#if defined(_WIN32)
|
||||
std::wstring message(m_maxSize, L'\0');
|
||||
message = _wcserror_s(&message[0], message.size(), static_cast<int>(error));
|
||||
setMessage(Term::Private::to_narrow(message.c_str()));
|
||||
#else
|
||||
std::string message(m_maxSize, '\0');
|
||||
message = ::strerror_r(static_cast<std::int32_t>(error), &message[0], message.size()); // NOLINT(readability-container-data-pointer)
|
||||
setMessage(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Private::ErrnoException::build_what() const noexcept
|
||||
{
|
||||
std::string what{"errno " + std::to_string(code()) + ": " + message()};
|
||||
if(!context().empty()) { what += +" [" + context() + "]"; }
|
||||
setWhat(what);
|
||||
}
|
||||
|
||||
void Term::Private::ExceptionHandler(const ExceptionDestination& destination) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
std::exception_ptr exception{std::current_exception()};
|
||||
if(exception != nullptr) { std::rethrow_exception(exception); }
|
||||
}
|
||||
catch(const Term::Exception& exception)
|
||||
{
|
||||
switch(destination)
|
||||
{
|
||||
case ExceptionDestination::MessageBox:
|
||||
#if defined(_WIN32)
|
||||
MessageBoxW(nullptr, Term::Private::to_wide(exception.what()).c_str(), Term::Private::to_wide("cpp-terminal v" + Term::Version::string()).c_str(), MB_OK | MB_ICONERROR);
|
||||
break;
|
||||
#endif
|
||||
case ExceptionDestination::StdErr:
|
||||
#if defined(_WIN32)
|
||||
(void)(fputws(Term::Private::to_wide(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n")).c_str(), stderr));
|
||||
#else
|
||||
(void)(fputs(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n").c_str(), stderr));
|
||||
#endif
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
catch(const std::exception& exception)
|
||||
{
|
||||
switch(destination)
|
||||
{
|
||||
case ExceptionDestination::MessageBox:
|
||||
#if defined(_WIN32)
|
||||
MessageBoxW(nullptr, Term::Private::to_wide(exception.what()).c_str(), Term::Private::to_wide("cpp-terminal v" + Term::Version::string()).c_str(), MB_OK | MB_ICONERROR);
|
||||
break;
|
||||
#endif
|
||||
case ExceptionDestination::StdErr:
|
||||
#if defined(_WIN32)
|
||||
(void)(fputws(Term::Private::to_wide(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n")).c_str(), stderr));
|
||||
#else
|
||||
(void)(fputs(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n").c_str(), stderr));
|
||||
#endif
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
switch(destination)
|
||||
{
|
||||
case ExceptionDestination::MessageBox:
|
||||
#if defined(_WIN32)
|
||||
MessageBoxW(nullptr, Term::Private::to_wide("cpp-terminal v" + Term::Version::string() + "Unknown error").c_str(), Term::Private::to_wide("cpp-terminal v" + Term::Version::string()).c_str(), MB_OK | MB_ICONERROR);
|
||||
break;
|
||||
#endif
|
||||
case ExceptionDestination::StdErr:
|
||||
#if defined(_WIN32)
|
||||
(void)(fputws(Term::Private::to_wide("cpp-terminal v" + Term::Version::string() + ": Unknown error\n").c_str(), stderr));
|
||||
#else
|
||||
(void)(fputs(("cpp-terminal v" + Term::Version::string() + ": Unknown error\n").c_str(), stderr));
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
(void)(std::fflush(stderr));
|
||||
std::_Exit(Term::returnCode());
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/exception.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
namespace Private
|
||||
{
|
||||
|
||||
#if defined(_WIN32)
|
||||
class WindowsError
|
||||
{
|
||||
public:
|
||||
WindowsError(const WindowsError&) = default;
|
||||
WindowsError(WindowsError&&) = default;
|
||||
WindowsError() noexcept = default;
|
||||
virtual ~WindowsError() noexcept = default;
|
||||
WindowsError& operator=(WindowsError&&) noexcept = default;
|
||||
WindowsError& operator=(const WindowsError&) noexcept = default;
|
||||
std::int64_t error() const noexcept;
|
||||
bool check_value() const noexcept;
|
||||
WindowsError& check_if(const bool& ret) noexcept;
|
||||
void throw_exception(const std::string& str = std::string()) const;
|
||||
|
||||
private:
|
||||
std::int64_t m_error{0};
|
||||
bool m_check_value{false};
|
||||
};
|
||||
|
||||
class WindowsException : public Term::Exception
|
||||
{
|
||||
public:
|
||||
WindowsException(const std::int64_t& error, const std::string& context = std::string());
|
||||
~WindowsException() override = default;
|
||||
|
||||
private:
|
||||
void build_what() const noexcept final;
|
||||
};
|
||||
#endif
|
||||
|
||||
class Errno
|
||||
{
|
||||
public:
|
||||
Errno(const Errno&) noexcept = default;
|
||||
Errno(Errno&&) noexcept = default;
|
||||
Errno() noexcept;
|
||||
virtual ~Errno() noexcept;
|
||||
Errno& operator=(Errno&&) noexcept = default;
|
||||
Errno& operator=(const Errno&) noexcept = default;
|
||||
std::int64_t error() const noexcept;
|
||||
bool check_value() const noexcept;
|
||||
Errno& check_if(const bool& ret) noexcept;
|
||||
void throw_exception(const std::string& str = {}) const;
|
||||
|
||||
private:
|
||||
std::int64_t m_errno{0};
|
||||
bool m_check_value{false};
|
||||
};
|
||||
|
||||
class ErrnoException : public Term::Exception
|
||||
{
|
||||
public:
|
||||
ErrnoException(const ErrnoException&) = default;
|
||||
ErrnoException(ErrnoException&&) = default;
|
||||
explicit ErrnoException(const std::int64_t& error, const std::string& context = {});
|
||||
~ErrnoException() override = default;
|
||||
ErrnoException& operator=(ErrnoException&&) = default;
|
||||
ErrnoException& operator=(const ErrnoException&) = default;
|
||||
|
||||
private:
|
||||
void build_what() const noexcept final;
|
||||
};
|
||||
|
||||
enum class ExceptionDestination : std::uint8_t
|
||||
{
|
||||
MessageBox = 0,
|
||||
StdErr,
|
||||
};
|
||||
|
||||
void ExceptionHandler(const ExceptionDestination& destination = ExceptionDestination::StdErr) noexcept;
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
|
||||
#include "cpp-terminal/private/exception.hpp"
|
||||
#include "cpp-terminal/tty.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <new>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <fcntl.h>
|
||||
|
||||
//FIXME Move this to other file
|
||||
|
||||
namespace
|
||||
{
|
||||
std::array<char, sizeof(Term::Private::InputFileHandler)> stdin_buffer; //NOLINT(fuchsia-statically-constructed-objects)
|
||||
std::array<char, sizeof(Term::Private::OutputFileHandler)> stdout_buffer; //NOLINT(fuchsia-statically-constructed-objects)
|
||||
} // namespace
|
||||
|
||||
Term::Private::InputFileHandler& Term::Private::in = reinterpret_cast<Term::Private::InputFileHandler&>(stdin_buffer);
|
||||
Term::Private::OutputFileHandler& Term::Private::out = reinterpret_cast<Term::Private::OutputFileHandler&>(stdout_buffer);
|
||||
|
||||
//
|
||||
|
||||
Term::Private::FileHandler::FileHandler(std::recursive_mutex& mutex, const std::string& file, const std::string& mode) noexcept
|
||||
try : m_mutex(mutex)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
m_handle = {CreateFile(file.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||
if(m_handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Term::Private::WindowsError().check_if((m_handle = CreateFile("NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)) == INVALID_HANDLE_VALUE).throw_exception("Problem opening NUL");
|
||||
m_null = true;
|
||||
}
|
||||
Term::Private::Errno().check_if((m_fd = _open_osfhandle(reinterpret_cast<intptr_t>(m_handle), _O_RDWR)) == -1).throw_exception("_open_osfhandle(reinterpret_cast<intptr_t>(m_handle), _O_RDWR)");
|
||||
Term::Private::Errno().check_if(nullptr == (m_file = _fdopen(m_fd, mode.c_str()))).throw_exception("_fdopen(m_fd, mode.c_str())");
|
||||
#else
|
||||
std::size_t flag{O_ASYNC | O_DSYNC | O_NOCTTY | O_SYNC | O_NDELAY};
|
||||
flag &= ~static_cast<std::size_t>(O_NONBLOCK);
|
||||
if(mode.find('r') != std::string::npos) { flag |= O_RDONLY; } //NOLINT(abseil-string-find-str-contains)
|
||||
else if(mode.find('w') != std::string::npos) { flag |= O_WRONLY; } //NOLINT(abseil-string-find-str-contains)
|
||||
else { flag |= O_RDWR; }
|
||||
m_fd = {::open(file.c_str(), static_cast<int>(flag))}; //NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
if(m_fd == -1)
|
||||
{
|
||||
Term::Private::Errno().check_if((m_fd = ::open("/dev/null", static_cast<int>(flag))) == -1).throw_exception(R"(::open("/dev/null", flag))"); //NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
m_null = true;
|
||||
}
|
||||
Term::Private::Errno().check_if(nullptr == (m_file = ::fdopen(m_fd, mode.c_str()))).throw_exception("::fdopen(m_fd, mode.c_str())");
|
||||
m_handle = m_file;
|
||||
#endif
|
||||
Term::Private::Errno().check_if(std::setvbuf(m_file, nullptr, _IONBF, 0) != 0).throw_exception("std::setvbuf(m_file, nullptr, _IONBF, 0)");
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
Term::Private::FileHandler::~FileHandler() noexcept
|
||||
try
|
||||
{
|
||||
flush();
|
||||
Term::Private::Errno().check_if(0 != std::fclose(m_file)).throw_exception("std::fclose(m_file)"); //NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
bool Term::Private::FileHandler::null() const noexcept { return m_null; }
|
||||
|
||||
std::FILE* Term::Private::FileHandler::file() const noexcept { return m_file; }
|
||||
|
||||
std::int32_t Term::Private::FileHandler::fd() const noexcept { return m_fd; }
|
||||
|
||||
Term::Private::FileHandler::Handle Term::Private::FileHandler::handle() const noexcept { return m_handle; }
|
||||
|
||||
std::size_t Term::Private::OutputFileHandler::write(const std::string& str) const
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
DWORD written{0};
|
||||
if(!str.empty()) { Term::Private::WindowsError().check_if(0 == WriteConsole(handle(), &str[0], static_cast<DWORD>(str.size()), &written, nullptr)).throw_exception("WriteConsole(handle(), &str[0], static_cast<DWORD>(str.size()), &written, nullptr)"); }
|
||||
return static_cast<std::size_t>(written);
|
||||
#else
|
||||
ssize_t written{0};
|
||||
if(!str.empty()) { Term::Private::Errno().check_if((written = ::write(fd(), str.data(), str.size())) == -1).throw_exception("::write(fd(), str.data(), str.size())"); }
|
||||
return static_cast<std::size_t>(written);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t Term::Private::OutputFileHandler::write(const char& character) const
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
DWORD written{0};
|
||||
Term::Private::WindowsError().check_if(0 == WriteConsole(handle(), &character, 1, &written, nullptr)).throw_exception("WriteConsole(handle(), &character, 1, &written, nullptr)");
|
||||
return static_cast<std::size_t>(written);
|
||||
#else
|
||||
ssize_t written{0};
|
||||
Term::Private::Errno().check_if((written = ::write(fd(), &character, 1)) == -1).throw_exception("::write(fd(), &character, 1)");
|
||||
return static_cast<std::size_t>(written);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Term::Private::InputFileHandler::read() const
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
DWORD nread{0};
|
||||
std::string ret(4096, '\0');
|
||||
errno = 0;
|
||||
ReadConsole(Private::in.handle(), &ret[0], static_cast<DWORD>(ret.size()), &nread, nullptr);
|
||||
return ret.c_str();
|
||||
#else
|
||||
#if defined(MAX_INPUT)
|
||||
static const constexpr std::size_t max_input{MAX_INPUT};
|
||||
#else
|
||||
static const constexpr std::size_t max_input{256};
|
||||
#endif
|
||||
#if defined(_POSIX_MAX_INPUT)
|
||||
static const constexpr std::size_t posix_max_input{_POSIX_MAX_INPUT};
|
||||
#else
|
||||
static const constexpr std::size_t posix_max_input{256};
|
||||
#endif
|
||||
static std::size_t nread{std::max(max_input, posix_max_input)};
|
||||
if(is_stdin_a_tty()) Term::Private::Errno().check_if(::ioctl(Private::in.fd(), FIONREAD, &nread) != 0).throw_exception("::ioctl(Private::in.fd(), FIONREAD, &nread)"); //NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
std::string ret(nread, '\0');
|
||||
if(nread != 0)
|
||||
{
|
||||
Term::Private::Errno().check_if(::read(Private::in.fd(), &ret[0], ret.size()) == -1).throw_exception("::read(Private::in.fd(), &ret[0], ret.size())"); //NOLINT(readability-container-data-pointer)
|
||||
}
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Private::FileHandler::flush() { Term::Private::Errno().check_if(0 != std::fflush(m_file)).throw_exception("std::fflush(m_file)"); }
|
||||
|
||||
void Term::Private::FileHandler::lockIO() { m_mutex.lock(); }
|
||||
void Term::Private::FileHandler::unlockIO() { m_mutex.unlock(); }
|
||||
|
||||
Term::Private::OutputFileHandler::OutputFileHandler(std::recursive_mutex& io_mutex) noexcept
|
||||
try : FileHandler(io_mutex, m_file, "w")
|
||||
{
|
||||
//noop
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
Term::Private::InputFileHandler::InputFileHandler(std::recursive_mutex& io_mutex) noexcept
|
||||
try : FileHandler(io_mutex, m_file, "r")
|
||||
{
|
||||
//noop
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
std::string Term::Private::ask(const std::string& str)
|
||||
{
|
||||
Term::Private::out.write(str);
|
||||
std::string ret{Term::Private::in.read()};
|
||||
for(std::size_t i = 0; i != ret.size(); ++i) { Term::Private::out.write("\b \b"); }
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/private/file_initializer.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
namespace Private
|
||||
{
|
||||
|
||||
class FileHandler
|
||||
{
|
||||
public:
|
||||
#if defined(_WIN32)
|
||||
using Handle = void*;
|
||||
#else
|
||||
using Handle = std::FILE*;
|
||||
#endif
|
||||
FileHandler(std::recursive_mutex& mutex, const std::string& file, const std::string& mode) noexcept;
|
||||
FileHandler(const FileHandler&) = delete;
|
||||
FileHandler(FileHandler&&) = delete;
|
||||
FileHandler& operator=(const FileHandler&) = delete;
|
||||
FileHandler& operator=(FileHandler&&) = delete;
|
||||
virtual ~FileHandler() noexcept;
|
||||
Handle handle() const noexcept;
|
||||
bool null() const noexcept;
|
||||
std::FILE* file() const noexcept;
|
||||
std::int32_t fd() const noexcept;
|
||||
void lockIO();
|
||||
void unlockIO();
|
||||
void flush();
|
||||
|
||||
private:
|
||||
// should be static but MacOS don't want it (crash at runtime)
|
||||
std::recursive_mutex& m_mutex; //NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
bool m_null{false};
|
||||
Handle m_handle{nullptr};
|
||||
FILE* m_file{nullptr};
|
||||
std::int32_t m_fd{-1};
|
||||
};
|
||||
|
||||
class OutputFileHandler : public FileHandler
|
||||
{
|
||||
public:
|
||||
explicit OutputFileHandler(std::recursive_mutex& io_mutex) noexcept;
|
||||
OutputFileHandler(const OutputFileHandler& other) = delete;
|
||||
OutputFileHandler(OutputFileHandler&& other) = delete;
|
||||
OutputFileHandler& operator=(OutputFileHandler&& rhs) = delete;
|
||||
OutputFileHandler& operator=(const OutputFileHandler& rhs) = delete;
|
||||
~OutputFileHandler() override = default;
|
||||
|
||||
std::size_t write(const std::string& str) const;
|
||||
std::size_t write(const char& character) const;
|
||||
#if defined(_WIN32)
|
||||
static const constexpr char* m_file{"CONOUT$"};
|
||||
#else
|
||||
static const constexpr char* m_file{"/dev/tty"};
|
||||
#endif
|
||||
};
|
||||
|
||||
class InputFileHandler : public FileHandler
|
||||
{
|
||||
public:
|
||||
explicit InputFileHandler(std::recursive_mutex& io_mutex) noexcept;
|
||||
InputFileHandler(const InputFileHandler&) = delete;
|
||||
InputFileHandler(InputFileHandler&&) = delete;
|
||||
InputFileHandler& operator=(InputFileHandler&&) = delete;
|
||||
InputFileHandler& operator=(const InputFileHandler&) = delete;
|
||||
~InputFileHandler() override = default;
|
||||
|
||||
std::string read() const;
|
||||
#if defined(_WIN32)
|
||||
static const constexpr char* m_file{"CONIN$"};
|
||||
#else
|
||||
static const constexpr char* m_file{"/dev/tty"};
|
||||
#endif
|
||||
};
|
||||
|
||||
extern InputFileHandler& in;
|
||||
extern OutputFileHandler& out;
|
||||
static const FileInitializer file_initializer;
|
||||
|
||||
std::string ask(const std::string& str);
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/file_initializer.hpp"
|
||||
|
||||
#include "cpp-terminal/private/exception.hpp"
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
|
||||
#include <io.h>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#if defined(MessageBox)
|
||||
#undef MessageBox
|
||||
#endif
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
bool Term::Private::FileInitializer::m_consoleCreated = {false};
|
||||
|
||||
std::size_t Term::Private::FileInitializer::m_counter = {0};
|
||||
|
||||
void Term::Private::FileInitializer::attachConsole() noexcept
|
||||
try
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// If something happen here we still don't have a console so we can only use a MessageBox to warn the users something is very bad and that they should contact us.
|
||||
Term::Private::WindowsError error{Term::Private::WindowsError().check_if(AttachConsole(ATTACH_PARENT_PROCESS) == 0)};
|
||||
bool need_allocation{false};
|
||||
switch(error.error())
|
||||
{
|
||||
case ERROR_SUCCESS: break; // Has been attached
|
||||
case ERROR_ACCESS_DENIED: need_allocation = false; break; // Already attached that's good !
|
||||
case ERROR_INVALID_PARAMETER: error.throw_exception("The specified process does not exist !"); break; // Should never happen !
|
||||
case ERROR_INVALID_HANDLE: need_allocation = true; break; // Need to allocate th console !
|
||||
}
|
||||
if(need_allocation)
|
||||
{
|
||||
Term::Private::WindowsError().check_if(AllocConsole() == 0).throw_exception("AllocConsole()");
|
||||
m_consoleCreated = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
detachConsole();
|
||||
ExceptionHandler(ExceptionDestination::MessageBox);
|
||||
}
|
||||
|
||||
void Term::Private::FileInitializer::detachConsole() noexcept
|
||||
try
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if(m_consoleCreated) { Term::Private::WindowsError().check_if(0 == FreeConsole()).throw_exception("FreeConsole()"); }
|
||||
#endif
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::MessageBox);
|
||||
}
|
||||
|
||||
Term::Private::FileInitializer::FileInitializer() noexcept
|
||||
try
|
||||
{
|
||||
// MacOS was not happy wish a static mutex in the class so we create it and pass to each class;
|
||||
static std::recursive_mutex ioMutex;
|
||||
if(0 == m_counter)
|
||||
{
|
||||
attachConsole();
|
||||
openStandardStreams();
|
||||
if(nullptr == new(&Term::Private::in) InputFileHandler(ioMutex)) { throw Term::Exception("new(&Term::Private::in) InputFileHandler(ioMutex)"); }
|
||||
if(nullptr == new(&Term::Private::out) OutputFileHandler(ioMutex)) { throw Term::Exception("new(&Term::Private::out) OutputFileHandler(ioMutex)"); }
|
||||
}
|
||||
++m_counter;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
Term::Private::FileInitializer::~FileInitializer() noexcept
|
||||
try
|
||||
{
|
||||
--m_counter;
|
||||
if(0 == m_counter)
|
||||
{
|
||||
(&Term::Private::in)->~InputFileHandler(); //NOSONAR(S3432)
|
||||
(&Term::Private::out)->~OutputFileHandler(); //NOSONAR(S3432)
|
||||
detachConsole();
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
void Term::Private::FileInitializer::openStandardStreams() noexcept
|
||||
try
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
FILE* fDummy{nullptr};
|
||||
if(_fileno(stderr) < 0 || _get_osfhandle(_fileno(stderr)) < 0) { Term::Private::Errno().check_if(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stderr) != 0).throw_exception(R"(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stderr))"); }
|
||||
if(_fileno(stdout) < 0 || _get_osfhandle(_fileno(stdout)) < 0) { Term::Private::Errno().check_if(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stdout) != 0).throw_exception(R"(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stdout))"); }
|
||||
if(_fileno(stdin) < 0 || _get_osfhandle(_fileno(stdin)) < 0) { Term::Private::Errno().check_if(_wfreopen_s(&fDummy, L"CONIN$", L"r", stdin) != 0).throw_exception(R"(_wfreopen_s(&fDummy, L"CONIN$", L"r", stdin))"); }
|
||||
const std::size_t bestSize{BUFSIZ > 4096 ? BUFSIZ : 4096};
|
||||
#else
|
||||
if(::fileno(stderr) < 0) { Term::Private::Errno().check_if(nullptr == std::freopen("/dev/tty", "w", stderr)).throw_exception(R"(std::freopen("/dev/tty", "w", stderr))"); } //NOLINT(cppcoreguidelines-owning-memory)
|
||||
if(::fileno(stdout) < 0) { Term::Private::Errno().check_if(nullptr == std::freopen("/dev/tty", "w", stdout)).throw_exception(R"(std::freopen("/dev/tty", "w", stdout))"); } //NOLINT(cppcoreguidelines-owning-memory)
|
||||
if(::fileno(stdin) < 0) { Term::Private::Errno().check_if(nullptr == std::freopen("/dev/tty", "r", stdin)).throw_exception(R"(std::freopen("/dev/tty", "r", stdin))"); } //NOLINT(cppcoreguidelines-owning-memory)
|
||||
struct stat stats = {};
|
||||
::stat("/dev/tty", &stats);
|
||||
const std::size_t bestSize{static_cast<std::size_t>(stats.st_blksize) > 0 ? static_cast<std::size_t>(stats.st_blksize) : BUFSIZ}; //NOSONAR(S1774)
|
||||
#endif
|
||||
Term::Private::Errno().check_if(std::setvbuf(stderr, nullptr, _IONBF, 0) != 0).throw_exception("std::setvbuf(stderr, nullptr, _IONBF, 0)");
|
||||
Term::Private::Errno().check_if(std::setvbuf(stdout, nullptr, _IOLBF, bestSize) != 0).throw_exception("std::setvbuf(stdout, nullptr, _IOLBF, bestSize)");
|
||||
Term::Private::Errno().check_if(std::setvbuf(stdin, nullptr, _IOLBF, bestSize) != 0).throw_exception("std::setvbuf(stdin, nullptr, _IOLBF, bestSize)");
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(ExceptionDestination::StdErr);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
namespace Private
|
||||
{
|
||||
|
||||
class FileInitializer
|
||||
{
|
||||
public:
|
||||
~FileInitializer() noexcept;
|
||||
FileInitializer() noexcept;
|
||||
FileInitializer(FileInitializer&&) = delete;
|
||||
FileInitializer(const FileInitializer&) = delete;
|
||||
FileInitializer& operator=(const FileInitializer&) = delete;
|
||||
FileInitializer& operator=(FileInitializer&&) = delete;
|
||||
|
||||
private:
|
||||
static bool m_consoleCreated;
|
||||
static std::size_t m_counter;
|
||||
|
||||
///
|
||||
///@brief Attach the console.
|
||||
///
|
||||
///Check if a console is attached to the process. If not, try to attach to the console. If there is no console, then create one.
|
||||
///
|
||||
static void attachConsole() noexcept;
|
||||
|
||||
///
|
||||
///@brief Detach the console.
|
||||
///
|
||||
///If a console as been created, then delete it.
|
||||
///
|
||||
static void detachConsole() noexcept;
|
||||
|
||||
///
|
||||
///@brief Open the standard streams.
|
||||
///
|
||||
///Open \b stdout \b stderr \b stdin and adjust their buffer size and line discipline.
|
||||
///
|
||||
static void openStandardStreams() noexcept;
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
|
||||
#include <vector>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#elif defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
|
||||
#include <cerrno>
|
||||
#include <csignal>
|
||||
#include <sys/ioctl.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <memory>
|
||||
#include <sys/epoll.h>
|
||||
#endif
|
||||
|
||||
#include "cpp-terminal/event.hpp"
|
||||
#include "cpp-terminal/exception.hpp"
|
||||
#include "cpp-terminal/input.hpp"
|
||||
#include "cpp-terminal/private/blocking_queue.hpp"
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
#include "cpp-terminal/private/input.hpp"
|
||||
#include "cpp-terminal/private/sigwinch.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
Term::Button::Action getAction(const std::int32_t& old_state, const std::int32_t& state, const std::int32_t& type)
|
||||
{
|
||||
if(((state - old_state) >> (type - 1)) == 1) return Term::Button::Action::Pressed;
|
||||
else if(((old_state - state) >> (type - 1)) == 1)
|
||||
return Term::Button::Action::Released;
|
||||
else
|
||||
return Term::Button::Action::None;
|
||||
}
|
||||
Term::Button setButton(const std::int32_t& old_state, const std::int32_t& state)
|
||||
{
|
||||
Term::Button::Action action;
|
||||
action = getAction(old_state, state, FROM_LEFT_1ST_BUTTON_PRESSED);
|
||||
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Left, action);
|
||||
|
||||
action = getAction(old_state, state, FROM_LEFT_2ND_BUTTON_PRESSED);
|
||||
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Button1, action);
|
||||
|
||||
action = getAction(old_state, state, FROM_LEFT_3RD_BUTTON_PRESSED);
|
||||
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Button2, action);
|
||||
|
||||
action = getAction(old_state, state, FROM_LEFT_4TH_BUTTON_PRESSED);
|
||||
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Button3, action);
|
||||
|
||||
action = getAction(old_state, state, RIGHTMOST_BUTTON_PRESSED);
|
||||
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Right, action);
|
||||
|
||||
return Term::Button(Term::Button::Type::None, Term::Button::Action::None);
|
||||
}
|
||||
|
||||
void sendString(Term::Private::BlockingQueue& events, std::wstring& str)
|
||||
{
|
||||
if(!str.empty())
|
||||
{
|
||||
events.push(Term::Event(Term::Private::to_narrow(str.c_str())));
|
||||
str.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Term::Private::Input::~Input()
|
||||
{
|
||||
if(m_thread.joinable()) m_thread.join();
|
||||
}
|
||||
|
||||
std::thread Term::Private::Input::m_thread{};
|
||||
|
||||
Term::Private::BlockingQueue Term::Private::Input::m_events;
|
||||
|
||||
int Term::Private::Input::m_poll{-1};
|
||||
|
||||
void Term::Private::Input::init_thread()
|
||||
{
|
||||
#if defined(__linux__)
|
||||
m_poll = {::epoll_create1(EPOLL_CLOEXEC)};
|
||||
::epoll_event signal;
|
||||
signal.events = {EPOLLIN};
|
||||
signal.data.fd = {Term::Private::Sigwinch::get()};
|
||||
::epoll_ctl(m_poll, EPOLL_CTL_ADD, Term::Private::Sigwinch::get(), &signal);
|
||||
::epoll_event input;
|
||||
input.events = {EPOLLIN};
|
||||
input.data.fd = {Term::Private::in.fd()};
|
||||
::epoll_ctl(m_poll, EPOLL_CTL_ADD, Term::Private::in.fd(), &input);
|
||||
#endif
|
||||
if(m_thread.joinable()) m_thread.join();
|
||||
std::thread thread(Term::Private::Input::read_event);
|
||||
m_thread.swap(thread);
|
||||
}
|
||||
|
||||
void Term::Private::Input::read_event()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
WaitForSingleObject(Term::Private::in.handle(), INFINITE);
|
||||
read_raw();
|
||||
#elif defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
|
||||
if(Term::Private::Sigwinch::isSigwinch()) m_events.push(screen_size());
|
||||
read_raw();
|
||||
#else
|
||||
::epoll_event ret;
|
||||
if(epoll_wait(m_poll, &ret, 1, -1) == 1)
|
||||
{
|
||||
if(Term::Private::Sigwinch::isSigwinch(ret.data.fd)) m_events.push(Term::Screen(screen_size()));
|
||||
else
|
||||
read_raw();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
void Term::Private::Input::read_windows_key(const std::uint16_t& virtual_key_code, const std::uint32_t& control_key_state, const std::size_t& occurrence)
|
||||
{
|
||||
// First check if we have ALT etc (CTRL is already done so skip it)
|
||||
Term::MetaKey toAdd{Term::MetaKey::Value::None};
|
||||
if(((control_key_state & LEFT_ALT_PRESSED) == LEFT_ALT_PRESSED) || ((control_key_state & RIGHT_ALT_PRESSED) == RIGHT_ALT_PRESSED)) toAdd += Term::MetaKey::Value::Alt;
|
||||
if(((control_key_state & LEFT_CTRL_PRESSED) == LEFT_CTRL_PRESSED) || ((control_key_state & RIGHT_CTRL_PRESSED) == RIGHT_CTRL_PRESSED)) toAdd += Term::MetaKey::Value::Ctrl;
|
||||
|
||||
switch(virtual_key_code)
|
||||
{
|
||||
case VK_CANCEL: //??
|
||||
case VK_CLEAR: //??
|
||||
case VK_SHIFT:
|
||||
case VK_CONTROL:
|
||||
case VK_MENU:
|
||||
case VK_PAUSE: //??
|
||||
case VK_CAPITAL:
|
||||
case VK_KANA: //??
|
||||
//case VK_HANGUL: // Same
|
||||
case VK_JUNJA: // ?
|
||||
case VK_FINAL: // ?
|
||||
case VK_HANJA:
|
||||
//case VK_KANJI: // Same
|
||||
case VK_CONVERT: // ?
|
||||
case VK_NONCONVERT: // ?
|
||||
case VK_ACCEPT: // ?
|
||||
case VK_MODECHANGE: // ?
|
||||
break;
|
||||
case VK_PRIOR: m_events.push(std::move(toAdd + Term::Key(Key::Value::PageUp)), occurrence); break;
|
||||
case VK_NEXT: m_events.push(std::move(toAdd + Term::Key(Key::Value::PageDown)), occurrence); break;
|
||||
case VK_END: m_events.push(std::move(toAdd + Term::Key(Key::Value::End)), occurrence); break;
|
||||
case VK_HOME: m_events.push(std::move(toAdd + Term::Key(Key::Value::Home)), occurrence); break;
|
||||
case VK_LEFT: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowLeft)), occurrence); break;
|
||||
case VK_UP: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowUp)), occurrence); break;
|
||||
case VK_RIGHT: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowRight)), occurrence); break;
|
||||
case VK_DOWN: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowDown)), occurrence); break;
|
||||
case VK_SELECT: //?
|
||||
case VK_PRINT: //?
|
||||
case VK_EXECUTE: //?
|
||||
break;
|
||||
case VK_SNAPSHOT: m_events.push(std::move(toAdd + Term::Key(Key::Value::PrintScreen)), occurrence); break;
|
||||
case VK_INSERT: m_events.push(std::move(toAdd + Term::Key(Key::Value::Insert)), occurrence); break;
|
||||
case VK_DELETE: m_events.push(std::move(toAdd + Term::Key(Key::Value::Del)), occurrence); break;
|
||||
case VK_HELP: //?
|
||||
case VK_LWIN: //Maybe allow to detect Windows key Left and right
|
||||
case VK_RWIN: //Maybe allow to detect Windows key Left and right
|
||||
case VK_APPS: //?
|
||||
case VK_SLEEP: //?
|
||||
break;
|
||||
case VK_F1: m_events.push(std::move(toAdd + Term::Key(Key::Value::F1)), occurrence); break;
|
||||
case VK_F2: m_events.push(std::move(toAdd + Term::Key(Key::Value::F2)), occurrence); break;
|
||||
case VK_F3: m_events.push(std::move(toAdd + Term::Key(Key::Value::F3)), occurrence); break;
|
||||
case VK_F4: m_events.push(std::move(toAdd + Term::Key(Key::Value::F4)), occurrence); break;
|
||||
case VK_F5: m_events.push(std::move(toAdd + Term::Key(Key::Value::F5)), occurrence); break;
|
||||
case VK_F6: m_events.push(std::move(toAdd + Term::Key(Key::Value::F6)), occurrence); break;
|
||||
case VK_F7: m_events.push(std::move(toAdd + Term::Key(Key::Value::F7)), occurrence); break;
|
||||
case VK_F8: m_events.push(std::move(toAdd + Term::Key(Key::Value::F8)), occurrence); break;
|
||||
case VK_F9: m_events.push(std::move(toAdd + Term::Key(Key::Value::F9)), occurrence); break;
|
||||
case VK_F10: m_events.push(std::move(toAdd + Term::Key(Key::Value::F10)), occurrence); break;
|
||||
case VK_F11: m_events.push(std::move(toAdd + Term::Key(Key::Value::F11)), occurrence); break;
|
||||
case VK_F12: m_events.push(std::move(toAdd + Term::Key(Key::Value::F12)), occurrence); break;
|
||||
case VK_F13: m_events.push(std::move(toAdd + Term::Key(Key::Value::F13)), occurrence); break;
|
||||
case VK_F14: m_events.push(std::move(toAdd + Term::Key(Key::Value::F14)), occurrence); break;
|
||||
case VK_F15: m_events.push(std::move(toAdd + Term::Key(Key::Value::F15)), occurrence); break;
|
||||
case VK_F16: m_events.push(std::move(toAdd + Term::Key(Key::Value::F16)), occurrence); break;
|
||||
case VK_F17: m_events.push(std::move(toAdd + Term::Key(Key::Value::F17)), occurrence); break;
|
||||
case VK_F18: m_events.push(std::move(toAdd + Term::Key(Key::Value::F18)), occurrence); break;
|
||||
case VK_F19: m_events.push(std::move(toAdd + Term::Key(Key::Value::F19)), occurrence); break;
|
||||
case VK_F20: m_events.push(std::move(toAdd + Term::Key(Key::Value::F20)), occurrence); break;
|
||||
case VK_F21: m_events.push(std::move(toAdd + Term::Key(Key::Value::F21)), occurrence); break;
|
||||
case VK_F22: m_events.push(std::move(toAdd + Term::Key(Key::Value::F22)), occurrence); break;
|
||||
case VK_F23: m_events.push(std::move(toAdd + Term::Key(Key::Value::F23)), occurrence); break;
|
||||
case VK_F24: m_events.push(std::move(toAdd + Term::Key(Key::Value::F24)), occurrence); break;
|
||||
case VK_NUMLOCK:
|
||||
case VK_SCROLL:
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Term::Private::Input::read_raw()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD to_read{0};
|
||||
GetNumberOfConsoleInputEvents(Private::in.handle(), &to_read);
|
||||
if(to_read == 0) return;
|
||||
DWORD read{0};
|
||||
std::vector<INPUT_RECORD> events{to_read};
|
||||
if(!ReadConsoleInputW(Private::in.handle(), &events[0], to_read, &read) || read != to_read) Term::Exception("ReadFile() failed");
|
||||
std::wstring ret;
|
||||
bool need_windows_size{false};
|
||||
for(std::size_t i = 0; i != read; ++i)
|
||||
{
|
||||
switch(events[i].EventType)
|
||||
{
|
||||
case KEY_EVENT:
|
||||
{
|
||||
if(events[i].Event.KeyEvent.bKeyDown)
|
||||
{
|
||||
if(events[i].Event.KeyEvent.uChar.UnicodeChar == 0) read_windows_key(events[i].Event.KeyEvent.wVirtualKeyCode, events[i].Event.KeyEvent.dwControlKeyState, events[i].Event.KeyEvent.wRepeatCount);
|
||||
else
|
||||
ret.append(events[i].Event.KeyEvent.wRepeatCount, events[i].Event.KeyEvent.uChar.UnicodeChar == Term::Key::Del ? static_cast<wchar_t>(Key(Term::Key::Value::Backspace)) : static_cast<wchar_t>(events[i].Event.KeyEvent.uChar.UnicodeChar));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FOCUS_EVENT:
|
||||
{
|
||||
sendString(m_events, ret);
|
||||
m_events.push(Event(Focus(static_cast<Term::Focus::Type>(events[i].Event.FocusEvent.bSetFocus))));
|
||||
break;
|
||||
}
|
||||
case MENU_EVENT:
|
||||
{
|
||||
sendString(m_events, ret);
|
||||
break;
|
||||
}
|
||||
case MOUSE_EVENT:
|
||||
{
|
||||
sendString(m_events, ret);
|
||||
static MOUSE_EVENT_RECORD old_state;
|
||||
if(events[i].Event.MouseEvent.dwEventFlags == MOUSE_WHEELED || events[i].Event.MouseEvent.dwEventFlags == MOUSE_HWHEELED)
|
||||
;
|
||||
else if(old_state.dwButtonState == events[i].Event.MouseEvent.dwButtonState && old_state.dwMousePosition.X == events[i].Event.MouseEvent.dwMousePosition.X && old_state.dwMousePosition.Y == events[i].Event.MouseEvent.dwMousePosition.Y && old_state.dwEventFlags == events[i].Event.MouseEvent.dwEventFlags)
|
||||
break;
|
||||
std::int32_t state{static_cast<std::int32_t>(events[i].Event.MouseEvent.dwButtonState)};
|
||||
switch(events[i].Event.MouseEvent.dwEventFlags)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m_events.push(Term::Mouse(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
;
|
||||
break;
|
||||
}
|
||||
case MOUSE_MOVED:
|
||||
{
|
||||
m_events.push(Term::Mouse(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
;
|
||||
break;
|
||||
}
|
||||
case DOUBLE_CLICK:
|
||||
{
|
||||
m_events.push(Term::Mouse(Term::Button(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state).type(), Term::Button::Action::DoubleClicked), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
break;
|
||||
}
|
||||
case MOUSE_WHEELED:
|
||||
{
|
||||
if(state > 0) m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::RolledUp), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
else
|
||||
m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::RolledDown), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
break;
|
||||
}
|
||||
case MOUSE_HWHEELED:
|
||||
{
|
||||
if(state > 0) m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::ToRight), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
else
|
||||
m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::ToLeft), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
old_state = events[i].Event.MouseEvent;
|
||||
break;
|
||||
}
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
{
|
||||
need_windows_size = true; // if we send directly it's too much generations
|
||||
sendString(m_events, ret);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
sendString(m_events, ret);
|
||||
if(need_windows_size == true) { m_events.push(screen_size()); }
|
||||
#else
|
||||
Private::in.lockIO();
|
||||
std::string ret = Term::Private::in.read();
|
||||
Private::in.unlockIO();
|
||||
if(!ret.empty()) m_events.push(Event(ret.c_str()));
|
||||
#endif
|
||||
}
|
||||
|
||||
Term::Private::Input::Input() {}
|
||||
|
||||
void Term::Private::Input::startReading()
|
||||
{
|
||||
static bool activated{false};
|
||||
if(!activated)
|
||||
{
|
||||
init_thread();
|
||||
m_thread.detach();
|
||||
activated = true;
|
||||
}
|
||||
}
|
||||
|
||||
Term::Event Term::Private::Input::getEvent() { return m_events.pop(); }
|
||||
|
||||
Term::Event Term::Private::Input::getEventBlocking()
|
||||
{
|
||||
static std::mutex cv_m;
|
||||
static std::unique_lock<std::mutex> lk(cv_m);
|
||||
if(m_events.empty()) m_events.wait_for_events(lk);
|
||||
return m_events.pop();
|
||||
}
|
||||
|
||||
static Term::Private::Input m_input;
|
||||
|
||||
Term::Event Term::read_event()
|
||||
{
|
||||
m_input.startReading();
|
||||
return m_input.getEventBlocking();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/event.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
namespace Private
|
||||
{
|
||||
|
||||
class BlockingQueue;
|
||||
|
||||
class Input final
|
||||
{
|
||||
public:
|
||||
Input();
|
||||
~Input();
|
||||
static void startReading();
|
||||
static Term::Event getEvent();
|
||||
static Term::Event getEventBlocking();
|
||||
|
||||
private:
|
||||
static void read_event();
|
||||
static void read_raw();
|
||||
#if defined(_WIN32)
|
||||
static void read_windows_key(const std::uint16_t& virtual_key_code, const std::uint32_t& control_key_state, const std::size_t& occurrence);
|
||||
#endif
|
||||
static void init_thread();
|
||||
static std::thread m_thread;
|
||||
static Term::Private::BlockingQueue m_events;
|
||||
static int m_poll; // for linux
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 7)
|
||||
#define CPP_TERMINAL_NODISCARD [[nodiscard]]
|
||||
#elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 8))
|
||||
#define CPP_TERMINAL_NODISCARD [[nodiscard]]
|
||||
#else
|
||||
#define CPP_TERMINAL_NODISCARD
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 5)
|
||||
#define CPP_TERMINAL_FALLTHROUGH [[fallthrough]]
|
||||
#elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 5))
|
||||
#define CPP_TERMINAL_FALLTHROUGH [[fallthrough]]
|
||||
#else
|
||||
#define CPP_TERMINAL_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 5)
|
||||
#define CPP_TERMINAL_MAYBE_UNUSED [[maybe_unused]]
|
||||
#elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 5))
|
||||
#define CPP_TERMINAL_MAYBE_UNUSED [[maybe_unused]]
|
||||
#else
|
||||
#define CPP_TERMINAL_MAYBE_UNUSED
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define CPP_TERMINAL_NODISCARD
|
||||
#define CPP_TERMINAL_FALLTHROUGH
|
||||
#define CPP_TERMINAL_MAYBE_UNUSED
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/return_code.hpp"
|
||||
|
||||
#include "cpp-terminal/private/env.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
std::uint16_t Term::returnCode() noexcept
|
||||
{
|
||||
static std::uint16_t code{EXIT_FAILURE};
|
||||
const std::pair<bool, std::string> returnCode{Private::getenv("CPP_TERMINAL_BADSTATE")};
|
||||
try
|
||||
{
|
||||
if(returnCode.first && (std::stoi(returnCode.second) != EXIT_SUCCESS)) { code = static_cast<std::uint16_t>(std::stoi(returnCode.second)); }
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
code = EXIT_FAILURE;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
std::uint16_t returnCode() noexcept;
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/screen.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
|
||||
Term::Screen Term::screen_size()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO inf;
|
||||
if(GetConsoleScreenBufferInfo(Private::out.handle(), &inf)) return Term::Screen({Term::Rows(inf.srWindow.Bottom - inf.srWindow.Top + 1), Term::Columns(inf.srWindow.Right - inf.srWindow.Left + 1)});
|
||||
return {};
|
||||
#else
|
||||
struct winsize window{0, 0, 0, 0};
|
||||
if(ioctl(Private::out.fd(), TIOCGWINSZ, &window) != -1) return Term::Screen({Term::Rows(window.ws_row), Term::Columns(window.ws_col)});
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/signals.hpp"
|
||||
|
||||
#include "cpp-terminal/terminal.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
|
||||
#ifndef NSIG
|
||||
#define NSIG (_SIGMAX + 1) /* For QNX */
|
||||
#endif
|
||||
|
||||
const std::size_t Term::Private::Signals::m_signals_number{NSIG - 1};
|
||||
|
||||
void Term::Private::Signals::setHandler(const sighandler_t& handler) noexcept
|
||||
{
|
||||
for(std::size_t signal = 0; signal != m_signals_number; ++signal) { sighandler_t hand = std::signal(signal, handler); }
|
||||
}
|
||||
|
||||
Term::Private::Signals::Signals(std::vector<sighandler_t>& m_han) noexcept
|
||||
{
|
||||
const static std::vector<int> ignore{
|
||||
#if defined(SIGCONT)
|
||||
SIGCONT,
|
||||
#endif
|
||||
#if defined(SIGSTOP)
|
||||
SIGSTOP,
|
||||
#endif
|
||||
#if defined(SIGTSTP)
|
||||
SIGTSTP,
|
||||
#endif
|
||||
#if defined(SIGTTIN)
|
||||
SIGTTIN,
|
||||
#endif
|
||||
#if defined(SIGTTOU)
|
||||
SIGTTOU,
|
||||
#endif
|
||||
};
|
||||
m_han.reserve(m_signals_number);
|
||||
for(std::size_t signal = 0; signal != m_signals_number; ++signal)
|
||||
{
|
||||
//if(std::find(ignore.begin(),ignore.end(),signal)==ignore.end())
|
||||
//{
|
||||
sighandler_t old = std::signal(signal, SIG_DFL);
|
||||
//sighandler_t dumb=std::signal(signal, old);
|
||||
m_han.push_back(old);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// std::signal(signal, SIG_IGN);
|
||||
// m_han.push_back(std::signal(signal, SIG_IGN));
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void Term::Private::Signals::reset_and_raise(int sign, std::vector<sighandler_t>& m_han, Term::Terminal& term) noexcept
|
||||
{
|
||||
const static std::vector<int> termin{
|
||||
#if defined(SIGHUP)
|
||||
SIGHUP,
|
||||
#endif
|
||||
#if defined(SIGHUP)
|
||||
SIGINT,
|
||||
#endif
|
||||
#if defined(SIGQUIT)
|
||||
SIGQUIT,
|
||||
#endif
|
||||
#if defined(SIGQUIT)
|
||||
SIGILL,
|
||||
#endif
|
||||
#if defined(SIGTRAP)
|
||||
SIGTRAP,
|
||||
#endif
|
||||
#if defined(SIGTRAP)
|
||||
SIGABRT,
|
||||
#endif
|
||||
#if defined(SIGIOT)
|
||||
SIGIOT,
|
||||
#endif
|
||||
#if defined(SIGBUS)
|
||||
SIGBUS,
|
||||
#endif
|
||||
#if defined(SIGBUS)
|
||||
SIGFPE,
|
||||
#endif
|
||||
#if defined(SIGKILL)
|
||||
SIGKILL,
|
||||
#endif
|
||||
#if defined(SIGUSR1)
|
||||
SIGUSR1,
|
||||
#endif
|
||||
#if defined(SIGSEGV)
|
||||
SIGSEGV,
|
||||
#endif
|
||||
#if defined(SIGUSR2)
|
||||
SIGUSR2,
|
||||
#endif
|
||||
#if defined(SIGUSR2)
|
||||
SIGPIPE,
|
||||
#endif
|
||||
#if defined(SIGALRM)
|
||||
SIGALRM,
|
||||
#endif
|
||||
#if defined(SIGSTKFLT)
|
||||
SIGSTKFLT,
|
||||
#endif
|
||||
#if defined(SIGCONT)
|
||||
SIGCONT,
|
||||
#endif
|
||||
#if defined(SIGXCPU)
|
||||
SIGXCPU,
|
||||
#endif
|
||||
#if defined(SIGXFSZ)
|
||||
SIGXFSZ,
|
||||
#endif
|
||||
#if defined(SIGVTALRM)
|
||||
SIGVTALRM,
|
||||
#endif
|
||||
#if defined(SIGPROF)
|
||||
SIGPROF,
|
||||
#endif
|
||||
#if defined(SIGPROF)
|
||||
SIGIO,
|
||||
#endif
|
||||
#if defined(SIGPOLL)
|
||||
SIGPOLL,
|
||||
#endif
|
||||
#if defined(SIGPWR)
|
||||
SIGPWR,
|
||||
#endif
|
||||
#if defined(SIGSYS)
|
||||
SIGSYS,
|
||||
#endif
|
||||
#if defined(SIGUNUSED)
|
||||
SIGUNUSED,
|
||||
#endif
|
||||
#if defined(SIGUNUSED)
|
||||
SIGUNUSED,
|
||||
#endif
|
||||
#if defined(SIGTERM)
|
||||
SIGTERM
|
||||
#endif
|
||||
};
|
||||
if(std::find(termin.begin(), termin.end(), sign) != termin.end())
|
||||
{
|
||||
sighandler_t old = std::signal(sign, m_han[sign]);
|
||||
old = std::signal(sign, m_han[sign]);
|
||||
term.clean();
|
||||
std::raise(sign);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
using sighandler_t = void (*)(int);
|
||||
|
||||
namespace Term
|
||||
{
|
||||
class Terminal;
|
||||
namespace Private
|
||||
{
|
||||
class Signals
|
||||
{
|
||||
public:
|
||||
Signals(std::vector<sighandler_t>& m_han) noexcept;
|
||||
~Signals() noexcept {}
|
||||
void setHandler(const sighandler_t& handler) noexcept;
|
||||
static void reset_and_raise(int sign, std::vector<sighandler_t>& m_han, Term::Terminal&) noexcept;
|
||||
|
||||
private:
|
||||
const static std::size_t m_signals_number;
|
||||
};
|
||||
} // namespace Private
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/sigwinch.hpp"
|
||||
|
||||
#include "cpp-terminal/private/exception.hpp"
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <csignal>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <sys/signalfd.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
|
||||
namespace Term
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
volatile std::sig_atomic_t m_signalStatus{0};
|
||||
static void sigwinchHandler(int sig)
|
||||
{
|
||||
if(sig == SIGWINCH) { m_signalStatus = 1; }
|
||||
else { m_signalStatus = 0; }
|
||||
}
|
||||
} // namespace Private
|
||||
} // namespace Term
|
||||
#endif
|
||||
|
||||
std::int32_t Term::Private::Sigwinch::get() noexcept
|
||||
{
|
||||
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
|
||||
return Term::Private::m_signalStatus;
|
||||
#else
|
||||
return m_fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::int32_t Term::Private::Sigwinch::m_fd{-1};
|
||||
|
||||
void Term::Private::Sigwinch::registerSigwinch()
|
||||
{
|
||||
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
|
||||
struct sigaction sa;
|
||||
Term::Private::Errno().check_if(sigemptyset(&sa.sa_mask) != 0).throw_exception("sigemptyset(&sa.sa_mask)");
|
||||
sa.sa_flags = {0};
|
||||
sa.sa_handler = {Term::Private::sigwinchHandler};
|
||||
Term::Private::Errno().check_if(sigaction(SIGWINCH, &sa, nullptr) != 0).throw_exception("sigaction(SIGWINCH, &sa, nullptr)");
|
||||
#elif defined(__linux__)
|
||||
::sigset_t windows_event = {};
|
||||
Term::Private::Errno().check_if(sigemptyset(&windows_event) != 0).throw_exception("sigemptyset(&windows_event)");
|
||||
Term::Private::Errno().check_if(sigaddset(&windows_event, SIGWINCH) != 0).throw_exception("sigaddset(&windows_event, SIGWINCH)");
|
||||
Term::Private::Errno().check_if((m_fd = ::signalfd(-1, &windows_event, SFD_NONBLOCK | SFD_CLOEXEC)) == -1).throw_exception("m_fd = ::signalfd(-1, &windows_event, SFD_NONBLOCK | SFD_CLOEXEC)");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Private::Sigwinch::blockSigwinch()
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
::sigset_t windows_event = {};
|
||||
Term::Private::Errno().check_if(sigemptyset(&windows_event) != 0).throw_exception("sigemptyset(&windows_event)");
|
||||
Term::Private::Errno().check_if(sigaddset(&windows_event, SIGWINCH) != 0).throw_exception("sigaddset(&windows_event, SIGWINCH)");
|
||||
Term::Private::Errno().check_if(::pthread_sigmask(SIG_BLOCK, &windows_event, nullptr) != 0).throw_exception("::pthread_sigmask(SIG_BLOCK, &windows_event, nullptr)");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Private::Sigwinch::unblockSigwinch()
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
::sigset_t windows_event = {};
|
||||
Term::Private::Errno().check_if(sigemptyset(&windows_event) != 0).throw_exception("sigemptyset(&windows_event)");
|
||||
Term::Private::Errno().check_if(sigaddset(&windows_event, SIGWINCH) != 0).throw_exception("sigaddset(&windows_event, SIGWINCH)");
|
||||
Term::Private::Errno().check_if(::pthread_sigmask(SIG_UNBLOCK, &windows_event, nullptr) != 0).throw_exception("::pthread_sigmask(SIG_UNBLOCK, &windows_event, nullptr)");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Term::Private::Sigwinch::isSigwinch(const std::int32_t& file_descriptor) noexcept
|
||||
{
|
||||
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
|
||||
if(Term::Private::m_signalStatus == 1)
|
||||
{
|
||||
static_cast<void>(file_descriptor); // suppress warning
|
||||
Term::Private::m_signalStatus = {0};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#elif defined(__linux__)
|
||||
if(m_fd == file_descriptor)
|
||||
{
|
||||
// read it to clean
|
||||
::signalfd_siginfo fdsi = {};
|
||||
::read(m_fd, &fdsi, sizeof(fdsi));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
static_cast<void>(file_descriptor); // suppress warning
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
|
||||
class Sigwinch
|
||||
{
|
||||
public:
|
||||
static void registerSigwinch();
|
||||
static void blockSigwinch();
|
||||
static void unblockSigwinch();
|
||||
static bool isSigwinch(const std::int32_t& file_descriptor = -1) noexcept;
|
||||
static std::int32_t get() noexcept;
|
||||
|
||||
private:
|
||||
static std::int32_t m_fd;
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/cursor.hpp"
|
||||
#include "cpp-terminal/private/env.hpp"
|
||||
#include "cpp-terminal/private/exception.hpp"
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
//#include "cpp-terminal/private/signals.hpp"
|
||||
#include "cpp-terminal/private/sigwinch.hpp"
|
||||
#include "cpp-terminal/terminal.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
||||
#endif
|
||||
#if !defined(DISABLE_NEWLINE_AUTO_RETURN)
|
||||
#define DISABLE_NEWLINE_AUTO_RETURN 0x0008
|
||||
#endif
|
||||
#if !defined(ENABLE_VIRTUAL_TERMINAL_INPUT)
|
||||
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
|
||||
#endif
|
||||
#else
|
||||
#include <termios.h>
|
||||
#endif
|
||||
|
||||
void Term::Terminal::set_unset_utf8()
|
||||
{
|
||||
static bool enabled{false};
|
||||
#if defined(_WIN32)
|
||||
static UINT out_code_page{0};
|
||||
static UINT in_code_page{0};
|
||||
if(!enabled)
|
||||
{
|
||||
if((out_code_page = GetConsoleOutputCP()) == 0) throw Term::Private::WindowsException(GetLastError(), "GetConsoleOutputCP()");
|
||||
if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleOutputCP(CP_UTF8)");
|
||||
if((in_code_page = GetConsoleCP()) == 0) throw Term::Private::WindowsException(GetLastError(), "GetConsoleCP()");
|
||||
if(!SetConsoleCP(CP_UTF8)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleCP(CP_UTF8)");
|
||||
enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!SetConsoleOutputCP(out_code_page)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleOutputCP(out_code_page)");
|
||||
if(!SetConsoleCP(in_code_page)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleCP(in_code_page)");
|
||||
}
|
||||
#else
|
||||
if(!enabled)
|
||||
{
|
||||
const Term::Cursor cursor_before{Term::cursor_position()};
|
||||
Term::Private::out.write("\u001b%G"); // Try to activate UTF-8 (NOT warranty)
|
||||
const std::string read{Term::Private::in.read()};
|
||||
const Term::Cursor cursor_after{Term::cursor_position()};
|
||||
const std::size_t moved{cursor_after.column() - cursor_before.column()};
|
||||
std::string rem;
|
||||
rem.reserve(moved * 3);
|
||||
for(std::size_t i = 0; i != moved; ++i) { rem += "\b \b"; }
|
||||
Term::Private::out.write(rem);
|
||||
enabled = 0 == moved;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Does not return the original charset but, the default defined by standard ISO 8859-1 (ISO 2022);
|
||||
Term::Private::out.write("\u001b%@");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Terminal::store_and_restore() noexcept
|
||||
try
|
||||
{
|
||||
static bool enabled{false};
|
||||
#if defined(_WIN32)
|
||||
static DWORD originalOut{0};
|
||||
static DWORD originalIn{0};
|
||||
if(!enabled)
|
||||
{
|
||||
Term::Private::WindowsError().check_if(GetConsoleMode(Private::out.handle(), &originalOut) == 0).throw_exception("GetConsoleMode(Private::out.handle(), &originalOut)");
|
||||
Term::Private::WindowsError().check_if(GetConsoleMode(Private::in.handle(), &originalIn) == 0).throw_exception("GetConsoleMode(Private::in.handle(), &originalIn)");
|
||||
DWORD in{static_cast<DWORD>((originalIn & ~(ENABLE_QUICK_EDIT_MODE | setFocusEvents() | setMouseEvents())) | (ENABLE_EXTENDED_FLAGS))};
|
||||
DWORD out{originalOut};
|
||||
// Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN can be activated, if not we are a legacy terminal.
|
||||
DWORD test = out;
|
||||
test |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
if(!SetConsoleMode(Private::out.handle(), test)) { SetConsoleMode(Private::out.handle(), out); }
|
||||
else
|
||||
{
|
||||
out |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
|
||||
in |= ENABLE_VIRTUAL_TERMINAL_INPUT;
|
||||
}
|
||||
if(!SetConsoleMode(Private::out.handle(), out)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::out.handle()"); }
|
||||
if(!SetConsoleMode(Private::in.handle(), in)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::in.handle(), in)"); }
|
||||
enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!SetConsoleMode(Private::out.handle(), originalOut)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::out.handle(), originalOut)"); }
|
||||
if(!SetConsoleMode(Private::in.handle(), originalIn)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::in.handle(), originalIn)"); }
|
||||
}
|
||||
#else
|
||||
static termios orig_termios;
|
||||
if(!enabled)
|
||||
{
|
||||
Term::Private::Sigwinch::blockSigwinch();
|
||||
Term::Private::Sigwinch::registerSigwinch();
|
||||
if(!Private::out.null()) { Term::Private::Errno().check_if(tcgetattr(Private::out.fd(), &orig_termios) == -1).throw_exception("tcgetattr() failed"); }
|
||||
enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Term::Private::Sigwinch::unblockSigwinch();
|
||||
unsetMouseEvents();
|
||||
unsetFocusEvents();
|
||||
if(!Private::out.null()) { Term::Private::Errno().check_if(tcsetattr(Private::out.fd(), TCSAFLUSH, &orig_termios) == -1).throw_exception("tcsetattr() failed in destructor"); }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
ExceptionHandler(Private::ExceptionDestination::StdErr);
|
||||
}
|
||||
|
||||
std::size_t Term::Terminal::setMouseEvents()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return static_cast<std::size_t>(ENABLE_MOUSE_INPUT);
|
||||
#else
|
||||
return Term::Private::out.write("\u001b[?1002h\u001b[?1003h\u001b[?1006h");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t Term::Terminal::unsetMouseEvents()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return static_cast<std::size_t>(ENABLE_MOUSE_INPUT);
|
||||
#else
|
||||
return Term::Private::out.write("\u001b[?1006l\u001b[?1003l\u001b[?1002l");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t Term::Terminal::setFocusEvents()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return static_cast<std::size_t>(ENABLE_WINDOW_INPUT);
|
||||
#else
|
||||
return Term::Private::out.write("\u001b[?1004h");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t Term::Terminal::unsetFocusEvents()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return static_cast<std::size_t>(ENABLE_WINDOW_INPUT);
|
||||
#else
|
||||
return Term::Private::out.write("\u001b[?1004l");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Terminal::setMode() const
|
||||
{
|
||||
static bool activated{false};
|
||||
#if defined(_WIN32)
|
||||
static DWORD flags{0};
|
||||
if(!activated)
|
||||
{
|
||||
if(!Private::out.null())
|
||||
if(!GetConsoleMode(Private::in.handle(), &flags)) { throw Term::Private::WindowsException(GetLastError()); }
|
||||
activated = true;
|
||||
return;
|
||||
}
|
||||
DWORD send = flags;
|
||||
if(m_options.has(Option::Raw))
|
||||
{
|
||||
send &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
|
||||
send |= (setFocusEvents() | setMouseEvents());
|
||||
}
|
||||
else if(m_options.has(Option::Cooked))
|
||||
{
|
||||
send |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
|
||||
send &= ~(setFocusEvents() | setMouseEvents());
|
||||
}
|
||||
if(m_options.has(Option::NoSignalKeys)) { send &= ~ENABLE_PROCESSED_INPUT; }
|
||||
else if(m_options.has(Option::SignalKeys)) { send |= ENABLE_PROCESSED_INPUT; }
|
||||
if(!Private::out.null())
|
||||
if(!SetConsoleMode(Private::in.handle(), send)) { throw Term::Private::WindowsException(GetLastError()); }
|
||||
#else
|
||||
if(!Private::out.null())
|
||||
{
|
||||
static ::termios raw = {};
|
||||
if(!activated)
|
||||
{
|
||||
Term::Private::Errno().check_if(tcgetattr(Private::out.fd(), &raw) == -1).throw_exception("tcgetattr(Private::out.fd(), &raw)");
|
||||
raw.c_cflag &= ~static_cast<std::size_t>(CSIZE | PARENB);
|
||||
raw.c_cflag |= CS8;
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
activated = true;
|
||||
return;
|
||||
}
|
||||
::termios send = raw;
|
||||
if(m_options.has(Option::Raw))
|
||||
{
|
||||
send.c_iflag &= ~static_cast<std::size_t>(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | INPCK);
|
||||
// This disables output post-processing, requiring explicit \r\n. We
|
||||
// keep it enabled, so that in C++, one can still just use std::endl
|
||||
// for EOL instead of "\r\n".
|
||||
//send.c_oflag &= ~static_cast<std::size_t>(OPOST);
|
||||
send.c_lflag &= ~static_cast<std::size_t>(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
setMouseEvents();
|
||||
setFocusEvents();
|
||||
}
|
||||
else if(m_options.has(Option::Cooked))
|
||||
{
|
||||
send = raw;
|
||||
unsetMouseEvents();
|
||||
unsetFocusEvents();
|
||||
}
|
||||
if(m_options.has(Option::NoSignalKeys)) { send.c_lflag &= ~static_cast<std::size_t>(ISIG); } //FIXME need others flags !
|
||||
if(m_options.has(Option::SignalKeys)) { send.c_lflag |= ISIG; }
|
||||
Term::Private::Errno().check_if(tcsetattr(Private::out.fd(), TCSAFLUSH, &send) == -1).throw_exception("tcsetattr(Private::out.fd(), TCSAFLUSH, &send)");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "cpp-terminal/cursor.hpp"
|
||||
#include "cpp-terminal/private/env.hpp"
|
||||
#include "cpp-terminal/private/file.hpp"
|
||||
#include "cpp-terminal/terminfo.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
Term::Terminfo::ColorMode Term::Terminfo::m_colorMode{ColorMode::Unset};
|
||||
Term::Terminfo::Booleans Term::Terminfo::m_booleans{};
|
||||
Term::Terminfo::Integers Term::Terminfo::m_integers{};
|
||||
Term::Terminfo::Strings Term::Terminfo::m_strings{};
|
||||
|
||||
bool Term::Terminfo::get(const Term::Terminfo::Bool& key)
|
||||
{
|
||||
check();
|
||||
return m_booleans[static_cast<std::size_t>(key)];
|
||||
}
|
||||
|
||||
std::uint32_t Term::Terminfo::get(const Term::Terminfo::Integer& key)
|
||||
{
|
||||
check();
|
||||
return m_integers[static_cast<std::size_t>(key)];
|
||||
}
|
||||
|
||||
std::string Term::Terminfo::get(const Term::Terminfo::String& key)
|
||||
{
|
||||
check();
|
||||
return m_strings[static_cast<std::size_t>(key)];
|
||||
}
|
||||
|
||||
void Term::Terminfo::set(const Term::Terminfo::Bool& key, const bool& value) { m_booleans[static_cast<std::size_t>(key)] = value; }
|
||||
void Term::Terminfo::set(const Term::Terminfo::Integer& key, const std::uint32_t& value) { m_integers[static_cast<std::size_t>(key)] = value; }
|
||||
void Term::Terminfo::set(const Term::Terminfo::String& key, const std::string& value) { m_strings[static_cast<std::size_t>(key)] = value; }
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool WindowsVersionGreater(const DWORD& major, const DWORD& minor, const DWORD& patch)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4191)
|
||||
#else
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
NTSTATUS(WINAPI * getVersion)
|
||||
(PRTL_OSVERSIONINFOW) = (reinterpret_cast<NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW)>(GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion")));
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
if(getVersion != nullptr)
|
||||
{
|
||||
RTL_OSVERSIONINFOW rovi;
|
||||
rovi.dwOSVersionInfoSize = sizeof(rovi);
|
||||
if(getVersion(&rovi) == 0)
|
||||
{
|
||||
if(rovi.dwMajorVersion > major || (rovi.dwMajorVersion == major && (rovi.dwMinorVersion > minor || (rovi.dwMinorVersion == minor && rovi.dwBuildNumber >= patch)))) return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Term::Terminfo::checkLegacy()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
||||
#endif
|
||||
if(!m_booleans[static_cast<std::size_t>(Terminfo::Bool::ControlSequences)]) { set(Terminfo::Bool::Legacy, true); }
|
||||
else
|
||||
{
|
||||
DWORD dwOriginalOutMode{0};
|
||||
GetConsoleMode(Private::out.handle(), &dwOriginalOutMode);
|
||||
if(!SetConsoleMode(Private::out.handle(), dwOriginalOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { set(Terminfo::Bool::Legacy, true); }
|
||||
else
|
||||
{
|
||||
SetConsoleMode(Private::out.handle(), dwOriginalOutMode);
|
||||
set(Terminfo::Bool::Legacy, false);
|
||||
}
|
||||
}
|
||||
#else
|
||||
set(Terminfo::Bool::Legacy, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Terminfo::checkTermEnv() { set(Terminfo::String::TermEnv, Private::getenv("TERM").second); }
|
||||
|
||||
void Term::Terminfo::checkTerminalName()
|
||||
{
|
||||
std::string name;
|
||||
name = Private::getenv("TERM_PROGRAM").second;
|
||||
if(name.empty()) { name = Private::getenv("TERMINAL_EMULATOR").second; }
|
||||
if(Private::getenv("ANSICON").first) { name = "ansicon"; }
|
||||
set(Term::Terminfo::String::TermName, name);
|
||||
}
|
||||
|
||||
void Term::Terminfo::checkTerminalVersion() { set(Terminfo::String::TermVersion, Private::getenv("TERM_PROGRAM_VERSION").second); }
|
||||
|
||||
void Term::Terminfo::check()
|
||||
{
|
||||
static bool checked{false};
|
||||
if(!checked)
|
||||
{
|
||||
checkTermEnv();
|
||||
checkTerminalName();
|
||||
checkTerminalVersion();
|
||||
checkControlSequences();
|
||||
checkLegacy();
|
||||
checkColorMode();
|
||||
checkUTF8();
|
||||
checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
Term::Terminfo::ColorMode Term::Terminfo::getColorMode()
|
||||
{
|
||||
checkColorMode();
|
||||
return m_colorMode;
|
||||
}
|
||||
|
||||
Term::Terminfo::Terminfo() { check(); }
|
||||
|
||||
void Term::Terminfo::checkColorMode()
|
||||
{
|
||||
std::string name{m_strings[static_cast<std::size_t>(Terminfo::String::TermName)]};
|
||||
if(name == "Apple_Terminal") { m_colorMode = Term::Terminfo::ColorMode::Bit8; }
|
||||
else if(name == "JetBrains-JediTerm") { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
|
||||
else if(name == "vscode") { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
|
||||
else if(name == "linux") { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
|
||||
else if(name == "ansicon") { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
|
||||
else if(m_strings[static_cast<std::size_t>(Terminfo::String::TermEnv)] == "linux") { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
|
||||
#if defined(_WIN32)
|
||||
else if(WindowsVersionGreater(10, 0, 10586) && !m_booleans[static_cast<std::size_t>(Terminfo::Bool::Legacy)]) { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
|
||||
else if(m_booleans[static_cast<std::size_t>(Terminfo::Bool::Legacy)]) { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
|
||||
#endif
|
||||
else { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
|
||||
std::string colorterm = Private::getenv("COLORTERM").second;
|
||||
if((colorterm == "truecolor" || colorterm == "24bit") && m_colorMode != ColorMode::Unset) { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
|
||||
}
|
||||
|
||||
void Term::Terminfo::checkControlSequences()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if(WindowsVersionGreater(10, 0, 10586)) { set(Term::Terminfo::Bool::ControlSequences, true); }
|
||||
else { set(Term::Terminfo::Bool::ControlSequences, false); }
|
||||
#else
|
||||
set(Term::Terminfo::Bool::ControlSequences, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Term::Terminfo::checkUTF8()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
(GetConsoleOutputCP() == CP_UTF8 && GetConsoleCP() == CP_UTF8) ? set(Terminfo::Bool::UTF8, true) : set(Terminfo::Bool::UTF8, false);
|
||||
#else
|
||||
Term::Cursor cursor_before{Term::cursor_position()};
|
||||
Term::Private::out.write("\xe2\x82\xac"); // € 3bits in utf8 one character
|
||||
std::string read{Term::Private::in.read()};
|
||||
Term::Cursor cursor_after{Term::cursor_position()};
|
||||
std::size_t moved{cursor_after.column() - cursor_before.column()};
|
||||
if(moved == 1) { set(Terminfo::Bool::UTF8, true); }
|
||||
else { set(Terminfo::Bool::UTF8, false); }
|
||||
for(std::size_t i = 0; i != moved; ++i) { Term::Private::out.write("\b \b"); }
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "cpp-terminal/tty.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool is_a_tty(const FILE* fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return static_cast<bool>(_isatty(_fileno(const_cast<FILE*>(fd))));
|
||||
#else
|
||||
return ::isatty(::fileno(const_cast<FILE*>(fd)));
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool Term::is_stdin_a_tty() { return ::is_a_tty(stdin); }
|
||||
|
||||
bool Term::is_stdout_a_tty() { return ::is_a_tty(stdout); }
|
||||
|
||||
bool Term::is_stderr_a_tty() { return ::is_a_tty(stderr); }
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/private/unicode.hpp"
|
||||
|
||||
#include "cpp-terminal/private/exception.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <limits>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4668)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::string Term::Private::to_narrow(const std::wstring& in)
|
||||
{
|
||||
if(in.empty()) return std::string();
|
||||
static constexpr DWORD flag{WC_ERR_INVALID_CHARS};
|
||||
std::size_t in_size{in.size()};
|
||||
if(in_size > static_cast<size_t>((std::numeric_limits<int>::max)())) throw Term::Exception("String size is to big " + std::to_string(in_size) + "/" + std::to_string((std::numeric_limits<int>::max)()));
|
||||
const int ret_size{::WideCharToMultiByte(CP_UTF8, flag, in.data(), static_cast<int>(in_size), nullptr, 0, nullptr, nullptr)};
|
||||
if(ret_size == 0) throw Term::Private::WindowsException(::GetLastError());
|
||||
std::string ret(static_cast<std::size_t>(ret_size), '\0');
|
||||
int ret_error{::WideCharToMultiByte(CP_UTF8, flag, in.data(), static_cast<int>(in_size), &ret[0], ret_size, nullptr, nullptr)};
|
||||
if(ret_error == 0) throw Term::Private::WindowsException(::GetLastError());
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::wstring Term::Private::to_wide(const std::string& in)
|
||||
{
|
||||
if(in.empty()) return std::wstring();
|
||||
static constexpr DWORD flag{MB_ERR_INVALID_CHARS};
|
||||
std::size_t in_size{in.size()};
|
||||
if(in_size > static_cast<size_t>((std::numeric_limits<int>::max)())) throw Term::Exception("String size is to big " + std::to_string(in_size) + "/" + std::to_string((std::numeric_limits<int>::max)()));
|
||||
const int ret_size{::MultiByteToWideChar(CP_UTF8, flag, in.data(), static_cast<int>(in_size), nullptr, 0)};
|
||||
if(ret_size == 0) throw Term::Private::WindowsException(::GetLastError());
|
||||
std::wstring ret(static_cast<std::size_t>(ret_size), '\0');
|
||||
int ret_error{::MultiByteToWideChar(CP_UTF8, flag, in.data(), static_cast<int>(in_size), &ret[0], ret_size)};
|
||||
if(ret_error == 0) throw Term::Private::WindowsException(::GetLastError());
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string Term::Private::utf32_to_utf8(const char32_t& codepoint, const bool& exception)
|
||||
{
|
||||
static const constexpr std::array<std::uint32_t, 4> size{0x7F, 0x07FF, 0xFFFF, 0x10FFFF};
|
||||
static const constexpr std::uint8_t mask{0x80};
|
||||
static const constexpr std::uint8_t add{0x3F};
|
||||
static const constexpr std::array<std::uint8_t, 3> mask_first{0x1F, 0x0F, 0x07};
|
||||
static const constexpr std::array<std::uint8_t, 3> add_first{0xC0, 0xE0, 0xF0};
|
||||
static const constexpr std::array<std::uint8_t, 4> shift{0, 6, 12, 18};
|
||||
static const constexpr std::uint8_t max_size{4};
|
||||
std::string ret;
|
||||
ret.reserve(max_size);
|
||||
if(codepoint <= size[0]) { ret = {static_cast<char>(codepoint)}; } // Plain ASCII
|
||||
else if(codepoint <= size[1]) { ret = {static_cast<char>(((codepoint >> shift[1]) & mask_first[0]) | add_first[0]), static_cast<char>(((codepoint >> shift[0]) & add) | mask)}; }
|
||||
else if(codepoint <= size[2]) { ret = {static_cast<char>(((codepoint >> shift[2]) & mask_first[1]) | add_first[1]), static_cast<char>(((codepoint >> shift[1]) & add) | mask), static_cast<char>(((codepoint >> shift[0]) & add) | mask)}; }
|
||||
else if(codepoint <= size[3]) { ret = {static_cast<char>(((codepoint >> shift[3]) & mask_first[2]) | add_first[2]), static_cast<char>(((codepoint >> shift[2]) & add) | mask), static_cast<char>(((codepoint >> shift[1]) & add) | mask), static_cast<char>(((codepoint >> shift[0]) & add) | mask)}; }
|
||||
else if(exception) { throw Term::Exception("Invalid UTF32 codepoint."); }
|
||||
else { ret = "\xEF\xBF\xBD"; }
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Term::Private::utf32_to_utf8(const std::u32string& str, const bool& exception)
|
||||
{
|
||||
std::string ret;
|
||||
for(const char32_t codepoint: str) { ret.append(utf32_to_utf8(codepoint, exception)); }
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
|
||||
// utf16 is useless and wstring too so utf16 inside wstring is useless^2 but windows use it so define this functions to deal with it and less the user forget it.
|
||||
#if defined(_WIN32)
|
||||
std::string to_narrow(const std::wstring& wstr);
|
||||
std::wstring to_wide(const std::string& str);
|
||||
#endif
|
||||
|
||||
///
|
||||
///@brief Encode a codepoint using UTF-8 \b std::string .
|
||||
///
|
||||
///@param codepoint The codepoint ( \b char32_t ) on range [0,0x10FFFF] to convert.
|
||||
///@param exception If \b true throw exception on error, otherwise change the out of range \b codepoint to "replacement character" \b � .
|
||||
///@return \b std::string the UTF-8 value.
|
||||
///@warning Internal use only.
|
||||
///
|
||||
std::string utf32_to_utf8(const char32_t& codepoint, const bool& exception = false);
|
||||
|
||||
///
|
||||
///@brief Encode a \b std::u32string into UTF-8 \b std::string .
|
||||
///
|
||||
///@param str The \b std::u32string to convert.
|
||||
///@param exception If \b true throw exception on error, otherwise change the out of range \b codepoint to "replacement character" \b � .
|
||||
///@return \b std::string encoded in UTF-8.
|
||||
///@warning Internal use only.
|
||||
///
|
||||
std::string utf32_to_utf8(const std::u32string& str, const bool& exception = false);
|
||||
|
||||
} // namespace Private
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2023 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpp-terminal/version.hpp"
|
||||
|
||||
// clang-format off
|
||||
std::uint16_t Term::Version::major() noexcept
|
||||
{
|
||||
static std::uint16_t ret{@cpp-terminal_VERSION_MAJOR@};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::uint16_t Term::Version::minor() noexcept
|
||||
{
|
||||
static std::uint16_t ret{@cpp-terminal_VERSION_MINOR@};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::uint16_t Term::Version::patch() noexcept
|
||||
{
|
||||
static std::uint16_t ret{@cpp-terminal_VERSION_PATCH@};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Term::Version::string() noexcept
|
||||
{
|
||||
static const char* ret{"@cpp-terminal_VERSION_MAJOR@.@cpp-terminal_VERSION_MINOR@.@cpp-terminal_VERSION_PATCH@"};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Term::homepage() noexcept
|
||||
{
|
||||
static const char* ret{"@cpp-terminal_HOMEPAGE_URL@"};
|
||||
return ret;
|
||||
}
|
||||
// clang-format on
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/terminal.hpp"
|
||||
#include "cpp-terminal/window.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
// indicates the results of prompt_blocking() and prompt_non_blocking
|
||||
enum class Result
|
||||
{
|
||||
Yes, ///< Returned if the user chose \b yes.
|
||||
No, ///< Returned if the user chose \b no.
|
||||
Error, ///< Returned if no terminal is attached to the program.
|
||||
None, ///< Returned if the enter key was pressed without additional input.
|
||||
Abort, ///< Returned if CTRL+C was pressed.
|
||||
Invalid ///< Returned if the given input did not match the case \b yes of \b no
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A simple yes/no prompt, requires the user to press the ENTER key to continue. The arguments are used like this: 1 [2/3]4 <user Input>.
|
||||
* The immediate switch indicates toggles whether pressing enter for confirming the input is required or not.
|
||||
*
|
||||
* @param message
|
||||
* @param first_option
|
||||
* @param second_option
|
||||
* @param prompt_indicator
|
||||
* @return Result
|
||||
*/
|
||||
Result prompt(const std::string& message, const std::string& first_option, const std::string& second_option, const std::string& prompt_indicator, bool);
|
||||
|
||||
// indicates the results of prompt_simple()
|
||||
enum class Result_simple
|
||||
{
|
||||
|
||||
Yes, ///< Returned if the user chose \b yes.
|
||||
No, ///< Returned if the user chose no or invalid / no input or if no terminal is attached.
|
||||
Abort ///< Returned if CTRL+C was pressed.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The most simple prompt possible, requires the user to press enter to continue. The arguments are used like this: 1 [y/N]:
|
||||
* Invalid input, errors (like no attached terminal) all result in \b no as default.
|
||||
*
|
||||
* @param message
|
||||
* @return Result_simple
|
||||
*/
|
||||
Result_simple prompt_simple(const std::string& message);
|
||||
|
||||
/* Multiline prompt */
|
||||
|
||||
// This model contains all the information about the state of the prompt in an
|
||||
// abstract way, irrespective of where or how it is rendered.
|
||||
class Model
|
||||
{
|
||||
public:
|
||||
std::string prompt_string; // The string to show as the prompt
|
||||
std::vector<std::string> lines{""}; // The current input string in the prompt as a vector of lines, without '\n' at the end.
|
||||
// The current cursor position in the "input" string, starting from (1,1)
|
||||
std::size_t cursor_col{1};
|
||||
std::size_t cursor_row{1};
|
||||
};
|
||||
|
||||
std::string concat(const std::vector<std::string>&);
|
||||
|
||||
std::vector<std::string> split(const std::string&);
|
||||
|
||||
void print_left_curly_bracket(Term::Window&, const std::size_t&, const std::size_t&, const std::size_t&);
|
||||
|
||||
void render(Term::Window&, const Model&, const std::size_t&);
|
||||
|
||||
std::string prompt_multiline(const std::string&, std::vector<std::string>&, std::function<bool(std::string)>&);
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/size.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
Screen() = default;
|
||||
explicit Screen(const Size& size);
|
||||
const Rows& rows() const noexcept;
|
||||
const Columns& columns() const noexcept;
|
||||
bool empty() const;
|
||||
bool operator==(const Term::Screen& screen) const;
|
||||
bool operator!=(const Term::Screen& screen) const;
|
||||
|
||||
private:
|
||||
Size m_size;
|
||||
};
|
||||
|
||||
// clear the screen
|
||||
std::string clear_screen();
|
||||
// save the current terminal state
|
||||
std::string screen_save();
|
||||
// load a previously saved terminal state
|
||||
std::string screen_load();
|
||||
// get the terminal size
|
||||
Screen screen_size();
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Rows
|
||||
{
|
||||
public:
|
||||
Rows() = default;
|
||||
explicit Rows(const std::uint16_t& rows) : m_rows(rows) {}
|
||||
operator std::size_t() const noexcept { return m_rows; }
|
||||
|
||||
private:
|
||||
std::uint16_t m_rows{0};
|
||||
};
|
||||
|
||||
class Columns
|
||||
{
|
||||
public:
|
||||
Columns() = default;
|
||||
explicit Columns(const std::uint16_t& columns) : m_columns(columns) {}
|
||||
operator std::size_t() const noexcept { return m_columns; }
|
||||
|
||||
private:
|
||||
std::uint16_t m_columns{0};
|
||||
};
|
||||
|
||||
class Size
|
||||
{
|
||||
public:
|
||||
Size() = default;
|
||||
Size(const Rows& rows, const Columns& columns) : m_rows(rows), m_columns(columns) {};
|
||||
Size(const Columns& columns, const Rows& rows) : m_rows(rows), m_columns(columns) {};
|
||||
std::size_t area() const noexcept { return static_cast<std::size_t>(m_rows) * static_cast<std::size_t>(m_columns); }
|
||||
const Rows& rows() const noexcept { return m_rows; }
|
||||
const Columns& columns() const noexcept { return m_columns; }
|
||||
|
||||
private:
|
||||
Rows m_rows;
|
||||
Columns m_columns;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/buffer.hpp"
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class TIstream
|
||||
{
|
||||
public:
|
||||
explicit TIstream(const Term::Buffer::Type& type = Term::Buffer::Type::LineBuffered, const std::streamsize& size = BUFSIZ);
|
||||
TIstream(const TIstream&) = delete;
|
||||
TIstream(TIstream&& other) = delete;
|
||||
TIstream& operator=(TIstream&&) = delete;
|
||||
TIstream& operator=(const TIstream&) = delete;
|
||||
~TIstream();
|
||||
std::streambuf* rdbuf() const;
|
||||
template<typename T> TIstream& operator>>(T& t)
|
||||
{
|
||||
m_stream >> t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Term::Buffer m_buffer;
|
||||
std::istream m_stream;
|
||||
};
|
||||
|
||||
class TOstream
|
||||
{
|
||||
public:
|
||||
explicit TOstream(const Term::Buffer::Type& type = Term::Buffer::Type::LineBuffered, const std::streamsize& size = BUFSIZ);
|
||||
~TOstream();
|
||||
TOstream(const TOstream&) = delete;
|
||||
TOstream(TOstream&&) = delete;
|
||||
TOstream& operator=(TOstream&&) = delete;
|
||||
TOstream& operator=(const TOstream&) = delete;
|
||||
template<typename T> TOstream& operator<<(const T& t)
|
||||
{
|
||||
m_stream << t;
|
||||
return *this;
|
||||
}
|
||||
TOstream& operator<<(std::ostream& (*t)(std::ostream&))
|
||||
{
|
||||
m_stream << t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Term::Buffer m_buffer;
|
||||
std::ostream m_stream;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/iostream.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
/*
|
||||
* Styles for text in the terminal
|
||||
*/
|
||||
enum class Style : std::uint8_t
|
||||
{
|
||||
|
||||
Reset = 0, ///< resets all attributes (styles and colors)
|
||||
Bold = 1, ///< Thick text font
|
||||
Dim = 2, ///< lighter, slimmer text font
|
||||
Italic = 3, ///< slightly bend text font
|
||||
Underline = 4, ///< draws a line below the text
|
||||
Blink = 5, ///< Sets blinking to less than 150 times per minute
|
||||
BlinkRapid = 6, ///< MS-DOS ANSI.SYS, 150+ per minute; not widely supported
|
||||
Reversed = 7, ///< swap foreground and background colors
|
||||
Conceal = 8, ///< Note: not widely supported
|
||||
Crossed = 9, ///< strikes through the text, mostly supported
|
||||
|
||||
// different fonts
|
||||
Font0 = 10, ///< Primary or default font
|
||||
ResetFont = 10,
|
||||
Font1 = 11,
|
||||
Font2 = 12,
|
||||
Font3 = 13,
|
||||
Font4 = 14,
|
||||
Font5 = 15,
|
||||
Font6 = 16,
|
||||
Font7 = 17,
|
||||
Font8 = 18,
|
||||
Font9 = 19,
|
||||
Font10 = 20, ///< Fraktur / Gothic font
|
||||
|
||||
// Double-underline per ECMA-48,[5] 8.3.117 but instead disables bold intensity on several terminals,
|
||||
// including in the Linux kernel's console before version 4.17
|
||||
DoublyUnderlinedOrNotBold = 21,
|
||||
|
||||
// resets corresponding styles
|
||||
ResetBold = 22,
|
||||
ResetDim = 22,
|
||||
ResetItalic = 23,
|
||||
ResetUnderline = 24,
|
||||
ResetBlink = 25,
|
||||
ResetBlinkRapid = 25,
|
||||
ResetReversed = 27,
|
||||
ResetConceal = 28,
|
||||
ResetCrossed = 29,
|
||||
|
||||
// sets the foreground and background color to the implementation defined colors
|
||||
DefaultForegroundColor = 39,
|
||||
DefaultBackgroundColor = 49,
|
||||
|
||||
Frame = 51,
|
||||
Encircle = 52,
|
||||
Overline = 53, // draw a line over the text, barely supported
|
||||
ResetFrame = 54,
|
||||
ResetEncircle = 54,
|
||||
ResetOverline = 55,
|
||||
|
||||
// sets the underline color to the implementation defined colors
|
||||
DefaultUnderlineColor = 59, ///< non standard, implemented in Kitty, VTE, mintty, and iTerm2
|
||||
|
||||
BarRight = 60, ///< draw a vertical bar on the right side of the character
|
||||
DoubleBarRight = 61, ///< draw a double vertical bar to the right
|
||||
BarLeft = 62, ///< draw a vertical bar on the left side of the character
|
||||
DoubleBarLeft = 63, ///< draw a double vertical bar to the left
|
||||
StressMarking = 64, ///< reset all bars left and right double and simple
|
||||
|
||||
ResetBar = 65, // resets 60 - 64 inclusive
|
||||
|
||||
Superscript = 73, // only implemented in mintty
|
||||
Subscript = 74, // only implemented in mintty
|
||||
ResetSuperscript = 75, // only implemented in mintty
|
||||
ResetSubscript = 75
|
||||
};
|
||||
|
||||
std::string style(const Term::Style& style);
|
||||
|
||||
template<class Stream> Stream& operator<<(Stream& stream, const Term::Style& style_type) { return stream << style(style_type); }
|
||||
// unabigify operator overload
|
||||
inline Term::TOstream& operator<<(Term::TOstream& term, const Term::Style& style_type) { return term << style(style_type); }
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/terminal_impl.hpp"
|
||||
#include "cpp-terminal/terminal_initializer.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
static const TerminalInitializer terminal_initializer; //NOLINT(cert-err58-cpp,fuchsia-statically-constructed-objects)
|
||||
extern Term::Terminal& terminal;
|
||||
|
||||
// change the title of the terminal, only supported by a few terminals
|
||||
std::string terminal_title(const std::string& title);
|
||||
// clear the screen and the scroll-back buffer
|
||||
std::string clear_buffer();
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/options.hpp"
|
||||
#include "cpp-terminal/private/signals.hpp"
|
||||
#include "cpp-terminal/terminal_initializer.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Terminal
|
||||
{
|
||||
public:
|
||||
friend class Private::Signals;
|
||||
~Terminal() noexcept;
|
||||
Terminal() noexcept;
|
||||
Terminal(const Terminal&) = delete;
|
||||
Terminal(Terminal&&) = delete;
|
||||
Terminal& operator=(Terminal&&) = delete;
|
||||
Terminal& operator=(const Terminal&) = delete;
|
||||
template<typename... Args> void setOptions(const Args&&... args)
|
||||
{
|
||||
m_options = {args...};
|
||||
applyOptions();
|
||||
}
|
||||
Term::Options getOptions() const noexcept;
|
||||
|
||||
private:
|
||||
///
|
||||
///@brief Store and restore the default state of the terminal. Configure the default mode for cpp-terminal.
|
||||
///
|
||||
static void store_and_restore() noexcept;
|
||||
|
||||
///
|
||||
///@brief Set mode raw/cooked.
|
||||
///First call is to save the good state set-up by cpp-terminal.
|
||||
///
|
||||
void setMode() const;
|
||||
|
||||
void setOptions();
|
||||
void applyOptions() const;
|
||||
|
||||
static std::size_t setMouseEvents();
|
||||
static std::size_t unsetMouseEvents();
|
||||
static std::size_t setFocusEvents();
|
||||
static std::size_t unsetFocusEvents();
|
||||
|
||||
static void set_unset_utf8();
|
||||
Term::Options m_options;
|
||||
void clean();
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class TerminalInitializer
|
||||
{
|
||||
public:
|
||||
~TerminalInitializer() noexcept;
|
||||
TerminalInitializer() noexcept;
|
||||
TerminalInitializer(const TerminalInitializer&) = delete;
|
||||
TerminalInitializer(TerminalInitializer&&) = delete;
|
||||
TerminalInitializer& operator=(TerminalInitializer&&) = delete;
|
||||
TerminalInitializer& operator=(const TerminalInitializer&) = delete;
|
||||
|
||||
private:
|
||||
static std::size_t m_counter;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Terminfo
|
||||
{
|
||||
public:
|
||||
// indicates the color mode (basically the original color resolution)
|
||||
// also used to manually override the original color resolution
|
||||
enum class ColorMode : std::uint8_t
|
||||
{
|
||||
Unset,
|
||||
// no color was used
|
||||
NoColor,
|
||||
// a 3bit color was used
|
||||
Bit3,
|
||||
// a 4bit color was used
|
||||
Bit4,
|
||||
// a 8bit color was used
|
||||
Bit8,
|
||||
// a 24bit (RGB) color was used
|
||||
Bit24
|
||||
};
|
||||
enum class Bool : std::uint8_t
|
||||
{
|
||||
UTF8 = 0, ///< terminal has UTF-8 activated.
|
||||
Legacy, ///< Terminal is in legacy mode (Windows only).
|
||||
ControlSequences, ///< Terminal support control sequences.
|
||||
};
|
||||
enum class String : std::uint8_t
|
||||
{
|
||||
TermEnv, ///< TERM environment variable value.
|
||||
TermName, ///< Name of the terminal program if available.
|
||||
TermVersion, ///< Terminal version.
|
||||
};
|
||||
enum class Integer : std::uint8_t
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
static bool get(const Term::Terminfo::Bool& key);
|
||||
static std::uint32_t get(const Term::Terminfo::Integer& key);
|
||||
static std::string get(const Term::Terminfo::String& key);
|
||||
|
||||
private:
|
||||
static const constexpr std::size_t BoolNumber{3};
|
||||
static const constexpr std::size_t StringNumber{3};
|
||||
static const constexpr std::size_t IntegerNumber{0};
|
||||
|
||||
public:
|
||||
using Booleans = std::array<bool, BoolNumber>;
|
||||
using Strings = std::array<std::string, StringNumber>;
|
||||
using Integers = std::array<std::uint32_t, IntegerNumber>;
|
||||
|
||||
Terminfo();
|
||||
|
||||
static ColorMode getColorMode();
|
||||
|
||||
private:
|
||||
static void check();
|
||||
static void checkTermEnv();
|
||||
static void checkTerminalName();
|
||||
static void checkTerminalVersion();
|
||||
static void checkColorMode();
|
||||
static void checkUTF8();
|
||||
static void checkLegacy();
|
||||
static void checkControlSequences();
|
||||
|
||||
static void set(const Term::Terminfo::Bool& key, const bool& value);
|
||||
static void set(const Term::Terminfo::Integer& key, const std::uint32_t& value);
|
||||
static void set(const Term::Terminfo::String& key, const std::string& value);
|
||||
|
||||
static ColorMode m_colorMode;
|
||||
static Booleans m_booleans;
|
||||
static Integers m_integers;
|
||||
static Strings m_strings;
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
///
|
||||
/// @brief Check if \b stdin is a \b tty.
|
||||
///
|
||||
/// @return true : \b stdin is a \b tty.
|
||||
/// @return false : \b stdin is not a \b tty.
|
||||
///
|
||||
bool is_stdin_a_tty();
|
||||
|
||||
///
|
||||
/// @brief Check if \b stdout is a \b tty.
|
||||
///
|
||||
/// @return true : \b stdout is a \b tty.
|
||||
/// @return false : \b stdout is not a \b tty.
|
||||
///
|
||||
bool is_stdout_a_tty();
|
||||
|
||||
///
|
||||
/// @brief Check if \b stderr is a \b tty.
|
||||
///
|
||||
/// @return true : \b stderr is a \b tty.
|
||||
/// @return false : \b stderr is not a \b tty.
|
||||
///
|
||||
bool is_stderr_a_tty();
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
namespace Version
|
||||
{
|
||||
|
||||
///
|
||||
///@brief Major version of cpp-terminal.
|
||||
///
|
||||
///@return std::uint16_t major version.
|
||||
///
|
||||
std::uint16_t major() noexcept;
|
||||
|
||||
///
|
||||
///@brief Minor version of cpp-terminal.
|
||||
///
|
||||
///@return std::uint16_t minor version.
|
||||
///
|
||||
std::uint16_t minor() noexcept;
|
||||
|
||||
///
|
||||
///@brief Patch version of cpp-terminal.
|
||||
///
|
||||
///@return std::uint16_t patch version.
|
||||
///
|
||||
std::uint16_t patch() noexcept;
|
||||
|
||||
///
|
||||
///@brief String version of cpp-terminal.
|
||||
///
|
||||
///@return std::string version in the format "Major.Minor.Patch"
|
||||
///
|
||||
std::string string() noexcept;
|
||||
|
||||
} // namespace Version
|
||||
|
||||
///
|
||||
///@brief Homepage of cpp-terminal.
|
||||
///
|
||||
///@return std::string return the URL of the cpp-terminal project.
|
||||
///
|
||||
std::string homepage() noexcept;
|
||||
|
||||
} // namespace Term
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* cpp-terminal
|
||||
* C++ library for writing multi-platform terminal applications.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp-terminal/color.hpp"
|
||||
#include "cpp-terminal/cursor.hpp"
|
||||
#include "cpp-terminal/size.hpp"
|
||||
#include "cpp-terminal/style.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Term
|
||||
{
|
||||
|
||||
class Screen;
|
||||
///
|
||||
/// @brief Represents a rectangular window, as a 2D array of characters and their attributes.
|
||||
///
|
||||
/// Represents a rectangular window, as a 2D array of characters and their attributes.
|
||||
/// The render method can convert this internal representation to a string that when printed will show the Window on the screen.
|
||||
//
|
||||
// @note the characters are represented by char32_t, representing their UTF-32 code point.
|
||||
/// The natural way to represent a character in a terminal would be a "unicode grapheme cluster", but due to a lack of a good library for C++ that could handle those, we simply use a Unicode code point as a character.
|
||||
///
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
explicit Window(const Size& size);
|
||||
explicit Window(const Screen& screen);
|
||||
const Columns& columns() const noexcept;
|
||||
const Rows& rows() const noexcept;
|
||||
|
||||
void set_char(const std::size_t& column, const std::size_t& row, const char32_t& character);
|
||||
|
||||
void set_fg_reset(const std::size_t& column, const std::size_t& row);
|
||||
|
||||
void set_bg_reset(const std::size_t& column, const std::size_t& row);
|
||||
|
||||
void set_fg(const std::size_t& column, const std::size_t& row, const Color& color);
|
||||
|
||||
void set_bg(const std::size_t& column, const std::size_t& row, const Color& color);
|
||||
|
||||
void set_style(const std::size_t& column, const std::size_t& row, const Style& style);
|
||||
|
||||
void set_cursor_pos(const std::size_t& column, const std::size_t& row);
|
||||
|
||||
void set_h(const std::size_t&);
|
||||
|
||||
void print_str(const std::size_t& column, const std::size_t&, const std::string&, const std::size_t& = 0, bool = false);
|
||||
|
||||
void fill_fg(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&, const Color&);
|
||||
|
||||
void fill_bg(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&, const Color&);
|
||||
|
||||
void fill_style(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&, const Style&);
|
||||
|
||||
void print_border();
|
||||
|
||||
void print_rect(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&);
|
||||
|
||||
void clear();
|
||||
|
||||
bool insideWindow(const std::size_t& column, const std::size_t& row) const;
|
||||
|
||||
// TODO: add Window/Screen parameter here, to be used like this:
|
||||
// old_scr = scr;
|
||||
// scr.print_str(...)
|
||||
// scr.render(1, 1, old_scr)
|
||||
std::string render(const std::size_t&, const std::size_t&, bool);
|
||||
|
||||
private:
|
||||
std::size_t index(const std::size_t& column, const std::size_t& row) const;
|
||||
Term::Size m_size;
|
||||
Term::Cursor m_cursor;
|
||||
std::vector<char32_t> m_chars; // the characters in row first order
|
||||
std::vector<Term::Color> m_fg;
|
||||
std::vector<Term::Color> m_bg;
|
||||
std::vector<bool> m_fg_reset;
|
||||
std::vector<bool> m_bg_reset;
|
||||
std::vector<Style> m_style;
|
||||
|
||||
char32_t get_char(const std::size_t& column, const std::size_t& row);
|
||||
|
||||
bool get_fg_reset(const std::size_t& column, const std::size_t& row);
|
||||
bool get_bg_reset(const std::size_t& column, const std::size_t& row);
|
||||
Term::Color get_fg(const std::size_t& column, const std::size_t& row);
|
||||
Term::Color get_bg(const std::size_t& column, const std::size_t& row);
|
||||
Term::Style get_style(const std::size_t& column, const std::size_t& row);
|
||||
};
|
||||
|
||||
} // namespace Term
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,169 @@
|
||||
#ifndef DOCTEST_MPI_H
|
||||
#define DOCTEST_MPI_H
|
||||
|
||||
#ifdef DOCTEST_CONFIG_IMPLEMENT
|
||||
|
||||
#include "doctest/extensions/mpi_sub_comm.h"
|
||||
#include "mpi_reporter.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
// Each time a MPI_TEST_CASE is executed on N procs,
|
||||
// we need a sub-communicator of N procs to execute it.
|
||||
// It is then registered here and can be re-used
|
||||
// by other tests that requires a sub-comm of the same size
|
||||
std::unordered_map<int,mpi_sub_comm> sub_comms_by_size;
|
||||
|
||||
// Record if at least one MPI_TEST_CASE was registered "skipped"
|
||||
// because there is not enought procs to execute it
|
||||
int nb_test_cases_skipped_insufficient_procs = 0;
|
||||
|
||||
|
||||
std::string thread_level_to_string(int thread_lvl);
|
||||
int mpi_init_thread(int argc, char *argv[], int required_thread_support);
|
||||
void mpi_finalize();
|
||||
|
||||
|
||||
// Can be safely called before MPI_Init()
|
||||
// This is needed for MPI_TEST_CASE because we use doctest::skip()
|
||||
// to prevent execution of tests where there is not enough procs,
|
||||
// but doctest::skip() is called during test registration, that is, before main(), and hence before MPI_Init()
|
||||
int mpi_comm_world_size() {
|
||||
#if defined(OPEN_MPI)
|
||||
const char* size_str = std::getenv("OMPI_COMM_WORLD_SIZE");
|
||||
#elif defined(I_MPI_VERSION) || defined(MPI_VERSION) // Intel MPI + MPICH (at least)
|
||||
const char* size_str = std::getenv("PMI_SIZE"); // see https://community.intel.com/t5/Intel-oneAPI-HPC-Toolkit/Environment-variables-defined-by-intel-mpirun/td-p/1096703
|
||||
#else
|
||||
#error "Unknown MPI implementation: please submit an issue or a PR to doctest. Meanwhile, you can look at the output of e.g. `mpirun -np 3 env` to search for an environnement variable that contains the size of MPI_COMM_WORLD and extend this code accordingly"
|
||||
#endif
|
||||
if (size_str==nullptr) return 1; // not launched with mpirun/mpiexec, so assume only one process
|
||||
return std::stoi(size_str);
|
||||
}
|
||||
|
||||
// Record size of MPI_COMM_WORLD with mpi_comm_world_size()
|
||||
int world_size_before_init = mpi_comm_world_size();
|
||||
|
||||
|
||||
std::string thread_level_to_string(int thread_lvl) {
|
||||
switch (thread_lvl) {
|
||||
case MPI_THREAD_SINGLE: return "MPI_THREAD_SINGLE";
|
||||
case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED";
|
||||
case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED";
|
||||
case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE";
|
||||
default: return "Invalid MPI thread level";
|
||||
}
|
||||
}
|
||||
int mpi_init_thread(int argc, char *argv[], int required_thread_support) {
|
||||
int provided_thread_support;
|
||||
MPI_Init_thread(&argc, &argv, required_thread_support, &provided_thread_support);
|
||||
|
||||
int world_size;
|
||||
MPI_Comm_size(MPI_COMM_WORLD,&world_size);
|
||||
if (world_size_before_init != world_size) {
|
||||
DOCTEST_INTERNAL_ERROR(
|
||||
"doctest found "+std::to_string(world_size_before_init)+" MPI processes before `MPI_Init_thread`,"
|
||||
" but MPI_COMM_WORLD is actually of size "+std::to_string(world_size)+".\n"
|
||||
"This is most likely due to your MPI implementation not being well supported by doctest. Please report this issue on GitHub"
|
||||
);
|
||||
}
|
||||
|
||||
if (provided_thread_support!=required_thread_support) {
|
||||
std::cout <<
|
||||
"WARNING: " + thread_level_to_string(required_thread_support) + " was asked, "
|
||||
+ "but only " + thread_level_to_string(provided_thread_support) + " is provided by the MPI library\n";
|
||||
}
|
||||
return provided_thread_support;
|
||||
}
|
||||
void mpi_finalize() {
|
||||
// We need to destroy all created sub-communicators before calling MPI_Finalize()
|
||||
doctest::sub_comms_by_size.clear();
|
||||
MPI_Finalize();
|
||||
}
|
||||
|
||||
} // doctest
|
||||
|
||||
#else // DOCTEST_CONFIG_IMPLEMENT
|
||||
|
||||
#include "doctest/extensions/mpi_sub_comm.h"
|
||||
#include <unordered_map>
|
||||
#include <exception>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
extern std::unordered_map<int,mpi_sub_comm> sub_comms_by_size;
|
||||
extern int nb_test_cases_skipped_insufficient_procs;
|
||||
extern int world_size_before_init;
|
||||
int mpi_comm_world_size();
|
||||
|
||||
int mpi_init_thread(int argc, char *argv[], int required_thread_support);
|
||||
void mpi_finalize();
|
||||
|
||||
template<int nb_procs, class F>
|
||||
void execute_mpi_test_case(F func) {
|
||||
auto it = sub_comms_by_size.find(nb_procs);
|
||||
if (it==end(sub_comms_by_size)) {
|
||||
bool was_emplaced = false;
|
||||
std::tie(it,was_emplaced) = sub_comms_by_size.emplace(std::make_pair(nb_procs,mpi_sub_comm(nb_procs)));
|
||||
assert(was_emplaced);
|
||||
}
|
||||
const mpi_sub_comm& sub = it->second;
|
||||
if (sub.comm != MPI_COMM_NULL) {
|
||||
func(sub.rank,nb_procs,sub.comm,std::integral_constant<int,nb_procs>{});
|
||||
};
|
||||
}
|
||||
|
||||
inline bool
|
||||
insufficient_procs(int test_nb_procs) {
|
||||
static const int world_size = mpi_comm_world_size();
|
||||
bool insufficient = test_nb_procs>world_size;
|
||||
if (insufficient) {
|
||||
++nb_test_cases_skipped_insufficient_procs;
|
||||
}
|
||||
return insufficient;
|
||||
}
|
||||
|
||||
} // doctest
|
||||
|
||||
|
||||
#define DOCTEST_MPI_GEN_ASSERTION(rank_to_test, assertion, ...) \
|
||||
static_assert(rank_to_test<test_nb_procs_as_int_constant.value,"Trying to assert on a rank greater than the number of procs of the test!"); \
|
||||
if(rank_to_test == test_rank) assertion(__VA_ARGS__)
|
||||
|
||||
#define DOCTEST_MPI_WARN(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_WARN,__VA_ARGS__)
|
||||
#define DOCTEST_MPI_CHECK(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_CHECK,__VA_ARGS__)
|
||||
#define DOCTEST_MPI_REQUIRE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_REQUIRE,__VA_ARGS__)
|
||||
#define DOCTEST_MPI_WARN_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_WARN_FALSE,__VA_ARGS__)
|
||||
#define DOCTEST_MPI_CHECK_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_CHECK_FALSE,__VA_ARGS__)
|
||||
#define DOCTEST_MPI_REQUIRE_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_REQUIRE_FALSE,__VA_ARGS__)
|
||||
|
||||
#define DOCTEST_CREATE_MPI_TEST_CASE(name,nb_procs,func) \
|
||||
static void func(DOCTEST_UNUSED int test_rank, DOCTEST_UNUSED int test_nb_procs, DOCTEST_UNUSED MPI_Comm test_comm, DOCTEST_UNUSED std::integral_constant<int,nb_procs>); \
|
||||
TEST_CASE(name * doctest::description("MPI_TEST_CASE") * doctest::skip(doctest::insufficient_procs(nb_procs))) { \
|
||||
doctest::execute_mpi_test_case<nb_procs>(func); \
|
||||
} \
|
||||
static void func(DOCTEST_UNUSED int test_rank, DOCTEST_UNUSED int test_nb_procs, DOCTEST_UNUSED MPI_Comm test_comm, DOCTEST_UNUSED std::integral_constant<int,nb_procs> test_nb_procs_as_int_constant)
|
||||
// DOC: test_rank, test_nb_procs, and test_comm are available UNDER THESE SPECIFIC NAMES in the body of the unit test
|
||||
// DOC: test_nb_procs_as_int_constant is equal to test_nb_procs, but as a compile time value
|
||||
// (used in CHECK-like macros to assert the checked rank exists)
|
||||
|
||||
#define DOCTEST_MPI_TEST_CASE(name,nb_procs) \
|
||||
DOCTEST_CREATE_MPI_TEST_CASE(name,nb_procs,DOCTEST_ANONYMOUS(DOCTEST_MPI_FUNC))
|
||||
|
||||
|
||||
// == SHORT VERSIONS OF THE MACROS
|
||||
#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
|
||||
#define MPI_WARN DOCTEST_MPI_WARN
|
||||
#define MPI_CHECK DOCTEST_MPI_CHECK
|
||||
#define MPI_REQUIRE DOCTEST_MPI_REQUIRE
|
||||
#define MPI_WARN_FALSE DOCTEST_MPI_WARN_FALSE
|
||||
#define MPI_CHECK_FALSE DOCTEST_MPI_CHECK_FALSE
|
||||
#define MPI_REQUIRE_FALSE DOCTEST_MPI_REQUIRE_FALSE
|
||||
|
||||
#define MPI_TEST_CASE DOCTEST_MPI_TEST_CASE
|
||||
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
|
||||
|
||||
|
||||
#endif // DOCTEST_CONFIG_IMPLEMENT
|
||||
|
||||
#endif // DOCTEST_MPI_H
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// doctest_util.h - an accompanying extensions header to the main doctest.h header
|
||||
//
|
||||
// Copyright (c) 2016-2023 Viktor Kirilov
|
||||
//
|
||||
// Distributed under the MIT Software License
|
||||
// See accompanying file LICENSE.txt or copy at
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// The documentation can be found at the library's page:
|
||||
// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md
|
||||
//
|
||||
|
||||
#ifndef DOCTEST_UTIL_H
|
||||
#define DOCTEST_UTIL_H
|
||||
|
||||
#ifndef DOCTEST_LIBRARY_INCLUDED
|
||||
#include "../doctest.h"
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
inline void applyCommandLine(doctest::Context& ctx, const std::vector<std::string>& args) {
|
||||
auto doctest_args = std::make_unique<const char*[]>(args.size());
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
doctest_args[i] = args[i].c_str();
|
||||
}
|
||||
ctx.applyCommandLine(args.size(), doctest_args.get());
|
||||
}
|
||||
|
||||
} // namespace doctest
|
||||
|
||||
#endif // DOCTEST_UTIL_H
|
||||
@@ -0,0 +1,271 @@
|
||||
#ifndef DOCTEST_MPI_REPORTER_H
|
||||
#define DOCTEST_MPI_REPORTER_H
|
||||
|
||||
// #include <doctest/doctest.h>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include "mpi.h"
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
extern int nb_test_cases_skipped_insufficient_procs;
|
||||
int mpi_comm_world_size();
|
||||
|
||||
namespace {
|
||||
|
||||
// https://stackoverflow.com/a/11826666/1583122
|
||||
struct NullBuffer : std::streambuf {
|
||||
int overflow(int c) { return c; }
|
||||
};
|
||||
class NullStream : public std::ostream {
|
||||
public:
|
||||
NullStream()
|
||||
: std::ostream(&nullBuff)
|
||||
{}
|
||||
private:
|
||||
NullBuffer nullBuff = {};
|
||||
};
|
||||
static NullStream nullStream;
|
||||
|
||||
|
||||
/* \brief Extends the ConsoleReporter of doctest
|
||||
* Each process writes its results to its own file
|
||||
* Intended to be used when a test assertion fails and the user wants to know exactly what happens on which process
|
||||
*/
|
||||
struct MpiFileReporter : public ConsoleReporter {
|
||||
std::ofstream logfile_stream = {};
|
||||
|
||||
MpiFileReporter(const ContextOptions& co)
|
||||
: ConsoleReporter(co,logfile_stream)
|
||||
{
|
||||
int rank = 0;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
|
||||
std::string logfile_name = "doctest_" + std::to_string(rank) + ".log";
|
||||
|
||||
logfile_stream = std::ofstream(logfile_name.c_str(), std::fstream::out);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* \brief Extends the ConsoleReporter of doctest
|
||||
* Allows to manage the execution of tests in a parallel framework
|
||||
* All results are collected on rank 0
|
||||
*/
|
||||
struct MpiConsoleReporter : public ConsoleReporter {
|
||||
private:
|
||||
static std::ostream& replace_by_null_if_not_rank_0(std::ostream* os) {
|
||||
int rank = 0;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
if (rank==0) {
|
||||
return *os;
|
||||
} else {
|
||||
return nullStream;
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<std::string, int>> m_failure_str_queue = {};
|
||||
public:
|
||||
MpiConsoleReporter(const ContextOptions& co)
|
||||
: ConsoleReporter(co,replace_by_null_if_not_rank_0(co.cout))
|
||||
{}
|
||||
|
||||
std::string file_line_to_string(const char* file, int line,
|
||||
const char* tail = ""){
|
||||
std::stringstream ss;
|
||||
ss << skipPathFromFilename(file)
|
||||
<< (opt.gnu_file_line ? ":" : "(")
|
||||
<< (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
|
||||
<< (opt.gnu_file_line ? ":" : "):") << tail;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void test_run_end(const TestRunStats& p) override {
|
||||
ConsoleReporter::test_run_end(p);
|
||||
|
||||
const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
|
||||
|
||||
// -----------------------------------------------------
|
||||
// > Gather information in rank 0
|
||||
int n_rank, rank;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &n_rank);
|
||||
|
||||
int g_numAsserts = 0;
|
||||
int g_numAssertsFailed = 0;
|
||||
int g_numTestCasesFailed = 0;
|
||||
|
||||
MPI_Reduce(&p.numAsserts , &g_numAsserts , 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||
MPI_Reduce(&p.numAssertsFailed , &g_numAssertsFailed , 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||
MPI_Reduce(&p.numTestCasesFailed, &g_numTestCasesFailed, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||
|
||||
std::vector<int> numAssertsFailedByRank;
|
||||
if(rank == 0){
|
||||
numAssertsFailedByRank.resize(static_cast<std::size_t>(n_rank));
|
||||
}
|
||||
|
||||
MPI_Gather(&p.numAssertsFailed, 1, MPI_INT, numAssertsFailedByRank.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
|
||||
|
||||
if(rank == 0) {
|
||||
separator_to_stream();
|
||||
s << Color::Cyan << "[doctest] " << Color::None << "assertions on all processes: " << std::setw(6)
|
||||
<< g_numAsserts << " | "
|
||||
<< ((g_numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
|
||||
<< std::setw(6) << (g_numAsserts - g_numAssertsFailed) << " passed" << Color::None
|
||||
<< " | " << (g_numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
|
||||
<< g_numAssertsFailed << " failed" << Color::None << " |\n";
|
||||
if (nb_test_cases_skipped_insufficient_procs>0) {
|
||||
s << Color::Cyan << "[doctest] " << Color::Yellow << "WARNING: Skipped ";
|
||||
if (nb_test_cases_skipped_insufficient_procs>1) {
|
||||
s << nb_test_cases_skipped_insufficient_procs << " tests requiring more than ";
|
||||
} else {
|
||||
s << nb_test_cases_skipped_insufficient_procs << " test requiring more than ";
|
||||
}
|
||||
if (mpi_comm_world_size()>1) {
|
||||
s << mpi_comm_world_size() << " MPI processes to run\n";
|
||||
} else {
|
||||
s << mpi_comm_world_size() << " MPI process to run\n";
|
||||
}
|
||||
}
|
||||
|
||||
separator_to_stream();
|
||||
if(g_numAssertsFailed > 0){
|
||||
|
||||
s << Color::Cyan << "[doctest] " << Color::None << "fail on rank:" << std::setw(6) << "\n";
|
||||
for(std::size_t i = 0; i < numAssertsFailedByRank.size(); ++i){
|
||||
if( numAssertsFailedByRank[i] > 0 ){
|
||||
s << std::setw(16) << " -> On rank [" << i << "] with " << numAssertsFailedByRank[i] << " test failed" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
s << Color::Cyan << "[doctest] " << Color::None
|
||||
<< "Status: " << (g_numTestCasesFailed > 0 ? Color::Red : Color::Green)
|
||||
<< ((g_numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void test_case_end(const CurrentTestCaseStats& st) override {
|
||||
if (is_mpi_test_case()) {
|
||||
// function called by every rank at the end of a test
|
||||
// if failed assertions happened, they have been sent to rank 0
|
||||
// here rank zero gathers them and prints them all
|
||||
|
||||
int rank;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
|
||||
std::vector<MPI_Request> requests;
|
||||
requests.reserve(m_failure_str_queue.size()); // avoid realloc & copy of MPI_Request
|
||||
for (const std::pair<std::string, int> &failure : m_failure_str_queue)
|
||||
{
|
||||
const std::string & failure_str = failure.first;
|
||||
const int failure_line = failure.second;
|
||||
|
||||
int failure_msg_size = static_cast<int>(failure_str.size());
|
||||
|
||||
requests.push_back(MPI_REQUEST_NULL);
|
||||
MPI_Isend(failure_str.c_str(), failure_msg_size, MPI_BYTE,
|
||||
0, failure_line, MPI_COMM_WORLD, &requests.back()); // Tag = file line
|
||||
}
|
||||
|
||||
|
||||
// Compute the number of assert with fail among all procs
|
||||
const int nb_fail_asserts = static_cast<int>(m_failure_str_queue.size());
|
||||
int nb_fail_asserts_glob = 0;
|
||||
MPI_Reduce(&nb_fail_asserts, &nb_fail_asserts_glob, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||
|
||||
if(rank == 0) {
|
||||
MPI_Status status;
|
||||
MPI_Status status_recv;
|
||||
|
||||
using id_string = std::pair<int,std::string>;
|
||||
std::vector<id_string> msgs(static_cast<std::size_t>(nb_fail_asserts_glob));
|
||||
|
||||
for (std::size_t i=0; i<static_cast<std::size_t>(nb_fail_asserts_glob); ++i) {
|
||||
MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
|
||||
|
||||
int count;
|
||||
MPI_Get_count(&status, MPI_BYTE, &count);
|
||||
|
||||
std::string recv_msg(static_cast<std::size_t>(count),'\0');
|
||||
void* recv_msg_data = const_cast<char*>(recv_msg.data()); // const_cast needed. Non-const .data() exists in C++11 though...
|
||||
MPI_Recv(recv_msg_data, count, MPI_BYTE, status.MPI_SOURCE,
|
||||
status.MPI_TAG, MPI_COMM_WORLD, &status_recv);
|
||||
|
||||
msgs[i] = {status.MPI_SOURCE,recv_msg};
|
||||
}
|
||||
|
||||
std::sort(begin(msgs),end(msgs),[](const id_string& x, const id_string& y){ return x.first < y.first; });
|
||||
|
||||
// print
|
||||
if (nb_fail_asserts_glob>0) {
|
||||
separator_to_stream();
|
||||
file_line_to_stream(tc->m_file.c_str(), static_cast<int>(tc->m_line), "\n");
|
||||
if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
|
||||
s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
|
||||
if(strncmp(tc->m_name, " Scenario:", 11) != 0)
|
||||
s << Color::Yellow << "TEST CASE: ";
|
||||
s << Color::None << tc->m_name << "\n\n";
|
||||
for(const auto& msg : msgs) {
|
||||
s << msg.second;
|
||||
}
|
||||
s << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
MPI_Waitall(static_cast<int>(requests.size()), requests.data(), MPI_STATUSES_IGNORE);
|
||||
m_failure_str_queue.clear();
|
||||
}
|
||||
|
||||
ConsoleReporter::test_case_end(st);
|
||||
}
|
||||
|
||||
bool is_mpi_test_case() const {
|
||||
return tc->m_description != nullptr
|
||||
&& std::string(tc->m_description) == std::string("MPI_TEST_CASE");
|
||||
}
|
||||
|
||||
void log_assert(const AssertData& rb) override {
|
||||
if (!is_mpi_test_case()) {
|
||||
ConsoleReporter::log_assert(rb);
|
||||
} else {
|
||||
int rank;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
|
||||
|
||||
if(!rb.m_failed && !opt.success)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
std::stringstream failure_msg;
|
||||
failure_msg << Color::Red << "On rank [" << rank << "] : " << Color::None;
|
||||
failure_msg << file_line_to_string(rb.m_file, rb.m_line, " ");
|
||||
|
||||
if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==0){
|
||||
failure_msg << Color::Cyan
|
||||
<< assertString(rb.m_at)
|
||||
<< "( " << rb.m_expr << " ) "
|
||||
<< Color::None
|
||||
|
||||
<< (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")
|
||||
<< " values: "
|
||||
<< assertString(rb.m_at)
|
||||
<< "( " << rb.m_decomp.c_str() << " )\n";
|
||||
}
|
||||
|
||||
m_failure_str_queue.push_back({failure_msg.str(), rb.m_line});
|
||||
}
|
||||
}
|
||||
}; // MpiConsoleReporter
|
||||
|
||||
// "1" is the priority - used for ordering when multiple reporters/listeners are used
|
||||
REGISTER_REPORTER("MpiConsoleReporter", 1, MpiConsoleReporter);
|
||||
REGISTER_REPORTER("MpiFileReporter", 1, MpiFileReporter);
|
||||
|
||||
} // anonymous
|
||||
} // doctest
|
||||
|
||||
#endif // DOCTEST_REPORTER_H
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifndef DOCTEST_MPI_SUB_COMM_H
|
||||
#define DOCTEST_MPI_SUB_COMM_H
|
||||
|
||||
#include "mpi.h"
|
||||
#include "doctest/doctest.h"
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
inline
|
||||
int mpi_world_nb_procs() {
|
||||
int n;
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &n);
|
||||
return n;
|
||||
}
|
||||
|
||||
struct mpi_sub_comm {
|
||||
int nb_procs;
|
||||
int rank;
|
||||
MPI_Comm comm;
|
||||
|
||||
mpi_sub_comm( mpi_sub_comm const& ) = delete;
|
||||
mpi_sub_comm& operator=( mpi_sub_comm const& ) = delete;
|
||||
|
||||
mpi_sub_comm(int nb_prcs) noexcept
|
||||
: nb_procs(nb_prcs)
|
||||
, rank(-1)
|
||||
, comm(MPI_COMM_NULL)
|
||||
{
|
||||
int comm_world_rank;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &comm_world_rank);
|
||||
if (nb_procs>mpi_world_nb_procs()) {
|
||||
if (comm_world_rank==0) {
|
||||
MESSAGE(
|
||||
"Unable to run test: need ", std::to_string(nb_procs), " procs",
|
||||
" but program launched with only ", std::to_string(doctest::mpi_world_nb_procs()), "."
|
||||
);
|
||||
CHECK(nb_procs<=mpi_world_nb_procs());
|
||||
}
|
||||
} else {
|
||||
int color = MPI_UNDEFINED;
|
||||
if(comm_world_rank < nb_procs){
|
||||
color = 0;
|
||||
}
|
||||
MPI_Comm_split(MPI_COMM_WORLD, color, comm_world_rank, &comm);
|
||||
|
||||
if(comm != MPI_COMM_NULL){
|
||||
MPI_Comm_rank(comm, &rank);
|
||||
assert(rank==comm_world_rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_comm() {
|
||||
if(comm != MPI_COMM_NULL){
|
||||
MPI_Comm_free(&comm);
|
||||
}
|
||||
}
|
||||
|
||||
mpi_sub_comm(mpi_sub_comm&& x)
|
||||
: nb_procs(x.nb_procs)
|
||||
, rank(x.rank)
|
||||
, comm(x.comm)
|
||||
{
|
||||
x.comm = MPI_COMM_NULL;
|
||||
}
|
||||
mpi_sub_comm& operator=(mpi_sub_comm&& x) {
|
||||
destroy_comm();
|
||||
nb_procs = x.nb_procs;
|
||||
rank = x.rank;
|
||||
comm = x.comm;
|
||||
x.comm = MPI_COMM_NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~mpi_sub_comm() {
|
||||
destroy_comm();
|
||||
}
|
||||
};
|
||||
|
||||
} // doctest
|
||||
|
||||
#endif // DOCTEST_SUB_COMM_H
|
||||
Reference in New Issue
Block a user