commit 89c5629176e179f525a2cf37cdde238c19036992 Author: nedko Date: Mon Oct 27 17:07:57 2025 +0200 Added everything diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70305b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +example/example +.vscode/* diff --git a/Progress.cpp b/Progress.cpp new file mode 100644 index 0000000..f83a6ca --- /dev/null +++ b/Progress.cpp @@ -0,0 +1,66 @@ +#include "Progress.h" +#include + +using std::ostream; +using std::string; +using std::stringstream; + +Progress::Progress(const string& message, size_t total, size_t current) +: m_current(current), +m_total(total), +m_message(message) +{ + if (m_current > m_total) + { + m_current = m_total; + } +} + +void Progress::set_total(size_t total) +{ + m_total = total; + + if (m_current > m_total) + { + m_current = m_total; + } +} + +void Progress::set_progress(size_t current) +{ + m_current = current; + + if (m_current > m_total) + { + m_current = m_total; + } +} + +void Progress::set_message(const std::string& message) +{ + m_message = message; +} + +void Progress::increment_progress(size_t inc) +{ + m_current += inc; + + if (m_current > m_total) + { + m_current = m_total; + } +} + +string Progress::print_progress() +{ + stringstream out; + out << m_current << "/" << m_total << " - " << m_message; + return out.str(); +} + +ostream& operator<<(ostream& out, const Progress& progress) +{ + out << progress.m_current << "/" << progress.m_total; + out << " - " << progress.m_message; + return out; +} diff --git a/Progress.h b/Progress.h new file mode 100644 index 0000000..7ebb065 --- /dev/null +++ b/Progress.h @@ -0,0 +1,29 @@ +#ifndef PROGRESS_H_ +#define PROGRESS_H_ + +#include +#include + +class Progress +{ +protected: + size_t m_current; + size_t m_total; + + std::string m_message; + +public: + Progress(const std::string& message, size_t total, size_t current = 0); + + void set_total(size_t total); + void set_progress(size_t current); + void set_message(const std::string& message); + + void increment_progress(size_t inc = 1); + + std::string print_progress(); + + friend std::ostream& operator<<(std::ostream& os, const Progress& progress); +}; + +#endif // PROGRESS_H_ diff --git a/README.md b/README.md new file mode 100644 index 0000000..5fecc25 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Terminal Status Line +This simple utility allows you to easily print progress messages at the bottom of any program that outputs to **STDOUT**. +Part of this code has been adapted from the package manager *apt*. + +# Example +An example of this in action can be found in the example folder and compiled with `make`. diff --git a/StatusBarManager.cpp b/StatusBarManager.cpp new file mode 100644 index 0000000..0d7a0a5 --- /dev/null +++ b/StatusBarManager.cpp @@ -0,0 +1,187 @@ +#include "StatusBarManager.h" + +#include +#include + +#include +#include + +using std::cout; +using std::flush; +using std::string; + +StatusBarManager::StatusBarManager() +: m_is_started(false) +{} + +StatusBarManager::~StatusBarManager() +{ + if(m_is_started) + { + stop(); + } +} + +StatusBarManager::TermSize StatusBarManager::get_terminal_size() +{ + struct winsize win; + StatusBarManager::TermSize result = { 0, 0 }; + + if(ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win) != 0) + { + return result; + } + + result.rows = win.ws_row; + result.columns = win.ws_col; + + return result; +} + +void StatusBarManager::setup_terminal_scroll_area(int nr_rows) +{ + int status_rows = m_progress_messages.size(); + if (nr_rows <= 1) + { + return; + } + + // scroll down a bit to avoid visual glitch when the screen + // area shrinks by needed ammount of rows + for (int i = 0; i < status_rows; ++i) + { + cout << std::endl; + } + + // save cursor + cout << "\e7"; + + // set scroll region (this will place the cursor in the top left) + cout << "\e[0;" << std::to_string(nr_rows - status_rows) << "r"; + + // restore cursor but ensure its inside the scrolling area + cout << "\e8"; + cout << "\e[" << status_rows << "A"; + + // ensure its flushed + flush(cout); + +// Leftovers from apt +#if 0 + // setup tty size to ensure xterm/linux console are working properly too + // see bug #731738 + struct winsize win; + if (ioctl(child_pty, TIOCGWINSZ, (char *)&win) != -1) + { + win.ws_row = nr_rows - 1; + ioctl(child_pty, TIOCSWINSZ, (char *)&win); + } +#endif +} + +void StatusBarManager::start() +{ + if (!m_is_started) + { + int const nr_terminal_rows = get_terminal_size().rows; + setup_terminal_scroll_area(nr_terminal_rows); + m_is_started = true; + } +} + +void StatusBarManager::stop() +{ + if (m_is_started) + { + int const nr_terminal_rows = get_terminal_size().rows; + if (nr_terminal_rows > 0) + { + setup_terminal_scroll_area(nr_terminal_rows + m_progress_messages.size()); + + // override the progress line (sledgehammer) + static const char* clear_screen_below_cursor = "\e[J"; + cout << clear_screen_below_cursor; + flush(cout); + } + + m_is_started = false; + } +} + +bool StatusBarManager::draw_status_line() +{ + if (!m_is_started) + { + return false; + } + + StatusBarManager::TermSize const size = get_terminal_size(); + if (size.rows < 1 || size.columns < 1) + { + return false; + } + + static string save_cursor = "\e7"; + static string restore_cursor = "\e8"; + + // green + static string set_bg_color = "\e[42m"; + // black + static string set_fg_color = "\e[30m"; + + static string restore_bg = "\e[49m"; + static string restore_fg = "\e[39m"; + + cout << save_cursor; + + int status_rows = m_progress_messages.size(); + for (int i = 0; i < status_rows; ++i) + { + // move cursor position to current row + cout << "\e[" << std::to_string(size.rows + 1 - status_rows + i) << ";0f" + << set_bg_color + << set_fg_color; + if (nullptr != m_progress_messages[i]) + { + cout << *(m_progress_messages[i]); + } + flush(cout); + } + + cout << restore_bg + << restore_fg; + flush(cout); + + // leftover progress-bar from apt +#if 0 + // draw text progress bar + int padding = 4; + auto const progressbar_size = size.columns - padding - String::DisplayLength(progress_str); + auto const current_percent = percentage / 100.0f; + cout << " " + << GetTextProgressStr(current_percent, progressbar_size) + << " "; + flush(cout); +#endif + + // restore + cout << restore_cursor; + flush(cout); + + return true; +} + +void StatusBarManager::add_progress_message(Progress* message) +{ + if (message == nullptr) + { + return; + } + + m_progress_messages.push_back(message); +} + +void StatusBarManager::clear() +{ + m_progress_messages.clear(); +} diff --git a/StatusBarManager.h b/StatusBarManager.h new file mode 100644 index 0000000..3d742ea --- /dev/null +++ b/StatusBarManager.h @@ -0,0 +1,35 @@ +#ifndef STATUS_BAR_MANAGER_H_ +#define STATUS_BAR_MANAGER_H_ + +#include + +#include "Progress.h" + +class StatusBarManager +{ +private: + bool m_is_started; + std::vector m_progress_messages; + + struct TermSize + { + int rows; + int columns; + }; + + TermSize get_terminal_size(); + + void setup_terminal_scroll_area(int nr_rows); + +public: + StatusBarManager(); + ~StatusBarManager(); + + bool draw_status_line(); + void start(); + void stop(); + void add_progress_message(Progress* message); + void clear(); +}; + +#endif // STATUS_BAR_MANAGER_H_ diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 0000000..3ece99d --- /dev/null +++ b/example/Makefile @@ -0,0 +1,4 @@ +all: + g++ main.cpp ../Progress.cpp ../StatusBarManager.cpp -o example + +.PHONY: all diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..fce41ef --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "../Progress.h" +#include "../StatusBarManager.h" + +using namespace std; + +StatusBarManager* g_manager = nullptr; + +// This ensures your terminal is left in normal state even after Ctrl+C +void INT_handler(int sig) +{ + if (nullptr != g_manager) + { + g_manager->stop(); + } + signal(sig, SIG_DFL); + raise(sig); +} + +int main() +{ + signal(SIGINT, INT_handler); + Progress p("Normal", 10); + Progress p2("Test", 10); + StatusBarManager manager; + g_manager = &manager; + manager.add_progress_message(&p); + manager.add_progress_message(&p2); + if (isatty(STDOUT_FILENO)) + { + manager.start(); + } + else + { + cerr << "NOT A TTY" << endl; + } + + string test[2] = {"Message 1", "Message 2"}; + + srand(time(nullptr)); + + manager.draw_status_line(); + this_thread::sleep_for(chrono::seconds(1)); + + for (int i = 0; i < 10; ++i) + { + int random = rand() % 2; + if (random != 2) + { + cout << test[random] << endl; + } + p.increment_progress(); + p2.increment_progress(); + + manager.draw_status_line(); + this_thread::sleep_for(chrono::seconds(1)); + } + + manager.stop(); + return 0; +}