Added everything

This commit is contained in:
nedko 2025-10-27 17:07:57 +02:00
commit 89c5629176
8 changed files with 398 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
example/example
.vscode/*

66
Progress.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "Progress.h"
#include <sstream>
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;
}

29
Progress.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef PROGRESS_H_
#define PROGRESS_H_
#include <ostream>
#include <string>
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_

6
README.md Normal file
View File

@ -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`.

187
StatusBarManager.cpp Normal file
View File

@ -0,0 +1,187 @@
#include "StatusBarManager.h"
#include <iostream>
#include <string>
#include <sys/ioctl.h>
#include <unistd.h>
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();
}

35
StatusBarManager.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef STATUS_BAR_MANAGER_H_
#define STATUS_BAR_MANAGER_H_
#include <vector>
#include "Progress.h"
class StatusBarManager
{
private:
bool m_is_started;
std::vector<Progress*> 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_

4
example/Makefile Normal file
View File

@ -0,0 +1,4 @@
all:
g++ main.cpp ../Progress.cpp ../StatusBarManager.cpp -o example
.PHONY: all

69
example/main.cpp Normal file
View File

@ -0,0 +1,69 @@
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#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;
}