diff --git a/Makefile b/Makefile index 520162e..79eff29 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,40 @@ +#* 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╚═════╝ ╚═╝ ╚═════╝ ╚═╝ + +BTOP_VERSION = $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown") +TIMESTAMP = $(shell date +%s) + PREFIX ?= /usr/local -DOCDIR ?= $(PREFIX)/share/btop/doc -#Compiler and Linker +#? 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 +#? Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10 ifneq ($(CXX),g++-10) - CXX_VERSION = $(shell $(CXX) -dumpfullversion -dumpversion | cut -f1 -d"." || echo 0) - ifneq ($(shell test $(CXX_VERSION) -ge 11; echo $$?),0) + V_MAJOR = $(shell echo $(CXX_VERSION) | cut -f1 -d"." || echo 0) + 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 -ARCH = $(shell uname -p) +#? Only enable fcf-protection if on x86_64 +ARCH = $(shell uname -p ||true) ifeq ($(ARCH),x86_64) ADDFLAGS = -fcf-protection endif +ifeq ($(ARCH),unknown) + ARCH = $(shell uname -m ||true) +endif +PLATFORM = $(shell uname -s ||true) -#The Target Binary Program -TARGET := btop +#? Use all CPU cores (will only be set if using Make >=4.3) +MAKEFLAGS := --jobs=$(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1) -#The Directories, Source, Includes, Objects and Binary +#? The Directories, Source, Includes, Objects and Binary SRCDIR := src INCDIR := include BUILDDIR := obj @@ -32,7 +43,7 @@ SRCEXT := cpp DEPEXT := d OBJEXT := o -#Flags, Libraries and Includes +#? Flags, Libraries and Includes REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic -pedantic-errors -Wfatal-errors OPTFLAGS := -O2 -ftree-loop-vectorize @@ -40,58 +51,97 @@ override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexce override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) INC := -I$(INCDIR) -I$(SRCDIR) +SU_USER := root +SU_GROUP := root SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT)) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) -#Default Make -all: directories $(TARGET) +#? Default Make +all: msg directories btop -#Make the Directories +msg: + @printf " $(BANNER)\n" + @printf "\033[1;97mCompiler : \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 btop++ v$(BTOP_VERSION) for $(PLATFORM) ($(ARCH))\033[0m\n" + +help: + @printf "\033[1;97mbtop++ makefile\033[0m\n" + @printf "usage: make [argument]\n\n" + @printf "arguments:\n" + @printf " all Compile btop (default argument)\n" + @printf " clean Remove built objects\n" + @printf " distclean Remove built objects and binaries\n" + @printf " install Install btop++ to \$$PREFIX\n" + @printf " setuid Set installed binary owner/group to \$$SU_USER/\$$SU_OWNER and set SUID bit\n" + @printf " uninstall Uninstall btop++ from \$$PREFIX\n" + +#? Make the Directories directories: @mkdir -p $(TARGETDIR) @mkdir -p $(BUILDDIR) -#Clean only Objects +#? Clean only Objects clean: + @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @rm -rf $(BUILDDIR) -#Full Clean, Objects and Binaries +#? Clean Objects and Binaries distclean: clean + @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @rm -rf $(TARGETDIR) install: + @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" @mkdir -p $(DESTDIR)$(PREFIX)/bin @cp -p $(TARGETDIR)/btop $(DESTDIR)$(PREFIX)/bin/btop - @mkdir -p $(DESTDIR)$(DOCDIR) - @cp -p README.md $(DESTDIR)$(DOCDIR) - @cp -pr themes $(DESTDIR)$(PREFIX)/share/btop @chmod 755 $(DESTDIR)$(PREFIX)/bin/btop + @printf "\033[1;92mInstalling doc to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\n" + @mkdir -p $(DESTDIR)$(PREFIX)/share/btop + @cp -p README.md $(DESTDIR)$(PREFIX)/share/btop + @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 to root, will make btop run with admin privileges regardless of actual user -su-setuid: - @su --session-command "chown root:root $(DESTDIR)$(PREFIX)/bin/btop && chmod 4755 $(DESTDIR)$(PREFIX)/bin/btop" root +#? Set suid bit for btop for $SU_USER in SU_GROUP, will make btop run with (root by default) privileges regardless of actual user +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" + @chown $(SU_USER):$(SU_GROUP) $(DESTDIR)$(PREFIX)/bin/btop + @printf "\033[1;92mSetting SUID bit\033[0m\n" + @chmod u+s $(DESTDIR)$(PREFIX)/bin/btop uninstall: + @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\033[0m\n" @rm -rf $(DESTDIR)$(PREFIX)/bin/btop - @rm -rf $(DESTDIR)$(DOCDIR) + @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\033[0m\n" @rm -rf $(DESTDIR)$(PREFIX)/share/btop -#Pull in dependency info for *existing* .o files +#? Pull in dependency info for *existing* .o files -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) -#Link +#? Link btop: $(OBJECTS) - $(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) + @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) - $(TIMESTAMP)) -u +%Mm:%Ss)\033[1;92m)\033[0m\n" -#Compile +#? Compile $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) - $(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< - @$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) + @sleep 0.1 2>/dev/null || true + @printf "\033[1;97mCompiling $< \n" + @$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< + @$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null @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 -#Non-File Targets -.PHONY: all +#? Non-File Targets +.PHONY: all msg help diff --git a/README.md b/README.md index 09762bb..7d73ae8 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,8 @@ Any support is greatly appreciated! For best experience, a terminal with support for: * 24-bit truecolor ([See list of terminals with truecolor support](https://gist.github.com/XVilka/8346728)) -* 256-color terminals are supported through 24-bit to 256-color conversion when setting "truecolor" to False in the options or with "-lc/--low-color" argument. - (16 color TTY mode now available as well.) +* 256-color terminals are supported through 24-bit to 256-color conversion when setting "truecolor" to False in the options or with "-lc/--low-color" arguments. +* 16 color TTY mode will be activated if a real tty device is detected. Can be forced with "-t/--tty_on" arguments. * Wide characters (Are sometimes problematic in web-based terminals) Also needs a UTF8 locale and a font that covers: @@ -105,7 +105,8 @@ See comments by @sgleizes [link](https://github.com/aristocratos/bpytop/issues/1 If text are misaligned and you are using Konsole or Yakuake, turning off "Bi-Directional text rendering" is a possible fix. -Characters clipping in to each other or text/border misalignments is not bugs caused by bpytop, but most likely a fontconfig or terminal problem where the braille characters making up the graphs aren't rendered correctly. +Characters clipping in to each other or text/border misalignments is not bugs caused by btop, but most likely a fontconfig or terminal problem where the braille characters making up the graphs aren't rendered correctly. + Look to the creators of the terminal emulator you use to fix these issues if the previous mentioned fixes don't work for you. ## Screenshots @@ -152,11 +153,12 @@ sudo make install # only use "sudo" when installing to a NON user owned directory ``` ->to make btop always run as root (no need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems) +>to make btop always run as root (or other user), (no need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems) ``` bash # run after make install and use same PREFIX if any was used at install -make su-setuid +sudo make setuid +# set SU_USER and SU_GROUP to select user and group, default is root:root ``` @@ -166,13 +168,13 @@ make su-setuid sudo make uninstall ``` ->to remove any object files +>to remove any object files from source dir ```bash make clean ``` ->to remove all object files, binaries and created directories +>to remove all object files, binaries and created directories in source dir ```bash make distclean diff --git a/src/btop.cpp b/src/btop.cpp index 49b4724..bb13365 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -27,6 +27,8 @@ tab-size = 4 #include #include #include +#include +#include #include #include @@ -53,8 +55,8 @@ tab-size = 4 #error Platform not supported! #endif -using std::string, std::string_view, std::vector, std::array, std::atomic, std::endl, std::cout, std::min; -using std::flush, std::endl, std::string_literals::operator""s, std::to_string, std::future, std::async, std::bitset, std::future_status; +using std::string, std::string_view, std::vector, std::atomic, std::endl, std::cout, std::min, std::flush, std::endl; +using std::string_literals::operator""s, std::to_string, std::future, std::async, std::bitset, std::future_status; namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; @@ -108,8 +110,9 @@ void argumentParser(const int& argc, char **argv) { << " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n" << " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n" << " +t, --tty_off force (OFF) tty mode\n" - << " --utf-foce force start even if no UTF-8 locale was detected" - << " --debug start in debug mode with loglevel set to DEBUG\n" + << " --utf-foce force start even if no UTF-8 locale was detected\n" + << " --debug start in DEBUG mode: shows microsecond timer for information collect\n" + << " and screen draw functions and sets loglevel to DEBUG\n" << endl; exit(0); } @@ -270,12 +273,89 @@ void banner_gen() { + Fx::i + "v" + Global::Version + Fx::ui; } +bool update_clock() { + const auto& clock_format = Config::getS("clock_format"); + if (not Cpu::shown or clock_format.empty()) return false; + + static const unordered_flat_map clock_custom_format = { + {"/user", Tools::username()}, + {"/host", Tools::hostname()}, + {"/uptime", ""} + }; + static time_t c_time = 0; + static size_t clock_len = 0; + + if (auto n_time = time(NULL); n_time == c_time) + return false; + else + c_time = n_time; + + auto& out = Global::clock; + const auto& cpu_bottom = Config::getB("cpu_bottom"); + const auto& x = Cpu::x; + const auto y = (cpu_bottom ? Cpu::y + Cpu::height - 1 : Cpu::y); + 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)) { + if (c_format == "/uptime") { + string upstr = sec_to_dhms(system_uptime()); + if (upstr.size() > 8) upstr.resize(upstr.size() - 3); + new_clock = s_replace(new_clock, c_format, upstr); + } + else { + new_clock = s_replace(new_clock, c_format, replacement); + } + } + + } + + new_clock = uresize(Tools::strf_time(new_clock), std::max(0, width - 56)); + out.clear(); + + if (new_clock.size() != clock_len) { + 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(); + } + + out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left + + Theme::c("title") + Fx::b + new_clock + Theme::c("cpu_box") + Fx::ub + title_right; + + return true; +} + //* Manages secondary thread for collection and drawing of boxes namespace Runner { atomic active (false); atomic stopping (false); atomic waiting (false); - atomic do_work (false); + + //* Setup semaphore for triggering thread to do work +#if __GNUC__ < 11 + #include + sem_t do_work; + inline void thread_sem_init() { sem_init(&do_work, 0, 0); } + inline void thread_wait() { sem_wait(&do_work); } + inline void thread_trigger() { sem_post(&do_work); } +#else + #include + std::binary_semaphore do_work(0); + inline void thread_sem_init() { ; } + inline void thread_wait() { do_work.acquire(); } + inline void thread_trigger() { do_work.release(); } +#endif + + //* RAII wrapper for pthread_mutex locking + class thread_lock { + pthread_mutex_t& pt_mutex; + public: + int status; + thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { status = pthread_mutex_lock(&pt_mutex); } + ~thread_lock() { if (status == 0) pthread_mutex_unlock(&pt_mutex); } + }; string output; sigset_t mask; @@ -296,6 +376,17 @@ namespace Runner { cpu_present, cpu_running }; + enum debug_actions { + collect_begin, + draw_begin, + draw_done + }; + + enum debug_array { + collect, + draw + }; + const uint_fast8_t proc_done = 0b0000'0011; const uint_fast8_t net_done = 0b0000'1100; const uint_fast8_t mem_done = 0b0011'0000; @@ -314,13 +405,35 @@ namespace Runner { struct runner_conf current_conf; + void debug_timer(const char* name, const int action) { + switch (action) { + case collect_begin: + debug_times[name].at(collect) = time_micros(); + return; + case draw_begin: + debug_times[name].at(draw) = time_micros(); + debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect); + debug_times["total"].at(collect) += debug_times[name].at(collect); + return; + case draw_done: + debug_times[name].at(draw) = time_micros() - debug_times[name].at(draw); + debug_times["total"].at(draw) += debug_times[name].at(draw); + return; + } + } + //? ------------------------------- Secondary thread: async launcher and drawing ---------------------------------- void * _runner(void * _) { (void)_; //? Block all signals in this thread to avoid deadlock from any signal handlers trying to stop this thread + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTSTP); + sigaddset(&mask, SIGWINCH); + sigaddset(&mask, SIGTERM); pthread_sigmask(SIG_BLOCK, &mask, NULL); - //? pthread_mutex_lock to make sure this thread is a single instance thread + //? pthread_mutex_lock to lock thread and monitor health from main thread thread_lock pt_lck(mtx); if (pt_lck.status != 0) { Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status); @@ -329,18 +442,18 @@ namespace Runner { stopping = true; } + //* ----------------------------------------------- THREAD LOOP ----------------------------------------------- while (not Global::quitting) { - atomic_wait(do_work, false); - do_work = false; + thread_wait(); if (stopping or Global::resized) { continue; } - auto& conf = current_conf; - - //? Secondary atomic lock used for signaling status to main thread + //? Atomic lock used for blocking non-thread safe actions in main thread atomic_lock lck(active); + auto& conf = current_conf; + //! DEBUG stats if (Global::debug) { debug_times.clear(); @@ -356,12 +469,17 @@ namespace Runner { future mem; future net; future&> proc; + + //? Loop until all box flags present in bitmask have been zeroed while (conf.box_mask.count() > 0) { if (stopping) break; + //? PROC if (conf.box_mask.test(proc_present)) { if (not conf.box_mask.test(proc_running)) { - if (Global::debug) debug_times["proc"].at(0) = time_micros(); + if (Global::debug) debug_timer("proc", collect_begin); + + //? Start async collect proc = async(Proc::collect, conf.no_update); conf.box_mask.set(proc_running); } @@ -370,16 +488,12 @@ namespace Runner { else if (proc.wait_for(std::chrono::microseconds(10)) == future_status::ready) { try { - if (Global::debug) { - debug_times["proc"].at(1) = time_micros(); - debug_times["proc"].at(0) = debug_times["proc"].at(1) - debug_times["proc"].at(0); - debug_times["total"].at(0) += debug_times["proc"].at(0); - } + if (Global::debug) debug_timer("proc", draw_begin); + + //? Draw box output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update); - if (Global::debug) { - debug_times["proc"].at(1) = time_micros() - debug_times["proc"].at(1); - debug_times["total"].at(1) += debug_times["proc"].at(1); - } + + if (Global::debug) debug_timer("proc", draw_done); } catch (const std::exception& e) { throw std::runtime_error("Proc:: -> " + (string)e.what()); @@ -387,10 +501,13 @@ namespace Runner { conf.box_mask ^= proc_done; } } + //? NET if (conf.box_mask.test(net_present)) { if (not conf.box_mask.test(net_running)) { - if (Global::debug) debug_times["net"].at(0) = time_micros(); + if (Global::debug) debug_timer("net", collect_begin); + + //? Start async collect net = async(Net::collect, conf.no_update); conf.box_mask.set(net_running); } @@ -399,16 +516,12 @@ namespace Runner { else if (net.wait_for(ZeroSec) == future_status::ready) { try { - if (Global::debug) { - debug_times["net"].at(1) = time_micros(); - debug_times["net"].at(0) = debug_times["net"].at(1) - debug_times["net"].at(0); - debug_times["total"].at(0) += debug_times["net"].at(0); - } + if (Global::debug) debug_timer("net", draw_begin); + + //? Draw box output += Net::draw(net.get(), conf.force_redraw, conf.no_update); - if (Global::debug) { - debug_times["net"].at(1) = time_micros() - debug_times["net"].at(1); - debug_times["total"].at(1) += debug_times["net"].at(1); - } + + if (Global::debug) debug_timer("net", draw_done); } catch (const std::exception& e) { throw std::runtime_error("Net:: -> " + (string)e.what()); @@ -416,10 +529,13 @@ namespace Runner { conf.box_mask ^= net_done; } } + //? MEM if (conf.box_mask.test(mem_present)) { if (not conf.box_mask.test(mem_running)) { - if (Global::debug) debug_times["mem"].at(0) = time_micros(); + if (Global::debug) debug_timer("mem", collect_begin); + + //? Start async collect mem = async(Mem::collect, conf.no_update); conf.box_mask.set(mem_running); } @@ -428,16 +544,12 @@ namespace Runner { else if (mem.wait_for(ZeroSec) == future_status::ready) { try { - if (Global::debug) { - debug_times["mem"].at(1) = time_micros(); - debug_times["mem"].at(0) = debug_times["mem"].at(1) - debug_times["mem"].at(0); - debug_times["total"].at(0) += debug_times["mem"].at(0); - } + if (Global::debug) debug_timer("mem", draw_begin); + + //? Draw box output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update); - if (Global::debug) { - debug_times["mem"].at(1) = time_micros() - debug_times["mem"].at(1); - debug_times["total"].at(1) += debug_times["mem"].at(1); - } + + if (Global::debug) debug_timer("mem", draw_done); } catch (const std::exception& e) { throw std::runtime_error("Mem:: -> " + (string)e.what()); @@ -445,10 +557,13 @@ namespace Runner { conf.box_mask ^= mem_done; } } + //? CPU if (conf.box_mask.test(cpu_present)) { if (not conf.box_mask.test(cpu_running)) { - if (Global::debug) debug_times["cpu"].at(0) = time_micros(); + if (Global::debug) debug_timer("cpu", collect_begin); + + //? Start async collect cpu = async(Cpu::collect, conf.no_update); conf.box_mask.set(cpu_running); } @@ -457,16 +572,12 @@ namespace Runner { else if (cpu.wait_for(ZeroSec) == future_status::ready) { try { - if (Global::debug) { - debug_times["cpu"].at(1) = time_micros(); - debug_times["cpu"].at(0) = debug_times["cpu"].at(1) - debug_times["cpu"].at(0); - debug_times["total"].at(0) += debug_times["cpu"].at(0); - } + if (Global::debug) debug_timer("cpu", draw_begin); + + //? Draw box output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update); - if (Global::debug) { - debug_times["cpu"].at(1) = time_micros() - debug_times["cpu"].at(1); - debug_times["total"].at(1) += debug_times["cpu"].at(1); - } + + if (Global::debug) debug_timer("cpu", draw_done); } catch (const std::exception& e) { throw std::runtime_error("Cpu:: -> " + (string)e.what()); @@ -501,9 +612,10 @@ namespace Runner { //? If overlay isn't empty, print output without color and then print overlay on top cout << Term::sync_start << (conf.overlay.empty() ? output + conf.clock - : Theme::c("inactive_fg") + Fx::ub + Fx::uncolor(output + conf.clock) + conf.overlay) + : Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output + conf.clock) + conf.overlay) << Term::sync_end << flush; } + //* ----------------------------------------------- THREAD LOOP ----------------------------------------------- pthread_exit(NULL); } @@ -516,20 +628,19 @@ namespace Runner { if (stopping or Global::resized) return; if (box == "overlay") { - cout << Term::sync_start << Global::overlay << Term::sync_end; + cout << Term::sync_start << Global::overlay << Term::sync_end << flush; } else if (box == "clock") { - if (not Global::clock.empty()) - cout << Term::sync_start << Global::clock << Term::sync_end; + cout << Term::sync_start << Global::clock << Term::sync_end << flush; } else if (box.empty() and Config::current_boxes.empty()) { - cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end; + cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end << flush; } else { Config::unlock(); Config::lock(); - //? Setup bitmask for selected boxes instead of parsing strings + //? Setup bitmask for selected boxes instead of parsing strings in _runner thread loop bitset<8> box_mask; for (const auto& box : (box == "all" ? Config::current_boxes : vector{box})) { box_mask |= box_bits.at(box); @@ -537,9 +648,9 @@ namespace Runner { current_conf = {box_mask, no_update, force_redraw, Global::overlay, Global::clock}; - do_work = true; - atomic_notify(do_work); + thread_trigger(); + //? Wait for _runner thread to be active before returning for (int i = 0; not active and i < 10; i++) sleep_ms(1); } } @@ -554,14 +665,9 @@ namespace Runner { exit(1); } else if (ret == EBUSY) { - if (not active) { - do_work = true; - atomic_notify(do_work); - sleep_ms(1); - } - else { - atomic_wait(active); - } + atomic_wait(active); + thread_trigger(); + sleep_ms(1); } stopping = false; } @@ -585,11 +691,6 @@ int main(int argc, char **argv) { std::signal(SIGTSTP, _signal_handler); std::signal(SIGCONT, _signal_handler); std::signal(SIGWINCH, _signal_handler); - sigemptyset(&Runner::mask); - sigaddset(&Runner::mask, SIGINT); - sigaddset(&Runner::mask, SIGTSTP); - sigaddset(&Runner::mask, SIGWINCH); - sigaddset(&Runner::mask, SIGTERM); //? Setup paths for config, log and user themes for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { @@ -707,6 +808,7 @@ int main(int argc, char **argv) { Theme::setTheme(); //? Start runner thread + Runner::thread_sem_init(); if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) { Global::exit_error_msg = "Failed to create _runner thread!"; exit(1); @@ -743,10 +845,16 @@ int main(int argc, char **argv) { if (Global::resized) { Global::resized = false; Draw::calcSizes(); + update_clock(); Runner::run("all", true); atomic_wait(Runner::active); } + //? Update clock if needed + if (update_clock()) { + Runner::run("clock"); + } + //? Start secondary collect & draw thread at the interval set by config value if (time_ms() >= future_time) { Runner::run("all"); @@ -767,14 +875,12 @@ 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(); + if (not Runner::active) Config::unlock(); Input::process(Input::get()); } //? Break the loop at 1000ms intervals or if input polling was interrupted - else - break; + else break; } } diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 688068c..ff6de59 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -47,6 +47,8 @@ namespace Config { {"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n" "#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."}, + {"rounded_corners", "#* Rounded corners on boxes, is ignored if TTY mode is ON."}, + {"graph_symbol", "#* Default symbols to use for graph creation, \"braille\", \"block\" or \"tty\".\n" "#* \"braille\" offers the highest resolution but might not be included in all fonts.\n" "#* \"block\" has half the resolution of braille but uses more common characters.\n" @@ -112,7 +114,7 @@ namespace Config { {"show_cpu_freq", "#* Show CPU frequency."}, - {"draw_clock", "#* 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."}, {"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."}, @@ -174,7 +176,7 @@ namespace Config { {"cpu_sensor", "Auto"}, {"cpu_core_map", ""}, {"temp_scale", "celsius"}, - {"draw_clock", "%X"}, + {"clock_format", "%X"}, {"custom_cpu_name", ""}, {"disks_filter", ""}, {"io_graph_speeds", ""}, @@ -188,6 +190,7 @@ namespace Config { unordered_flat_map bools = { {"theme_background", true}, {"truecolor", true}, + {"rounded_corners", true}, {"proc_reversed", false}, {"proc_tree", false}, {"proc_colors", true}, @@ -362,6 +365,10 @@ namespace Config { cread >> value; if (not isint(value)) load_warnings.push_back("Got an invalid integer value for config name: " + name); + else if (name == "update_ms" and stoi(value) < 100) { + load_warnings.push_back("Config value update_ms set too low (<100), setting (100)."); + ints.at(name) = 100; + } else ints.at(name) = stoi(value); } @@ -376,6 +383,8 @@ namespace Config { load_warnings.push_back("Invalid log_level: " + value); else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value)) load_warnings.push_back("Invalid graph symbol identifier: " + value); + else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value))) + load_warnings.push_back("Invalid graph symbol identifier for" + name + ": " + value); else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) load_warnings.push_back("Invalid box name(s) in shown_boxes: " + value); else diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index aa30e70..6b668a8 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -36,33 +36,6 @@ using namespace Tools; namespace rng = std::ranges; namespace Symbols { - const string h_line = "─"; - const string v_line = "│"; - const string dotted_v_line = "╎"; - const string left_up = "┌"; - const string right_up = "┐"; - const string left_down = "└"; - const string right_down = "┘"; - const string round_left_up = "╭"; - const string round_right_up = "╮"; - const string round_left_down = "╰"; - const string round_right_down = "╯"; - const string title_left_down = "┘"; - const string title_right_down = "└"; - const string title_left = "┐"; - const string title_right = "┌"; - const string div_right = "┤"; - const string div_left = "├"; - const string div_up = "┬"; - const string div_down = "┴"; - - - const string up = "↑"; - const string down = "↓"; - const string left = "←"; - const string right = "→"; - const string enter = "↲"; - const string meter = "■"; const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; @@ -201,11 +174,12 @@ namespace Draw { string out; if (line_color.empty()) line_color = Theme::c("div_line"); const auto& tty_mode = Config::getB("tty_mode"); + const auto& rounded = Config::getB("rounded_corners"); const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript.at(clamp(num, 0, 9))); - const auto& right_up = (tty_mode ? Symbols::right_up : Symbols::round_right_up); - const auto& left_up = (tty_mode ? Symbols::left_up : Symbols::round_left_up); - const auto& right_down = (tty_mode ? Symbols::right_down : Symbols::round_right_down); - const auto& left_down = (tty_mode ? Symbols::left_down : Symbols::round_left_down); + 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); out = Fx::reset + line_color; @@ -583,14 +557,14 @@ namespace Mem { for (const auto& name : mem_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name)}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name)}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } @@ -694,7 +668,7 @@ namespace Mem { const auto& disks = mem.disks; cx = x + mem_width - 1; cy = 0; const bool big_disk = disks_width >= 25; - divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Symbols::div_right + Mv::l(disks_width - 1); + divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Fx::ub + Symbols::div_right + Mv::l(disks_width - 1); if (io_mode) { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; @@ -758,7 +732,7 @@ namespace Mem { if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' - + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 7)); + + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++; } @@ -792,7 +766,7 @@ namespace Net { auto& net_sync = Config::getB("net_sync"); auto& net_auto = Config::getB("net_auto"); auto& tty_mode = Config::getB("tty_mode"); - auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc")); + auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_net")); string ip_addr = (net.ipv4.empty() ? net.ipv6 : net.ipv4); if (old_ip != ip_addr) { old_ip = ip_addr; @@ -1191,7 +1165,7 @@ namespace Proc { //* Iteration over processes int lc = 0; for (int n=0; auto& p : plist) { - if (p.filtered or n++ < start) continue; + if (n++ < start or p.filtered) continue; bool is_selected = (lc + 1 == selected); if (is_selected) selected_pid = (int)p.pid; @@ -1261,7 +1235,7 @@ namespace Proc { out += c_color + uresize(p.name, width_left - 1) + end + ' '; width_left -= (ulen(p.name) + 1); } - if (width_left > 7 and (not p.short_cmd.empty() or p.short_cmd == p.name)) { + if (width_left > 7 and p.short_cmd != p.name) { out += g_color + '(' + uresize(p.short_cmd, width_left - 3, true) + ") "; width_left -= (ulen(p.short_cmd, true) + 3); } @@ -1287,7 +1261,7 @@ namespace Proc { } out += Fx::reset; - while (lc++ < height - 4) out += Mv::to(y+lc+2, x+1) + string(width - 2, ' '); + while (lc++ < height - 5) out += Mv::to(y+lc+1, x+1) + string(width - 2, ' '); //? Draw scrollbar if needed if (numpids > select_max) { @@ -1337,6 +1311,8 @@ namespace Draw { Mem::box.clear(); Net::box.clear(); Proc::box.clear(); + Global::clock.clear(); + Global::overlay.clear(); Input::mouse_mappings.clear(); diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index 094f751..f8adf6b 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -25,6 +25,35 @@ tab-size = 4 using std::string, std::vector, robin_hood::unordered_flat_map, std::deque; +namespace Symbols { + const string h_line = "─"; + const string v_line = "│"; + const string dotted_v_line = "╎"; + const string left_up = "┌"; + const string right_up = "┐"; + const string left_down = "└"; + const string right_down = "┘"; + const string round_left_up = "╭"; + const string round_right_up = "╮"; + const string round_left_down = "╰"; + const string round_right_down = "╯"; + const string title_left_down = "┘"; + const string title_right_down = "└"; + const string title_left = "┐"; + const string title_right = "┌"; + const string div_right = "┤"; + const string div_left = "├"; + const string div_up = "┬"; + const string div_down = "┴"; + + + const string up = "↑"; + const string down = "↓"; + const string left = "←"; + const string right = "→"; + const string enter = "↲"; +} + namespace Draw { //* An editable text field diff --git a/src/btop_input.cpp b/src/btop_input.cpp index c1701e6..63a26e0 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -137,10 +137,10 @@ namespace Input { key = mouse_event; - if (not Menu::active and key == "mouse_click") { + if (key == "mouse_click") { const auto& [col, line] = mouse_pos; - for (const auto& [mapped_key, pos] : mouse_mappings) { + for (const auto& [mapped_key, pos] : (Menu::active ? Menu::mouse_mappings : mouse_mappings)) { if (col >= pos.col and col < pos.col + pos.width and line >= pos.line and line < pos.line + pos.height) { key = mapped_key; break; @@ -235,8 +235,10 @@ namespace Input { Proc::filter = { Config::getS("proc_filter") }; old_filter = Proc::filter.text; } - else if (key == "e") + else if (key == "e") { Config::flip("proc_tree"); + no_update = false; + } else if (key == "r") Config::flip("proc_reversed"); @@ -313,6 +315,7 @@ namespace Input { auto& pid = Config::getI("selected_pid"); if (key == "+" or key == "space") Proc::expand = pid; if (key == "-" or key == "space") Proc::collapse = pid; + no_update = false; } else if (key == "t") { Logger::debug(key); diff --git a/src/btop_linux.cpp b/src/btop_linux.cpp index ae0442c..e2b7704 100644 --- a/src/btop_linux.cpp +++ b/src/btop_linux.cpp @@ -75,6 +75,10 @@ namespace Cpu { unordered_flat_map core_mapping; } +namespace Net { + uint64_t timestamp = 0; +} + namespace Shared { fs::path procPath, passwd_path; @@ -137,6 +141,7 @@ namespace Shared { Mem::collect(); //? Init for namespace Net + Net::timestamp = time_ms(); Net::collect(); } @@ -813,6 +818,7 @@ namespace Net { auto& config_iface = Config::getS("net_iface"); auto& net_sync = Config::getB("net_sync"); auto& net_auto = Config::getB("net_auto"); + auto new_timestamp = time_ms(); if (not no_update and errors < 3) { //? Get interface list using getifaddrs() wrapper @@ -864,7 +870,7 @@ namespace Net { const uint64_t val = max(stoul(readfile(sys_file, "0")), saved_stat.last); //? Update speed, total and top values - saved_stat.speed = val - (saved_stat.last == 0 ? val : saved_stat.last); + saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000)); if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed; if (saved_stat.offset > val) saved_stat.offset = 0; saved_stat.total = val - saved_stat.offset; @@ -899,6 +905,8 @@ namespace Net { } net.compact(); } + + timestamp = new_timestamp; } //? Return empty net_info struct if no interfaces was found @@ -964,6 +972,9 @@ namespace Proc { vector current_procs; unordered_flat_map uid_user; + string current_sort; + string current_filter; + bool current_rev = false; fs::file_time_type passwd_time; @@ -976,17 +987,19 @@ namespace Proc { detail_container detailed; //* Generate process tree list - void _tree_gen(const proc_info& cur_proc, const vector& in_procs, vector& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false) { + void _tree_gen(proc_info& cur_proc, vector& in_procs, vector>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false) { auto cur_pos = out_procs.size(); bool filtering = false; //? If filtering, include children of matching processes - if (not filter.empty() and not found) { + if (not found and (should_filter or not filter.empty())) { if (not s_contains(std::to_string(cur_proc.pid), filter) and not s_contains(cur_proc.name, filter) and not s_contains(cur_proc.cmd, filter) and not s_contains(cur_proc.user, filter)) { filtering = true; + cur_proc.filtered = true; + filter_found++; } else { found = true; @@ -994,37 +1007,44 @@ namespace Proc { } } - //? Add process to vector if not filtered out or currently in a collapsed sub-tree + //? Set tree index position for process if not filtered out or currently in a collapsed sub-tree if (not collapsed and not filtering) { - out_procs.push_back(cur_proc); + out_procs.push_back(std::ref(cur_proc)); + cur_proc.tree_index = out_procs.size() - 1; //? Try to find name of the binary file and append to program name if not the same if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) { std::string_view cmd_view = cur_proc.cmd; cmd_view = cmd_view.substr(0, min(cmd_view.find(' '), cmd_view.size())); cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size())); - out_procs.back().short_cmd = (string)cmd_view; + cur_proc.short_cmd = (string)cmd_view; } } + else { + cur_proc.tree_index = in_procs.size(); + } //? Recursive iteration over all children int children = 0; for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { + if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) { + out_procs.back().get().cpu_p += p.cpu_p; + out_procs.back().get().mem += p.mem; + out_procs.back().get().threads += p.threads; + } if (collapsed and not filtering) { - out_procs.back().cpu_p += p.cpu_p; - out_procs.back().mem += p.mem; - out_procs.back().threads += p.threads; + cur_proc.filtered = true; } else children++; - _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found); + _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter); } if (collapsed or filtering) return; //? Add tree terminator symbol if it's the last child in a sub-tree - if (out_procs.size() > cur_pos + 1 and not out_procs.back().prefix.ends_with("]─")) - out_procs.back().prefix.replace(out_procs.back().prefix.size() - 8, 8, " └─ "); + if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─")) + out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ "); //? Add collapse/expand symbols if process have any children - out_procs.at(cur_pos).prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); + out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); } //* Get detailed info for selected process @@ -1133,12 +1153,18 @@ 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; + if (should_filter) current_filter = filter; + const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter); + if (sorted_change) { + current_sort = sorting; + current_rev = reverse; + } ifstream pread; string long_string; string short_str; - filter_found = 0; - const double uptime = system_uptime(); + const double uptime = system_uptime(); const int cmult = (per_core) ? Shared::coreCount : 1; bool got_detailed = false; @@ -1193,7 +1219,7 @@ namespace Proc { const size_t pid = stoul(pid_str); found.push_back(pid); - //? Check if pid already exists + //? Check if pid already exists in current_procs auto find_old = rng::find(current_procs, pid, &proc_info::pid); bool no_cache = false; if (find_old == current_procs.end()) { @@ -1210,6 +1236,7 @@ namespace Proc { if (not pread.good()) continue; getline(pread, new_proc.name); pread.close(); + //? Check for whitespace characters in name and set offset to get correct fields from stat file new_proc.name_offset = rng::count(new_proc.name, ' '); pread.open(d.path() / "cmdline"); @@ -1241,7 +1268,6 @@ namespace Proc { pread.open(d.path() / "stat"); if (not pread.good()) continue; - //? Check cached value for whitespace characters in name and set offset to get correct fields from stat file const auto& offset = new_proc.name_offset; short_str.clear(); size_t x = 0, next_x = 3; @@ -1318,19 +1344,7 @@ namespace Proc { } } - // //? Clear dead processes from cache at a regular interval - // if (++counter >= 1000 or (cache.size() > found.size() + 100)) { - // counter = 0; - // for (auto it = cache.begin(); it != cache.end();) { - // if (not v_contains(found, it->first)) - // it = cache.erase(it); - // else - // it++; - // } - // cache.compact(); - // } - - //? Clear dead processes from list + //? Clear dead processes from current_procs auto eraser = rng::remove_if(current_procs, [&](const auto& element){ return not v_contains(found, element.pid); }); current_procs.erase(eraser.begin(), eraser.end()); @@ -1343,97 +1357,102 @@ namespace Proc { redraw = true; } - old_cputimes = cputimes; - // current_procs.clear(); - // current_procs = procs; } //* ---------------------------------------------Collection done----------------------------------------------- //* Sort processes - switch (v_index(sort_vector, sorting)) { - case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break; - case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break; - case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break; - case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break; - case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break; - case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break; - case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; - case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; - } - if (reverse) rng::reverse(current_procs); + if (sorted_change or not no_update) { + switch (v_index(sort_vector, sorting)) { + case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break; + case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break; + case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break; + case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break; + case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break; + case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break; + case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; + case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; + } + if (reverse) rng::reverse(current_procs); - //* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage - if (not tree and not reverse and sorting == "cpu lazy") { - double max = 10.0, target = 30.0; - for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) { - if (i <= 5 and current_procs.at(i).cpu_p > max) - max = current_procs.at(i).cpu_p; - else if (i == 6) - target = (max > 30.0) ? max : 10.0; - if (i == offset and current_procs.at(i).cpu_p > 30.0) - offset++; - else if (current_procs.at(i).cpu_p > target) { - rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1); - if (++x > 10) break; + //* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage + if (not tree and not reverse and sorting == "cpu lazy") { + double max = 10.0, target = 30.0; + for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) { + if (i <= 5 and current_procs.at(i).cpu_p > max) + max = current_procs.at(i).cpu_p; + else if (i == 6) + target = (max > 30.0) ? max : 10.0; + if (i == offset and current_procs.at(i).cpu_p > 30.0) + offset++; + else if (current_procs.at(i).cpu_p > target) { + rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1); + if (++x > 10) break; + } } } } + //* Match filter if defined - for (auto& p : current_procs) { - if (not tree and not filter.empty()) { - if (not s_contains(to_string(p.pid), filter) + if (should_filter) { + filter_found = 0; + for (auto& p : current_procs) { + if (not tree and not filter.empty()) { + if (not s_contains(to_string(p.pid), filter) and not s_contains(p.name, filter) and not s_contains(p.cmd, filter) and not s_contains(p.user, filter)) { p.filtered = true; filter_found++; + } + else { + p.filtered = false; } + } + else { + p.filtered = false; } - else if (not tree) { - p.filtered = false; } } - - //* Generate tree view if enabled - if (tree) { + if (tree and (not no_update or should_filter or sorted_change)) { if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) { auto collapser = rng::find(current_procs, find_pid, &proc_info::pid); if (collapser != current_procs.end()) { if (collapse == expand) { collapser->collapsed = not collapser->collapsed; - collapse = expand = -1; } else if (collapse > -1) { collapser->collapsed = true; - collapse = -1; } else if (expand > -1) { collapser->collapsed = false; - expand = -1; } } + collapse = expand = -1; } + if (should_filter or not filter.empty()) filter_found = 0; - vector tree_procs; + vector> tree_procs; tree_procs.reserve(current_procs.size()); //? Stable sort to retain selected sorting among processes with the same parent rng::stable_sort(current_procs, rng::less{}, &proc_info::ppid); //? Start recursive iteration over processes with the lowest shared parent pids - for (const auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { - _tree_gen(p, current_procs, tree_procs, 0, false, filter); + for (auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { + _tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter); } - //procs.clear(); - current_procs = std::move(tree_procs); + //? Final sort based on tree index + rng::sort(current_procs, rng::less{}, &proc_info::tree_index); + if (reverse) rng::reverse(current_procs); + } - numpids = (not filter.empty() ? filter_found : (int)current_procs.size()); + numpids = (int)current_procs.size() - filter_found; return current_procs; } diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 609627b..444d813 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -34,6 +34,8 @@ namespace Menu { atomic active (false); string output; + unordered_flat_map mouse_mappings; + const unordered_flat_map>> menus = { { "options", { { "normal", { diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index af36785..fc106b7 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -21,6 +21,8 @@ tab-size = 4 #include #include +#include + using std::string, std::atomic; namespace Menu { @@ -28,5 +30,7 @@ namespace Menu { extern atomic active; extern string output; + //? line, col, height, width + extern unordered_flat_map mouse_mappings; } diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 0393c85..5f40d0a 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -215,7 +215,7 @@ namespace Proc { char state = '0'; uint64_t cpu_n = 0, p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0; string prefix = ""; - size_t depth = 0; + size_t depth = 0, tree_index = 0; bool collapsed = false, filtered = false; }; diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 757860c..a42d96d 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -26,6 +26,7 @@ tab-size = 4 #include #include +#include #include #include @@ -121,7 +122,7 @@ namespace Term { void restore() { if (initialized) { tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings); - cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush; + cout << mouse_off << clear << Fx::reset << normal_screen << show_cursor << flush; initialized = false; } } @@ -167,6 +168,15 @@ namespace Tools { return str; } + string s_replace(const string& str, const string& from, const string& to) { + size_t start_pos = str.find(from); + if(start_pos == std::string::npos) + return str; + string out = str; + out.replace(start_pos, from.length(), to); + return out; + } + string ltrim(const string& str, const string& t_str) { string_view str_v = str; while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size()); @@ -310,16 +320,6 @@ namespace Tools { atomic_notify(this->atom); } - thread_lock::thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { - status = pthread_mutex_lock(&pt_mutex); - } - - thread_lock::~thread_lock() { - if (status == 0) { - pthread_mutex_unlock(&pt_mutex); - } - } - string readfile(const std::filesystem::path& path, const string& fallback) { if (not fs::exists(path)) return fallback; string out; @@ -345,6 +345,18 @@ namespace Tools { return {0, ""}; } + string hostname() { + char host[HOST_NAME_MAX]; + gethostname(host, HOST_NAME_MAX); + return (string)host; + } + + string username() { + auto user = getenv("LOGNAME"); + if (user == NULL or strcmp(user, "")) user = getenv("USER"); + return (user != NULL ? user : ""); + } + } namespace Logger { diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 3b4f1ba..5885726 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -144,6 +144,9 @@ namespace Tools { //* Resize a string consisting of UTF8 characters from left (only reduces size) string luresize(const string str, const size_t len, const bool wide=false); + //* Replace in with and return new string + string s_replace(const string& str, const string& from, const string& to); + //* Capatilize inline string capitalize(string str) { str.at(0) = toupper(str.at(0)); @@ -263,8 +266,11 @@ namespace Tools { //* Return current time in format string strf_time(const string& strf); + string hostname(); + string username(); + #if __GNUC__ < 11 - inline void atomic_wait(const atomic& atom, const bool old=true) noexcept { while (atom.load() == old); } + inline void atomic_wait(const atomic& atom, const bool old=true) noexcept { while (atom.load() == old) sleep_ms(1); } inline void atomic_notify(const atomic& atom) noexcept { (void)atom; } #else inline void atomic_wait(const atomic& atom, const bool old=true) noexcept { atom.wait(old); } @@ -280,15 +286,6 @@ namespace Tools { ~atomic_lock(); }; - //* RAII wrapper for pthread_mutex_lock & unlock - class thread_lock { - pthread_mutex_t& pt_mutex; - public: - int status; - thread_lock(pthread_mutex_t& mtx); - ~thread_lock(); - }; - //* Read a complete file and return as a string string readfile(const std::filesystem::path& path, const string& fallback="");