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 #* 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") override 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 TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0")
PREFIX ?= /usr/local PREFIX ?= /usr/local
#? Compiler and Linker #? NOTICE! Manually set PLATFORM and ARCH if not compiling for host system
CXX ?= g++ PLATFORM ?= $(shell uname -s || echo unknown)
CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0) ARCH ?= $(shell uname -p || echo unknown)
#? 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
#? Only enable fcf-protection if on x86_64 #? Only enable fcf-protection if on x86_64
ARCH := $(shell uname -p || echo unknown)
ifeq ($(ARCH),unknown) ifeq ($(ARCH),unknown)
ARCH := $(shell uname -m || echo unknown) ARCH := $(shell uname -m || echo unknown)
endif endif
@ -30,11 +19,34 @@ ifeq ($(ARCH),x86_64)
ADDFLAGS = -fcf-protection ADDFLAGS = -fcf-protection
endif endif
#? Manually set this to (Linux|FreeBSD|Darwin) if not building for host platform #? Make sure PLATFORM Darwin is OSX and not Darwin
PLATFORM ?= $(shell uname -s || echo unknown) 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+) #? 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 #? The Directories, Source, Includes, Objects and Binary
SRCDIR := src SRCDIR := src
@ -46,10 +58,10 @@ DEPEXT := d
OBJEXT := o OBJEXT := o
#? Flags, Libraries and Includes #? Flags, Libraries and Includes
REQFLAGS := -std=c++20 override REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic -pedantic-errors -Wfatal-errors WARNFLAGS := -Wall -Wextra -pedantic -pedantic-errors -Wfatal-errors
OPTFLAGS := -O2 -ftree-loop-vectorize OPTFLAGS ?= -O2 -ftree-loop-vectorize -flto=$(THREADS)
override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -flto $(ADDFLAGS) LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection $(ADDFLAGS)
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR) INC := -I$(INCDIR) -I$(SRCDIR)
@ -58,29 +70,38 @@ SU_GROUP := root
SOURCES := $(shell find $(SRCDIR) -maxdepth 1 -type f -name *.$(SRCEXT)) SOURCES := $(shell find $(SRCDIR) -maxdepth 1 -type f -name *.$(SRCEXT))
#? Pull in platform specific source files
ifeq ($(PLATFORM),Linux) ifeq ($(PLATFORM),Linux)
SOURCES += $(shell find $(SRCDIR)/linux -type f -name *.$(SRCEXT)) PLATFORM_DIR = linux
endif else ifeq ($(PLATFORM),FreeBSD)
ifeq ($(PLATFORM),FreeBSD) PLATFORM_DIR = freebsd
SOURCES += $(shell find $(SRCDIR)/freebsd -type f -name *.$(SRCEXT)) else ifeq ($(PLATFORM),OSX)
endif PLATFORM_DIR = osx
ifeq ($(PLATFORM),Darwin) else
SOURCES += $(shell find $(SRCDIR)/osx -type f -name *.$(SRCEXT)) $(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m"))
endif endif
SOURCES += $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
#? Default Make #? Default Make
all: msg directories btop all: pre directories btop
msg: pre:
@printf " $(BANNER)\n" @printf " $(BANNER)\n"
@printf "\033[1;97mCXX : \033[0m$(CXX) ($(CXX_VERSION))\n" @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n"
@printf "\033[1;97mREQFLAGS : \033[0m$(REQFLAGS)\n" @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n"
@printf "\033[1;97mWARNFLAGS : \033[0m$(WARNFLAGS)\n" @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n"
@printf "\033[1;97mOPTFLAGS : \033[0m$(OPTFLAGS)\n" @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n"
@printf "\033[1;97mLDCXXFLAGS : \033[0m$(LDCXXFLAGS)\n" @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\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;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: help:
@printf "\033[1;97mbtop++ makefile\033[0m\n" @printf "\033[1;97mbtop++ makefile\033[0m\n"
@ -96,10 +117,7 @@ help:
#? Make the Directories #? Make the Directories
directories: directories:
@mkdir -p $(TARGETDIR) @mkdir -p $(TARGETDIR)
@mkdir -p $(BUILDDIR) @mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)
@mkdir -p $(BUILDDIR)/linux
@mkdir -p $(BUILDDIR)/freebsd
@mkdir -p $(BUILDDIR)/osx
#? Clean only Objects #? Clean only Objects
clean: clean:
@ -122,7 +140,7 @@ install:
@printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\n" @printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\n"
@cp -pr themes $(DESTDIR)$(PREFIX)/share/btop @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: setuid:
@printf "\033[1;97mFile: $(DESTDIR)$(PREFIX)/bin/btop\n" @printf "\033[1;97mFile: $(DESTDIR)$(PREFIX)/bin/btop\n"
@printf "\033[1;92mSetting owner \033[1;97m$(SU_USER):$(SU_GROUP)\033[0m\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)) -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
#? Link #? Link
.ONESHELL:
btop: $(OBJECTS) btop: $(OBJECTS)
@sleep 0.1 2>/dev/null || true @sleep 0.1 2>/dev/null || true
@printf "\n\033[1;92mLinking and optimizing binary\033[0m\n" @TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) @printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n"
@printf "\033[1;97m./$(TARGETDIR)/btop ($$(du -ah $(TARGETDIR)/btop | cut -f1)iB)\033[0m\n" @$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) || exit 1
@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" @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 #? Compile
.ONESHELL:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
@sleep 0.1 2>/dev/null || true @sleep 0.1 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@printf "\033[1;97mCompiling $<\033[0m\n" @printf "\033[1;97mCompiling $<\033[0m\n"
@$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< @$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< || exit 1
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null @$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null || exit 1
@cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp @cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp
@sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT) @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) @sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT)
@rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp @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 #? Non-File Targets
.PHONY: all msg help .PHONY: all msg help pre

