diff options
-rw-r--r-- | Makefile | 114 | ||||
-rw-r--r-- | README.md | 16 | ||||
-rw-r--r-- | src/btop.cpp | 258 | ||||
-rw-r--r-- | src/btop_config.cpp | 13 | ||||
-rw-r--r-- | src/btop_draw.cpp | 54 | ||||
-rw-r--r-- | src/btop_draw.hpp | 29 | ||||
-rw-r--r-- | src/btop_input.cpp | 9 | ||||
-rw-r--r-- | src/btop_linux.cpp | 169 | ||||
-rw-r--r-- | src/btop_menu.cpp | 2 | ||||
-rw-r--r-- | src/btop_menu.hpp | 4 | ||||
-rw-r--r-- | src/btop_shared.hpp | 2 | ||||
-rw-r--r-- | src/btop_tools.cpp | 34 | ||||
-rw-r--r-- | src/btop_tools.hpp | 17 |
13 files changed, 465 insertions, 256 deletions
@@ -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) - -#Make the Directories +#? Default Make +all: msg directories btop + +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 @@ -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 <cmath> #include <iostream> #include <exception> +#include <tuple> +#include <regex> #include <btop_shared.hpp> #include <btop_tools.hpp> @@ -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<string, string> 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<bool> active (false); atomic<bool> stopping (false); atomic<bool> waiting (false); - atomic<bool> do_work (false); + + //* Setup semaphore for triggering thread to do work +#if __GNUC__ < 11 + #include <semaphore.h> + 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 <semaphore> + 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::mem_info&> mem; future<Net::net_info&> net; future<vector<Proc::proc_info>&> 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 <update_ms> 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<string, bool> 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<string, 10> 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 = (t |