Added Proc::_collect_details for process info box collection

This commit is contained in:
aristocratos 2021-07-04 01:18:48 +02:00
parent 3634633e21
commit ad5864266a
11 changed files with 224 additions and 75 deletions

View file

@ -55,19 +55,15 @@ distclean: clean
install:
@mkdir -p $(DESTDIR)$(PREFIX)/bin
@cp -p bin/btop $(DESTDIR)$(PREFIX)/bin/btop
@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
#Set suid bit for btop to root, will make btop run as root regardless of actual user
#Set suid bit for btop to root, will make btop run with admin privileges regardless of actual user
su-setuid:
@su --session-command "make as-root-setuid" root
as-root-setuid:
@chown root:root $(DESTDIR)$(PREFIX)/bin/btop
@chmod 4755 $(DESTDIR)$(PREFIX)/bin/btop
@su --session-command "chown root:root $(DESTDIR)$(PREFIX)/bin/btop && chmod 4755 $(DESTDIR)$(PREFIX)/bin/btop" root
uninstall:
@rm -rf $(DESTDIR)$(PREFIX)/bin/btop
@ -78,8 +74,8 @@ uninstall:
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
#Link
$(TARGET): $(OBJECTS)
$(CXX) -o $(TARGETDIR)/$(TARGET) $^
btop: $(OBJECTS)
$(CXX) -o $(TARGETDIR)/btop $^
#Compile
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)

View file

