Added menus and boxes for signal sending

This commit is contained in:
aristocratos 2021-09-01 21:40:13 +02:00
parent 5dcc1b7829
commit db96a20e16
11 changed files with 638 additions and 178 deletions

125
Makefile
View file

@ -1,28 +1,17 @@
#* Btop++ makefile v1.0
BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝ \033[1;3;38;5;240mMakefile v1.0\033[0m
BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝ \033[1;3;38;5;240mMakefile v1.2\033[0m
BTOP_VERSION := $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown")
TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0")
override BTOP_VERSION := $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown")
override TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0")
PREFIX ?= /usr/local
#? Compiler and Linker
CXX ?= g++
CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
#? Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10
ifneq ($(CXX),g++-10)
V_MAJOR = $(shell echo $(CXX_VERSION) | cut -f1 -d".")
ifneq ($(shell test $(V_MAJOR) -ge 11; echo $$?),0)
ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0)
override CXX = g++-11
endif
endif
endif
#? NOTICE! Manually set PLATFORM and ARCH if not compiling for host system
PLATFORM ?= $(shell uname -s || echo unknown)
ARCH ?= $(shell uname -p || echo unknown)
#? Only enable fcf-protection if on x86_64
ARCH := $(shell uname -p || echo unknown)
ifeq ($(ARCH),unknown)
ARCH := $(shell uname -m || echo unknown)
endif
@ -30,11 +19,34 @@ ifeq ($(ARCH),x86_64)
ADDFLAGS = -fcf-protection
endif
#? Manually set this to (Linux|FreeBSD|Darwin) if not building for host platform
PLATFORM ?= $(shell uname -s || echo unknown)
#? Make sure PLATFORM Darwin is OSX and not Darwin
ifeq ($(PLATFORM),Darwin)
ifeq ($(shell sw_vers >/dev/null 2>&1; echo $$?),0)
PLATFORM = OSX
endif
endif
#? Compiler and Linker
CXX ?= g++
override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
#? Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10
ifneq ($(CXX),g++-10)
V_MAJOR = $(shell echo $(CXX_VERSION) | cut -f1 -d".")
ifneq ($(shell test $(V_MAJOR) -ge 11; echo $$?),0)
ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0)
override CXX = g++-11
override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
endif
endif
endif
#? Use all CPU cores (will only be set if using Make 4.3+)
MAKEFLAGS := --jobs=$(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
THREADS := $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
MAKEFLAGS := --jobs=$(THREADS)
ifeq ($(THREADS),1)
override THREADS := auto
endif
#? The Directories, Source, Includes, Objects and Binary
SRCDIR := src
@ -46,10 +58,10 @@ DEPEXT := d
OBJEXT := o
#? Flags, Libraries and Includes
REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic -pedantic-errors -Wfatal-errors
OPTFLAGS := -O2 -ftree-loop-vectorize
override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -flto $(ADDFLAGS)
override REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -pedantic -pedantic-errors -Wfatal-errors
OPTFLAGS ?= -O2 -ftree-loop-vectorize -flto=$(THREADS)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection $(ADDFLAGS)
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR)
@ -58,29 +70,38 @@ SU_GROUP := root
SOURCES := $(shell find $(SRCDIR) -maxdepth 1 -type f -name *.$(SRCEXT))
#? Pull in platform specific source files
ifeq ($(PLATFORM),Linux)
SOURCES += $(shell find $(SRCDIR)/linux -type f -name *.$(SRCEXT))
endif
ifeq ($(PLATFORM),FreeBSD)
SOURCES += $(shell find $(SRCDIR)/freebsd -type f -name *.$(SRCEXT))
endif
ifeq ($(PLATFORM),Darwin)
SOURCES += $(shell find $(SRCDIR)/osx -type f -name *.$(SRCEXT))
PLATFORM_DIR = linux
else ifeq ($(PLATFORM),FreeBSD)
PLATFORM_DIR = freebsd
else ifeq ($(PLATFORM),OSX)
PLATFORM_DIR = osx
else
$(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m"))
endif
SOURCES += $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
#? Default Make
all: msg directories btop
all: pre directories btop
msg:
pre:
@printf " $(BANNER)\n"
@printf "\033[1;97mCXX : \033[0m$(CXX) ($(CXX_VERSION))\n"
@printf "\033[1;97mREQFLAGS : \033[0m$(REQFLAGS)\n"
@printf "\033[1;97mWARNFLAGS : \033[0m$(WARNFLAGS)\n"
@printf "\033[1;97mOPTFLAGS : \033[0m$(OPTFLAGS)\n"
@printf "\033[1;97mLDCXXFLAGS : \033[0m$(LDCXXFLAGS)\n"
@printf "\n\033[1;92mBuilding \033[1;91mbtop++ \033[1;93mv$(BTOP_VERSION) \033[0;37mfor \033[1;97m$(PLATFORM) \033[1;96m($(ARCH))\033[0m\n"
@printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n"
@printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n"
@printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n"
@printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n"
@printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n"
@printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n"
@printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n"
@printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n"
@printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m)\n"
@printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m)\n"
@printf "\n\033[1;92mBuilding btop++ \033[93m(\033[97mv$(BTOP_VERSION)\033[93m)\033[0m\n"
help:
@printf "\033[1;97mbtop++ makefile\033[0m\n"
@ -96,10 +117,7 @@ help:
#? Make the Directories
directories:
@mkdir -p $(TARGETDIR)
@mkdir -p $(BUILDDIR)
@mkdir -p $(BUILDDIR)/linux
@mkdir -p $(BUILDDIR)/freebsd
@mkdir -p $(BUILDDIR)/osx
@mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)
#? Clean only Objects
clean:
@ -122,7 +140,7 @@ install:
@printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\n"
@cp -pr themes $(DESTDIR)$(PREFIX)/share/btop
#? Set suid bit for btop for $SU_USER in SU_GROUP, will make btop run with (root by default) privileges regardless of actual user
#? Set SUID bit for btop as $SU_USER in $SU_GROUP
setuid:
@printf "\033[1;97mFile: $(DESTDIR)$(PREFIX)/bin/btop\n"
@printf "\033[1;92mSetting owner \033[1;97m$(SU_USER):$(SU_GROUP)\033[0m\n"
@ -140,23 +158,28 @@ uninstall:
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
#? Link
.ONESHELL:
btop: $(OBJECTS)
@sleep 0.1 2>/dev/null || true
@printf "\n\033[1;92mLinking and optimizing binary\033[0m\n"
@$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS)
@printf "\033[1;97m./$(TARGETDIR)/btop ($$(du -ah $(TARGETDIR)/btop | cut -f1)iB)\033[0m\n"
@printf "\n\033[1;92mBuild complete in (\033[1;97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP)) -u +%Mm:%Ss)\033[1;92m)\033[0m\n"
@TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n"
@$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) || exit 1
@printf "\033[1;92m-> \033[1;37m$(TARGETDIR)/btop \033[100D\033[35C\033[1;93m(\033[1;97m$$(du -ah $(TARGETDIR)/btop | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"
@printf "\n\033[1;92mBuild complete in \033[92m(\033[97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n"
#? Compile
.ONESHELL:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
@sleep 0.1 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@printf "\033[1;97mCompiling $<\033[0m\n"
@$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $<
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null
@$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< || exit 1
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null || exit 1
@cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp
@sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT)
@sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT)
@rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp
@printf "\033[1;92m-> \033[1;37m$@ \033[100D\033[35C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"
#? Non-File Targets
.PHONY: all msg help
.PHONY: all msg help pre