View File

@ -29,6 +29,7 @@ tab-size = 4
#include <exception> #include <exception>
#include <tuple> #include <tuple>
#include <regex> #include <regex>
#include <chrono>
#include <btop_shared.hpp> #include <btop_shared.hpp>
#include <btop_tools.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 fs = std::filesystem;
namespace rng = std::ranges; namespace rng = std::ranges;
using namespace Tools; using namespace Tools;
using namespace std::chrono_literals;
namespace Global { namespace Global {
const vector<array<string, 2>> Banner_src = { const vector<array<string, 2>> Banner_src = {
@ -140,12 +142,13 @@ void term_resize(bool force) {
Global::resized = true; Global::resized = true;
Runner::stop(); 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); sleep_ms(100);
if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) { 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) 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) << "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 << " 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; << "Width = " << min_size.at(0) << " Height = " << min_size.at(1) << flush;
while (not Term::refresh() and not Input::poll()) sleep_ms(10); while (not Term::refresh() and not Input::poll()) sleep_ms(10);
if (Input::poll() and Input::get() == "q") exit(0); if (Input::poll() and Input::get() == "q") exit(0);
min_size = Term::get_min_size(boxes);
} }
else if (not Term::refresh()) break; else if (not Term::refresh()) break;
} }
@ -167,8 +171,9 @@ void clean_quit(int sig) {
if (Global::quitting) return; if (Global::quitting) return;
Global::quitting = true; Global::quitting = true;
Runner::stop(); 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!"); Logger::error("Failed to join _runner thread!");
}
Config::write(); Config::write();
Input::clear(); Input::clear();
@ -257,7 +262,6 @@ void banner_gen() {
else else
letter = line[1].substr(i, 3); letter = line[1].substr(i, 3);
// if (tty_mode and letter != "█" and letter != " ") letter = "░";
b_color = (letter == "") ? fg : bg; b_color = (letter == "") ? fg : bg;
if (b_color != oc) Global::banner += b_color; if (b_color != oc) Global::banner += b_color;
Global::banner += letter; 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); if (++z < Global::Banner_src.size()) Global::banner += Mv::l(ulen(line[1])) + Mv::d(1);
} }
Global::banner += Mv::r(18 - Global::Version.size()) 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)) + (tty_mode ? "\x1b[0;40;37m" : Theme::dec_to_color(0,0,0, lowcolor, "bg")
+ Fx::i + "v" + Global::Version + Fx::ui; + Theme::dec_to_color(150, 150, 150, lowcolor))
+ Fx::i + "v" + Global::Version + Fx::ui;
} }
bool update_clock() { bool update_clock() {
@ -281,11 +286,17 @@ bool update_clock() {
}; };
static time_t c_time = 0; static time_t c_time = 0;
static size_t clock_len = 0; static size_t clock_len = 0;
static string old_clock;
string new_clock;
if (auto n_time = time(NULL); n_time == c_time) if (auto n_time = time(NULL); n_time == c_time)
return false; return false;
else else {
c_time = n_time; 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; auto& out = Global::clock;
const auto& cpu_bottom = Config::getB("cpu_bottom"); const auto& cpu_bottom = Config::getB("cpu_bottom");
@ -294,7 +305,7 @@ bool update_clock() {
const auto& width = Cpu::width; const auto& width = Cpu::width;
const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); 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); 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) { for (const auto& [c_format, replacement] : clock_custom_format) {
if (s_contains(new_clock, c_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(); out.clear();
if (new_clock.size() != clock_len) { 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(); clock_len = new_clock.size();
} }
@ -483,7 +494,7 @@ namespace Runner {
else if (not proc.valid()) else if (not proc.valid())
throw std::runtime_error("Proc::collect() future not 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 { try {
if (Global::debug) debug_timer("proc", draw_begin); if (Global::debug) debug_timer("proc", draw_begin);
@ -511,7 +522,7 @@ namespace Runner {
else if (not net.valid()) else if (not net.valid())
throw std::runtime_error("Net::collect() future not 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 { try {
if (Global::debug) debug_timer("net", draw_begin); if (Global::debug) debug_timer("net", draw_begin);
@ -539,7 +550,7 @@ namespace Runner {
else if (not mem.valid()) else if (not mem.valid())
throw std::runtime_error("Mem::collect() future not 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 { try {
if (Global::debug) debug_timer("mem", draw_begin); if (Global::debug) debug_timer("mem", draw_begin);
@ -567,7 +578,7 @@ namespace Runner {
else if (not cpu.valid()) else if (not cpu.valid())
throw std::runtime_error("Cpu::collect() future not 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 { try {
if (Global::debug) debug_timer("cpu", draw_begin); if (Global::debug) debug_timer("cpu", draw_begin);
@ -696,7 +707,11 @@ int main(int argc, char **argv) {
break; 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)) { 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" 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; << "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")); 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); 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")) { else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) {
Config::set("tty_mode", true); 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 //? Platform dependent init and error check
@ -854,7 +869,8 @@ int main(int argc, char **argv) {
Draw::calcSizes(); Draw::calcSizes();
update_clock(); update_clock();
Global::resized = false; Global::resized = false;
Runner::run("all", true); if (Menu::active) Menu::process();
else Runner::run("all", true, true);
atomic_wait(Runner::active); 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 //? 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"); Runner::run("all");
update_ms = Config::getI("update_ms"); update_ms = Config::getI("update_ms");
future_time = time_ms() + 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 //? Poll for input and process any input detected
else if (Input::poll(min(1000ul, future_time - current_time))) { else if (Input::poll(min(1000ul, future_time - current_time))) {
if (not Runner::active) Config::unlock(); 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 //? Break the loop at 1000ms intervals or if input polling was interrupted
else break; else break;
} }
} }

View File

@ -20,12 +20,13 @@ tab-size = 4
#include <ranges> #include <ranges>
#include <atomic> #include <atomic>
#include <fstream> #include <fstream>
#include <string_view>
#include <btop_config.hpp> #include <btop_config.hpp>
#include <btop_shared.hpp> #include <btop_shared.hpp>
#include <btop_tools.hpp> #include <btop_tools.hpp>
using std::array, std::atomic; using std::array, std::atomic, std::string_view;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace rng = std::ranges; namespace rng = std::ranges;
using namespace Tools; using namespace Tools;
@ -114,7 +115,8 @@ namespace Config {
{"show_cpu_freq", "#* Show CPU frequency."}, {"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."}, {"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"}, {"log_level", "WARNING"},
{"proc_filter", ""}, {"proc_filter", ""},
{"proc_command", ""}, {"proc_command", ""},
{"selected_name", ""},
}; };
unordered_flat_map<string, string> stringsTmp; unordered_flat_map<string, string> stringsTmp;
@ -272,6 +275,7 @@ namespace Config {
try { try {
if (Proc::shown) { if (Proc::shown) {
ints.at("selected_pid") = Proc::selected_pid; ints.at("selected_pid") = Proc::selected_pid;
strings.at("selected_name") = Proc::selected_name;
ints.at("proc_start") = Proc::start; ints.at("proc_start") = Proc::start;
ints.at("proc_selected") = Proc::selected; ints.at("proc_selected") = Proc::selected;
} }
@ -337,7 +341,7 @@ namespace Config {
valid_names.push_back(n[0]); valid_names.push_back(n[0]);
string v_string; string v_string;
getline(cread, v_string, '\n'); getline(cread, v_string, '\n');
if (not v_string.ends_with(Global::Version)) if (not s_contains(v_string, Global::Version))
write_new = true; write_new = true;
while (not cread.eof()) { while (not cread.eof()) {
cread >> std::ws; cread >> std::ws;

View File

@ -27,6 +27,7 @@ tab-size = 4
#include <btop_shared.hpp> #include <btop_shared.hpp>
#include <btop_tools.hpp> #include <btop_tools.hpp>
#include <btop_input.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, 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 class ------------------------------------------------------------------------------------------------------------>
Meter::Meter() {} Meter::Meter() {}
Meter::Meter(const int width, const string& color_gradient, const bool invert) : width(width), color_gradient(color_gradient), invert(invert) { 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, "");
}
string Meter::operator()(int value) { string Meter::operator()(int value) {
if (width < 1) return ""; if (width < 1) return "";
value = clamp(value, 0, 100); value = clamp(value, 0, 100);
if (not cache.at(value).empty()) return cache.at(value); 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)) { for (const int& i : iota(1, width + 1)) {
int y = round((double)i * 100.0 / width); int y = round((double)i * 100.0 / width);
if (value >= y) if (value >= y)
@ -292,11 +291,11 @@ namespace Draw {
out += graphs.at(current).at(0); out += graphs.at(current).at(0);
} }
else { else {
for (const int& i : iota(0, height)) { for (const int& i : iota(1, height + 1)) {
if (i > 0) out += Mv::d(1) + Mv::l(width); if (i > 1) out += Mv::d(1) + Mv::l(width);
if (not color_gradient.empty()) 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) ? 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 - 1) - i) : graphs.at(current).at(i); out += (invert) ? graphs.at(current).at(height - i) : graphs.at(current).at(i-1);
} }
} }
if (not color_gradient.empty()) out += Fx::reset; if (not color_gradient.empty()) out += Fx::reset;
@ -520,6 +519,7 @@ namespace Mem {
int x = 1, y, width = 20, height; int x = 1, y, width = 20, height;
int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter; int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter;
int disks_io_h = 0; int disks_io_h = 0;
int disks_io_half = 0;
bool shown = true, redraw = true; bool shown = true, redraw = true;
string box; string box;
unordered_flat_map<string, Draw::Meter> mem_meters; unordered_flat_map<string, Draw::Meter> mem_meters;
@ -573,45 +573,50 @@ namespace Mem {
//? Disk meters and io graphs //? Disk meters and io graphs
if (show_disks) { if (show_disks) {
if (show_io_stat or io_mode) { 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; unordered_flat_map<string, int> custom_speeds;
if (not Config::getS("io_graph_speeds").empty()) { int half_height = 0;
auto split = ssplit(Config::getS("io_graph_speeds")); if (io_mode) {
for (const auto& entry : split) { disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2));
auto vals = ssplit(entry); half_height = ceil((double)disks_io_h / 2);
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)); 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) { for (const auto& [name, disk] : mem.disks) {
if (disk.io_read.empty()) continue; 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 io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "available", disk.io_activity, graph_symbol, false, true};
if (not io_mode or (io_mode and io_graph_combined)) {
deque<long long> combined(disk.io_read.size(), 0); if (io_mode) {
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>()); //? Create one combined graph for IO read/write if enabled
io_graphs[name] = Draw::Graph{disks_width - (io_mode ? 0 : 6), disks_io_h, "available", combined, graph_symbol, false, true, speed}; long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 10) << 20;
} if (io_graph_combined) {
else { deque<long long> combined(disk.io_read.size(), 0);
io_graphs[name + "_read"] = Draw::Graph{disks_width, half_height, "free", disk.io_read, graph_symbol, false, true, speed}; rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
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] = 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) { for (int i = 0; const auto& [name, ignored] : mem.disks) {
if (i * 2 > height - 2) break; if (i * 2 > height - 2) break;
disk_meters_used[name] = Draw::Meter{disk_meter, "used"}; disk_meters_used[name] = Draw::Meter{disk_meter, "used"};
if (cmp_less_equal(mem.disks_order.size() * 3, height - 1)) if (cmp_less_equal(mem.disks.size() * 3, height - 1))
disk_meters_free[name] = Draw::Meter{disk_meter, "free"}; 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") 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; + 'i' + Theme::c("title") + 'o' + Fx::ub + Theme::c("mem_box") + Symbols::title_right;
Input::mouse_mappings["i"] = {y, x + width - 5, 1, 2}; 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); 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+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 (++cy > height - 3) break;
if (io_graph_combined) { if (io_graph_combined) {
auto comb_val = disk.io_read.back() + disk.io_write.back(); 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()) 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; 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 (++cy > height - 3) break;
if (show_io_stat and io_graphs.contains(mount)) { 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)({comb_val}, redraw or data_same); 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 (not big_disk) out += Mv::to(y+1+cy, x+cx) + Theme::c("main_fg") + human_io;
if (++cy > height - 3) break; if (++cy > height - 3) break;
} }
@ -786,6 +792,8 @@ namespace Net {
out = box; out = box;
//? Graphs //? Graphs
graphs.clear(); 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["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}; 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; int start, selected, select_max;
bool shown = true, redraw = true; bool shown = true, redraw = true;
int selected_pid = 0; int selected_pid = 0;
string selected_name;
unordered_flat_map<size_t, Draw::Graph> p_graphs; unordered_flat_map<size_t, Draw::Graph> p_graphs;
unordered_flat_map<size_t, int> p_counters; unordered_flat_map<size_t, int> p_counters;
int counter = 0; int counter = 0;
@ -990,7 +999,7 @@ namespace Proc {
int mouse_x = d_x + 2; int mouse_x = d_x + 2;
out += Mv::to(d_y, d_x + 1); out += Mv::to(d_y, d_x + 1);
if (width > 55) { 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}; if (alive and selected == 0) Input::mouse_mappings["t"] = {d_y, mouse_x, 1, 9};
mouse_x += 11; mouse_x += 11;
} }
@ -1121,7 +1130,7 @@ namespace Proc {
string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : ""); string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : "");
if (alive) { if (alive) {
cpu_str.resize((detailed.entry.cpu_p < 10 or detailed.entry.cpu_p >= 100 ? 3 : 4)); 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)) 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; + 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) { for (int n=0; auto& p : plist) {
if (n++ < start or p.filtered) continue; if (n++ < start or p.filtered) continue;
bool is_selected = (lc + 1 == selected); 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 //? 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); const bool has_graph = p_counters.contains(p.pid);
@ -1275,7 +1287,7 @@ namespace Proc {
//? Current selection and number of processes //? Current selection and number of processes
string location = to_string(start + selected) + '/' + to_string(numpids); string location = to_string(start + selected) + '/' + to_string(numpids);
string loc_clear = Symbols::h_line * max(0ul, 9 - location.size()); 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; + 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 //? Clear out left over graphs from dead processes at a regular interval
@ -1293,7 +1305,10 @@ namespace Proc {
p_counters.compact(); 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; redraw = false;
return out + Fx::reset; return out + Fx::reset;
} }
@ -1303,6 +1318,7 @@ namespace Proc {
namespace Draw { namespace Draw {
void calcSizes() { void calcSizes() {
atomic_wait(Runner::active); atomic_wait(Runner::active);
Config::unlock();
auto& boxes = Config::getS("shown_boxes"); auto& boxes = Config::getS("shown_boxes");
auto& cpu_bottom = Config::getB("cpu_bottom"); auto& cpu_bottom = Config::getB("cpu_bottom");
auto& mem_below_net = Config::getB("mem_below_net"); auto& mem_below_net = Config::getB("mem_below_net");
@ -1314,8 +1330,10 @@ namespace Draw {
Proc::box.clear(); Proc::box.clear();
Global::clock.clear(); Global::clock.clear();
Global::overlay.clear(); Global::overlay.clear();
if (Menu::active) Menu::redraw = true;
Input::mouse_mappings.clear(); Input::mouse_mappings.clear();
Menu::mouse_mappings.clear();
Cpu::x = Mem::x = Net::x = Proc::x = 1; Cpu::x = Mem::x = Net::x = Proc::x = 1;
Cpu::y = Mem::y = Net::y = Proc::y = 1; Cpu::y = Mem::y = Net::y = Proc::y = 1;

View File

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

View File

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

View File

@ -16,25 +16,47 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <vector>
#include <deque> #include <deque>
#include <robin_hood.h> #include <robin_hood.h>
#include <array> #include <array>
#include <ranges>
#include <signal.h>
#include <errno.h>
#include <btop_menu.hpp> #include <btop_menu.hpp>
#include <btop_tools.hpp> #include <btop_tools.hpp>
#include <btop_config.hpp> #include <btop_config.hpp>
#include <btop_theme.hpp> #include <btop_theme.hpp>
#include <btop_draw.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 { namespace Menu {
atomic<bool> active (false); 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 = { const unordered_flat_map<string, unordered_flat_map<string, vector<string>>> menus = {
{ "options", { { "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 not use this file except in compliance with the License.
You may obtain a copy of the License at 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 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -20,17 +20,63 @@ tab-size = 4
#include <string> #include <string>
#include <atomic> #include <atomic>
#include <vector>
#include <bitset>
#include <btop_input.hpp> #include <btop_input.hpp>
using std::string, std::atomic; using std::string, std::atomic, std::vector, std::bitset;
namespace Menu { namespace Menu {
extern atomic<bool> active; extern atomic<bool> active;
extern string output; 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; 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 = ""; std::filesystem::path stat = "";
int64_t total = 0, used = 0, free = 0; int64_t total = 0, used = 0, free = 0;
int used_percent = 0, free_percent = 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_read = {};
deque<long long> io_write = {}; deque<long long> io_write = {};
deque<long long> io_activity = {};
}; };
struct mem_info { struct mem_info {
@ -175,6 +176,7 @@ namespace Proc {
extern int select_max; extern int select_max;
extern atomic<int> detailed_pid; extern atomic<int> detailed_pid;
extern int selected_pid, start, selected, collapse, expand; extern int selected_pid, start, selected, collapse, expand;
extern string selected_name;
//? Contains the valid sorting options for processes //? Contains the valid sorting options for processes
const vector<string> sort_vector = { const vector<string> sort_vector = {
@ -208,12 +210,13 @@ namespace Proc {
size_t pid = 0; size_t pid = 0;
string name = "", cmd = ""; string name = "", cmd = "";
string short_cmd = ""; string short_cmd = "";
size_t threads = 0, name_offset = 0; size_t threads = 0;
int name_offset = 0;
string user = ""; string user = "";
uint64_t mem = 0; uint64_t mem = 0;
double cpu_p = 0.0, cpu_c = 0.0; double cpu_p = 0.0, cpu_c = 0.0;
char state = '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 = ""; string prefix = "";
size_t depth = 0, tree_index = 0; size_t depth = 0, tree_index = 0;
bool collapsed = false, filtered = false; bool collapsed = false, filtered = false;

View File

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