mirror of
https://github.com/aristocratos/btop.git
synced 2024-06-17 09:54:57 +12:00
Store config in a struct
This commit is contained in:
parent
2a864f6f2e
commit
759fb8ac72
|
@ -18,6 +18,7 @@ tab-size = 4
|
|||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
|
@ -40,265 +41,27 @@ using namespace Tools;
|
|||
|
||||
//* Functions and variables for reading and writing the btop config file
|
||||
namespace Config {
|
||||
|
||||
atomic<bool> locked (false);
|
||||
atomic<bool> writelock (false);
|
||||
bool write_new;
|
||||
robin_hood::unordered_flat_set<std::string_view> cached{};
|
||||
ConfigSet current = ConfigSet::with_defaults();
|
||||
ConfigSet cache{};
|
||||
|
||||
const vector<array<string, 2>> descriptions = {
|
||||
{"color_theme", "#* Name of a btop++/bpytop/bashtop formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes.\n"
|
||||
"#* Themes should be placed in \"../share/btop/themes\" relative to binary or \"$HOME/.config/btop/themes\""},
|
||||
const ConfigSet& get() {
|
||||
return current;
|
||||
}
|
||||
|
||||
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
|
||||
|
||||
{"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
|
||||
|
||||
{"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."},
|
||||
|
||||
{"presets", "#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.\n"
|
||||
"#* Format: \"box_name:P:G,box_name:P:G\" P=(0 or 1) for alternate positions, G=graph symbol to use for box.\n"
|
||||
"#* Use whitespace \" \" as separator between different presets.\n"
|
||||
"#* Example: \"cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty\""},
|
||||
|
||||
{"vim_keys", "#* Set to True to enable \"h,j,k,l,g,G\" keys for directional control in lists.\n"
|
||||
"#* Conflicting keys for h:\"help\" and k:\"kill\" is accessible while holding shift."},
|
||||
|
||||
{"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"
|
||||
"#* \"tty\" uses only 3 different symbols but will work with most fonts and should work in a real TTY.\n"
|
||||
"#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."},
|
||||
|
||||
{"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
|
||||
{"graph_symbol_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
|
||||
{"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
|
||||
{"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
|
||||
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
|
||||
|
||||
{"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."},
|
||||
|
||||
{"proc_sorting", "#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu direct\",\n"
|
||||
"#* \"cpu lazy\" sorts top process over time (easier to follow), \"cpu direct\" updates top process directly."},
|
||||
|
||||
{"proc_reversed", "#* Reverse sorting order, True or False."},
|
||||
|
||||
{"proc_tree", "#* Show processes as a tree."},
|
||||
|
||||
{"proc_colors", "#* Use the cpu graph colors in the process list."},
|
||||
|
||||
{"proc_gradient", "#* Use a darkening gradient in the process list."},
|
||||
|
||||
{"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."},
|
||||
|
||||
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
|
||||
|
||||
{"proc_cpu_graphs", "#* Show cpu graph for each process."},
|
||||
|
||||
{"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)"},
|
||||
|
||||
{"proc_left", "#* Show proc box on left side of screen instead of right."},
|
||||
|
||||
{"proc_filter_kernel", "#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop)."},
|
||||
|
||||
{"proc_aggregate", "#* In tree-view, always accumulate child process resources in the parent process."},
|
||||
|
||||
{"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n"
|
||||
"#* Select from a list of detected attributes from the options menu."},
|
||||
|
||||
{"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n"
|
||||
"#* Select from a list of detected attributes from the options menu."},
|
||||
|
||||
{"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
|
||||
|
||||
{"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
|
||||
|
||||
{"cpu_bottom", "#* Show cpu box at bottom of screen instead of top."},
|
||||
|
||||
{"show_uptime", "#* Shows the system uptime in the CPU box."},
|
||||
|
||||
{"check_temp", "#* Show cpu temperature."},
|
||||
|
||||
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
|
||||
|
||||
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
|
||||
|
||||
{"cpu_core_map", "#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.\n"
|
||||
"#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.\n"
|
||||
"#* Format \"x:y\" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.\n"
|
||||
"#* Example: \"4:0 5:1 6:3\""},
|
||||
|
||||
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
|
||||
|
||||
{"base_10_sizes", "#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024."},
|
||||
|
||||
{"show_cpu_freq", "#* Show CPU frequency."},
|
||||
|
||||
{"clock_format", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.\n"
|
||||
"#* Special formatting: /host = hostname | /user = username | /uptime = system uptime"},
|
||||
|
||||
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
|
||||
|
||||
{"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
|
||||
|
||||
{"disks_filter", "#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace \" \".\n"
|
||||
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot /home/user\"."},
|
||||
|
||||
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
|
||||
|
||||
{"mem_below_net", "#* Show mem box below net box instead of above."},
|
||||
|
||||
{"zfs_arc_cached", "#* Count ZFS ARC in cached and available memory."},
|
||||
|
||||
{"show_swap", "#* If swap memory should be shown in memory box."},
|
||||
|
||||
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
|
||||
|
||||
{"show_disks", "#* If mem box should be split to also show disks info."},
|
||||
|
||||
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
|
||||
|
||||
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
|
||||
|
||||
{"zfs_hide_datasets", "#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)"},
|
||||
|
||||
{"disk_free_priv", "#* Set to true to show available disk space for privileged users."},
|
||||
|
||||
{"show_io_stat", "#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view."},
|
||||
|
||||
{"io_mode", "#* Toggles io mode for disks, showing big graphs for disk read/write speeds."},
|
||||
|
||||
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
|
||||
|
||||
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (100 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n"
|
||||
"#* Example: \"/mnt/media:100 /:20 /boot:1\"."},
|
||||
|
||||
{"net_download", "#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False."},
|
||||
|
||||
{"net_upload", ""},
|
||||
|
||||
{"net_auto", "#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
|
||||
|
||||
{"net_sync", "#* Sync the auto scaling for download and upload to whichever currently has the highest scale."},
|
||||
|
||||
{"net_iface", "#* Starts with the Network Interface specified here."},
|
||||
|
||||
{"show_battery", "#* Show battery stats in top right if battery is present."},
|
||||
|
||||
{"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."},
|
||||
|
||||
{"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n"
|
||||
"#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}
|
||||
};
|
||||
|
||||
unordered_flat_map<std::string_view, string> strings = {
|
||||
{"color_theme", "Default"},
|
||||
{"shown_boxes", "cpu mem net proc"},
|
||||
{"graph_symbol", "braille"},
|
||||
{"presets", "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"},
|
||||
{"graph_symbol_cpu", "default"},
|
||||
{"graph_symbol_mem", "default"},
|
||||
{"graph_symbol_net", "default"},
|
||||
{"graph_symbol_proc", "default"},
|
||||
{"proc_sorting", "cpu lazy"},
|
||||
{"cpu_graph_upper", "total"},
|
||||
{"cpu_graph_lower", "total"},
|
||||
{"cpu_sensor", "Auto"},
|
||||
{"selected_battery", "Auto"},
|
||||
{"cpu_core_map", ""},
|
||||
{"temp_scale", "celsius"},
|
||||
{"clock_format", "%X"},
|
||||
{"custom_cpu_name", ""},
|
||||
{"disks_filter", ""},
|
||||
{"io_graph_speeds", ""},
|
||||
{"net_iface", ""},
|
||||
{"log_level", "WARNING"},
|
||||
{"proc_filter", ""},
|
||||
{"proc_command", ""},
|
||||
{"selected_name", ""},
|
||||
};
|
||||
unordered_flat_map<std::string_view, string> stringsTmp;
|
||||
|
||||
unordered_flat_map<std::string_view, bool> bools = {
|
||||
{"theme_background", true},
|
||||
{"truecolor", true},
|
||||
{"rounded_corners", true},
|
||||
{"proc_reversed", false},
|
||||
{"proc_tree", false},
|
||||
{"proc_colors", true},
|
||||
{"proc_gradient", true},
|
||||
{"proc_per_core", false},
|
||||
{"proc_mem_bytes", true},
|
||||
{"proc_cpu_graphs", true},
|
||||
{"proc_info_smaps", false},
|
||||
{"proc_left", false},
|
||||
{"proc_filter_kernel", false},
|
||||
{"cpu_invert_lower", true},
|
||||
{"cpu_single_graph", false},
|
||||
{"cpu_bottom", false},
|
||||
{"show_uptime", true},
|
||||
{"check_temp", true},
|
||||
{"show_coretemp", true},
|
||||
{"show_cpu_freq", true},
|
||||
{"background_update", true},
|
||||
{"mem_graphs", true},
|
||||
{"mem_below_net", false},
|
||||
{"zfs_arc_cached", true},
|
||||
{"show_swap", true},
|
||||
{"swap_disk", true},
|
||||
{"show_disks", true},
|
||||
{"only_physical", true},
|
||||
{"use_fstab", true},
|
||||
{"zfs_hide_datasets", false},
|
||||
{"show_io_stat", true},
|
||||
{"io_mode", false},
|
||||
{"base_10_sizes", false},
|
||||
{"io_graph_combined", false},
|
||||
{"net_auto", true},
|
||||
{"net_sync", true},
|
||||
{"show_battery", true},
|
||||
{"vim_keys", false},
|
||||
{"tty_mode", false},
|
||||
{"disk_free_priv", false},
|
||||
{"force_tty", false},
|
||||
{"lowcolor", false},
|
||||
{"show_detailed", false},
|
||||
{"proc_filtering", false},
|
||||
{"proc_aggregate", false},
|
||||
};
|
||||
unordered_flat_map<std::string_view, bool> boolsTmp;
|
||||
|
||||
unordered_flat_map<std::string_view, int> ints = {
|
||||
{"update_ms", 2000},
|
||||
{"net_download", 100},
|
||||
{"net_upload", 100},
|
||||
{"detailed_pid", 0},
|
||||
{"selected_pid", 0},
|
||||
{"selected_depth", 0},
|
||||
{"proc_start", 0},
|
||||
{"proc_selected", 0},
|
||||
{"proc_last_selected", 0},
|
||||
};
|
||||
unordered_flat_map<std::string_view, int> intsTmp;
|
||||
|
||||
bool _locked(const std::string_view name) {
|
||||
atomic_wait(writelock, true);
|
||||
if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end())
|
||||
ConfigSet& get_mut(bool locked, bool write) {
|
||||
if(write)
|
||||
write_new = true;
|
||||
return locked.load();
|
||||
return locked ? cache : current;
|
||||
}
|
||||
|
||||
fs::path conf_dir;
|
||||
fs::path conf_file;
|
||||
|
||||
vector<string> available_batteries = {"Auto"};
|
||||
|
||||
vector<string> current_boxes;
|
||||
vector<string> preset_list = {"cpu:0:default,mem:0:default,net:0:default,proc:0:default"};
|
||||
int current_preset = -1;
|
||||
|
@ -358,13 +121,26 @@ namespace Config {
|
|||
|
||||
for (const auto& box : ssplit(preset, ',')) {
|
||||
const auto& vals = ssplit(box, ':');
|
||||
if (vals.at(0) == "cpu") set("cpu_bottom", (vals.at(1) == "0" ? false : true));
|
||||
else if (vals.at(0) == "mem") set("mem_below_net", (vals.at(1) == "0" ? false : true));
|
||||
else if (vals.at(0) == "proc") set("proc_left", (vals.at(1) == "0" ? false : true));
|
||||
set("graph_symbol_" + vals.at(0), vals.at(2));
|
||||
if (vals.at(0) == "cpu") {
|
||||
CONFIG_SET(cpu_bottom, vals.at(1) != "0");
|
||||
CONFIG_SET(graph_symbol_cpu, vals.at(2));
|
||||
} else if (vals.at(0) == "mem") {
|
||||
CONFIG_SET(mem_below_net, vals.at(1) != "0");
|
||||
CONFIG_SET(graph_symbol_mem, vals.at(2));
|
||||
} else if (vals.at(0) == "proc") {
|
||||
CONFIG_SET(proc_left, vals.at(1) != "0");
|
||||
CONFIG_SET(graph_symbol_proc, vals.at(2));
|
||||
} else if(vals.at(0) == "net") {
|
||||
CONFIG_SET(graph_symbol_net, vals.at(2));
|
||||
}
|
||||
}
|
||||
|
||||
if (check_boxes(boxes)) set("shown_boxes", boxes);
|
||||
if (check_boxes(boxes)) CONFIG_SET(shown_boxes, boxes);
|
||||
}
|
||||
|
||||
bool _locked() {
|
||||
atomic_wait(writelock);
|
||||
return locked.load();
|
||||
}
|
||||
|
||||
void lock() {
|
||||
|
@ -461,56 +237,54 @@ namespace Config {
|
|||
return false;
|
||||
}
|
||||
|
||||
string getAsString(const std::string_view name) {
|
||||
if (bools.contains(name))
|
||||
return (bools.at(name) ? "True" : "False");
|
||||
else if (ints.contains(name))
|
||||
return to_string(ints.at(name));
|
||||
else if (strings.contains(name))
|
||||
return strings.at(name);
|
||||
return "";
|
||||
template<typename T>
|
||||
inline void copy_field(ConfigSet& dest, const ConfigSet& src, size_t offset) {
|
||||
dynamic_set<T>(dest, offset, dynamic_get<T>(src, offset));
|
||||
}
|
||||
|
||||
void flip(const std::string_view name) {
|
||||
if (_locked(name)) {
|
||||
if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name);
|
||||
else boolsTmp.insert_or_assign(name, (not bools.at(name)));
|
||||
string getAsString(const std::string_view name) {
|
||||
auto& entry = parse_table.at(name);
|
||||
switch(entry.type) {
|
||||
case ConfigType::BOOL:
|
||||
return dynamic_get<bool>(current, entry.offset) ? "True" : "False";
|
||||
case ConfigType::INT:
|
||||
return to_string(dynamic_get<int>(current, entry.offset));
|
||||
case ConfigType::STRING:
|
||||
return dynamic_get<string>(current, entry.offset);
|
||||
default:
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
else bools.at(name) = not bools.at(name);
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
if (not locked) return;
|
||||
atomic_wait(Runner::active);
|
||||
atomic_lock lck(writelock, true);
|
||||
try {
|
||||
if (Proc::shown) {
|
||||
ints.at("selected_pid") = Proc::selected_pid;
|
||||
strings.at("selected_name") = Proc::selected_name;
|
||||
ints.at("proc_start") = Proc::start;
|
||||
ints.at("proc_selected") = Proc::selected;
|
||||
ints.at("selected_depth") = Proc::selected_depth;
|
||||
}
|
||||
|
||||
for (auto& item : stringsTmp) {
|
||||
strings.at(item.first) = item.second;
|
||||
}
|
||||
stringsTmp.clear();
|
||||
|
||||
for (auto& item : intsTmp) {
|
||||
ints.at(item.first) = item.second;
|
||||
}
|
||||
intsTmp.clear();
|
||||
|
||||
for (auto& item : boolsTmp) {
|
||||
bools.at(item.first) = item.second;
|
||||
}
|
||||
boolsTmp.clear();
|
||||
if(Proc::shown) {
|
||||
current.selected_pid = Proc::selected_pid;
|
||||
current.selected_name = Proc::selected_name;
|
||||
current.proc_start = Proc::start;
|
||||
current.proc_selected = Proc::selected;
|
||||
current.selected_depth = Proc::selected_depth;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Global::exit_error_msg = "Exception during Config::unlock() : " + string{e.what()};
|
||||
clean_quit(1);
|
||||
|
||||
for(auto& item: cached) {
|
||||
auto& data = parse_table.at(item);
|
||||
|
||||
switch(data.type) {
|
||||
case ConfigType::STRING:
|
||||
copy_field<string>(current, cache, data.offset);
|
||||
break;
|
||||
case ConfigType::INT:
|
||||
copy_field<int>(current, cache, data.offset);
|
||||
break;
|
||||
case ConfigType::BOOL:
|
||||
copy_field<bool>(current, cache, data.offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
cached.clear();
|
||||
|
||||
locked = false;
|
||||
}
|
||||
|
@ -545,10 +319,11 @@ namespace Config {
|
|||
return;
|
||||
}
|
||||
|
||||
Config::set("shown_boxes", new_boxes);
|
||||
CONFIG_SET(shown_boxes, new_boxes);
|
||||
}
|
||||
|
||||
void load(const fs::path& conf_file, vector<string>& load_warnings) {
|
||||
ConfigSet set = ConfigSet::with_defaults();
|
||||
if (conf_file.empty())
|
||||
return;
|
||||
else if (not fs::exists(conf_file)) {
|
||||
|
@ -557,9 +332,6 @@ namespace Config {
|
|||
}
|
||||
std::ifstream cread(conf_file);
|
||||
if (cread.good()) {
|
||||
vector<string> valid_names;
|
||||
for (auto &n : descriptions)
|
||||
valid_names.push_back(n[0]);
|
||||
if (string v_string; cread.peek() != '#' or (getline(cread, v_string, '\n') and not s_contains(v_string, Global::Version)))
|
||||
write_new = true;
|
||||
while (not cread.eof()) {
|
||||
|
@ -571,46 +343,51 @@ namespace Config {
|
|||
string name, value;
|
||||
getline(cread, name, '=');
|
||||
if (name.ends_with(' ')) name = trim(name);
|
||||
if (not v_contains(valid_names, name)) {
|
||||
auto found = parse_table.find(name);
|
||||
if (found == parse_table.end()) {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
cread >> std::ws;
|
||||
|
||||
if (bools.contains(name)) {
|
||||
cread >> value;
|
||||
if (not isbool(value))
|
||||
load_warnings.push_back("Got an invalid bool value for config name: " + name);
|
||||
else
|
||||
bools.at(name) = stobool(value);
|
||||
}
|
||||
else if (ints.contains(name)) {
|
||||
cread >> value;
|
||||
if (not isint(value))
|
||||
load_warnings.push_back("Got an invalid integer value for config name: " + name);
|
||||
else if (not intValid(name, value)) {
|
||||
load_warnings.push_back(validError);
|
||||
}
|
||||
else
|
||||
ints.at(name) = stoi(value);
|
||||
}
|
||||
else if (strings.contains(name)) {
|
||||
if (cread.peek() == '"') {
|
||||
cread.ignore(1);
|
||||
getline(cread, value, '"');
|
||||
}
|
||||
else cread >> value;
|
||||
switch(found->second.type) {
|
||||
case ConfigType::BOOL:
|
||||
cread >> value;
|
||||
if (not isbool(value))
|
||||
load_warnings.push_back("Got an invalid bool value for config name: " + name);
|
||||
else
|
||||
dynamic_set(set, found->second.offset, stobool(value));
|
||||
break;
|
||||
case ConfigType::INT:
|
||||
cread >> value;
|
||||
if (not isint(value))
|
||||
load_warnings.push_back("Got an invalid integer value for config name: " + name);
|
||||
else if (not intValid(name, value)) {
|
||||
load_warnings.push_back(validError);
|
||||
}
|
||||
else
|
||||
dynamic_set(set, found->second.offset, stoi(value));
|
||||
break;
|
||||
case ConfigType::STRING:
|
||||
if (cread.peek() == '"') {
|
||||
cread.ignore(1);
|
||||
getline(cread, value, '"');
|
||||
}
|
||||
else cread >> value;
|
||||
|
||||
if (not stringValid(name, value))
|
||||
load_warnings.push_back(validError);
|
||||
else
|
||||
strings.at(name) = value;
|
||||
if (not stringValid(name, value))
|
||||
load_warnings.push_back(validError);
|
||||
else
|
||||
dynamic_set(set, found->second.offset, value);
|
||||
break;
|
||||
}
|
||||
|
||||
cread.ignore(SSmax, '\n');
|
||||
}
|
||||
|
||||
if (not load_warnings.empty()) write_new = true;
|
||||
|
||||
current = set;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -619,18 +396,31 @@ namespace Config {
|
|||
Logger::debug("Writing new config file");
|
||||
if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return;
|
||||
std::ofstream cwrite(conf_file, std::ios::trunc);
|
||||
auto write_field = [&](const std::string_view name, const std::string_view description) {
|
||||
cwrite << "\n\n";
|
||||
if(not description.empty()) {
|
||||
cwrite << description << "\n";
|
||||
}
|
||||
cwrite << name << " = ";
|
||||
auto& data = parse_table.at(name);
|
||||
switch(data.type) {
|
||||
case ConfigType::BOOL:
|
||||
cwrite << (dynamic_get<bool>(current, data.offset) ? "True" : "False");
|
||||
break;
|
||||
case ConfigType::INT:
|
||||
cwrite << dynamic_get<int>(current, data.offset);
|
||||
break;
|
||||
case ConfigType::STRING:
|
||||
cwrite << '"' << dynamic_get<string>(current, data.offset) << '"';
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (cwrite.good()) {
|
||||
cwrite << "#? Config file for btop v. " << Global::Version;
|
||||
for (auto [name, description] : descriptions) {
|
||||
cwrite << "\n\n" << (description.empty() ? "" : description + "\n")
|
||||
<< name << " = ";
|
||||
if (strings.contains(name))
|
||||
cwrite << "\"" << strings.at(name) << "\"";
|
||||
else if (ints.contains(name))
|
||||
cwrite << ints.at(name);
|
||||
else if (bools.contains(name))
|
||||
cwrite << (bools.at(name) ? "True" : "False");
|
||||
}
|
||||
#define X(T, name, v, desc) write_field(#name, desc);
|
||||
OPTIONS_WRITABLE_LIST
|
||||
#undef X
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,19 +28,236 @@ using std::string;
|
|||
using std::vector;
|
||||
using robin_hood::unordered_flat_map;
|
||||
|
||||
#define OPTIONS_WRITABLE_LIST \
|
||||
X(string, color_theme, "Default", \
|
||||
"#* Name of a btop++/bpytop/bashtop formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes.\n" \
|
||||
"#* Themes should be placed in \"../share/btop/themes\" relative to binary or \"$HOME/.config/btop/themes\"") \
|
||||
X(bool, theme_background, true, \
|
||||
"#* If the theme set background should be shown, set to False if you want terminal background transparency.") \
|
||||
X(bool, truecolor, true, \
|
||||
"#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.") \
|
||||
X(bool, force_tty, false, \
|
||||
"#* 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.") \
|
||||
X(string, presets, "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty", \
|
||||
"#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.\n" \
|
||||
"#* Format: \"box_name:P:G,box_name:P:G\" P=(0 or 1) for alternate positions, G=graph symbol to use for box.\n" \
|
||||
"#* Use whitespace \" \" as separator between different presets.\n" \
|
||||
"#* Example: \"cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty\"") \
|
||||
X(bool, vim_keys, false, \
|
||||
"#* Set to True to enable \"h,j,k,l,g,G\" keys for directional control in lists.\n" \
|
||||
"#* Conflicting keys for h:\"help\" and k:\"kill\" is accessible while holding shift.") \
|
||||
X(bool, rounded_corners, true, \
|
||||
"#* Rounded corners on boxes, is ignored if TTY mode is ON.") \
|
||||
X(string, graph_symbol, "braille", \
|
||||
"#* 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" \
|
||||
"#* \"tty\" uses only 3 different symbols but will work with most fonts and should work in a real TTY.\n" \
|
||||
"#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view.") \
|
||||
X(string, graph_symbol_cpu, "default", \
|
||||
"# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\".") \
|
||||
X(string, graph_symbol_mem, "default", \
|
||||
"# Graph symbol to use for graphs in mem box, \"default\", \"braille\", \"block\" or \"tty\".") \
|
||||
X(string, graph_symbol_net, "default", \
|
||||
"# Graph symbol to use for graphs in net box, \"default\", \"braille\", \"block\" or \"tty\".") \
|
||||
X(string, graph_symbol_proc, "default", \
|
||||
"# Graph symbol to use for graphs in proc box, \"default\", \"braille\", \"block\" or \"tty\".") \
|
||||
X(string, shown_boxes, "cpu mem net proc", \
|
||||
"#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace.") \
|
||||
X(int, update_ms, 2000, \
|
||||
"#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.") \
|
||||
X(string, proc_sorting, "cpu lazy", \
|
||||
"#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu direct\",\n" \
|
||||
"#* \"cpu lazy\" sorts top process over time (easier to follow), \"cpu direct\" updates top process directly.") \
|
||||
X(bool, proc_reversed, false, \
|
||||
"#* Reverse sorting order, True or False.") \
|
||||
X(bool, proc_tree, false, \
|
||||
"#* Show processes as a tree.") \
|
||||
X(bool, proc_colors, true, \
|
||||
"#* Use the cpu graph colors in the process list.") \
|
||||
X(bool, proc_gradient, true, \
|
||||
"#* Use a darkening gradient in the process list.") \
|
||||
X(bool, proc_per_core, false, \
|
||||
"#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.") \
|
||||
X(bool, proc_mem_bytes, true, \
|
||||
"#* Show process memory as bytes instead of percent.") \
|
||||
X(bool, proc_cpu_graphs, true, \
|
||||
"#* Show cpu graph for each process.") \
|
||||
X(bool, proc_cpu_smaps, false, \
|
||||
"#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)") \
|
||||
X(bool, proc_left, false, \
|
||||
"#* Show proc box on left side of screen instead of right.") \
|
||||
X(bool, proc_filter_kernel, false, \
|
||||
"#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).") \
|
||||
X(bool, proc_aggregate, false, \
|
||||
"#* In tree-view, always accumulate child process resources in the parent process.") \
|
||||
X(string, cpu_graph_upper, "total", \
|
||||
"#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n" \
|
||||
"#* Select from a list of detected attributes from the options menu.") \
|
||||
X(string, cpu_graph_lower, "total", \
|
||||
"#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n" \
|
||||
"#* Select from a list of detected attributes from the options menu.") \
|
||||
X(bool, cpu_invert_lower, true, \
|
||||
"#* Toggles if the lower CPU graph should be inverted.") \
|
||||
X(bool, cpu_single_graph, false, \
|
||||
"#* Set to True to completely disable the lower CPU graph.") \
|
||||
X(bool, cpu_bottom, false, \
|
||||
"#* Show cpu box at bottom of screen instead of top.") \
|
||||
X(bool, show_uptime, true, \
|
||||
"#* Shows the system uptime in the CPU box.") \
|
||||
X(bool, check_temp, true, \
|
||||
"#* Shows the system uptime in the CPU box.") \
|
||||
X(string, cpu_sensor, "Auto", \
|
||||
"#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.") \
|
||||
X(bool, show_coretemp, true, \
|
||||
"#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.") \
|
||||
X(string, cpu_core_map, "", \
|
||||
"#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.\n" \
|
||||
"#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.\n" \
|
||||
"#* Format \"x:y\" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.\n" \
|
||||
"#* Example: \"4:0 5:1 6:3\"") \
|
||||
X(string, temp_scale, "celsius", \
|
||||
"#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\".") \
|
||||
X(bool, base_10_sizes, false, \
|
||||
"#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.") \
|
||||
X(bool, show_cpu_freq, true, \
|
||||
"#* Show CPU frequency.") \
|
||||
X(string, clock_format, "%X", \
|
||||
"#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.\n" \
|
||||
"#* Special formatting: /host = hostname | /user = username | /uptime = system uptime") \
|
||||
X(bool, background_update, true, \
|
||||
"#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.") \
|
||||
X(string, custom_cpu_name, "", \
|
||||
"#* Custom cpu model name, empty string to disable.") \
|
||||
X(string, disks_filter, "", \
|
||||
"#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace \" \".\n" \
|
||||
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot /home/user\".") \
|
||||
X(bool, mem_graphs, true, \
|
||||
"#* Show graphs instead of meters for memory values.") \
|
||||
X(bool, mem_below_net, false, \
|
||||
"#* Show mem box below net box instead of above.") \
|
||||
X(bool, zfs_arc_cached, true, \
|
||||
"#* Count ZFS ARC in cached and available memory.") \
|
||||
X(bool, show_swap, true, \
|
||||
"#* If swap memory should be shown in memory box.") \
|
||||
X(bool, swap_disk, true, \
|
||||
"#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.") \
|
||||
X(bool, show_disks, true, \
|
||||
"#* If mem box should be split to also show disks info.") \
|
||||
X(bool, only_physical, true, \
|
||||
"#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.") \
|
||||
X(bool, use_fstab, true, \
|
||||
"#* Read disks list from /etc/fstab. This also disables only_physical.") \
|
||||
X(bool, zfs_hide_datasets, false, \
|
||||
"#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)") \
|
||||
X(bool, disk_free_priv, false, \
|
||||
"#* Set to true to show available disk space for privileged users.") \
|
||||
X(bool, show_io_stat, true, \
|
||||
"#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.") \
|
||||
X(bool, io_mode, false, \
|
||||
"#* Toggles io mode for disks, showing big graphs for disk read/write speeds.") \
|
||||
X(bool, io_graph_combined, false, \
|
||||
"#* Set to True to show combined read/write io graphs in io mode.") \
|
||||
X(string, io_graph_speeds, "", \
|
||||
"#* Set the top speed for the io graphs in MiB/s (100 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n" \
|
||||
"#* Example: \"/mnt/media:100 /:20 /boot:1\".") \
|
||||
X(int, net_download, 100, \
|
||||
"#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.") \
|
||||
X(int, net_upload, 100, "") \
|
||||
X(bool, net_auto, true, \
|
||||
"#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.") \
|
||||
X(bool, net_sync, true, \
|
||||
"#* Sync the auto scaling for download and upload to whichever currently has the highest scale.") \
|
||||
X(string, net_iface, "", \
|
||||
"#* Starts with the Network Interface specified here.") \
|
||||
X(bool, show_battery, true, \
|
||||
"#* Show battery stats in top right if battery is present.") \
|
||||
X(string, selected_battery, "Auto", \
|
||||
"#* Which battery to use if multiple are present. \"Auto\" for auto detection.") \
|
||||
X(string, log_level, "WARNING", \
|
||||
"#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" \
|
||||
"#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info.") \
|
||||
|
||||
#define OPTIONS_INTERNAL_LIST \
|
||||
X(int, selected_pid, 0, "") \
|
||||
X(string, selected_name, "", "") \
|
||||
X(int, proc_start, 0, "") \
|
||||
X(int, proc_selected, 0, "") \
|
||||
X(int, proc_last_selected, 0, "") \
|
||||
X(int, selected_depth, 0, "") \
|
||||
X(bool, lowcolor, false, "") \
|
||||
X(bool, tty_mode, false, "") \
|
||||
X(bool, show_detailed, false, "") \
|
||||
X(int, detailed_pid, 0, "") \
|
||||
X(bool, proc_filtering, false, "") \
|
||||
X(string, proc_filter, "", "") \
|
||||
|
||||
|
||||
#define OPTIONS_LIST OPTIONS_WRITABLE_LIST OPTIONS_INTERNAL_LIST
|
||||
|
||||
#define CONFIG_SET(name, v) Config::set< \
|
||||
decltype(std::declval<Config::ConfigSet>().name) \
|
||||
>(offsetof(Config::ConfigSet, name), #name, v, Config::is_writable(#name))
|
||||
|
||||
//* Functions and variables for reading and writing the btop config file
|
||||
namespace Config {
|
||||
enum class ConfigType {
|
||||
BOOL,
|
||||
INT,
|
||||
STRING
|
||||
};
|
||||
|
||||
namespace details {
|
||||
template<typename T> struct TypeToEnum;
|
||||
template<>
|
||||
struct TypeToEnum<bool> {
|
||||
static constexpr ConfigType value = ConfigType::BOOL;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TypeToEnum<int> {
|
||||
static constexpr ConfigType value = ConfigType::INT;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TypeToEnum<std::string> {
|
||||
static constexpr ConfigType value = ConfigType::STRING;
|
||||
};
|
||||
}
|
||||
|
||||
struct ParseData {
|
||||
// Offset will be 32bit to save space/make this fit in a register
|
||||
uint32_t offset;
|
||||
ConfigType type;
|
||||
};
|
||||
|
||||
struct ConfigSet {
|
||||
#define X(T, name, v, desc) T name;
|
||||
OPTIONS_LIST
|
||||
#undef X
|
||||
|
||||
static ConfigSet with_defaults() {
|
||||
return {
|
||||
#define X(T, name, v, desc) .name = v,
|
||||
OPTIONS_LIST
|
||||
#undef X
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const unordered_flat_map<std::string_view, ParseData> parse_table = {
|
||||
#define X(T, name, v, desc) { #name, { \
|
||||
.offset = offsetof(ConfigSet, name), \
|
||||
.type = Config::details::TypeToEnum<decltype(std::declval<ConfigSet>().name)>::value, \
|
||||
} },
|
||||
OPTIONS_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
extern std::filesystem::path conf_dir;
|
||||
extern std::filesystem::path conf_file;
|
||||
|
||||
extern unordered_flat_map<std::string_view, string> strings;
|
||||
extern unordered_flat_map<std::string_view, string> stringsTmp;
|
||||
extern unordered_flat_map<std::string_view, bool> bools;
|
||||
extern unordered_flat_map<std::string_view, bool> boolsTmp;
|
||||
extern unordered_flat_map<std::string_view, int> ints;
|
||||
extern unordered_flat_map<std::string_view, int> intsTmp;
|
||||
|
||||
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
|
||||
const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" };
|
||||
const vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
|
||||
|
@ -50,6 +267,21 @@ namespace Config {
|
|||
extern vector<string> preset_list;
|
||||
extern vector<string> available_batteries;
|
||||
extern int current_preset;
|
||||
extern robin_hood::unordered_flat_set<std::string_view> cached;
|
||||
|
||||
constexpr static bool is_writable(const std::string_view option) {
|
||||
#define X(T, name, v, desc) if(option == #name) return false;
|
||||
OPTIONS_INTERNAL_LIST
|
||||
#undef X
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//* Get the current config object
|
||||
const ConfigSet& get();
|
||||
|
||||
//* Get the current config object for writing
|
||||
ConfigSet& get_mut(bool locked, bool write);
|
||||
|
||||
//* Check if string only contains space separated valid names for boxes
|
||||
bool check_boxes(const string& boxes);
|
||||
|
@ -63,45 +295,34 @@ namespace Config {
|
|||
//* Apply selected preset
|
||||
void apply_preset(const string& preset);
|
||||
|
||||
bool _locked(const std::string_view name);
|
||||
|
||||
//* Return bool for config key <name>
|
||||
inline bool getB(const std::string_view name) { return bools.at(name); }
|
||||
|
||||
//* Return integer for config key <name>
|
||||
inline const int& getI(const std::string_view name) { return ints.at(name); }
|
||||
|
||||
//* Return string for config key <name>
|
||||
inline const string& getS(const std::string_view name) { return strings.at(name); }
|
||||
bool _locked();
|
||||
|
||||
string getAsString(const std::string_view name);
|
||||
|
||||
template<typename T>
|
||||
inline const T& dynamic_get(const ConfigSet& src, size_t offset) {
|
||||
return *(const T*)((char*)&src + offset);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void dynamic_set(ConfigSet& dest, size_t offset, const T& value) {
|
||||
*(T*)((char*)&dest + offset) = value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void set(const size_t offset, const std::string_view name, const T& value, const bool writable) {
|
||||
bool locked = _locked();
|
||||
auto& set = get_mut(locked, writable);
|
||||
if(locked)
|
||||
cached.insert(name);
|
||||
dynamic_set(set, offset, value);
|
||||
}
|
||||
|
||||
extern string validError;
|
||||
|
||||
bool intValid(const std::string_view name, const string& value);
|
||||
bool stringValid(const std::string_view name, const string& value);
|
||||
|
||||
//* Set config key <name> to bool <value>
|
||||
inline void set(const std::string_view name, bool value) {
|
||||
if (_locked(name)) boolsTmp.insert_or_assign(name, value);
|
||||
else bools.at(name) = value;
|
||||
}
|
||||
|
||||
//* Set config key <name> to int <value>
|
||||
inline void set(const std::string_view name, const int& value) {
|
||||
if (_locked(name)) intsTmp.insert_or_assign(name, value);
|
||||
else ints.at(name) = value;
|
||||
}
|
||||
|
||||
//* Set config key <name> to string <value>
|
||||
inline void set(const std::string_view name, const string& value) {
|
||||
if (_locked(name)) stringsTmp.insert_or_assign(name, value);
|
||||
else strings.at(name) = value;
|
||||
}
|
||||
|
||||
//* Flip config key bool <name>
|
||||
void flip(const std::string_view name);
|
||||
|
||||
//* Lock config and cache changes until unlocked
|
||||
void lock();
|
||||
|
||||
|
|
Loading…
Reference in a new issue