View file

@ -29,6 +29,7 @@ tab-size = 4
#include <exception>
#include <tuple>
#include <regex>
#include <chrono>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
@ -43,6 +44,7 @@ using std::string_literals::operator""s, std::to_string, std::future, std::async
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
using namespace std::chrono_literals;
namespace Global {
const vector<array<string, 2>> Banner_src = {
@ -140,12 +142,13 @@ void term_resize(bool force) {
Global::resized = true;
Runner::stop();
auto min_size = Term::get_min_size(Config::getS("shown_boxes"));
while (not force) {
auto boxes = Config::getS("shown_boxes");
auto min_size = Term::get_min_size(boxes);
while (not force or (Term::width < min_size.at(0) or Term::height < min_size.at(1))) {
sleep_ms(100);
if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) {
min_size = Term::get_min_size(Config::getS("shown_boxes"));
cout << Term::clear << Global::bg_black << Global::fg_white << Mv::to((Term::height / 2) - 2, (Term::width / 2) - 11)
<< "Terminal size too small:" << Mv::to((Term::height / 2) - 1, (Term::width / 2) - 10)
<< " Width = " << (Term::width < min_size.at(1) ? Global::fg_red : Global::fg_green) << Term::width
@ -155,6 +158,7 @@ void term_resize(bool force) {
<< "Width = " << min_size.at(0) << " Height = " << min_size.at(1) << flush;
while (not Term::refresh() and not Input::poll()) sleep_ms(10);
if (Input::poll() and Input::get() == "q") exit(0);
min_size = Term::get_min_size(boxes);
}
else if (not Term::refresh()) break;
}
@ -167,8 +171,9 @@ void clean_quit(int sig) {
if (Global::quitting) return;
Global::quitting = true;
Runner::stop();
if (pthread_join(Runner::runner_id, NULL) != 0)
if (pthread_join(Runner::runner_id, NULL) != 0) {
Logger::error("Failed to join _runner thread!");
}
Config::write();
Input::clear();
@ -257,7 +262,6 @@ void banner_gen() {
else
letter = line[1].substr(i, 3);
// if (tty_mode and letter != "█" and letter != " ") letter = "░";
b_color = (letter == "") ? fg : bg;
if (b_color != oc) Global::banner += b_color;
Global::banner += letter;
@ -266,8 +270,9 @@ void banner_gen() {
if (++z < Global::Banner_src.size()) Global::banner += Mv::l(ulen(line[1])) + Mv::d(1);
}
Global::banner += Mv::r(18 - Global::Version.size())
+ (tty_mode ? "\x1b[0;40;37m" : Theme::dec_to_color(0,0,0, lowcolor, "bg") + Theme::dec_to_color(150, 150, 150, lowcolor))
+ Fx::i + "v" + Global::Version + Fx::ui;
+ (tty_mode ? "\x1b[0;40;37m" : Theme::dec_to_color(0,0,0, lowcolor, "bg")
+ Theme::dec_to_color(150, 150, 150, lowcolor))
+ Fx::i + "v" + Global::Version + Fx::ui;
}
bool update_clock() {
@ -281,11 +286,17 @@ bool update_clock() {
};
static time_t c_time = 0;
static size_t clock_len = 0;
static string old_clock;
string new_clock;
if (auto n_time = time(NULL); n_time == c_time)
return false;
else
else {
c_time = n_time;
new_clock = Tools::strf_time(clock_format);
if (new_clock == old_clock) return false;
old_clock = new_clock;
}
auto& out = Global::clock;
const auto& cpu_bottom = Config::getB("cpu_bottom");
@ -294,7 +305,7 @@ bool update_clock() {
const auto& width = Cpu::width;
const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
const auto& title_right = (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
string new_clock = clock_format;
for (const auto& [c_format, replacement] : clock_custom_format) {
if (s_contains(new_clock, c_format)) {
@ -310,11 +321,11 @@ bool update_clock() {
}
new_clock = uresize(Tools::strf_time(new_clock), std::max(0, width - 56));
new_clock = uresize(new_clock, std::max(0, width - 56));
out.clear();
if (new_clock.size() != clock_len) {
if (not Global::resized) out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
if (not Global::resized and clock_len > 0) out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
clock_len = new_clock.size();
}
@ -483,7 +494,7 @@ namespace Runner {
else if (not proc.valid())
throw std::runtime_error("Proc::collect() future not valid.");
else if (proc.wait_for(std::chrono::microseconds(10)) == future_status::ready) {
else if (proc.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("proc", draw_begin);
@ -511,7 +522,7 @@ namespace Runner {
else if (not net.valid())
throw std::runtime_error("Net::collect() future not valid.");
else if (net.wait_for(ZeroSec) == future_status::ready) {
else if (net.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("net", draw_begin);
@ -539,7 +550,7 @@ namespace Runner {
else if (not mem.valid())
throw std::runtime_error("Mem::collect() future not valid.");
else if (mem.wait_for(ZeroSec) == future_status::ready) {
else if (mem.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("mem", draw_begin);
@ -567,7 +578,7 @@ namespace Runner {
else if (not cpu.valid())
throw std::runtime_error("Cpu::collect() future not valid.");
else if (cpu.wait_for(ZeroSec) == future_status::ready) {
else if (cpu.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("cpu", draw_begin);
@ -696,7 +707,11 @@ int main(int argc, char **argv) {
break;
}
}
if (not Config::conf_dir.empty()) {
if (Config::conf_dir.empty()) {
cout << "WARNING: Could not get path user HOME folder.\n"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
}
else {
if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) {
cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
@ -741,7 +756,7 @@ int main(int argc, char **argv) {
}
else Logger::set(Config::getS("log_level"));
Logger::info("Logger set to " + Config::getS("log_level"));
Logger::info("Logger set to " + (Global::debug ? "DEBUG" : Config::getS("log_level")));
for (const auto& err_str : load_warnings) Logger::warning(err_str);
}
@ -788,7 +803,7 @@ int main(int argc, char **argv) {
}
else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) {
Config::set("tty_mode", true);
Logger::info("Real tty detected, setting 16 color mode and using tty friendly graph symbols");
Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols");
}
//? Platform dependent init and error check
@ -854,7 +869,8 @@ int main(int argc, char **argv) {
Draw::calcSizes();
update_clock();
Global::resized = false;
Runner::run("all", true);
if (Menu::active) Menu::process();
else Runner::run("all", true, true);
atomic_wait(Runner::active);
}
@ -864,7 +880,7 @@ int main(int argc, char **argv) {
}
//? Start secondary collect & draw thread at the interval set by <update_ms> config value
if (time_ms() >= future_time) {
if (time_ms() >= future_time and not Global::resized) {
Runner::run("all");
update_ms = Config::getI("update_ms");
future_time = time_ms() + update_ms;
@ -884,11 +900,14 @@ int main(int argc, char **argv) {
//? Poll for input and process any input detected
else if (Input::poll(min(1000ul, future_time - current_time))) {
if (not Runner::active) Config::unlock();
Input::process(Input::get());
if (Menu::active) Menu::process(Input::get());
else Input::process(Input::get());
}
//? Break the loop at 1000ms intervals or if input polling was interrupted
else break;
}
}

View file

@ -20,12 +20,13 @@ tab-size = 4
#include <ranges>
#include <atomic>
#include <fstream>
#include <string_view>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
using std::array, std::atomic;
using std::array, std::atomic, std::string_view;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
@ -114,7 +115,8 @@ namespace Config {
{"show_cpu_freq", "#* Show CPU frequency."},
{"clock_format", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
{"clock_format", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.\n"
"#* Special formatting: /host = hostname | /user = username | /uptime = system uptime"},
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
@ -184,6 +186,7 @@ namespace Config {
{"log_level", "WARNING"},
{"proc_filter", ""},
{"proc_command", ""},
{"selected_name", ""},
};
unordered_flat_map<string, string> stringsTmp;
@ -272,6 +275,7 @@ namespace Config {
try {
if (Proc::shown) {
ints.at("selected_pid") = Proc::selected_pid;
strings.at("selected_name") = Proc::selected_name;
ints.at("proc_start") = Proc::start;
ints.at("proc_selected") = Proc::selected;
}
@ -337,7 +341,7 @@ namespace Config {
valid_names.push_back(n[0]);
string v_string;
getline(cread, v_string, '\n');
if (not v_string.ends_with(Global::Version))
if (not s_contains(v_string, Global::Version))
write_new = true;
while (not cread.eof()) {
cread >> std::ws;

View file

@ -27,6 +27,7 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include <btop_input.hpp>
#include <btop_menu.hpp>
using std::round, std::views::iota, std::string_literals::operator""s, std::clamp, std::array, std::floor, std::max, std::min,
@ -217,15 +218,13 @@ namespace Draw {
//* Meter class ------------------------------------------------------------------------------------------------------------>
Meter::Meter() {}
Meter::Meter(const int width, const string& color_gradient, const bool invert) : width(width), color_gradient(color_gradient), invert(invert) {
cache.insert(cache.begin(), 101, "");
}
Meter::Meter(const int width, const string& color_gradient, const bool invert) : width(width), color_gradient(color_gradient), invert(invert) {}
string Meter::operator()(int value) {
if (width < 1) return "";
value = clamp(value, 0, 100);
if (not cache.at(value).empty()) return cache.at(value);
string& out = cache.at(value);
auto& out = cache.at(value);
for (const int& i : iota(1, width + 1)) {
int y = round((double)i * 100.0 / width);
if (value >= y)
@ -292,11 +291,11 @@ namespace Draw {
out += graphs.at(current).at(0);
}
else {
for (const int& i : iota(0, height)) {
if (i > 0) out += Mv::d(1) + Mv::l(width);
for (const int& i : iota(1, height + 1)) {
if (i > 1) out += Mv::d(1) + Mv::l(width);
if (not color_gradient.empty())
out += (invert) ? Theme::g(color_gradient).at(i * 100 / (height - 1)) : Theme::g(color_gradient).at(100 - (i * 100 / (height - 1)));
out += (invert) ? graphs.at(current).at((height - 1) - i) : graphs.at(current).at(i);
out += (invert) ? Theme::g(color_gradient).at((i - 1) * 100 / (height - 1)) : Theme::g(color_gradient).at(100 - (i * 100 / height));
out += (invert) ? graphs.at(current).at(height - i) : graphs.at(current).at(i-1);
}
}
if (not color_gradient.empty()) out += Fx::reset;
@ -520,6 +519,7 @@ namespace Mem {
int x = 1, y, width = 20, height;
int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter;
int disks_io_h = 0;
int disks_io_half = 0;
bool shown = true, redraw = true;
string box;
unordered_flat_map<string, Draw::Meter> mem_meters;
@ -573,45 +573,50 @@ namespace Mem {
//? Disk meters and io graphs
if (show_disks) {
if (show_io_stat or io_mode) {
if (io_mode)
disks_io_h = max((int)floor((double)(height - 2 - disk_ios) / max(1, disk_ios)), (io_graph_combined ? 1 : 2));
else
disks_io_h = 1;
int half_height = ceil((double)disks_io_h / 2);
unordered_flat_map<string, int> custom_speeds;
if (not Config::getS("io_graph_speeds").empty()) {
auto split = ssplit(Config::getS("io_graph_speeds"));
for (const auto& entry : split) {
auto vals = ssplit(entry);
if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1)))
custom_speeds[vals.at(0)] = std::stoi(vals.at(1));
int half_height = 0;
if (io_mode) {
disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2));
half_height = ceil((double)disks_io_h / 2);
if (not Config::getS("io_graph_speeds").empty()) {
auto split = ssplit(Config::getS("io_graph_speeds"));
for (const auto& entry : split) {
auto vals = ssplit(entry);
if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1)))
custom_speeds[vals.at(0)] = std::stoi(vals.at(1));
}
}
}
for (const auto& [name, disk] : mem.disks) {
if (disk.io_read.empty()) continue;
long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 10) << 20;
//? Create one combined graph for IO read/write if enabled
if (not io_mode or (io_mode and io_graph_combined)) {
deque<long long> combined(disk.io_read.size(), 0);
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
io_graphs[name] = Draw::Graph{disks_width - (io_mode ? 0 : 6), disks_io_h, "available", combined, graph_symbol, false, true, speed};
}
else {
io_graphs[name + "_read"] = Draw::Graph{disks_width, half_height, "free", disk.io_read, graph_symbol, false, true, speed};
io_graphs[name + "_write"] = Draw::Graph{disks_width, disks_io_h - half_height, "used", disk.io_write, graph_symbol, true, true, speed};
io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "available", disk.io_activity, graph_symbol, false, true};
if (io_mode) {
//? Create one combined graph for IO read/write if enabled
long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 10) << 20;
if (io_graph_combined) {
deque<long long> combined(disk.io_read.size(), 0);
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
io_graphs[name] = Draw::Graph{disks_width - (io_mode ? 0 : 6), disks_io_h, "available", combined, graph_symbol, false, true, speed};
}
else {
io_graphs[name + "_read"] = Draw::Graph{disks_width, half_height, "free", disk.io_read, graph_symbol, false, true, speed};
io_graphs[name + "_write"] = Draw::Graph{disks_width, disks_io_h - half_height, "used", disk.io_write, graph_symbol, true, true, speed};
}
}
}
}
if (disk_meter > 0) {
for (int i = 0; const auto& name : mem.disks_order) {
if (i * 2 > height - 2) break;
disk_meters_used[name] = Draw::Meter{disk_meter, "used"};
if (cmp_less_equal(mem.disks_order.size() * 3, height - 1))
disk_meters_free[name] = Draw::Meter{disk_meter, "free"};
}
for (int i = 0; const auto& [name, ignored] : mem.disks) {
if (i * 2 > height - 2) break;
disk_meters_used[name] = Draw::Meter{disk_meter, "used"};
if (cmp_less_equal(mem.disks.size() * 3, height - 1))
disk_meters_free[name] = Draw::Meter{disk_meter, "free"};
}
out += Mv::to(y, x + width - 6) + Fx::ub + Theme::c("mem_box") + Symbols::title_left + (io_mode ? Fx::b : "") + Theme::c("hi_fg")
+ 'i' + Theme::c("title") + 'o' + Fx::ub + Theme::c("mem_box") + Symbols::title_right;
Input::mouse_mappings["i"] = {y, x + width - 5, 1, 2};
@ -683,6 +688,7 @@ namespace Mem {
const string used_percent = to_string(disk.used_percent);
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2)) + Theme::c("main_fg") + used_percent + '%';
}
out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same);
if (++cy > height - 3) break;
if (io_graph_combined) {
auto comb_val = disk.io_read.back() + disk.io_write.back();
@ -721,8 +727,8 @@ namespace Mem {
if (big_disk and not human_io.empty())
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)human_io.size() / 2)) + Theme::c("main_fg") + human_io;
if (++cy > height - 3) break;
if (show_io_stat and io_graphs.contains(mount)) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO: " : " IO " + Mv::l(2)) + io_graphs.at(mount)({comb_val}, redraw or data_same);
if (show_io_stat and io_graphs.contains(mount + "_activity")) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same);
if (not big_disk) out += Mv::to(y+1+cy, x+cx) + Theme::c("main_fg") + human_io;
if (++cy > height - 3) break;
}
@ -786,6 +792,8 @@ namespace Net {
out = box;
//? Graphs
graphs.clear();
if (net.bandwidth.at("download").empty() or net.bandwidth.at("upload").empty())
return out + Fx::reset;
graphs["download"] = Draw::Graph{width - b_width - 2, u_graph_height, "download", net.bandwidth.at("download"), graph_symbol, false, true, down_max};
graphs["upload"] = Draw::Graph{width - b_width - 2, d_graph_height, "upload", net.bandwidth.at("upload"), graph_symbol, true, true, up_max};
@ -851,6 +859,7 @@ namespace Proc {
int start, selected, select_max;
bool shown = true, redraw = true;
int selected_pid = 0;
string selected_name;
unordered_flat_map<size_t, Draw::Graph> p_graphs;
unordered_flat_map<size_t, int> p_counters;
int counter = 0;
@ -990,7 +999,7 @@ namespace Proc {
int mouse_x = d_x + 2;
out += Mv::to(d_y, d_x + 1);
if (width > 55) {
out += title_left + hi_color + Fx::b + 't' + t_color + "erminate" + Fx::ub + title_right;
out += Fx::ub + title_left + hi_color + Fx::b + 't' + t_color + "erminate" + Fx::ub + title_right;
if (alive and selected == 0) Input::mouse_mappings["t"] = {d_y, mouse_x, 1, 9};
mouse_x += 11;
}
@ -1121,7 +1130,7 @@ namespace Proc {
string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : "");
if (alive) {
cpu_str.resize((detailed.entry.cpu_p < 10 or detailed.entry.cpu_p >= 100 ? 3 : 4));
cpu_str += '%' + Mv::r(1) + (dgraph_width < 20 ? "C" : "Core") + to_string(detailed.entry.cpu_n);
cpu_str += '%';
}
out += Mv::to(d_y + 1, dgraph_x + 1) + Fx::ub + detailed_cpu_graph(detailed.cpu_percent, (redraw or data_same or not alive))
+ Mv::to(d_y + 1, dgraph_x + 1) + Theme::c("title") + Fx::b + cpu_str;
@ -1168,7 +1177,10 @@ namespace Proc {
for (int n=0; auto& p : plist) {
if (n++ < start or p.filtered) continue;
bool is_selected = (lc + 1 == selected);
if (is_selected) selected_pid = (int)p.pid;
if (is_selected) {
selected_pid = (int)p.pid;
selected_name = p.name;
}
//? Update graphs for processes with above 0.0% cpu usage, delete if below 0.1% 10x times
const bool has_graph = p_counters.contains(p.pid);
@ -1275,7 +1287,7 @@ namespace Proc {
//? Current selection and number of processes
string location = to_string(start + selected) + '/' + to_string(numpids);
string loc_clear = Symbols::h_line * max(0ul, 9 - location.size());
out += Mv::to(y + height - 1, x+width - 3 - max(9, (int)location.size())) + Theme::c("proc_box") + loc_clear
out += Mv::to(y + height - 1, x+width - 3 - max(9, (int)location.size())) + Fx::ub + Theme::c("proc_box") + loc_clear
+ Symbols::title_left_down + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right_down;
//? Clear out left over graphs from dead processes at a regular interval
@ -1293,7 +1305,10 @@ namespace Proc {
p_counters.compact();
}
if (selected == 0 and selected_pid != 0) selected_pid = 0;
if (selected == 0 and selected_pid != 0) {
selected_pid = 0;
selected_name.clear();
}
redraw = false;
return out + Fx::reset;
}
@ -1303,6 +1318,7 @@ namespace Proc {
namespace Draw {
void calcSizes() {
atomic_wait(Runner::active);
Config::unlock();
auto& boxes = Config::getS("shown_boxes");
auto& cpu_bottom = Config::getB("cpu_bottom");
auto& mem_below_net = Config::getB("mem_below_net");
@ -1314,8 +1330,10 @@ namespace Draw {
Proc::box.clear();
Global::clock.clear();
Global::overlay.clear();
if (Menu::active) Menu::redraw = true;
Input::mouse_mappings.clear();
Menu::mouse_mappings.clear();
Cpu::x = Mem::x = Net::x = Proc::x = 1;
Cpu::y = Mem::y = Net::y = Proc::y = 1;

View file

@ -20,10 +20,11 @@ tab-size = 4
#include <string>
#include <vector>
#include <array>
#include <robin_hood.h>
#include <deque>
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque;
using std::string, std::array, std::vector, robin_hood::unordered_flat_map, std::deque;
namespace Symbols {
const string h_line = "";
@ -76,7 +77,7 @@ namespace Draw {
int width;
string color_gradient;
bool invert;
vector<string> cache;
array<string, 101> cache;
public:
Meter();
Meter(const int width, const string& color_gradient, const bool invert = false);

View file

@ -18,6 +18,7 @@ tab-size = 4
#include <iostream>
#include <ranges>
#include <vector>
#include <btop_input.hpp>
#include <btop_tools.hpp>
@ -27,7 +28,7 @@ tab-size = 4
#include <btop_draw.hpp>
#include <signal.h>
using std::cin, std::string_literals::operator""s;
using std::cin, std::vector, std::string_literals::operator""s;
using namespace Tools;
namespace rng = std::ranges;
@ -80,7 +81,10 @@ namespace Input {
bool poll(int timeout) {
if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
while (timeout > 0) {
if (interrupt) return interrupt = false;
if (interrupt) {
interrupt = false;
return false;
}
if (cin.rdbuf()->in_avail() > 0) return true;
sleep_ms(timeout < 10 ? timeout : 10);
timeout -= 10;
@ -178,12 +182,14 @@ namespace Input {
if (key.empty()) return;
try {
auto& filtering = Config::getB("proc_filtering");
if (not filtering and key == "q") exit(0);
//? Global input actions
if (not filtering) {
bool keep_going = false;
if (is_in(key, "1", "2", "3", "4")) {
if (str_to_lower(key) == "q") {
exit(0);
}
else if (is_in(key, "1", "2", "3", "4")) {
atomic_wait(Runner::active);
static const array<string, 4> boxes = {"cpu", "mem", "net", "proc"};
Config::toggle_box(boxes.at(std::stoi(key) - 1));
@ -250,15 +256,9 @@ namespace Input {
Config::set("proc_filter", ""s);
else if (key == "ö") {
if (Global::overlay.empty()) {
Global::overlay = Mv::to(Term::height / 2, Term::width / 2) + "\x1b[1;32mTESTING";
Menu::active = true;
}
else {
Global::overlay.clear();
Menu::active = false;
}
Runner::run("all", true, true);
Menu::menuMask.set(Menu::Menus::SignalSend);
Menu::process();
return;
}
else if (key.starts_with("mouse_")) {
redraw = false;
@ -321,16 +321,20 @@ namespace Input {
if (key == "-" or key == "space") Proc::collapse = pid;
no_update = false;
}
else if (key == "t") {
Logger::debug(key);
else if (is_in(key, "t", "k") and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) {
atomic_wait(Runner::active);
if (Config::getB("show_detailed") and Config::getI("proc_selected") == 0 and Proc::detailed.status == "Dead") return;
Menu::menuMask.set(Menu::SignalSend);
Menu::signalToSend = (key == "t" ? SIGTERM : SIGKILL);
Menu::process();
return;
}
else if (key == "k") {
Logger::debug(key);
return;
}
else if (key == "s") {
Logger::debug(key);
else if (key == "s" and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) {
atomic_wait(Runner::active);
if (Config::getB("show_detailed") and Config::getI("proc_selected") == 0 and Proc::detailed.status == "Dead") return;
Menu::menuMask.set(Menu::SignalChoose);
Menu::signalToSend = -1;
Menu::process();
return;
}
else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) {

View file

@ -16,25 +16,47 @@ indent = tab
tab-size = 4
*/
#include <vector>
#include <deque>
#include <robin_hood.h>
#include <array>
#include <ranges>
#include <signal.h>
#include <errno.h>
#include <btop_menu.hpp>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_theme.hpp>
#include <btop_draw.hpp>
#include <btop_shared.hpp>
using std::vector, std::deque, robin_hood::unordered_flat_map, std::array;
using std::deque, robin_hood::unordered_flat_map, std::array, std::views::iota, std::ref;
using namespace Tools;
namespace rng = std::ranges;
namespace Menu {
atomic<bool> active (false);
string output;
string bg;
bool redraw = true;
int currentMenu = -1;
msgBox messageBox;
int signalToSend = 0;
int signalKillRet = 0;
unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
const array<string, 32> P_Signals = {
"0",
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
"SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2",
"SIGPIPE", "SIGALRM", "SIGTERM", "16", "SIGCHLD",
"SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN",
"SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ",
"SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO",
"SIGPWR", "SIGSYS"
};
unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
const unordered_flat_map<string, unordered_flat_map<string, vector<string>>> menus = {
{ "options", {
@ -74,4 +96,317 @@ namespace Menu {
} }
} }
};
msgBox::msgBox() {};
msgBox::msgBox(int width, int boxtype, vector<string>& content, string title)
: width(width), boxtype(boxtype) {
const auto& tty_mode = Config::getB("tty_mode");
const auto& rounded = Config::getB("rounded_corners");
const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
const auto& left_down = (tty_mode or not rounded ? Symbols::left_down : Symbols::round_left_down);
height = content.size() + 7;
x = Term::width / 2 - width / 2;
y = Term::height/2 - height/2;
if (boxtype == 2) selected = 1;
button_left = left_up + Symbols::h_line * 6 + Mv::l(7) + Mv::d(2) + left_down + Symbols::h_line * 6 + Mv::l(7) + Mv::u(1) + Symbols::v_line;
button_right = Symbols::v_line + Mv::l(7) + Mv::u(1) + Symbols::h_line * 6 + right_up + Mv::l(7) + Mv::d(2) + Symbols::h_line * 6 + right_down + Mv::u(2);
box_contents = Draw::createBox(x, y, width, height, Theme::c("hi_fg"), true, title) + Mv::d(1);
for (const auto& line : content) {
box_contents += Mv::save + Mv::r(width / 2 - Fx::uncolor(line).size() / 2) + line + Mv::restore + Mv::d(1);
}
}
string msgBox::operator()() {
string out;
int pos = width / 2 - (boxtype == 0 ? 6 : 14);
auto& first_color = (selected == 0 ? Theme::c("hi_fg") : Theme::c("div_line"));
out = Mv::d(1) + Mv::r(pos) + Fx::b + first_color + button_left + (selected == 0 ? Theme::c("title") : Theme::c("main_fg") + Fx::ub)
+ (boxtype == 0 ? " Ok " : " Yes ") + first_color + button_right;
mouse_mappings["button1"] = Input::Mouse_loc{y + height - 4, x + pos + 1, 3, 12 + (boxtype > 0 ? 1 : 0)};
if (boxtype > 0) {
auto& second_color = (selected == 1 ? Theme::c("hi_fg") : Theme::c("div_line"));
out += Mv::r(2) + second_color + button_left + (selected == 1 ? Theme::c("title") : Theme::c("main_fg") + Fx::ub)
+ " No " + second_color + button_right;
mouse_mappings["button2"] = Input::Mouse_loc{y + height - 4, x + pos + 15 + (boxtype > 0 ? 1 : 0), 3, 12};
}
return box_contents + out + Fx::reset;
}
//? Process input
int msgBox::input(string key) {
if (key.empty()) return Invalid;
if (is_in(key, "escape", "backspace", "q") or key == "button2") {
return No_Esc;
}
else if (key == "button1" or (boxtype == 0 and str_to_upper(key) == "O")) {
return Ok_Yes;
}
else if (is_in(key, "enter", "space")) {
return selected + 1;
}
else if (boxtype == 0) {
return Invalid;
}
else if (str_to_upper(key) == "Y") {
return Ok_Yes;
}
else if (str_to_upper(key) == "N") {
return No_Esc;
}
else if (is_in(key, "right", "tab")) {
if (++selected > 1) selected = 0;
return Select;
}
else if (is_in(key, "left", "shift_tab")) {
if (--selected < 0) selected = 1;
return Select;
}
return Invalid;
}
void msgBox::clear() {
box_contents.clear();
box_contents.shrink_to_fit();
button_left.clear();
button_left.shrink_to_fit();
button_right.clear();
button_right.shrink_to_fit();
if (mouse_mappings.contains("button1")) mouse_mappings.erase("button1");
if (mouse_mappings.contains("button2")) mouse_mappings.erase("button2");
}
enum menuReturnCodes {
NoChange,
Changed,
Closed
};
int signalChoose(const string& key) {
auto& s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
static int x = 0, y = 0, selected_signal = -1;
if (bg.empty()) selected_signal = -1;
auto& out = Global::overlay;
int retval = Changed;
if (redraw) {
x = Term::width/2 - 40;
y = Term::height/2 - 9;
bg = Draw::createBox(x, y, 80, 18, Theme::c("hi_fg"), true, "signals");
bg += Mv::to(y+2, x+1) + Theme::c("title") + Fx::b + cjust("Send signal to PID " + to_string(s_pid) + " ("
+ uresize((s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name")), 30) + ")", 78);
}
if (is_in(key, "escape", "q")) {
return Closed;
}
else if (is_in(key, "enter", "space") and selected_signal >= 0) {
signalKillRet = 0;
if (s_pid < 1) {
signalKillRet = ESRCH;
menuMask.set(SignalReturn);
}
else if (kill(s_pid, selected_signal) != 0) {
signalKillRet = errno;
menuMask.set(SignalReturn);
}
return Closed;
}
else if (key.size() == 1 and isdigit(key.at(0)) and selected_signal < 10) {
selected_signal = std::min(std::stoi((selected_signal < 1 ? key : to_string(selected_signal) + key)), 64);
}
else if (key == "backspace" and selected_signal != -1) {
selected_signal = (selected_signal < 10 ? -1 : selected_signal / 10);
}
else if (key == "up" and selected_signal != 16) {
if (selected_signal < 6) selected_signal += 25;
else {
bool offset = (selected_signal > 16);
selected_signal -= 5;
if (selected_signal <= 16 and offset) selected_signal--;
}
}
else if (key == "down") {
if (selected_signal < 1 or selected_signal == 16) selected_signal = 1;
else if (selected_signal > 26) selected_signal -= 25;
else {
bool offset = (selected_signal < 16);
selected_signal += 5;
if (selected_signal >= 16 and offset) selected_signal++;
if (selected_signal > 31) selected_signal = 31;
}
}
else if (key == "left" and selected_signal > 1 and selected_signal != 16) {
selected_signal--;
if (selected_signal == 16) selected_signal--;
}
else if (key == "right" and selected_signal < 31 and selected_signal != 16) {
selected_signal++;
if (selected_signal == 16) selected_signal++;
}
else {
retval = NoChange;
}
int cy = y+3;
out = bg + Mv::to(cy++, x+1) + Theme::c("main_fg") + Fx::ub
+ rjust("Enter signal number: ", 48) + (selected_signal >= 0 ? to_string(selected_signal) : "") + Fx::bl + "" + Fx::ubl;
out += Mv::to(++cy, x+4);
auto sig_str = to_string(selected_signal);
for (int count = 0, i = 0; const auto& sig : P_Signals) {
if (count == 0 or count == 16) { count++; continue; }
if (i++ % 5 == 0) out += Mv::to(++cy, x+4);
if (count == selected_signal) out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b + ljust(to_string(count), 3) + ljust('(' + sig + ')', 12) + Fx::reset;
else out += Theme::c("hi_fg") + ljust(to_string(count), 3) + Theme::c("main_fg") + ljust('(' + sig + ')', 12);
count++;
}
cy++;
out += Mv::to(++cy, x+1) + Fx::b + rjust("ENTER | ", 35) + Fx::ub + "To send signal.";
out += Mv::to(++cy, x+1) + Fx::b + rjust( "↑ ↓ ← → | ", 35, true) + Fx::ub + "To choose signal.";
out += Mv::to(++cy, x+1) + Fx::b + rjust("ESC or \"q\" | ", 35) + Fx::ub + "To abort.";
out += Fx::reset;
return (redraw ? Changed : retval);
}
int signalSend(const string& key) {
auto& s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
if (s_pid == 0) return Closed;
if (redraw) {
atomic_wait(Runner::active);
auto& p_name = (s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name"));
vector<string> cont_vec = {
Fx::b + Theme::c("main_fg") + "Send signal: " + Fx::ub + Theme::c("hi_fg") + to_string(signalToSend)
+ (signalToSend > 0 and signalToSend <= 32 ? Theme::c("main_fg") + " (" + P_Signals.at(signalToSend) + ')' : ""),
Fx::b + Theme::c("main_fg") + "To PID: " + Fx::ub + Theme::c("hi_fg") + to_string(s_pid) + Theme::c("main_fg") + " ("
+ uresize(p_name, 16) + ')' + Fx::reset,
};
messageBox = Menu::msgBox{50, 1, cont_vec, (signalToSend > 1 and signalToSend <= 32 and signalToSend != 17 ? P_Signals.at(signalToSend) : "signal")};
Global::overlay = messageBox();
}
auto ret = messageBox.input(key);
if (ret == msgBox::Ok_Yes) {
signalKillRet = 0;
if (kill(s_pid, signalToSend) != 0) {
signalKillRet = errno;
menuMask.set(SignalReturn);
}
messageBox.clear();
return Closed;
}
else if (ret == msgBox::No_Esc) {
messageBox.clear();
return Closed;
}
else if (ret == msgBox::Select) {
Global::overlay = messageBox();
return Changed;
}
else if (redraw) {
return Changed;
}
return NoChange;
}
int signalReturn(const string& key) {
if (redraw) {
vector<string> cont_vec;
cont_vec.push_back(Fx::b + Theme::g("used")[100] + "Failure:" + Theme::c("main_fg") + Fx::ub);
if (signalKillRet == EINVAL) {
cont_vec.push_back("Unsupported signal!" + Fx::reset);
}
else if (signalKillRet == EPERM) {
cont_vec.push_back("Insufficient permissions to send signal!" + Fx::reset);
}
else if (signalKillRet == ESRCH) {
cont_vec.push_back("Process not found!" + Fx::reset);
}
else {
cont_vec.push_back("Unknown error! (errno: " + to_string(signalKillRet) + ')' + Fx::reset);
}
messageBox = Menu::msgBox{50, 0, cont_vec, "error"};
Global::overlay = messageBox();
}
auto ret = messageBox.input(key);
if (ret == msgBox::Ok_Yes or ret == msgBox::No_Esc) {
messageBox.clear();
return Closed;
}
else if (redraw) {
return Changed;
}
return NoChange;
}
int mainMenu(const string& key) {
(void)key;
return NoChange;
}
int optionsMenu(const string& key) {
(void)key;
return NoChange;
}
int helpMenu(const string& key) {
(void)key;
return NoChange;
}
//* Add menus here and update enum Menus in header
const auto menuFunc = vector{
ref(signalChoose),
ref(signalSend),
ref(signalReturn),
ref(optionsMenu),
ref(helpMenu),
ref(mainMenu),
};
bitset<8> menuMask;
void process(string key) {
if (menuMask.none()) {
Menu::active = false;
Global::overlay.clear();
Global::overlay.shrink_to_fit();
bg.clear();
bg.shrink_to_fit();
currentMenu = -1;
Runner::run("all", true, true);
return;
}
if (currentMenu < 0 or not menuMask.test(currentMenu)) {
Menu::active = true;
redraw = true;
for (const auto& i : iota(0, (int)menuMask.size())) {
if (menuMask.test(i)) currentMenu = i;
}
}
auto retCode = menuFunc.at(currentMenu)(key);
if (retCode == Closed) {
menuMask.reset(currentMenu);
process();
}
else if (redraw) {
redraw = false;
Runner::run("all", true, true);
}
else if (retCode == Changed)
Runner::run("overlay");
}
}

View file

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@ -20,17 +20,63 @@ tab-size = 4
#include <string>
#include <atomic>
#include <vector>
#include <bitset>
#include <btop_input.hpp>
using std::string, std::atomic;
using std::string, std::atomic, std::vector, std::bitset;
namespace Menu {
extern atomic<bool> active;
extern string output;
extern int signalToSend;
extern bool redraw;
//? line, col, height, width
//? line, col, height, width
extern unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
//* Creates a message box centered on screen
//? Height of box is determined by size of content vector
//? Boxtypes: 0 = OK button | 1 = YES and NO with YES selected | 2 = Same as 1 but with NO selected
//? Strings in content vector is not checked for box width overflow
class msgBox {
string box_contents, button_left, button_right;
int height = 0, width = 0, boxtype = 0, selected = 0, x = 0, y = 0;
public:
enum msgReturn {
Invalid,
Ok_Yes,
No_Esc,
Select
};
msgBox();
msgBox(int width, int boxtype, vector<string>& content, string title);
//? Draw and return box as a string
string operator()();
//? Process input and returns value from enum Ret
int input(string key);
//? Clears content vector and private strings
void clear();
};
extern bitset<8> menuMask;
//* Enum for functions in vector menuFuncs
enum Menus {
SignalChoose,
SignalSend,
SignalReturn,
Options,
Help,
Main
};
//* Handles redirection of input for menu functions and handles return codes
void process(string key="");
}

View file

@ -116,9 +116,10 @@ namespace Mem {
std::filesystem::path stat = "";
int64_t total = 0, used = 0, free = 0;
int used_percent = 0, free_percent = 0;
array<int64_t, 2> old_io = {0, 0};
array<int64_t, 3> old_io = {0, 0, 0};
deque<long long> io_read = {};
deque<long long> io_write = {};
deque<long long> io_activity = {};
};
struct mem_info {
@ -175,6 +176,7 @@ namespace Proc {
extern int select_max;
extern atomic<int> detailed_pid;
extern int selected_pid, start, selected, collapse, expand;
extern string selected_name;
//? Contains the valid sorting options for processes
const vector<string> sort_vector = {
@ -208,12 +210,13 @@ namespace Proc {
size_t pid = 0;
string name = "", cmd = "";
string short_cmd = "";
size_t threads = 0, name_offset = 0;
size_t threads = 0;
int name_offset = 0;
string user = "";
uint64_t mem = 0;
double cpu_p = 0.0, cpu_c = 0.0;
char state = '0';
uint64_t cpu_n = 0, p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0;
uint64_t p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0;
string prefix = "";
size_t depth = 0, tree_index = 0;
bool collapsed = false, filtered = false;

View file

@ -65,6 +65,7 @@ namespace Fx {
//* Return a string with all colors and text styling removed
inline string uncolor(const string& s) { return regex_replace(s, color_regex, ""); }
}
//* Collection of escape codes and functions for cursor manipulation
@ -129,7 +130,6 @@ namespace Term {
namespace Tools {
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
constexpr auto ZeroSec = std::chrono::seconds(0);
extern atomic<int> active_locks;
//* Return number of UTF8 characters in a string (wide=true counts UTF-8 characters with a width > 1 as 2 characters)

View file

@ -31,8 +31,8 @@ tab-size = 4
#include <btop_config.hpp>
#include <btop_tools.hpp>
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize, std::round, std::max, std::min,
std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min;
using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
@ -73,8 +73,8 @@ namespace Cpu {
unordered_flat_map<int, int> core_mapping;
}
namespace Net {
uint64_t timestamp = 0;
namespace Mem {
double old_uptime;
}
namespace Shared {
@ -136,12 +136,9 @@ namespace Shared {
Cpu::core_mapping = Cpu::get_core_mapping();
//? Init for namespace Mem
Mem::old_uptime = system_uptime();
Mem::collect();
//? Init for namespace Net
Net::timestamp = time_ms();
Net::collect();
}
}
@ -545,7 +542,6 @@ namespace Mem {
int disk_ios = 0;
vector<string> last_found;
mem_info current_mem {};
auto collect(const bool no_update) -> mem_info& {
@ -614,6 +610,7 @@ namespace Mem {
//? Get disks stats
if (show_disks) {
double uptime = system_uptime();
try {
auto& disks_filter = Config::getS("disks_filter");
bool filter_exclude = false;
@ -753,7 +750,7 @@ namespace Mem {
if (not is_in(name, "/", "swap")) mem.disks_order.push_back(name);
//? Get disks IO
int64_t sectors_read, sectors_write;
int64_t sectors_read, sectors_write, io_ticks;
disk_ios = 0;
for (auto& [ignored, disk] : disks) {
if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue;
@ -777,9 +774,19 @@ namespace Mem {
disk.io_write.push_back(max(0l, (sectors_write - disk.old_io.at(1)) * 512));
disk.old_io.at(1) = sectors_write;
while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front();
for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
diskread >> io_ticks;
if (disk.io_activity.empty())
disk.io_activity.push_back(0);
else
disk.io_activity.push_back(clamp((long)round((double)(io_ticks - disk.old_io.at(2)) / (uptime - old_uptime) / 10), 0l, 100l));
disk.old_io.at(2) = io_ticks;
while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front();
}
diskread.close();
}
old_uptime = uptime;
}
catch (const std::exception& e) {
Logger::warning("Error in Mem::collect() : " + (string)e.what());
@ -800,6 +807,7 @@ namespace Net {
unordered_flat_map<string, uint64_t> graph_max = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, array<int, 2>> max_count = { {"download", {}}, {"upload", {}} };
bool rescale = true;
uint64_t timestamp = 0;
//* RAII wrapper for getifaddrs
class getifaddr_wrapper {
@ -824,6 +832,7 @@ namespace Net {
if (if_wrap.status != 0) {
errors++;
Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status));
redraw = true;
return empty_net;
}
int family = 0;
@ -931,7 +940,9 @@ namespace Net {
break;
}
//? If no interface is connected set to first available
if (selected_iface.empty()) selected_iface = sorted_interfaces.at(0);
if (selected_iface.empty() and not sorted_interfaces.empty()) selected_iface = sorted_interfaces.at(0);
else if (sorted_interfaces.empty()) return empty_net;
}
}
@ -1152,7 +1163,7 @@ namespace Proc {
const auto& tree = Config::getB("proc_tree");
const auto& show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid");
const bool should_filter = current_filter != filter;
bool should_filter = current_filter != filter;
if (should_filter) current_filter = filter;
const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
if (sorted_change) {
@ -1174,6 +1185,8 @@ namespace Proc {
}
//* ---------------------------------------------Collection start----------------------------------------------
else {
should_filter = true;
//? Update uid_user map if /etc/passwd changed since last run
if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != passwd_time) {
string r_uid, r_user;
@ -1269,20 +1282,18 @@ namespace Proc {
const auto& offset = new_proc.name_offset;
short_str.clear();
size_t x = 0, next_x = 3;
int x = 0, next_x = 3;
uint64_t cpu_t = 0;
try {
while (x < 40) {
while (pread.good() and ++x < next_x + offset) {
pread.ignore(SSmax, ' ');
}
if (pread.bad()) break;
for (;;) {
while (++x < next_x + offset) pread.ignore(SSmax, ' ');
getline(pread, short_str, ' ');
if (not pread.good()) break;
switch (x-offset) {
case 3: //? Process state
new_proc.state = short_str.at(0);
if (new_proc.ppid != 0) next_x = 14;
continue;
case 4: //? Parent pid
new_proc.ppid = stoull(short_str);
@ -1313,18 +1324,14 @@ namespace Proc {
continue;
case 24: //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x)
new_proc.mem = stoull(short_str) * Shared::pageSize;
next_x = 39;
continue;
case 39: //? CPU number last executed on
new_proc.cpu_n = stoull(short_str);
x++;
break;
}
break;
}
}
catch (const std::invalid_argument&) { continue; }
catch (const std::out_of_range&) { continue; }
pread.close();
if (x-offset < 24) continue;