@ -16,7 +16,6 @@ indent = tab
tab-size = 4
*/
#include <string>
#include <array>
#include <list>
@ -32,6 +31,7 @@ tab-size = 4
#include <robin_hood.h>
#include <cmath>
#include <iostream>
#include <exception>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
@ -77,7 +77,6 @@ namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
namespace Global {
string banner;
size_t banner_width = 0;
@ -89,7 +88,7 @@ namespace Global {
uint64_t start_time;
bool quitting = false;
atomic<bool> quitting (false);
bool arg_tty = false;
bool arg_low_color = false;
@ -277,12 +276,12 @@ int main(int argc, char **argv){
}
if (std::error_code ec; not Global::self_path.empty()) {
Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
if (ec or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
if (ec or not fs::is_directory(Theme::theme_dir) or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
}
if (Theme::theme_dir.empty()) {
for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) {
if (fs::exists(fs::path(theme_path)) and access(theme_path, R_OK) != -1) {
if (fs::is_directory(fs::path(theme_path)) and access(theme_path, R_OK) != -1) {
Theme::theme_dir = fs::path(theme_path);
break;
}
@ -465,7 +464,7 @@ int main(int argc, char **argv){
if (false) {
vector<long long> mydata;
deque<long long> mydata;
for (long long i = 0; i <= 100; i++) mydata.push_back(i);
for (long long i = 100; i >= 0; i--) mydata.push_back(i);
// mydata.push_back(0);
@ -606,6 +605,7 @@ int main(int argc, char **argv){
string filter;
string filter_cur;
string key;
vector<Proc::proc_info> plist;
int xc;
for (size_t i : iota(0, (int)Term::height - 19)){
@ -613,14 +613,20 @@ int main(int argc, char **argv){
greyscale.push_back(Theme::dec_to_color(xc, xc, xc));
}
string pbox = Draw::createBox({.x = 1, .y = 10, .width = Term::width, .height = Term::height - 16, .line_color = Theme::c("proc_box"), .title = "testbox", .title2 = "below", .fill = false, .num = 7});
string pbox = Draw::createBox({.x = 1, .y = 10, .width = Term::width, .height = Term::height - 18, .line_color = Theme::c("proc_box"), .title = "testbox", .title2 = "below", .fill = false, .num = 7});
pbox += Mv::r(1) + Theme::c("title") + Fx::b + rjust("Pid:", 8) + " " + ljust("Program:", 16) + " " + ljust("Command:", Term::width - 70) + " Threads: " +
ljust("User:", 10) + " " + rjust("MemB", 5) + " " + rjust("Cpu%", 14) + "\n" + Fx::reset + Mv::save;
while (key != "q") {
timestamp = time_micros();
tsl = time_ms() + timer;
auto plist = Proc::collect();
try {
plist = Proc::collect();
}
catch (std::exception const& e) {
Logger::error("Caught exception in Proc::collect() : "s + e.what());
exit(1);
}
timestamp2 = time_micros();
timestamp = timestamp2 - timestamp;
ostring.clear();
@ -645,21 +651,25 @@ int main(int argc, char **argv){
cmd_cond = p.cmd.substr(0, std::min(p.cmd.find(' '), p.cmd.size()));
cmd_cond = cmd_cond.substr(std::min(cmd_cond.find_last_of('/') + 1, cmd_cond.size()));
}
ostring += Mv::r(1) + (Config::getB("tty_mode") ? "" : greyscale[lc]) + ljust(p.prefix + to_string(p.pid) + " " + p.name + " " + (not cmd_cond.empty() and cmd_cond != p.name ? "(" + cmd_cond + ")" : ""), Term::width - 40, true) + " "
ostring += Mv::r(1) + (Config::getB("tty_mode") ? "" : greyscale[lc]) + ljust(p.prefix + to_string(p.pid) + " " + p.name + " "
+ (not cmd_cond.empty() and cmd_cond != p.name ? "(" + cmd_cond + ")" : ""), Term::width - 40, true) + " "
+ rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ')
+ (p.cpu_p < 10 or p.cpu_p >= 100 ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4))
+ "\n";
}
if (lc++ > Term::height - 21) break;
if (lc++ > Term::height - 23) break;
}
while (lc++ < Term::height - 19) ostring += Mv::r(1) + string(Term::width - 2, ' ') + "\n";
while (lc++ < Term::height - 21) ostring += Mv::r(1) + string(Term::width - 2, ' ') + "\n";
avgtimes.push_front(timestamp);
if (avgtimes.size() > 30) avgtimes.pop_back();
cout << pbox << ostring << Fx::reset << "\n" << endl;
cout << " Details for " << Proc::detailed.entry.name << " (" << Proc::detailed.entry.pid << ") Status: " << Proc::detailed.status << " Elapsed: " << Proc::detailed.elapsed
<< " Mem: " << floating_humanizer(Proc::detailed.entry.mem) << " "
<< "\n Parent: " << Proc::detailed.parent << " IO in/out: " << Proc::detailed.io_read << "/" << Proc::detailed.io_write << " " << endl;
cout << Mv::to(Term::height - 4, 1) << "Processes call took: " << rjust(to_string(timestamp), 5) << " μs. Average: " <<
rjust(to_string(accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size()), 5) << " μs of " << avgtimes.size() <<
" samples. Drawing took: " << time_micros() - timestamp2 << " μs.\nNumber of processes: " << Proc::numpids << ". Number in vector: " << plist.size() << ". Run count: " <<
@ -675,6 +685,7 @@ int main(int argc, char **argv){
else if (ulen(key) == 1 ) filter.append(key);
else { key.clear(); continue; }
if (filter != Config::getS("proc_filter")) Config::set("proc_filter", filter);
key.clear();
break;
}
else if (key == "q") break;
@ -693,6 +704,12 @@ int main(int argc, char **argv){
else if (key == "r") Config::flip("proc_reversed");
else if (key == "c") Config::flip("proc_per_core");
else if (key == "delete") { filter.clear(); Config::set("proc_filter", filter); }
else if (key == "d" ) {
Config::flip("show_detailed");
if (Config::getB("show_detailed")) {
Config::set("detailed_pid", (int)plist.at(0).pid);
}
}
else continue;
break;
}

View file

@ -222,6 +222,8 @@ namespace Config {
};
unordered_flat_map<string, int> intsTmp;
vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
bool _locked(const string& name){
atomic_wait(writelock);
if (not write_new and rng::find_if(descriptions, [&name](const auto& a){ return a.at(0) == name; }) != descriptions.end())
@ -297,6 +299,13 @@ namespace Config {
writelock = false;
}
bool check_boxes(string boxes){
for (auto& box : ssplit(boxes)) {
if (not v_contains(valid_boxes, box)) return false;
}
return true;
}
void load(fs::path conf_file, vector<string>& load_errors){
if (conf_file.empty())
return;
@ -352,6 +361,8 @@ namespace Config {
load_errors.push_back("Invalid log_level: " + value);
else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value))
load_errors.push_back("Invalid graph symbol identifier: " + value);
else if (name == "shown_boxes" and not value.empty() and not check_boxes(value))
load_errors.push_back("Invalid box name in shown_boxes. Using default.");
else
strings.at(name) = value;
}

View file

@ -32,6 +32,9 @@ namespace Config {
extern const vector<string> valid_graph_symbols;
//* Check if string only contains space seperated valid names for boxes
bool check_boxes(string boxes);
//* Return bool for config key <name>
const bool& getB(string name);

View file

@ -25,8 +25,10 @@ tab-size = 4
#include <btop_draw.hpp>
#include <btop_config.hpp>
#include <btop_theme.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
using std::round, std::views::iota, std::string_literals::operator""s, std::clamp, std::array, std::floor;
namespace rng = std::ranges;
@ -98,7 +100,6 @@ namespace Draw {
using namespace Tools;
//* Create a box using values from a BoxConf struct and return as a string
string createBox(BoxConf c){
string out;
string lcolor = (c.line_color.empty()) ? Theme::c("div_line") : c.line_color;
@ -137,7 +138,7 @@ namespace Draw {
return out + Fx::reset + Mv::to(c.y + 1, c.x + 1);
}
//* Meter class ------------------------------------------------------------------------------------------------------------>
void Meter::operator()(int width, string color_gradient, bool invert) {
this->width = width;
this->color_gradient = color_gradient;
@ -164,7 +165,8 @@ namespace Draw {
return out;
}
void Graph::_create(const vector<long long>& data, int data_offset) {
//* Graph class ------------------------------------------------------------------------------------------------------------>
void Graph::_create(const deque<long long>& data, int data_offset) {
const bool mult = (data.size() - data_offset > 1);
if (mult and (data.size() - data_offset) % 2 != 0) data_offset--;
auto& graph_symbol = Symbols::graph_symbols.at(symbol + '_' + (invert ? "down" : "up"));
@ -203,7 +205,6 @@ namespace Draw {
graphs[current][horizon] += (height == 1 and result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])];
}
if (mult and i > data_offset) last = data_value;
}
last = data_value;
if (height == 1)
@ -219,8 +220,7 @@ namespace Draw {
out += Fx::reset;
}
void Graph::operator()(int width, int height, string color_gradient, const vector<long long>& data, string symbol, bool invert, bool no_zero, long long max_value, long long offset) {
void Graph::operator()(int width, int height, string color_gradient, const deque<long long>& data, string symbol, bool invert, bool no_zero, long long max_value, long long offset) {
graphs[true].clear(); graphs[false].clear();
this->width = width; this->height = height;
this->invert = invert; this->offset = offset;
@ -246,8 +246,7 @@ namespace Draw {
this->_create(data, data_offset);
}
string& Graph::operator()(const vector<long long>& data, bool data_same) {
string& Graph::operator()(const deque<long long>& data, bool data_same) {
if (data_same) return out;
//? Make room for new characters on graph
@ -263,18 +262,38 @@ namespace Draw {
string& Graph::operator()() {
return out;
}
//*------------------------------------------------------------------------------------------------------------------------->
void calcSizes(){
}
}
namespace Box {
namespace Cpu {
Draw::BoxConf box;
string background;
}
namespace Mem {
Draw::BoxConf box;
string background;
}
namespace Net {
Draw::BoxConf box;
string background;
}
namespace Proc {
// Draw::BoxConf box;
Draw::BoxConf box;
string background;
}

View file

@ -21,8 +21,9 @@ tab-size = 4
#include <string>
#include <vector>
#include <robin_hood.h>
#include <deque>
using std::string, std::vector, robin_hood::unordered_flat_map;
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque;
namespace Symbols {
extern const string h_line;
@ -74,30 +75,44 @@ namespace Draw {
unordered_flat_map<bool, vector<string>> graphs = { {true, {}}, {false, {}}};
//* Create two representations of the graph to switch between to represent two values for each braille character
void _create(const vector<long long>& data, int data_offset);
void _create(const deque<long long>& data, int data_offset);
public:
//* Set graph options and initialize with data
void operator()(int width, int height, string color_gradient, const vector<long long>& data, string symbol="default", bool invert=false, bool no_zero=false, long long max_value=0, long long offset=0);
void operator()(int width, int height, string color_gradient, const deque<long long>& data, string symbol="default", bool invert=false, bool no_zero=false, long long max_value=0, long long offset=0);
//* Add last value from back of <data> and return string representation of graph
string& operator()(const vector<long long>& data, bool data_same=false);
string& operator()(const deque<long long>& data, bool data_same=false);
//* Return string representation of graph
string& operator()();
};
//* Calculate sizes of boxes, draw outlines and save to enabled boxes namespaces
void calcSizes();
}
namespace Box {
namespace Cpu {
extern Draw::BoxConf box;
}
namespace Mem {
extern Draw::BoxConf box;
}
namespace Net {
extern Draw::BoxConf box;
}
namespace Proc {
// Draw::BoxConf box;
extern Draw::BoxConf box;
}

View file

@ -66,12 +66,15 @@ namespace Input {
};
}
std::atomic<bool> interrupt (false);
string last = "";
bool poll(int timeout){
if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
while (timeout > 0) {
if (cin.rdbuf()->in_avail() > 0) return true;
if (interrupt) { interrupt = false; return false; }
sleep_ms(timeout < 10 ? timeout : 10);
timeout -= 10;
}
@ -91,7 +94,10 @@ namespace Input {
}
string wait(){
while (cin.rdbuf()->in_avail() < 1) sleep_ms(10);
while (cin.rdbuf()->in_avail() < 1) {
if (interrupt) { interrupt = false; return string(); }
sleep_ms(10);
}
return get();
}

View file

@ -19,6 +19,7 @@ tab-size = 4
#pragma once
#include <string>
#include <atomic>
/* The input functions relies on the following std::cin options being set:
cin.sync_with_stdio(false);
@ -29,6 +30,8 @@ tab-size = 4
//* Functions and variables for handling keyboard and mouse input
namespace Input {
extern std::atomic<bool> interrupt;
//* Last entered key
extern std::string last;

View file

@ -25,7 +25,6 @@ tab-size = 4
#include <filesystem>
#include <ranges>
#include <list>
#include <robin_hood.h>
#include <cmath>
#include <iostream>
#include <cmath>
@ -39,7 +38,7 @@ tab-size = 4
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
std::round, std::string_literals::operator""s, robin_hood::unordered_flat_map;
std::round, std::string_literals::operator""s;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
@ -101,6 +100,8 @@ namespace Proc {
size_t depth = 0;
bool collapsed = false;
};
vector<proc_info> current_procs;
unordered_flat_map<size_t, p_cache> cache;
unordered_flat_map<string, string> uid_user;
@ -120,6 +121,19 @@ namespace Proc {
"cpu direct",
"cpu lazy",
};
unordered_flat_map<char, string> proc_states = {
{'R', "Running"},
{'S', "Sleeping"},
{'D', "Waiting"},
{'Z', "Zombie"},
{'T', "Stopped"},
{'t', "Tracing"},
{'X', "Dead"},
{'x', "Dead"},
{'K', "Wakekill"},
{'W', "Unknown"},
{'P', "Parked"}
};
detail_container detailed;
@ -163,42 +177,100 @@ namespace Proc {
out_procs.at(cur_pos).prefix = ""s * cur_depth + (children > 0 ? (cache.at(cur_proc.pid).collapsed ? "[+] " : "[-] ") : " ├─ ");
}
//* Get datiled info for selected process
void _collect_details(proc_info p){
fs::path pid_path = Shared::proc_path / std::to_string(p.pid);
//* Get detailed info for selected process
void _collect_details(size_t pid, uint64_t uptime, vector<proc_info>& procs, bool is_filtered){
fs::path pid_path = Shared::proc_path / std::to_string(pid);
if (pid != detailed.last_pid) {
detailed.last_pid = pid;
detailed.cpu_percent.clear();
detailed.parent.clear();
detailed.io_read.clear();
detailed.io_write.clear();
detailed.skip_smaps = false;
}
//? Copy proc_info for process from proc vector
auto p = rng::find(procs, pid, &proc_info::pid);
detailed.entry = *p;
//? Update cpu percent deque for process cpu graph
detailed.cpu_percent.push_back(round(detailed.entry.cpu_c));
while (detailed.cpu_percent.size() > Term::width) detailed.cpu_percent.pop_front();
//? Process runtime
detailed.elapsed = sec_to_dhms(uptime - (cache.at(pid).cpu_s / Shared::clk_tck));
//? Get parent process name
if (detailed.parent.empty() and cache.contains(detailed.entry.ppid)) detailed.parent = cache.at(detailed.entry.ppid).name;
//? Expand process status from single char to explanative string
detailed.status = (proc_states.contains(detailed.entry.state)) ? proc_states.at(detailed.entry.state) : "Unknown";
detailed.entry = p;
ifstream d_read;
string short_str;
//* Get RSS mem from smaps
if (fs::exists(pid_path / "smaps")) {
pid_path /= "smaps";
d_read.open(pid_path);
//? Try to get RSS mem from proc/[pid]/smaps
detailed.memory.clear();
if (not detailed.skip_smaps and fs::exists(pid_path / "smaps")) {
d_read.open(pid_path / "smaps");
if (d_read.good()) {
uint64_t rss = 0;
string val;
try {
while (not d_read.eof()) {
d_read.ignore(SSmax, 'R');
if (d_read.peek() == 's') {
d_read.ignore(SSmax, ':');
getline(d_read, val, 'k');
rss += stoull(val) << 10;
getline(d_read, short_str, 'k');
rss += stoull(short_str);
}
}
detailed.entry.mem = rss;
if (rss == detailed.entry.mem >> 10)
detailed.skip_smaps = true;
else
detailed.memory = floating_humanizer(rss, false, 1);
}
catch (std::invalid_argument const&) {}
catch (std::out_of_range const&) {}
}
d_read.close();
}
if (detailed.memory.empty()) detailed.memory = floating_humanizer(detailed.entry.mem, false);
//? Get bytes read and written from proc/[pid]/io
if (fs::exists(pid_path / "io")) {
d_read.open(pid_path / "io");
if (d_read.good()) {
try {
string name;
while (not d_read.eof()) {
getline(d_read, name, ':');
if (name.ends_with("read_bytes")) {
getline(d_read, short_str);
detailed.io_read = floating_humanizer(stoull(short_str));
}
else if (name.ends_with("write_bytes")) {
getline(d_read, short_str);
detailed.io_write = floating_humanizer(stoull(short_str));
break;
}
else
d_read.ignore(SSmax, '\n');
}
}
catch (std::invalid_argument const&) {}
catch (std::out_of_range const&) {}
}
d_read.close();
}
if (is_filtered) procs.erase(p);
}
vector<proc_info> current_procs;
//* Collects and sorts process information from /proc, saves to and returns reference to Proc::current_procs;
vector<proc_info>& collect(){
//* Collects and sorts process information from /proc
vector<proc_info>& collect(bool return_last){
if (return_last) return current_procs;
atomic_wait_set(collecting);
auto& sorting = Config::getS("proc_sorting");
auto reverse = Config::getB("proc_reversed");
@ -210,14 +282,15 @@ namespace Proc {
ifstream pread;
string long_string;
string short_str;
auto uptime = system_uptime();
double uptime = system_uptime();
vector<proc_info> procs;
procs.reserve((numpids + 10));
int npids = 0;
int cmult = (per_core) ? Global::coreCount : 1;
bool got_detailed = false;
bool detailed_filtered = false;
//* 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) != Shared::passwd_time) {
string r_uid, r_user;
Shared::passwd_time = fs::last_write_time(Shared::passwd_path);
@ -254,7 +327,6 @@ namespace Proc {
return current_procs;
}
bool new_cache = false;
string pid_str = d.path().filename();
if (not isdigit(pid_str[0])) continue;
@ -264,7 +336,6 @@ namespace Proc {
//* Cache program name, command and username
if (not cache.contains(new_proc.pid)) {
string name, cmd, user;
new_cache = true;
pread.open(d.path() / "comm");
if (not pread.good()) continue;
getline(pread, name);
@ -302,13 +373,14 @@ namespace Proc {
//* Match filter if defined
if (not tree and not filter.empty()
and not (show_detailed and new_proc.pid == detailed_pid)
and pid_str.find(filter) == string::npos
and cache[new_proc.pid].name.find(filter) == string::npos
and cache[new_proc.pid].cmd.find(filter) == string::npos
and cache[new_proc.pid].user.find(filter) == string::npos) {
if (new_cache) cache.erase(new_proc.pid);
continue;
if (show_detailed and new_proc.pid == detailed_pid)
detailed_filtered = true;
else
continue;
}
new_proc.name = cache[new_proc.pid].name;
new_proc.cmd = cache[new_proc.pid].cmd;
@ -356,7 +428,7 @@ namespace Proc {
}
case 20: { //? Number of threads
new_proc.threads = stoull(short_str);
next_x = (new_cache) ? 22 : 24;
next_x = (cache[new_proc.pid].cpu_s == 0) ? 22 : 24;
continue;
}
case 22: { //? Save cpu seconds to cache if missing
@ -386,8 +458,6 @@ namespace Proc {
if (x-offset < 24) continue;
// _parse_smaps(new_proc);
//? Process cpu usage since last update
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0;
@ -397,9 +467,7 @@ namespace Proc {
//? Update cache with latest cpu times
cache[new_proc.pid].cpu_t = cpu_t;
//? Update the details info box for process if active
if (show_detailed and new_proc.pid == detailed_pid) {
_collect_details(new_proc);
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
got_detailed = true;
}
@ -408,8 +476,12 @@ namespace Proc {
}
if (show_detailed and not got_detailed) {
detailed.entry.state = 'X';
//* Update the details info box for process if active
if (show_detailed and got_detailed) {
_collect_details(detailed_pid, round(uptime), procs, detailed_filtered);
}
else if (show_detailed and not got_detailed) {
detailed.status = "Dead";
}
//* Sort processes

View file

@ -22,8 +22,10 @@ tab-size = 4
#include <vector>
#include <filesystem>
#include <atomic>
#include <deque>
#include <robin_hood.h>
using std::string, std::vector;
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map;
namespace Global {
extern const string Version;
@ -49,6 +51,9 @@ namespace Proc {
//? Contains the valid sorting options for processes
extern vector<string> sort_vector;
//? Translation from process state char to explanative string
extern unordered_flat_map<char, string> proc_states;
//* Container for process information
struct proc_info {
size_t pid;
@ -65,13 +70,14 @@ namespace Proc {
//* Container for process info box
struct detail_container {
proc_info entry;
string elapsed, parent, status, io_read, io_write;
string elapsed, parent, status, io_read, io_write, memory;
size_t last_pid = 0;
bool skip_smaps = false;
deque<long long> cpu_percent;
};
extern detail_container detailed;
extern vector<proc_info> current_procs;
//* Collects and sorts process information from /proc, saves and returns reference to Proc::current_procs;
vector<proc_info>& collect();
vector<proc_info>& collect(bool return_last=false);
}

View file

@ -363,6 +363,7 @@ namespace Theme {
themes.push_back("TTY");
for (const auto& path : { theme_dir, user_theme_dir } ) {
if (path.empty()) continue;
for (auto& file : fs::directory_iterator(path)){
if (file.path().extension() == ".theme" and access(file.path().c_str(), R_OK) != -1) {
themes.push_back(file.path().c_str());