chore: import upstream snapshot with attribution
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
|
||||
####### Any changes to this file will be overwritten by the next CMake run ####
|
||||
####### The input file was cpp-terminalConfig.cmake.in ########
|
||||
|
||||
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
|
||||
|
||||
macro(set_and_check _var _file)
|
||||
set(${_var} "${_file}")
|
||||
if(NOT EXISTS "${_file}")
|
||||
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(check_required_components _NAME)
|
||||
foreach(comp ${${_NAME}_FIND_COMPONENTS})
|
||||
if(NOT ${_NAME}_${comp}_FOUND)
|
||||
if(${_NAME}_FIND_REQUIRED_${comp})
|
||||
set(${_NAME}_FOUND FALSE)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
####################################################################################
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cpp-terminalTargets.cmake")
|
||||
|
||||
check_required_components(cpp-terminal)
|
||||
@@ -0,0 +1,48 @@
|
||||
# This is a basic version file for the Config-mode of find_package().
|
||||
# It is used by write_basic_package_version_file() as input file for configure_file()
|
||||
# to create a version-file which can be installed along a config.cmake file.
|
||||
#
|
||||
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
|
||||
# the requested version string are exactly the same and it sets
|
||||
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
|
||||
# The variable CVF_VERSION must be set before calling configure_file().
|
||||
|
||||
set(PACKAGE_VERSION "1.0.0")
|
||||
|
||||
if (PACKAGE_FIND_VERSION_RANGE)
|
||||
# Package version must be in the requested version range
|
||||
if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN)
|
||||
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX)
|
||||
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX)))
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
|
||||
set(PACKAGE_VERSION_EXACT TRUE)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# if the installed project requested no architecture check, don't perform the check
|
||||
if("FALSE")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
|
||||
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8")
|
||||
math(EXPR installedBits "8 * 8")
|
||||
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
|
||||
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||
endif()
|
||||
@@ -0,0 +1,19 @@
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file for configuration "Debug".
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Import target "cpp-terminal::cpp-terminal" for configuration "Debug"
|
||||
set_property(TARGET cpp-terminal::cpp-terminal APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(cpp-terminal::cpp-terminal PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
|
||||
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/cpp-terminal.lib"
|
||||
)
|
||||
|
||||
list(APPEND _cmake_import_check_targets cpp-terminal::cpp-terminal )
|
||||
list(APPEND _cmake_import_check_files_for_cpp-terminal::cpp-terminal "${_IMPORT_PREFIX}/lib/cpp-terminal.lib" )
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
@@ -0,0 +1,19 @@
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file for configuration "Release".
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Import target "cpp-terminal::cpp-terminal" for configuration "Release"
|
||||
set_property(TARGET cpp-terminal::cpp-terminal APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(cpp-terminal::cpp-terminal PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
|
||||
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/cpp-terminal.lib"
|
||||
)
|
||||
|
||||
list(APPEND _cmake_import_check_targets cpp-terminal::cpp-terminal )
|
||||
list(APPEND _cmake_import_check_files_for_cpp-terminal::cpp-terminal "${_IMPORT_PREFIX}/lib/cpp-terminal.lib" )
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
@@ -0,0 +1,122 @@
|
||||
# Generated by CMake
|
||||
|
||||
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
|
||||
message(FATAL_ERROR "CMake >= 2.8.0 required")
|
||||
endif()
|
||||
if(CMAKE_VERSION VERSION_LESS "2.8.3")
|
||||
message(FATAL_ERROR "CMake >= 2.8.3 required")
|
||||
endif()
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION 2.8.3...3.23)
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file.
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
|
||||
set(_cmake_targets_defined "")
|
||||
set(_cmake_targets_not_defined "")
|
||||
set(_cmake_expected_targets "")
|
||||
foreach(_cmake_expected_target IN ITEMS cpp-terminal::cpp-terminal cpp-terminal::cpp-terminalWarnings cpp-terminal::cpp-terminal-private)
|
||||
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
|
||||
if(TARGET "${_cmake_expected_target}")
|
||||
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
|
||||
else()
|
||||
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_cmake_expected_target)
|
||||
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
|
||||
unset(_cmake_targets_defined)
|
||||
unset(_cmake_targets_not_defined)
|
||||
unset(_cmake_expected_targets)
|
||||
unset(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
return()
|
||||
endif()
|
||||
if(NOT _cmake_targets_defined STREQUAL "")
|
||||
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
|
||||
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
|
||||
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
|
||||
endif()
|
||||
unset(_cmake_targets_defined)
|
||||
unset(_cmake_targets_not_defined)
|
||||
unset(_cmake_expected_targets)
|
||||
|
||||
|
||||
# Compute the installation prefix relative to this file.
|
||||
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
if(_IMPORT_PREFIX STREQUAL "/")
|
||||
set(_IMPORT_PREFIX "")
|
||||
endif()
|
||||
|
||||
# Create imported target cpp-terminal::cpp-terminal
|
||||
add_library(cpp-terminal::cpp-terminal STATIC IMPORTED)
|
||||
|
||||
set_target_properties(cpp-terminal::cpp-terminal PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
|
||||
INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:cpp-terminal::cpp-terminal-private>"
|
||||
)
|
||||
|
||||
# Create imported target cpp-terminal::cpp-terminalWarnings
|
||||
add_library(cpp-terminal::cpp-terminalWarnings INTERFACE IMPORTED)
|
||||
|
||||
set_target_properties(cpp-terminal::cpp-terminalWarnings PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "\$<\$<C_COMPILER_ID:MSVC>:/W4;/Wall;/WL;/wd5045;/wd4820;/wd4514>;\$<\$<C_COMPILER_ID:GNU,Clang,AppleClang>:-Wall;-Wextra>;\$<\$<C_COMPILER_ID:GNU>:\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.8.0>:-Wpedantic>;\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.9.0>:-fdiagnostics-color=auto>;-Wpointer-arith;-Wredundant-decls;-Wundef;-Wwrite-strings>;\$<\$<C_COMPILER_ID:Clang,AppleClang>:-Wno-c++98-compat>;\$<\$<C_COMPILER_ID:Intel,IntelLLVM>:\$<IF:\$<PLATFORM_ID:Windows>,/W5;/Wall,-w3;-Wall;-Wextra-tokens;-Wformat;-Wmain;-Wpointer-arith;-Wreorder;-Wreturn-type;-Wshadow;-Wsign-compare;-Wstrict-aliasing;-Wuninitialized;-Wwrite-strings;-Wno-debug-disables-optimization>>;\$<\$<CXX_COMPILER_ID:MSVC>:/W4;/Wall;/WL;/wd5045;/wd4820;/wd4514>;\$<\$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall;-Wextra>;\$<\$<CXX_COMPILER_ID:GNU>:\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.8.0>:-Wpedantic>;\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.9.0>:-fdiagnostics-color=auto>;-Wpointer-arith;-Wredundant-decls;-Wundef;-Wwrite-strings>;\$<\$<CXX_COMPILER_ID:Clang,AppleClang>:-Wno-c++98-compat>;\$<\$<OR:\$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang,GNU>,\$<COMPILE_LANG_AND_ID:OBJCXX,Clang,AppleClang,GNU>>:>;\$<\$<OR:\$<COMPILE_LANG_AND_ID:CXX,GNU>,\$<COMPILE_LANG_AND_ID:OBJCXX,GNU>>:-Wdelete-non-virtual-dtor;\$<\$<VERSION_GREATER_EQUAL:\$<CXX_COMPILER_VERSION>,4.8.0>:-Wpedantic>;\$<\$<VERSION_GREATER_EQUAL:\$<CXX_COMPILER_VERSION>,4.9.0>:-fdiagnostics-color=auto>;-Wnoexcept>;\$<\$<OR:\$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>,\$<COMPILE_LANG_AND_ID:OBJCXX,Clang,AppleClang>>:>;\$<\$<CXX_COMPILER_ID:Intel,IntelLLVM>:\$<IF:\$<PLATFORM_ID:Windows>,/W5;/Wall,-w3;-Wall;-Wextra-tokens;-Wformat;-Wmain;-Wpointer-arith;-Wreorder;-Wreturn-type;-Wshadow;-Wsign-compare;-Wstrict-aliasing;-Wuninitialized;-Wwrite-strings;-Wno-debug-disables-optimization>>"
|
||||
)
|
||||
|
||||
# Create imported target cpp-terminal::cpp-terminal-private
|
||||
add_library(cpp-terminal::cpp-terminal-private INTERFACE IMPORTED)
|
||||
|
||||
set_target_properties(cpp-terminal::cpp-terminal-private PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "\$<\$<CXX_COMPILER_ID:MSVC>:/utf-8>"
|
||||
INTERFACE_LINK_LIBRARIES "cpp-terminal::cpp-terminalWarnings;Threads::Threads"
|
||||
)
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.1.0)
|
||||
message(FATAL_ERROR "This file relies on consumers using CMake 3.1.0 or greater.")
|
||||
endif()
|
||||
|
||||
# Load information for each installed configuration.
|
||||
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/cpp-terminalTargets-*.cmake")
|
||||
foreach(_cmake_config_file IN LISTS _cmake_config_files)
|
||||
include("${_cmake_config_file}")
|
||||
endforeach()
|
||||
unset(_cmake_config_file)
|
||||
unset(_cmake_config_files)
|
||||
|
||||
# Cleanup temporary variables.
|
||||
set(_IMPORT_PREFIX)
|
||||
|
||||
# Loop over all imported files and verify that they actually exist
|
||||
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
|
||||
foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
|
||||
if(NOT EXISTS "${_cmake_file}")
|
||||
message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
|
||||
\"${_cmake_file}\"
|
||||
but this file does not exist. Possible reasons include:
|
||||
* The file was deleted, renamed, or moved to another location.
|
||||
* An install or uninstall procedure did not complete successfully.
|
||||
* The installation package was faulty and contained
|
||||
\"${CMAKE_CURRENT_LIST_FILE}\"
|
||||
but not all the files it references.
|
||||
")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_cmake_file)
|
||||
unset("_cmake_import_check_files_for_${_cmake_target}")
|
||||
endforeach()
|
||||
unset(_cmake_target)
|
||||
unset(_cmake_import_check_targets)
|
||||
|
||||
# This file does not depend on other imported targets which have
|
||||
# been exported from the same project but in a separate export set.
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
@@ -0,0 +1,191 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
doctest
|
||||
-----
|
||||
|
||||
This module defines a function to help use the doctest test framework.
|
||||
|
||||
The :command:`doctest_discover_tests` discovers tests by asking the compiled test
|
||||
executable to enumerate its tests. This does not require CMake to be re-run
|
||||
when tests change. However, it may not work in a cross-compiling environment,
|
||||
and setting test properties is less convenient.
|
||||
|
||||
This command is intended to replace use of :command:`add_test` to register
|
||||
tests, and will create a separate CTest test for each doctest test case. Note
|
||||
that this is in some cases less efficient, as common set-up and tear-down logic
|
||||
cannot be shared by multiple test cases executing in the same instance.
|
||||
However, it provides more fine-grained pass/fail information to CTest, which is
|
||||
usually considered as more beneficial. By default, the CTest test name is the
|
||||
same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
|
||||
.. command:: doctest_discover_tests
|
||||
|
||||
Automatically add tests with CTest by querying the compiled test executable
|
||||
for available tests::
|
||||
|
||||
doctest_discover_tests(target
|
||||
[TEST_SPEC arg1...]
|
||||
[EXTRA_ARGS arg1...]
|
||||
[WORKING_DIRECTORY dir]
|
||||
[TEST_PREFIX prefix]
|
||||
[TEST_SUFFIX suffix]
|
||||
[PROPERTIES name1 value1...]
|
||||
[ADD_LABELS value]
|
||||
[TEST_LIST var]
|
||||
[JUNIT_OUTPUT_DIR dir]
|
||||
)
|
||||
|
||||
``doctest_discover_tests`` sets up a post-build command on the test executable
|
||||
that generates the list of tests by parsing the output from running the test
|
||||
with the ``--list-test-cases`` argument. This ensures that the full
|
||||
list of tests is obtained. Since test discovery occurs at build time, it is
|
||||
not necessary to re-run CMake when the list of tests changes.
|
||||
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
|
||||
in order to function in a cross-compiling environment.
|
||||
|
||||
Additionally, setting properties on tests is somewhat less convenient, since
|
||||
the tests are not available at CMake time. Additional test properties may be
|
||||
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
|
||||
more fine-grained test control is needed, custom content may be provided
|
||||
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
|
||||
directory property. The set of discovered tests is made accessible to such a
|
||||
script via the ``<target>_TESTS`` variable.
|
||||
|
||||
The options are:
|
||||
|
||||
``target``
|
||||
Specifies the doctest executable, which must be a known CMake executable
|
||||
target. CMake will substitute the location of the built executable when
|
||||
running the test.
|
||||
|
||||
``TEST_SPEC arg1...``
|
||||
Specifies test cases, wildcarded test cases, tags and tag expressions to
|
||||
pass to the doctest executable with the ``--list-test-cases`` argument.
|
||||
|
||||
``EXTRA_ARGS arg1...``
|
||||
Any extra arguments to pass on the command line to each test case.
|
||||
|
||||
``WORKING_DIRECTORY dir``
|
||||
Specifies the directory in which to run the discovered test cases. If this
|
||||
option is not provided, the current binary directory is used.
|
||||
|
||||
``TEST_PREFIX prefix``
|
||||
Specifies a ``prefix`` to be prepended to the name of each discovered test
|
||||
case. This can be useful when the same test executable is being used in
|
||||
multiple calls to ``doctest_discover_tests()`` but with different
|
||||
``TEST_SPEC`` or ``EXTRA_ARGS``.
|
||||
|
||||
``TEST_SUFFIX suffix``
|
||||
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
|
||||
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
|
||||
be specified.
|
||||
|
||||
``PROPERTIES name1 value1...``
|
||||
Specifies additional properties to be set on all tests discovered by this
|
||||
invocation of ``doctest_discover_tests``.
|
||||
|
||||
``ADD_LABELS value``
|
||||
Specifies if the test labels should be set automatically.
|
||||
|
||||
``TEST_LIST var``
|
||||
Make the list of tests available in the variable ``var``, rather than the
|
||||
default ``<target>_TESTS``. This can be useful when the same test
|
||||
executable is being used in multiple calls to ``doctest_discover_tests()``.
|
||||
Note that this variable is only available in CTest.
|
||||
|
||||
``JUNIT_OUTPUT_DIR dir``
|
||||
If specified, the parameter is passed along with ``--reporters=junit``
|
||||
and ``--out=`` to the test executable. The actual file name is the same
|
||||
as the test target, including prefix and suffix. This should be used
|
||||
instead of EXTRA_ARGS to avoid race conditions writing the XML result
|
||||
output when using parallel test execution.
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
function(doctest_discover_tests TARGET)
|
||||
cmake_parse_arguments(
|
||||
""
|
||||
""
|
||||
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR"
|
||||
"TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS"
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
if(NOT _WORKING_DIRECTORY)
|
||||
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
if(NOT _TEST_LIST)
|
||||
set(_TEST_LIST ${TARGET}_TESTS)
|
||||
endif()
|
||||
|
||||
## Generate a unique name based on the extra arguments
|
||||
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
|
||||
string(SUBSTRING ${args_hash} 0 7 args_hash)
|
||||
|
||||
# Define rule to generate test list for aforementioned test executable
|
||||
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
|
||||
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
|
||||
get_property(crosscompiling_emulator
|
||||
TARGET ${TARGET}
|
||||
PROPERTY CROSSCOMPILING_EMULATOR
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET} POST_BUILD
|
||||
BYPRODUCTS "${ctest_tests_file}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "TEST_TARGET=${TARGET}"
|
||||
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||
-D "TEST_ADD_LABELS=${_ADD_LABELS}"
|
||||
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||
-D "TEST_LIST=${_TEST_LIST}"
|
||||
-D "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}"
|
||||
-D "CTEST_FILE=${ctest_tests_file}"
|
||||
-P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(WRITE "${ctest_include_file}"
|
||||
"if(EXISTS \"${ctest_tests_file}\")\n"
|
||||
" include(\"${ctest_tests_file}\")\n"
|
||||
"else()\n"
|
||||
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
|
||||
"endif()\n"
|
||||
)
|
||||
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.10)
|
||||
# Add discovered tests to directory TEST_INCLUDE_FILES
|
||||
set_property(DIRECTORY
|
||||
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
|
||||
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
|
||||
if(NOT ${test_include_file_set})
|
||||
set_property(DIRECTORY
|
||||
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Cannot set more than one TEST_INCLUDE_FILE"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
###############################################################################
|
||||
|
||||
set(_DOCTEST_DISCOVER_TESTS_SCRIPT
|
||||
${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake
|
||||
CACHE INTERNAL "The location of the doctestAddTests script"
|
||||
)
|
||||
mark_as_advanced(_DOCTEST_DISCOVER_TESTS_SCRIPT)
|
||||
@@ -0,0 +1,120 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
set(prefix "${TEST_PREFIX}")
|
||||
set(suffix "${TEST_SUFFIX}")
|
||||
set(spec ${TEST_SPEC})
|
||||
set(extra_args ${TEST_EXTRA_ARGS})
|
||||
set(properties ${TEST_PROPERTIES})
|
||||
set(add_labels ${TEST_ADD_LABELS})
|
||||
set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}")
|
||||
set(script)
|
||||
set(suite)
|
||||
set(tests)
|
||||
|
||||
function(add_command NAME)
|
||||
set(_args "")
|
||||
foreach(_arg ${ARGN})
|
||||
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||
else()
|
||||
set(_args "${_args} ${_arg}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Run test executable to get list of available tests
|
||||
if(NOT EXISTS "${TEST_EXECUTABLE}")
|
||||
message(FATAL_ERROR
|
||||
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
|
||||
)
|
||||
endif()
|
||||
|
||||
if("${spec}" MATCHES .)
|
||||
set(spec "--test-case=${spec}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases
|
||||
OUTPUT_VARIABLE output
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
)
|
||||
if(NOT ${result} EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${result}\n"
|
||||
" Output: ${output}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" output "${output}")
|
||||
|
||||
# Parse output
|
||||
foreach(line ${output})
|
||||
if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==])
|
||||
continue()
|
||||
endif()
|
||||
set(test ${line})
|
||||
set(labels "")
|
||||
if(${add_labels})
|
||||
# get test suite that test belongs to
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites
|
||||
OUTPUT_VARIABLE labeloutput
|
||||
RESULT_VARIABLE labelresult
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
)
|
||||
if(NOT ${labelresult} EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${labelresult}\n"
|
||||
" Output: ${labeloutput}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" labeloutput "${labeloutput}")
|
||||
foreach(labelline ${labeloutput})
|
||||
if("${labelline}" STREQUAL "===============================================================================" OR "${labelline}" MATCHES [==[^\[doctest\] ]==])
|
||||
continue()
|
||||
endif()
|
||||
list(APPEND labels ${labelline})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(NOT "${junit_output_dir}" STREQUAL "")
|
||||
# turn testname into a valid filename by replacing all special characters with "-"
|
||||
string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}")
|
||||
set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml")
|
||||
else()
|
||||
unset(TEST_JUNIT_OUTPUT_PARAM)
|
||||
endif()
|
||||
# use escape commas to handle properly test cases with commas inside the name
|
||||
string(REPLACE "," "\\," test_name ${test})
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
"${prefix}${test}${suffix}"
|
||||
${TEST_EXECUTOR}
|
||||
"${TEST_EXECUTABLE}"
|
||||
"--test-case=${test_name}"
|
||||
"${TEST_JUNIT_OUTPUT_PARAM}"
|
||||
${extra_args}
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
"${prefix}${test}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
LABELS ${labels}
|
||||
)
|
||||
unset(labels)
|
||||
list(APPEND tests "${prefix}${test}${suffix}")
|
||||
endforeach()
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${TEST_LIST} ${tests})
|
||||
|
||||
# Write CTest script
|
||||
file(WRITE "${CTEST_FILE}" "${script}")
|
||||
@@ -0,0 +1,6 @@
|
||||
if(NOT TARGET doctest::doctest)
|
||||
# Provide path for scripts
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/doctestTargets.cmake")
|
||||
endif()
|
||||
@@ -0,0 +1,70 @@
|
||||
# This is a basic version file for the Config-mode of find_package().
|
||||
# It is used by write_basic_package_version_file() as input file for configure_file()
|
||||
# to create a version-file which can be installed along a config.cmake file.
|
||||
#
|
||||
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
|
||||
# the requested version string are exactly the same and it sets
|
||||
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version,
|
||||
# but only if the requested major version is the same as the current one.
|
||||
# The variable CVF_VERSION must be set before calling configure_file().
|
||||
|
||||
|
||||
set(PACKAGE_VERSION "2.4.11")
|
||||
|
||||
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
|
||||
if("2.4.11" MATCHES "^([0-9]+)\\.")
|
||||
set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||
if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0)
|
||||
string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}")
|
||||
endif()
|
||||
else()
|
||||
set(CVF_VERSION_MAJOR "2.4.11")
|
||||
endif()
|
||||
|
||||
if(PACKAGE_FIND_VERSION_RANGE)
|
||||
# both endpoints of the range must have the expected major version
|
||||
math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1")
|
||||
if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
|
||||
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR)
|
||||
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT)))
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
|
||||
AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX)
|
||||
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX)))
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
endif()
|
||||
else()
|
||||
if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR)
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
endif()
|
||||
|
||||
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
|
||||
set(PACKAGE_VERSION_EXACT TRUE)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# if the installed project requested no architecture check, don't perform the check
|
||||
if("FALSE")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "" STREQUAL "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
|
||||
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "")
|
||||
math(EXPR installedBits " * 8")
|
||||
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
|
||||
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||
endif()
|
||||
@@ -0,0 +1,107 @@
|
||||
# Generated by CMake
|
||||
|
||||
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
|
||||
message(FATAL_ERROR "CMake >= 2.8.0 required")
|
||||
endif()
|
||||
if(CMAKE_VERSION VERSION_LESS "2.8.3")
|
||||
message(FATAL_ERROR "CMake >= 2.8.3 required")
|
||||
endif()
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION 2.8.3...3.23)
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file.
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
|
||||
set(_cmake_targets_defined "")
|
||||
set(_cmake_targets_not_defined "")
|
||||
set(_cmake_expected_targets "")
|
||||
foreach(_cmake_expected_target IN ITEMS doctest::doctest)
|
||||
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
|
||||
if(TARGET "${_cmake_expected_target}")
|
||||
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
|
||||
else()
|
||||
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_cmake_expected_target)
|
||||
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
|
||||
unset(_cmake_targets_defined)
|
||||
unset(_cmake_targets_not_defined)
|
||||
unset(_cmake_expected_targets)
|
||||
unset(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
return()
|
||||
endif()
|
||||
if(NOT _cmake_targets_defined STREQUAL "")
|
||||
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
|
||||
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
|
||||
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
|
||||
endif()
|
||||
unset(_cmake_targets_defined)
|
||||
unset(_cmake_targets_not_defined)
|
||||
unset(_cmake_expected_targets)
|
||||
|
||||
|
||||
# Compute the installation prefix relative to this file.
|
||||
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
if(_IMPORT_PREFIX STREQUAL "/")
|
||||
set(_IMPORT_PREFIX "")
|
||||
endif()
|
||||
|
||||
# Create imported target doctest::doctest
|
||||
add_library(doctest::doctest INTERFACE IMPORTED)
|
||||
|
||||
set_target_properties(doctest::doctest PROPERTIES
|
||||
INTERFACE_COMPILE_FEATURES "cxx_std_11"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
|
||||
)
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.0.0)
|
||||
message(FATAL_ERROR "This file relies on consumers using CMake 3.0.0 or greater.")
|
||||
endif()
|
||||
|
||||
# Load information for each installed configuration.
|
||||
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/doctestTargets-*.cmake")
|
||||
foreach(_cmake_config_file IN LISTS _cmake_config_files)
|
||||
include("${_cmake_config_file}")
|
||||
endforeach()
|
||||
unset(_cmake_config_file)
|
||||
unset(_cmake_config_files)
|
||||
|
||||
# Cleanup temporary variables.
|
||||
set(_IMPORT_PREFIX)
|
||||
|
||||
# Loop over all imported files and verify that they actually exist
|
||||
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
|
||||
foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
|
||||
if(NOT EXISTS "${_cmake_file}")
|
||||
message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
|
||||
\"${_cmake_file}\"
|
||||
but this file does not exist. Possible reasons include:
|
||||
* The file was deleted, renamed, or moved to another location.
|
||||
* An install or uninstall procedure did not complete successfully.
|
||||
* The installation package was faulty and contained
|
||||
\"${CMAKE_CURRENT_LIST_FILE}\"
|
||||
but not all the files it references.
|
||||
")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_cmake_file)
|
||||
unset("_cmake_import_check_files_for_${_cmake_target}")
|
||||
endforeach()
|
||||
unset(_cmake_target)
|
||||
unset(_cmake_import_check_targets)
|
||||
|
||||
# This file does not depend on other imported targets which have
|
||||
# been exported from the same project but in a separate export set.
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
Binary file not shown.
Reference in New Issue
Block a user