diff --git a/src/btop_config.cpp b/src/btop_config.cpp index fc71e41..1df5eef 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -18,6 +18,7 @@ tab-size = 4 #include #include +#include #include #include #include @@ -40,265 +41,27 @@ using namespace Tools; //* Functions and variables for reading and writing the btop config file namespace Config { - atomic locked (false); atomic writelock (false); bool write_new; + robin_hood::unordered_flat_set cached{}; + ConfigSet current = ConfigSet::with_defaults(); + ConfigSet cache{}; - const vector> 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 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 stringsTmp; - - unordered_flat_map 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 boolsTmp; - - unordered_flat_map 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 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 available_batteries = {"Auto"}; - vector current_boxes; vector 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 + inline void copy_field(ConfigSet& dest, const ConfigSet& src, size_t offset) { + dynamic_set(dest, offset, dynamic_get(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(current, entry.offset) ? "True" : "False"; + case ConfigType::INT: + return to_string(dynamic_get(current, entry.offset)); + case ConfigType::STRING: + return dynamic_get(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(current, cache, data.offset); + break; + case ConfigType::INT: + copy_field(current, cache, data.offset); + break; + case ConfigType::BOOL: + copy_field(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& 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 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(current, data.offset) ? "True" : "False"); + break; + case ConfigType::INT: + cwrite << dynamic_get(current, data.offset); + break; + case ConfigType::STRING: + cwrite << '"' << dynamic_get(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 } } } diff --git a/src/btop_config.hpp b/src/btop_config.hpp index e5f4ac1..bdfd9b9 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -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().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 struct TypeToEnum; + template<> + struct TypeToEnum { + static constexpr ConfigType value = ConfigType::BOOL; + }; + + template<> + struct TypeToEnum { + static constexpr ConfigType value = ConfigType::INT; + }; + + template<> + struct TypeToEnum { + 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 parse_table = { +#define X(T, name, v, desc) { #name, { \ + .offset = offsetof(ConfigSet, name), \ + .type = Config::details::TypeToEnum().name)>::value, \ + } }, + OPTIONS_LIST +#undef X + }; extern std::filesystem::path conf_dir; extern std::filesystem::path conf_file; - extern unordered_flat_map strings; - extern unordered_flat_map stringsTmp; - extern unordered_flat_map bools; - extern unordered_flat_map boolsTmp; - extern unordered_flat_map ints; - extern unordered_flat_map intsTmp; - const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; const vector valid_boxes = { "cpu", "mem", "net", "proc" }; @@ -50,6 +267,21 @@ namespace Config { extern vector preset_list; extern vector available_batteries; extern int current_preset; + extern robin_hood::unordered_flat_set 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 - inline bool getB(const std::string_view name) { return bools.at(name); } - - //* Return integer for config key - inline const int& getI(const std::string_view name) { return ints.at(name); } - - //* Return string for config key - inline const string& getS(const std::string_view name) { return strings.at(name); } + bool _locked(); string getAsString(const std::string_view name); + template + inline const T& dynamic_get(const ConfigSet& src, size_t offset) { + return *(const T*)((char*)&src + offset); + } + + template + inline void dynamic_set(ConfigSet& dest, size_t offset, const T& value) { + *(T*)((char*)&dest + offset) = value; + } + + template + 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 to bool - 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 to int - 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 to string - 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 - void flip(const std::string_view name); - //* Lock config and cache changes until unlocked void lock();