btop/src/btop_menu.cpp
aristocratos 19bcff894b Squashed commit of the following:
commit 0267eba2bb
Merge: 50bbab0 e81cf2b
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Wed Nov 15 21:43:18 2023 +0100

    Merge pull request #659 from ivanp7/patch-1

    Add alternative key codes for Delete, Insert, Home, End

commit 50bbab0512
Merge: 9edbf27 5a14c7b
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Wed Nov 15 21:35:50 2023 +0100

    Merge pull request #660 from stradicat/feature/elementarish

    Elementarish theme: color update according to Elementary palette

commit 5a14c7b6fa
Merge: 979506f 71eb414
Author: Dennis Mayr <dmayr.dev@gmail.com>
Date:   Wed Nov 15 17:27:34 2023 -0300

    Merge branch 'main' of https://github.com/stradicat/btop

commit 979506f18e
Author: Dennis Mayr <dmayr.dev@gmail.com>
Date:   Wed Nov 8 11:17:47 2023 -0300

    Elementarish theme: color update according to Elementary palette

commit 71eb4142e8
Author: Dennis Mayr <dmayr.dev@gmail.com>
Date:   Wed Nov 8 11:17:47 2023 -0300

    Elementarish theme: color update according to Elementary palette

commit e81cf2b7ff
Author: vân <3432246+ivanp7@users.noreply.github.com>
Date:   Tue Nov 7 15:12:27 2023 +0000

    Add alternative key codes for Insert, Home, End

commit f9452ff6d5
Author: vân <3432246+ivanp7@users.noreply.github.com>
Date:   Mon Nov 6 13:31:53 2023 +0000

    Add alternative Delete key code

    Delete key not always produces ^[[3~, on some terminals (like st) it produces ^[[P.

commit 9edbf27f1b
Merge: 2a864f6 ff1f51c
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Oct 21 02:09:55 2023 +0200

    Merge pull request #649 from nobounce/workflow-timeout

    Set FreeBSD workflow timeout

commit ff1f51ccbb
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Wed Oct 18 22:26:36 2023 +0200

    Set FreeBSD workflow timeout

    Recently the FreeBSD workflow has started to hang in a boot loop when
    the VM starts up. The issue is being tracked upstream but there is not
    response at the moment.

    To work around this set a timeout to not waste CI minutes. Other
    workflows might also want this change since they don't take 20 minutes
    anyway.

commit 2a864f6f2e
Merge: 636eb25 b2bf8ef
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Oct 7 10:40:54 2023 +0200

    Merge pull request #643 from DecklynKern/main

    Fix scrollbar not clearing sometimes.

commit b2bf8ef504
Author: DecklynKern <DecklynKern@gmail.com>
Date:   Fri Oct 6 17:33:38 2023 -0600

    Fix scrollbar not clearing sometimes.

commit 636eb25f5e
Merge: 260c0f6 b5ba2fc
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Sep 30 19:51:03 2023 +0200

    Merge pull request #623 from rahulaggarwal965/main

    Add keybind for toggling memory display mode in PROC box

commit b5ba2fc963
Author: Rahul Aggarwal <rahulaggarwal965@gmail.com>
Date:   Wed Sep 20 22:55:56 2023 -0400

    Add keybind for toggling memory display mode in PROC box

commit 260c0f6623
Merge: 52bfff7 e6a06eb
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Sep 30 18:56:25 2023 +0200

    Merge pull request #635 from lvxnull/editorconfig

    Add hpp files to .editorconfig

commit e6a06eb729
Author: lvxnull <86745229+lvxnull@users.noreply.github.com>
Date:   Thu Sep 28 19:44:47 2023 +0200

    Add hpp files to .editorconfig

commit 52bfff7ceb
Merge: 1f72e56 19dbbe1
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Sep 30 18:55:08 2023 +0200

    Merge pull request #636 from nobounce/performance-iili

    Minor string initialization improvement

commit 19dbbe1a17
Author: nobounce <steffen.winter@proton.me>
Date:   Fri Sep 29 12:20:59 2023 +0200

    Minor string initialization improvement

commit 1f72e56c7d
Merge: 278a0e6 cdcf8bc
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Fri Sep 29 10:43:21 2023 +0200

    Merge pull request #633 from crestfallnatwork/main

    [fix] Made disks statvfs logic asynchronous.

commit cdcf8bc929
Author: crestfalln <guptahiman01@gmail.com>
Date:   Fri Sep 29 09:07:27 2023 +0530

    fixed bug where updated disks stats overrided disk io data

commit 9b4e85f08d
Author: crestfalln <no-reply@crestfalln.com>
Date:   Thu Sep 28 04:57:05 2023 +0530

    fixed bug where updated disks stats overrided disk io data

commit 889623874e
Author: crestfalln <no-reply@crestfalln.com>
Date:   Wed Sep 27 23:57:06 2023 +0530

    made disks stat logic async

commit 278a0e6b17
Merge: d16adc9 e89519f
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Thu Sep 28 18:32:09 2023 +0200

    Merge pull request #630 from lvxnull/signal-list

    Fix signal list on non-linux/weird linux platforms

commit e89519fbb2
Author: lvxnull <86745229+lvxnull@users.noreply.github.com>
Date:   Sun Sep 24 21:44:38 2023 +0200

    Fix signal list on non-linux/weird linux platforms

commit d16adc9fd0
Merge: 2c3ac48 f34b408
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Thu Sep 28 18:20:42 2023 +0200

    Merge pull request #618 from nobounce/aggregate-child-processes

    Add option to accumulate a child's resources in parent in tree-view

commit f34b40892f
Author: nobounce <steffen.winter@proton.me>
Date:   Sun Sep 24 16:34:50 2023 +0200

    Make process thread count better readable when wider than 5 digits

commit 6027cedd42
Author: nobounce <steffen.winter@proton.me>
Date:   Thu Sep 14 23:27:05 2023 +0200

    Add option to accumulate a child's resources in parent in tree-view

commit 2c3ac4855d
Merge: f90dc37 5c6a281
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Wed Sep 13 21:14:56 2023 +0200

    Merge pull request #589 from nobounce/cmake

    Add CMake support for Linux

commit f90dc37c26
Merge: 0cac861 68a49c1
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Wed Sep 13 20:27:05 2023 +0200

    Merge pull request #610 from SidVeld/feature/horizon-theme

    Horizon theme

commit 5c6a281002
Author: nobounce <steffen.winter@proton.me>
Date:   Tue Aug 29 20:39:00 2023 +0200

    Add CMake support

    Linux is completly supported

    FreeBSD is not able to create a static executable for now. See
    https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273398

    MacOS was not tested

commit 68a49c10a6
Author: SidVeld <sidveld@gmail.com>
Date:   Wed Sep 6 18:03:31 2023 +0300

    Add horizon theme

commit 0cac861910
Merge: 31be436 f798acd
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Sep 5 19:27:38 2023 +0200

    Merge pull request #609 from scorpion-26/byteconv

    Fix short conversion of 1000-1023 *iB

commit f798acdaf7
Author: scorpion-26 <dev.scorpion26@gmail.com>
Date:   Tue Sep 5 18:00:47 2023 +0200

    Fix short conversion of 1000-1023*iB

    floating_humanizer([1000-1024], true) with base 8 returns "2K", whereas it should return
    "1.0K" to align with other formats. The conversion is also broken for
    all other units(e.g. 1023M is also broken and returns "2G")

commit 31be4362ce
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun Aug 27 02:00:07 2023 +0200

    FreeBSD Github action 13.1 -> 13.2 and static libgcc and libstdc++

commit fc523fd1d0
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun Aug 27 01:36:26 2023 +0200

    Fix for FreeBSD github action not failing "correctly"...
2023-11-25 21:01:11 +01:00

1627 lines
52 KiB
C++

/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
indent = tab
tab-size = 4
*/
#include <deque>
#include <robin_hood.h>
#include <array>
#include <signal.h>
#include <errno.h>
#include <cmath>
#include <filesystem>
#include "btop_menu.hpp"
#include "btop_tools.hpp"
#include "btop_config.hpp"
#include "btop_theme.hpp"
#include "btop_draw.hpp"
#include "btop_shared.hpp"
using robin_hood::unordered_flat_map;
using std::array;
using std::ceil;
using std::max;
using std::min;
using std::ref;
using std::views::iota;
using namespace Tools;
namespace fs = std::filesystem;
namespace Menu {
atomic<bool> active (false);
string bg;
bool redraw{true};
int currentMenu = -1;
msgBox messageBox;
int signalToSend{}; // defaults to 0
int signalKillRet{}; // defaults to 0
const array<string, 32> P_Signals = {
"0",
#ifdef __linux__
#if defined(__hppa__)
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGSTKFLT", "SIGFPE",
"SIGKILL", "SIGBUS", "SIGSEGV", "SIGXCPU",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1",
"SIGUSR2", "SIGCHLD", "SIGPWR", "SIGVTALRM",
"SIGPROF", "SIGIO", "SIGWINCH", "SIGSTOP",
"SIGTSTP", "SIGCONT", "SIGTTIN", "SIGTTOU",
"SIGURG", "SIGXFSZ", "SIGSYS"
#elif defined(__mips__)
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1",
"SIGUSR2", "SIGCHLD", "SIGPWR", "SIGWINCH",
"SIGURG", "SIGIO", "SIGSTOP", "SIGTSTP",
"SIGCONT", "SIGTTIN", "SIGTTOU", "SIGVTALRM",
"SIGPROF", "SIGXCPU", "SIGXFSZ"
#elif defined(__alpha__)
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGPWR", "SIGUSR1", "SIGUSR2"
#elif defined (__sparc__)
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGLOST", "SIGUSR1", "SIGUSR2"
#else
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
"SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT",
"SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP",
"SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGIO", "SIGPWR", "SIGSYS"
#endif
#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGINFO", "SIGUSR1", "SIGUSR2"
#else
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "7", "SIGFPE",
"SIGKILL", "10", "SIGSEGV", "12",
"SIGPIPE", "SIGALRM", "SIGTERM", "16",
"17", "18", "19", "20",
"21", "22", "23", "24",
"25", "26", "27", "28",
"29", "30", "31"
#endif
};
unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
const array<array<string, 3>, 3> menu_normal = {
array<string, 3>{
"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
"│ │├─┘ │ ││ ││││└─┐",
"└─┘┴ ┴ ┴└─┘┘└┘└─┘"
},
{
"┬ ┬┌─┐┬ ┌─┐",
"├─┤├┤ │ ├─┘",
"┴ ┴└─┘┴─┘┴ "
},
{
"┌─┐ ┬ ┬ ┬┌┬┐",
"│─┼┐│ │ │ │ ",
"└─┘└└─┘ ┴ ┴ "
}
};
const array<array<string, 3>, 3> menu_selected = {
array<string, 3>{
"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝"
},
{
"╦ ╦╔═╗╦ ╔═╗",
"╠═╣║╣ ║ ╠═╝",
"╩ ╩╚═╝╩═╝╩ "
},
{
"╔═╗ ╦ ╦ ╦╔╦╗ ",
"║═╬╗║ ║ ║ ║ ",
"╚═╝╚╚═╝ ╩ ╩ "
}
};
const array<int, 3> menu_width = {19, 12, 12};
const vector<array<string, 2>> help_text = {
{"Mouse 1", "Clicks buttons and selects in process list."},
{"Mouse scroll", "Scrolls any scrollable list/text under cursor."},
{"Esc, m", "Toggles main menu."},
{"p", "Cycle view presets forwards."},
{"shift + p", "Cycle view presets backwards."},
{"1", "Toggle CPU box."},
{"2", "Toggle MEM box."},
{"3", "Toggle NET box."},
{"4", "Toggle PROC box."},
{"5", "Toggle GPU box."},
{"d", "Toggle disks view in MEM box."},
{"F2, o", "Shows options."},
{"F1, ?, h", "Shows this window."},
{"ctrl + z", "Sleep program and put in background."},
{"q, ctrl + c", "Quits program."},
{"+, -", "Add/Subtract 100ms to/from update timer."},
{"Up, Down", "Select in process list."},
{"Enter", "Show detailed information for selected process."},
{"Spacebar", "Expand/collapse the selected process in tree view."},
{"Pg Up, Pg Down", "Jump 1 page in process list."},
{"Home, End", "Jump to first or last page in process list."},
{"Left, Right", "Select previous/next sorting column."},
{"b, n", "Select previous/next network device."},
{"i", "Toggle disks io mode with big graphs."},
{"z", "Toggle totals reset for current network device"},
{"a", "Toggle auto scaling for the network graphs."},
{"y", "Toggle synced scaling mode for network graphs."},
{"f, /", "To enter a process filter."},
{"delete", "Clear any entered filter."},
{"c", "Toggle per-core cpu usage of processes."},
{"r", "Reverse sorting order in processes box."},
{"e", "Toggle processes tree view."},
{"%", "Toggles memory display mode in processes box."},
{"Selected +, -", "Expand/collapse the selected process in tree view."},
{"Selected t", "Terminate selected process with SIGTERM - 15."},
{"Selected k", "Kill selected process with SIGKILL - 9."},
{"Selected s", "Select or enter signal to send to process."},
{"", " "},
{"", "For bug reporting and project updates, visit:"},
{"", "https://github.com/aristocratos/btop"},
};
const vector<vector<vector<string>>> categories = {
{
{"color_theme",
"Set color theme.",
"",
"Choose from all theme files in (usually)",
"\"/usr/[local/]share/btop/themes\" and",
"\"~/.config/btop/themes\".",
"",
"\"Default\" for builtin default theme.",
"\"TTY\" for builtin 16-color theme.",
"",
"For theme updates see:",
"https://github.com/aristocratos/btop"},
{"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.",
"",
"Set to False if your terminal doesn't have",
"truecolor support and can't convert to",
"256-color."},
{"force_tty",
"TTY mode.",
"",
"Set to true to force tty mode regardless",
"if a real tty has been detected or not.",
"",
"Will force 16-color mode and TTY theme,",
"set all graph symbols to \"tty\" and swap",
"out other non tty friendly symbols."},
{"vim_keys",
"Enable vim keys.",
"Set to True to enable \"h,j,k,l\" keys for",
"directional control in lists.",
"",
"Conflicting keys for",
"h (help) and k (kill)",
"is accessible while holding shift."},
{"presets",
"Define presets for the layout of the boxes.",
"",
"Preset 0 is always all boxes shown with",
"default settings.",
"Max 9 presets.",
"",
"Format: \"box_name:P:G,box_name:P:G\"",
"P=(0 or 1) for alternate positions.",
"G=graph symbol to use for box.",
"",
"Use whitespace \" \" as separator between",
"different presets.",
"",
"Example:",
"\"mem:0:tty,proc:1:default cpu:0:braille\""},
{"shown_boxes",
"Manually set which boxes to show.",
"",
"Available values are \"cpu mem net proc\".",
#ifdef GPU_SUPPORT
"Or \"gpu0\" through \"gpu5\" for GPU boxes.",
#endif
"Separate values with whitespace.",
"",
"Toggle between presets with key \"p\"."},
{"update_ms",
"Update time in milliseconds.",
"",
"Recommended 2000 ms or above for better",
"sample times for graphs.",
"",
"Min value: 100 ms",
"Max value: 86400000 ms = 24 hours."},
{"rounded_corners",
"Rounded corners on boxes.",
"",
"True or False",
"",
"Is always False if TTY mode is ON."},
{"graph_symbol",
"Default symbols to use for graph creation.",
"",
"\"braille\", \"block\" or \"tty\".",
"",
"\"braille\" offers the highest resolution but",
"might not be included in all fonts.",
"",
"\"block\" has half the resolution of braille",
"but uses more common characters.",
"",
"\"tty\" uses only 3 different symbols but will",
"work with most fonts.",
"",
"Note that \"tty\" only has half the horizontal",
"resolution of the other two,",
"so will show a shorter historical view."},
{"clock_format",
"Draw a clock at top of screen.",
"(Only visible if cpu box is enabled!)",
"",
"Formatting according to strftime, empty",
"string to disable.",
"",
"Custom formatting options:",
"\"/host\" = hostname",
"\"/user\" = username",
"\"/uptime\" = system uptime",
"",
"Examples of strftime formats:",
"\"%X\" = locale HH:MM:SS",
"\"%H\" = 24h hour, \"%I\" = 12h hour",
"\"%M\" = minute, \"%S\" = second",
"\"%d\" = day, \"%m\" = month, \"%y\" = year"},
{"base_10_sizes",
"Use base 10 for bits and bytes sizes.",
"",
"Uses KB = 1000 instead of KiB = 1024,",
"MB = 1000KB instead of MiB = 1024KiB,",
"and so on.",
"",
"True or False."},
{"background_update",
"Update main ui when menus are showing.",
"",
"True or False.",
"",
"Set this to false if the menus is flickering",
"too much for a comfortable experience."},
{"show_battery",
"Show battery stats.",
"(Only visible if cpu box is enabled!)",
"",
"Show battery stats in the top right corner",
"if a battery is present."},
{"selected_battery",
"Select battery.",
"",
"Which battery to use if multiple are present.",
"Can be both batteries and UPS.",
"",
"\"Auto\" for auto detection."},
{"log_level",
"Set loglevel for error.log",
"",
"\"ERROR\", \"WARNING\", \"INFO\" and \"DEBUG\".",
"",
"The level set includes all lower levels,",
"i.e. \"DEBUG\" will show all logging info."}
},
{
{"cpu_bottom",
"Cpu box location.",
"",
"Show cpu box at bottom of screen instead",
"of top."},
{"graph_symbol_cpu",
"Graph symbol to use for graphs in cpu box.",
"",
"\"default\", \"braille\", \"block\" or \"tty\".",
"",
"\"default\" for the general default symbol.",},
{"cpu_graph_upper",
"Cpu upper graph.",
"",
"Sets the CPU/GPU stat shown in upper half of",
"the CPU graph.",
"",
"CPU:",
"\"total\" = Total cpu usage. (Auto)",
"\"user\" = User mode cpu usage.",
"\"system\" = Kernel mode cpu usage.",
"+ more depending on kernel.",
#ifdef GPU_SUPPORT
"",
"GPU:",
"\"gpu-totals\" = GPU usage split by device.",
"\"gpu-vram-totals\" = VRAM usage split by GPU.",
"\"gpu-pwr-totals\" = Power usage split by GPU.",
"\"gpu-average\" = Avg usage of all GPUs.",
"\"gpu-vram-total\" = VRAM usage of all GPUs.",
"\"gpu-pwr-total\" = Power usage of all GPUs.",
"Not all stats are supported on all devices."
#endif
},
{"cpu_graph_lower",
"Cpu lower graph.",
"",
"Sets the CPU/GPU stat shown in lower half of",
"the CPU graph.",
"",
"CPU:",
"\"total\" = Total cpu usage.",
"\"user\" = User mode cpu usage.",
"\"system\" = Kernel mode cpu usage.",
"+ more depending on kernel.",
#ifdef GPU_SUPPORT
"",
"GPU:",
"\"gpu-totals\" = GPU usage split/device. (Auto)",
"\"gpu-vram-totals\" = VRAM usage split by GPU.",
"\"gpu-pwr-totals\" = Power usage split by GPU.",
"\"gpu-average\" = Avg usage of all GPUs.",
"\"gpu-vram-total\" = VRAM usage of all GPUs.",
"\"gpu-pwr-total\" = Power usage of all GPUs.",
"Not all stats are supported on all devices."
#endif
},
{"cpu_invert_lower",
"Toggles orientation of the lower CPU graph.",
"",
"True or False."},
{"cpu_single_graph",
"Completely disable the lower CPU graph.",
"",
"Shows only upper CPU graph and resizes it",
"to fit to box height.",
"",
"True or False."},
#ifdef GPU_SUPPORT
{"show_gpu_info",
"Show gpu info in cpu box.",
"",
"Toggles gpu stats in cpu box and the",
"gpu graph (if \"cpu_graph_lower\" is set to",
"\"Auto\").",
"",
"\"Auto\" to show when no gpu box is shown.",
"\"On\" to always show.",
"\"Off\" to never show."},
#endif
{"check_temp",
"Enable cpu temperature reporting.",
"",
"True or False."},
{"cpu_sensor",
"Cpu temperature sensor.",
"",
"Select the sensor that corresponds to",
"your cpu temperature.",
"",
"Set to \"Auto\" for auto detection."},
{"show_coretemp",
"Show temperatures for cpu cores.",
"",
"Only works if check_temp is True and",
"the system is reporting core temps."},
{"cpu_core_map",
"Custom mapping between core and coretemp.",
"",
"Can be needed on certain cpus to get correct",
"temperature for correct core.",
"",
"Use lm-sensors or similar to see which cores",
"are reporting temperatures on your machine.",
"",
"Format: \"X:Y\"",
"X=core with wrong temp.",
"Y=core with correct temp.",
"Use space as separator between multiple",
"entries.",
"",
"Example: \"4:0 5:1 6:3\""},
{"temp_scale",
"Which temperature scale to use.",
"",
"Celsius, default scale.",
"",
"Fahrenheit, the american one.",
"",
"Kelvin, 0 = absolute zero, 1 degree change",
"equals 1 degree change in Celsius.",
"",
"Rankine, 0 = abosulte zero, 1 degree change",
"equals 1 degree change in Fahrenheit."},
{"show_cpu_freq",
"Show CPU frequency.",
"",
"Can cause slowdowns on systems with many",
"cores and certain kernel versions."},
{"custom_cpu_name",
"Custom cpu model name in cpu percentage box.",
"",
"Empty string to disable."},
{"show_uptime",
"Shows the system uptime in the CPU box.",
"",
"Can also be shown in the clock by using",
"\"/uptime\" in the formatting.",
"",
"True or False."},
},
#ifdef GPU_SUPPORT
{
{"nvml_measure_pcie_speeds",
"Measure PCIe throughput on NVIDIA cards.",
"",
"May impact performance on certain cards.",
"",
"True or False."},
{"graph_symbol_gpu",
"Graph symbol to use for graphs in gpu box.",
"",
"\"default\", \"braille\", \"block\" or \"tty\".",
"",
"\"default\" for the general default symbol.",},
{"gpu_mirror_graph",
"Horizontally mirror the GPU graph.",
"",
"True or False."},
{"custom_gpu_name0",
"Custom gpu0 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name1",
"Custom gpu1 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name2",
"Custom gpu2 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name3",
"Custom gpu3 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name4",
"Custom gpu4 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name5",
"Custom gpu5 model name in gpu stats box.",
"",
"Empty string to disable."},
},
#endif
{
{"mem_below_net",
"Mem box location.",
"",
"Show mem box below net box instead of above."},
{"graph_symbol_mem",
"Graph symbol to use for graphs in mem box.",
"",
"\"default\", \"braille\", \"block\" or \"tty\".",
"",
"\"default\" for the general default symbol.",},
{"mem_graphs",
"Show graphs for memory values.",
"",
"True or False."},
{"show_disks",
"Split memory box to also show disks.",
"",
"True or False."},
{"show_io_stat",
"Toggle IO activity graphs.",
"",
"Show small IO graphs that for disk activity",
"(disk busy time) when not in IO mode.",
"",
"True or False."},
{"io_mode",
"Toggles io mode for disks.",
"",
"Shows big graphs for disk read/write speeds",
"instead of used/free percentage meters.",
"",
"True or False."},
{"io_graph_combined",
"Toggle combined read and write graphs.",
"",
"Only has effect if \"io mode\" is True.",
"",
"True or False."},
{"io_graph_speeds",
"Set top speeds for the io graphs.",
"",
"Manually set which speed in MiB/s that",
"equals 100 percent in the io graphs.",
"(100 MiB/s by default).",
"",
"Format: \"device:speed\" separate disks with",
"whitespace \" \".",
"",
"Example: \"/dev/sda:100, /dev/sdb:20\"."},
{"show_swap",
"If swap memory should be shown in memory box.",
"",
"True or False."},
{"swap_disk",
"Show swap as a disk.",
"",
"Ignores show_swap value above.",
"Inserts itself after first disk."},
{"only_physical",
"Filter out non physical disks.",
"",
"Set this to False to include network disks,",
"RAM disks and similar.",
"",
"True or False."},
{"use_fstab",
"(Linux) Read disks list from /etc/fstab.",
"",
"This also disables only_physical.",
"",
"True or False."},
{"zfs_hide_datasets",
"(Linux) Hide ZFS datasets in disks list.",
"",
"Setting this to True will hide all datasets,",
"and only show ZFS pools.",
"",
"(IO stats will be calculated per-pool)",
"",
"True or False."},
{"disk_free_priv",
"(Linux) Type of available disk space.",
"",
"Set to true to show how much disk space is",
"available for privileged users.",
"",
"Set to false to show available for normal",
"users."},
{"disks_filter",
"Optional filter for shown disks.",
"",
"Should be full path of a mountpoint.",
"Separate multiple values with",
"whitespace \" \".",
"",
"Begin line with \"exclude=\" to change to",
"exclude filter.",
"Oterwise defaults to \"most include\" filter.",
"",
"Example:",
"\"exclude=/boot /home/user\""},
{"zfs_arc_cached",
"(Linux) Count ZFS ARC as cached memory.",
"",
"Add ZFS ARC used to cached memory and",
"ZFS ARC available to available memory.",
"These are otherwise reported by the Linux",
"kernel as used memory.",
"",
"True or False."},
},
{
{"graph_symbol_net",
"Graph symbol to use for graphs in net box.",
"",
"\"default\", \"braille\", \"block\" or \"tty\".",
"",
"\"default\" for the general default symbol.",},
{"net_download",
"Fixed network graph download value.",
"",
"Value in Mebibits, default \"100\".",
"",
"Can be toggled with auto button."},
{"net_upload",
"Fixed network graph upload value.",
"",
"Value in Mebibits, default \"100\".",
"",
"Can be toggled with auto button."},
{"net_auto",
"Start in network graphs auto rescaling mode.",
"",
"Ignores any values set above at start and",
"rescales down to 10Kibibytes at the lowest.",
"",
"True or False."},
{"net_sync",
"Network scale sync.",
"",
"Syncs the scaling for download and upload to",
"whichever currently has the highest scale.",
"",
"True or False."},
{"net_iface",
"Network Interface.",
"",
"Manually set the starting Network Interface.",
"",
"Will otherwise automatically choose the NIC",
"with the highest total download since boot."},
},
{
{"proc_left",
"Proc box location.",
"",
"Show proc box on left side of screen",
"instead of right."},
{"graph_symbol_proc",
"Graph symbol to use for graphs in proc box.",
"",
"\"default\", \"braille\", \"block\" or \"tty\".",
"",
"\"default\" for the general default symbol.",},
{"proc_sorting",
"Processes sorting option.",
"",
"Possible values:",
"\"pid\", \"program\", \"arguments\", \"threads\",",
"\"user\", \"memory\", \"cpu lazy\" and",
"\"cpu direct\".",
"",
"\"cpu lazy\" updates top process over time.",
"\"cpu direct\" updates top process",
"directly."},
{"proc_reversed",
"Reverse processes sorting order.",
"",
"True or False."},
{"proc_tree",
"Processes tree view.",
"",
"Set true to show processes grouped by",
"parents with lines drawn between parent",
"and child process."},
{"proc_aggregate",
"Aggregate child's resources in parent.",
"",
"In tree-view, include all child resources",
"with the parent even while expanded."},
{"proc_colors",
"Enable colors in process view.",
"",
"True or False."},
{"proc_gradient",
"Enable process view gradient fade.",
"",
"Fades from top or current selection.",
"Max fade value is equal to current themes",
"\"inactive_fg\" color value."},
{"proc_per_core",
"Process usage per core.",
"",
"If process cpu usage should be of the core",
"it's running on or usage of the total",
"available cpu power.",
"",
"If true and process is multithreaded",
"cpu usage can reach over 100%."},
{"proc_mem_bytes",
"Show memory as bytes in process list.",
" ",
"Will show percentage of total memory",
"if False."},
{"proc_cpu_graphs",
"Show cpu graph for each process.",
"",
"True or False"},
{"proc_filter_kernel",
"(Linux) Filter kernel processes from output.",
"",
"Set to 'True' to filter out internal",
"processes started by the Linux kernel."},
}
};
msgBox::msgBox() {}
msgBox::msgBox(int width, int boxtype, vector<string> content, string title)
: width(width), boxtype(boxtype) {
auto tty_mode = Config::getB("tty_mode");
auto rounded = Config::getB("rounded_corners");
const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
const auto& left_down = (tty_mode or not rounded ? Symbols::left_down : Symbols::round_left_down);
height = content.size() + 7;
x = Term::width / 2 - width / 2;
y = Term::height/2 - height/2;
if (boxtype == 2) selected = 1;
button_left = left_up + Symbols::h_line * 6 + Mv::l(7) + Mv::d(2) + left_down + Symbols::h_line * 6 + Mv::l(7) + Mv::u(1) + Symbols::v_line;
button_right = Symbols::v_line + Mv::l(7) + Mv::u(1) + Symbols::h_line * 6 + right_up + Mv::l(7) + Mv::d(2) + Symbols::h_line * 6 + right_down + Mv::u(2);
box_contents = Draw::createBox(x, y, width, height, Theme::c("hi_fg"), true, title) + Mv::d(1);
for (const auto& line : content) {
box_contents += Mv::save + Mv::r(max((size_t)0, (width / 2) - (Fx::uncolor(line).size() / 2) - 1)) + line + Mv::restore + Mv::d(1);
}
}
string msgBox::operator()() {
string out;
int pos = width / 2 - (boxtype == 0 ? 6 : 14);
auto& first_color = (selected == 0 ? Theme::c("hi_fg") : Theme::c("div_line"));
out = Mv::d(1) + Mv::r(pos) + Fx::b + first_color + button_left + (selected == 0 ? Theme::c("title") : Theme::c("main_fg") + Fx::ub)
+ (boxtype == 0 ? " Ok " : " Yes ") + first_color + button_right;
mouse_mappings["button1"] = Input::Mouse_loc{y + height - 4, x + pos + 1, 3, 12 + (boxtype > 0 ? 1 : 0)};
if (boxtype > 0) {
auto& second_color = (selected == 1 ? Theme::c("hi_fg") : Theme::c("div_line"));
out += Mv::r(2) + second_color + button_left + (selected == 1 ? Theme::c("title") : Theme::c("main_fg") + Fx::ub)
+ " No " + second_color + button_right;
mouse_mappings["button2"] = Input::Mouse_loc{y + height - 4, x + pos + 15 + (boxtype > 0 ? 1 : 0), 3, 12};
}
return box_contents + out + Fx::reset;
}
//? Process input
int msgBox::input(string key) {
if (key.empty()) return Invalid;
if (is_in(key, "escape", "backspace", "q") or key == "button2") {
return No_Esc;
}
else if (key == "button1" or (boxtype == 0 and str_to_upper(key) == "O")) {
return Ok_Yes;
}
else if (is_in(key, "enter", "space")) {
return selected + 1;
}
else if (boxtype == 0) {
return Invalid;
}
else if (str_to_upper(key) == "Y") {
return Ok_Yes;
}
else if (str_to_upper(key) == "N") {
return No_Esc;
}
else if (is_in(key, "right", "tab")) {
if (++selected > 1) selected = 0;
return Select;
}
else if (is_in(key, "left", "shift_tab")) {
if (--selected < 0) selected = 1;
return Select;
}
return Invalid;
}
void msgBox::clear() {
box_contents.clear();
box_contents.shrink_to_fit();
button_left.clear();
button_left.shrink_to_fit();
button_right.clear();
button_right.shrink_to_fit();
if (mouse_mappings.contains("button1")) mouse_mappings.erase("button1");
if (mouse_mappings.contains("button2")) mouse_mappings.erase("button2");
}
enum menuReturnCodes {
NoChange,
Changed,
Closed,
Switch
};
int signalChoose(const string& key) {
auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
static int x{}; // defaults to 0
static int y{}; // defaults to 0
static int selected_signal = -1;
if (bg.empty()) selected_signal = -1;
auto& out = Global::overlay;
int retval = Changed;
if (redraw) {
x = Term::width/2 - 40;
y = Term::height/2 - 9;
bg = Draw::createBox(x + 2, y, 78, 19, Theme::c("hi_fg"), true, "signals");
bg += Mv::to(y+2, x+3) + Theme::c("title") + Fx::b + cjust("Send signal to PID " + to_string(s_pid) + " ("
+ uresize((s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name")), 30) + ")", 76);
}
else if (is_in(key, "escape", "q")) {
return Closed;
}
else if (key.starts_with("button_")) {
if (int new_select = stoi(key.substr(7)); new_select == selected_signal)
goto ChooseEntering;
else
selected_signal = new_select;
}
else if (is_in(key, "enter", "space") and selected_signal >= 0) {
ChooseEntering:
signalKillRet = 0;
if (s_pid < 1) {
signalKillRet = ESRCH;
menuMask.set(SignalReturn);
}
else if (kill(s_pid, selected_signal) != 0) {
signalKillRet = errno;
menuMask.set(SignalReturn);
}
return Closed;
}
else if (key.size() == 1 and isdigit(key.at(0)) and selected_signal < 10) {
selected_signal = std::min(std::stoi((selected_signal < 1 ? key : to_string(selected_signal) + key)), 64);
}
else if (key == "backspace" and selected_signal != -1) {
selected_signal = (selected_signal < 10 ? -1 : selected_signal / 10);
}
else if (is_in(key, "up", "k") and selected_signal != 16) {
if (selected_signal == 1) selected_signal = 31;
else if (selected_signal < 6) selected_signal += 25;
else {
bool offset = (selected_signal > 16);
selected_signal -= 5;
if (selected_signal <= 16 and offset) selected_signal--;
}
}
else if (is_in(key, "down", "j")) {
if (selected_signal == 31) selected_signal = 1;
else if (selected_signal < 1 or selected_signal == 16) selected_signal = 1;
else if (selected_signal > 26) selected_signal -= 25;
else {
bool offset = (selected_signal < 16);
selected_signal += 5;
if (selected_signal >= 16 and offset) selected_signal++;
if (selected_signal > 31) selected_signal = 31;
}
}
else if (is_in(key, "left", "h") and selected_signal > 0 and selected_signal != 16) {
if (--selected_signal < 1) selected_signal = 31;
else if (selected_signal == 16) selected_signal--;
}
else if (is_in(key, "right", "l") and selected_signal <= 31 and selected_signal != 16) {
if (++selected_signal > 31) selected_signal = 1;
else if (selected_signal == 16) selected_signal++;
}
else {
retval = NoChange;
}
if (retval == Changed) {
int cy = y+4, cx = x+4;
out = bg + Mv::to(cy++, x+3) + Theme::c("main_fg") + Fx::ub
+ rjust("Enter signal number: ", 48) + Theme::c("hi_fg") + (selected_signal >= 0 ? to_string(selected_signal) : "") + Theme::c("main_fg") + Fx::bl + "" + Fx::ubl;
auto sig_str = to_string(selected_signal);
for (int count = 0, i = 0; const auto& sig : P_Signals) {
if (count == 0 or count == 16) { count++; continue; }
if (i++ % 5 == 0) { ++cy; cx = x+4; }
out += Mv::to(cy, cx);
if (count == selected_signal) out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b + ljust(to_string(count), 3) + ljust('(' + sig + ')', 12) + Fx::reset;
else out += Theme::c("hi_fg") + ljust(to_string(count), 3) + Theme::c("main_fg") + ljust('(' + sig + ')', 12);
if (redraw) mouse_mappings["button_" + to_string(count)] = {cy, cx, 1, 15};
count++;
cx += 15;
}
cy++;
out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust( "↑ ↓ ← →", 33, true) + Theme::c("main_fg") + Fx::ub + " | To choose signal.";
out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("0-9", 33) + Theme::c("main_fg") + Fx::ub + " | Enter manually.";
out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ENTER", 33) + Theme::c("main_fg") + Fx::ub + " | To send signal.";
mouse_mappings["enter"] = {cy, x, 1, 73};
out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ESC or \"q\"", 33) + Theme::c("main_fg") + Fx::ub + " | To abort.";
mouse_mappings["escape"] = {cy, x, 1, 73};
out += Fx::reset;
}
return (redraw ? Changed : retval);
}
int sizeError(const string& key) {
if (redraw) {
vector<string> cont_vec;
cont_vec.push_back(Fx::b + Theme::g("used")[100] + "Error:" + Theme::c("main_fg") + Fx::ub);
cont_vec.push_back("Terminal size to small to" + Fx::reset);
cont_vec.push_back("display menu or box!" + Fx::reset);
messageBox = Menu::msgBox{45, 0, cont_vec, "error"};
Global::overlay = messageBox();
}
auto ret = messageBox.input(key);
if (ret == msgBox::Ok_Yes or ret == msgBox::No_Esc) {
messageBox.clear();
return Closed;
}
else if (redraw) {
return Changed;
}
return NoChange;
}
int signalSend(const string& key) {
auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
if (s_pid == 0) return Closed;
if (redraw) {
atomic_wait(Runner::active);
auto& p_name = (s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name"));
vector<string> cont_vec = {
Fx::b + Theme::c("main_fg") + "Send signal: " + Fx::ub + Theme::c("hi_fg") + to_string(signalToSend)
+ (signalToSend > 0 and signalToSend <= 32 ? Theme::c("main_fg") + " (" + P_Signals.at(signalToSend) + ')' : ""),
Fx::b + Theme::c("main_fg") + "To PID: " + Fx::ub + Theme::c("hi_fg") + to_string(s_pid) + Theme::c("main_fg") + " ("
+ uresize(p_name, 16) + ')' + Fx::reset,
};
messageBox = Menu::msgBox{50, 1, cont_vec, (signalToSend > 1 and signalToSend <= 32 and signalToSend != 17 ? P_Signals.at(signalToSend) : "signal")};
Global::overlay = messageBox();
}
auto ret = messageBox.input(key);
if (ret == msgBox::Ok_Yes) {
signalKillRet = 0;
if (kill(s_pid, signalToSend) != 0) {
signalKillRet = errno;
menuMask.set(SignalReturn);
}
messageBox.clear();
return Closed;
}
else if (ret == msgBox::No_Esc) {
messageBox.clear();
return Closed;
}
else if (ret == msgBox::Select) {
Global::overlay = messageBox();
return Changed;
}
else if (redraw) {
return Changed;
}
return NoChange;
}
int signalReturn(const string& key) {
if (redraw) {
vector<string> cont_vec;
cont_vec.push_back(Fx::b + Theme::g("used")[100] + "Failure:" + Theme::c("main_fg") + Fx::ub);
if (signalKillRet == EINVAL) {
cont_vec.push_back("Unsupported signal!" + Fx::reset);
}
else if (signalKillRet == EPERM) {
cont_vec.push_back("Insufficient permissions to send signal!" + Fx::reset);
}
else if (signalKillRet == ESRCH) {
cont_vec.push_back("Process not found!" + Fx::reset);
}
else {
cont_vec.push_back("Unknown error! (errno: " + to_string(signalKillRet) + ')' + Fx::reset);
}
messageBox = Menu::msgBox{50, 0, cont_vec, "error"};
Global::overlay = messageBox();
}
auto ret = messageBox.input(key);
if (ret == msgBox::Ok_Yes or ret == msgBox::No_Esc) {
messageBox.clear();
return Closed;
}
else if (redraw) {
return Changed;
}
return NoChange;
}
int mainMenu(const string& key) {
enum MenuItems { Options, Help, Quit };
static int y{}; // defaults to 0
static int selected{}; // defaults to 0
static vector<string> colors_selected;
static vector<string> colors_normal;
auto tty_mode = Config::getB("tty_mode");
if (bg.empty()) selected = 0;
int retval = Changed;
if (redraw) {
y = Term::height/2 - 10;
bg = Draw::banner_gen(y, 0, true);
if (not tty_mode) {
colors_selected = {
Theme::hex_to_color(Global::Banner_src.at(0).at(0)),
Theme::hex_to_color(Global::Banner_src.at(2).at(0)),
Theme::hex_to_color(Global::Banner_src.at(4).at(0))
};
colors_normal = {
Theme::hex_to_color("#CC"),
Theme::hex_to_color("#AA"),
Theme::hex_to_color("#80")
};
}
}
else if (is_in(key, "escape", "q", "m", "mouse_click")) {
return Closed;
}
else if (key.starts_with("button_")) {
if (int new_select = key.back() - '0'; new_select == selected)
goto MainEntering;
else
selected = new_select;
}
else if (is_in(key, "enter", "space")) {
MainEntering:
switch (selected) {
case Options:
menuMask.set(Menus::Options);
currentMenu = Menus::Options;
return Switch;
case Help:
menuMask.set(Menus::Help);
currentMenu = Menus::Help;
return Switch;
case Quit:
clean_quit(0);
}
}
else if (is_in(key, "down", "tab", "mouse_scroll_down", "j")) {
if (++selected > 2) selected = 0;
}
else if (is_in(key, "up", "shift_tab", "mouse_scroll_up", "k")) {
if (--selected < 0) selected = 2;
}
else {
retval = NoChange;
}
if (retval == Changed) {
auto& out = Global::overlay;
out = bg + Fx::reset + Fx::b;
auto cy = y + 7;
for (const auto& i : iota(0, 3)) {
if (tty_mode) out += (i == selected ? Theme::c("hi_fg") : Theme::c("main_fg"));
const auto& menu = (not tty_mode and i == selected ? menu_selected[i] : menu_normal[i]);
const auto& colors = (i == selected ? colors_selected : colors_normal);
if (redraw) mouse_mappings["button_" + to_string(i)] = {cy, Term::width/2 - menu_width[i]/2, 3, menu_width[i]};
for (int ic = 0; const auto& line : menu) {
out += Mv::to(cy++, Term::width/2 - menu_width[i]/2) + (tty_mode ? "" : colors[ic++]) + line;
}
}
out += Fx::reset;
}
return (redraw ? Changed : retval);
}
int optionsMenu(const string& key) {
enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable};
static int y{}; // defaults to 0
static int x{}; // defaults to 0
static int height{}; // defaults to 0
static int page{}; // defaults to 0
static int pages{}; // defaults to 0
static int selected{}; // defaults to 0
static int select_max{}; // defaults to 0
static int item_height{}; // defaults to 0
static int selected_cat{}; // defaults to 0
static int max_items{}; // defaults to 0
static int last_sel{}; // defaults to 0
static bool editing{}; // defaults to false
static Draw::TextEdit editor;
static string warnings;
static bitset<8> selPred;
static const unordered_flat_map<string, std::reference_wrapper<const vector<string>>> optionsList = {
{"color_theme", std::cref(Theme::themes)},
{"log_level", std::cref(Logger::log_levels)},
{"temp_scale", std::cref(Config::temp_scales)},
{"proc_sorting", std::cref(Proc::sort_vector)},
{"graph_symbol", std::cref(Config::valid_graph_symbols)},
{"graph_symbol_cpu", std::cref(Config::valid_graph_symbols_def)},
{"graph_symbol_mem", std::cref(Config::valid_graph_symbols_def)},
{"graph_symbol_net", std::cref(Config::valid_graph_symbols_def)},
{"graph_symbol_proc", std::cref(Config::valid_graph_symbols_def)},
{"cpu_graph_upper", std::cref(Cpu::available_fields)},
{"cpu_graph_lower", std::cref(Cpu::available_fields)},
{"cpu_sensor", std::cref(Cpu::available_sensors)},
{"selected_battery", std::cref(Config::available_batteries)},
#ifdef GPU_SUPPORT
{"show_gpu_info", std::cref(Config::show_gpu_values)},
{"graph_symbol_gpu", std::cref(Config::valid_graph_symbols_def)},
#endif
};
auto tty_mode = Config::getB("tty_mode");
auto vim_keys = Config::getB("vim_keys");
if (max_items == 0) {
for (const auto& cat : categories) {
if ((int)cat.size() > max_items) max_items = cat.size();
}
}
if (bg.empty()) {
page = selected = selected_cat = last_sel = 0;
redraw = true;
Theme::updateThemes();
}
int retval = Changed;
bool recollect{}; // defaults to false
bool screen_redraw{}; // defaults to false
bool theme_refresh{}; // defaults to false
//? Draw background if needed else process input
if (redraw) {
mouse_mappings.clear();
selPred.reset();
y = max(1, Term::height/2 - 3 - max_items);
x = Term::width/2 - 39;
height = min(Term::height - 7, max_items * 2 + 4);
if (height % 2 != 0) height--;
bg = Draw::banner_gen(y, 0, true)
+ Draw::createBox(x, y + 6, 78, height, Theme::c("hi_fg"), true, "tab" + Symbols::right)
+ Mv::to(y+8, x) + Theme::c("hi_fg") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * 29
+ Symbols::div_up + Symbols::h_line * (78 - 32) + Theme::c("hi_fg") + Symbols::div_right
+ Mv::to(y+6+height - 1, x+30) + Symbols::div_down + Theme::c("div_line");
for (const auto& i : iota(0, height - 4)) {
bg += Mv::to(y+9 + i, x + 30) + Symbols::v_line;
}
}
else if (not warnings.empty() and not key.empty()) {
auto ret = messageBox.input(key);
if (ret == msgBox::msgReturn::Ok_Yes or ret == msgBox::msgReturn::No_Esc) {
warnings.clear();
messageBox.clear();
}
}
else if (editing and not key.empty()) {
if (is_in(key, "escape", "mouse_click")) {
editor.clear();
editing = false;
}
else if (key == "enter") {
const auto& option = categories[selected_cat][item_height * page + selected][0];
if (selPred.test(isString) and Config::stringValid(option, editor.text)) {
Config::set(option, editor.text);
if (option == "custom_cpu_name" or option.starts_with("custom_gpu_name"))
screen_redraw = true;
else if (is_in(option, "shown_boxes", "presets")) {
screen_redraw = true;
Config::current_preset = -1;
}
else if (option == "clock_format") {
Draw::update_clock(true);
screen_redraw = true;
}
else if (option == "cpu_core_map") {
atomic_wait(Runner::active);
Cpu::core_mapping = Cpu::get_core_mapping();
}
}
else if (selPred.test(isInt) and Config::intValid(option, editor.text)) {
Config::set(option, stoi(editor.text));
}
else
warnings = Config::validError;
editor.clear();
editing = false;
}
else if (not editor.command(key))
retval = NoChange;
}
else if (key == "mouse_click") {
const auto [mouse_x, mouse_y] = Input::mouse_pos;
if (mouse_x < x or mouse_x > x + 80 or mouse_y < y + 6 or mouse_y > y + 6 + height) {
return Closed;
}
else if (mouse_x < x + 30 and mouse_y > y + 8) {
auto m_select = ceil((double)(mouse_y - y - 8) / 2) - 1;
if (selected != m_select)
selected = m_select;
else if (selPred.test(isEditable))
goto mouseEnter;
else retval = NoChange;
}
}
else if (is_in(key, "enter", "e", "E") and selPred.test(isEditable)) {
mouseEnter:
const auto& option = categories[selected_cat][item_height * page + selected][0];
editor = Draw::TextEdit{Config::getAsString(option), selPred.test(isInt)};
editing = true;
mouse_mappings.clear();
}
else if (is_in(key, "escape", "q", "o", "backspace")) {
return Closed;
}
else if (is_in(key, "down", "mouse_scroll_down") or (vim_keys and key == "j")) {
if (++selected > select_max or selected >= item_height) {
if (page < pages - 1) page++;
else if (pages > 1) page = 0;
selected = 0;
}
}
else if (is_in(key, "up", "mouse_scroll_up") or (vim_keys and key == "k")) {
if (--selected < 0) {
if (page > 0) page--;
else if (pages > 1) page = pages - 1;
selected = item_height - 1;
}
}
else if (pages > 1 and key == "page_down") {
if (++page >= pages) page = 0;
selected = 0;
}
else if (pages > 1 and key == "page_up") {
if (--page < 0) page = pages - 1;
selected = 0;
}
else if (key == "tab") {
if (++selected_cat >= (int)categories.size()) selected_cat = 0;
page = selected = 0;
}
else if (key == "shift_tab") {
if (--selected_cat < 0) selected_cat = (int)categories.size() - 1;
page = selected = 0;
}
else if (is_in(key, "1", "2", "3", "4", "5", "6") or key.starts_with("select_cat_")) {
selected_cat = key.back() - '0' - 1;
page = selected = 0;
}
else if (is_in(key, "left", "right") or (vim_keys and is_in(key, "h", "l"))) {
const auto& option = categories[selected_cat][item_height * page + selected][0];
if (selPred.test(isInt)) {
const int mod = (option == "update_ms" ? 100 : 1);
long value = Config::getI(option);
if (key == "right" or (vim_keys and key == "l")) value += mod;
else value -= mod;
if (Config::intValid(option, to_string(value)))
Config::set(option, static_cast<int>(value));
else {
warnings = Config::validError;
}
}
else if (selPred.test(isBool)) {
Config::flip(option);
screen_redraw = true;
if (option == "truecolor") {
theme_refresh = true;
Config::flip("lowcolor");
}
else if (option == "force_tty") {
theme_refresh = true;
Config::flip("tty_mode");
}
else if (is_in(option, "rounded_corners", "theme_background"))
theme_refresh = true;
else if (option == "background_update") {
Runner::pause_output = false;
}
else if (option == "base_10_sizes") {
recollect = true;
}
}
else if (selPred.test(isBrowseable)) {
auto& optList = optionsList.at(option).get();
int i = v_index(optList, Config::getS(option));
if ((key == "right" or (vim_keys and key == "l")) and ++i >= (int)optList.size()) i = 0;
else if ((key == "left" or (vim_keys and key == "h")) and --i < 0) i = optList.size() - 1;
Config::set(option, optList.at(i));
if (option == "color_theme")
theme_refresh = true;
else if (option == "log_level") {
Logger::set(optList.at(i));
Logger::info("Logger set to " + optList.at(i));
}
else if (is_in(option, "proc_sorting", "cpu_sensor", "show_gpu_info") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_"))
screen_redraw = true;
}
else
retval = NoChange;
}
else {
retval = NoChange;
}
//? Draw the menu
if (retval == Changed) {
Config::unlock();
auto& out = Global::overlay;
out = bg;
item_height = min((int)categories[selected_cat].size(), (int)floor((double)(height - 4) / 2));
pages = ceil((double)categories[selected_cat].size() / item_height);
if (page > pages - 1) page = pages - 1;
select_max = min(item_height - 1, (int)categories[selected_cat].size() - 1 - item_height * page);
if (selected > select_max) {
selected = select_max;
}
//? Get variable properties for currently selected option
if (selPred.none() or last_sel != (selected_cat << 8) + selected) {
selPred.reset();
last_sel = (selected_cat << 8) + selected;
const auto& selOption = categories[selected_cat][item_height * page + selected][0];
if (Config::ints.contains(selOption))
selPred.set(isInt);
else if (Config::bools.contains(selOption))
selPred.set(isBool);
else
selPred.set(isString);
if (not selPred.test(isString))
selPred.set(is2D);
else if (optionsList.contains(selOption)) {
selPred.set(isBrowseable);
}
if (not selPred.test(isBrowseable) and (selPred.test(isString) or selPred.test(isInt)))
selPred.set(isEditable);
}
//? Category buttons
out += Mv::to(y+7, x+4);
#ifdef GPU_SUPPORT
for (int i = 0; const auto& m : {"general", "cpu", "gpu", "mem", "net", "proc"}) {
#else
for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) {
#endif
out += Fx::b + (i == selected_cat
? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']'
: Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ')
#ifdef GPU_SUPPORT
+ Mv::r(7);
#else
+ Mv::r(10);
#endif
if (string button_name = "select_cat_" + to_string(i + 1); not editing and not mouse_mappings.contains(button_name))
mouse_mappings[button_name] = {y+6, x+2 + 15*i, 3, 15};
i++;
}
if (pages > 1) {
out += Mv::to(y+6 + height - 1, x+2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page "
+ to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down;
}
//? Option name and value
auto cy = y+9;
for (int c = 0, i = max(0, item_height * page); c++ < item_height and i < (int)categories[selected_cat].size(); i++) {
const auto& option = categories[selected_cat][i][0];
const auto& value = (option == "color_theme" ? (string) fs::path(Config::getS("color_theme")).stem() : Config::getAsString(option));
out += Mv::to(cy++, x + 1) + (c-1 == selected ? Theme::c("selected_bg") + Theme::c("selected_fg") : Theme::c("title"))
+ Fx::b + cjust(capitalize(s_replace(option, "_", " "))
+ (c-1 == selected and selPred.test(isBrowseable)
? ' ' + to_string(v_index(optionsList.at(option).get(), (option == "color_theme" ? Config::getS("color_theme") : value)) + 1) + '/' + to_string(optionsList.at(option).get().size())
: ""), 29);
out += Mv::to(cy++, x + 1) + (c-1 == selected ? "" : Theme::c("main_fg")) + Fx::ub + " "
+ (c-1 == selected and editing ? cjust(editor(24), 34, true) : cjust(value, 25, true)) + " ";
if (c-1 == selected) {
if (not editing and (selPred.test(is2D) or selPred.test(isBrowseable))) {
out += Fx::b + Mv::to(cy-1, x+2) + Symbols::left + Mv::to(cy-1, x+28) + Symbols::right;
mouse_mappings["left"] = {cy-2, x, 2, 5};
mouse_mappings["right"] = {cy-2, x+25, 2, 5};
}
if (selPred.test(isEditable)) {
out += Fx::b + Mv::to(cy-1, x+28 - (not editing and selPred.test(isInt) ? 2 : 0)) + (tty_mode ? "E" : Symbols::enter);
}
//? Description of selected option
out += Fx::reset + Theme::c("title") + Fx::b;
for (int cyy = y+7; const auto& desc : categories[selected_cat][i]) {
if (cyy++ == y+7) continue;
else if (cyy == y+10) out += Theme::c("main_fg") + Fx::ub;
else if (cyy > y + height + 4) break;
out += Mv::to(cyy, x+32) + desc;
}
}
}
if (not warnings.empty()) {
messageBox = msgBox{min(78, (int)ulen(warnings) + 10), msgBox::BoxTypes::OK, {uresize(warnings, 74)}, "warning"};
out += messageBox();
}
out += Fx::reset;
}
if (theme_refresh) {
Theme::setTheme();
Draw::banner_gen(0, 0, false, true);
screen_redraw = true;
redraw = true;
optionsMenu("");
}
if (screen_redraw) {
auto overlay_bkp = std::move(Global::overlay);
auto clock_bkp = std::move(Global::clock);
Draw::calcSizes();
Global::overlay = std::move(overlay_bkp);
Global::clock = std::move(clock_bkp);
recollect = true;
}
if (recollect) {
Runner::run("all", false, true);
retval = NoChange;
}
return (redraw ? Changed : retval);
}
int helpMenu(const string& key) {
static int y{}; // defaults to 0
static int x{}; // defaults to 0
static int height{}; // defaults to 0
static int page{}; // defaults to 0
static int pages{}; // defaults to 0
if (bg.empty()) page = 0;
int retval = Changed;
if (redraw) {
y = max(1, Term::height/2 - 4 - (int)(help_text.size() / 2));
x = Term::width/2 - 39;
height = min(Term::height - 6, (int)help_text.size() + 3);
pages = ceil((double)help_text.size() / (height - 3));
page = 0;
bg = Draw::banner_gen(y, 0, true);
bg += Draw::createBox(x, y + 6, 78, height, Theme::c("hi_fg"), true, "help");
}
else if (is_in(key, "escape", "q", "h", "backspace", "space", "enter", "mouse_click")) {
return Closed;
}
else if (pages > 1 and is_in(key, "down", "page_down", "tab", "mouse_scroll_down")) {
if (++page >= pages) page = 0;
}
else if (pages > 1 and is_in(key, "up", "page_up", "shift_tab", "mouse_scroll_up")) {
if (--page < 0) page = pages - 1;
}
else {
retval = NoChange;
}
if (retval == Changed) {
auto& out = Global::overlay;
out = bg;
if (pages > 1) {
out += Mv::to(y+height+6, x + 2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page "
+ to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down;
}
auto cy = y+7;
out += Mv::to(cy++, x + 1) + Theme::c("title") + Fx::b + cjust("Key:", 20) + "Description:";
for (int c = 0, i = max(0, (height - 3) * page); c++ < height - 3 and i < (int)help_text.size(); i++) {
out += Mv::to(cy++, x + 1) + Theme::c("hi_fg") + Fx::b + cjust(help_text[i][0], 20)
+ Theme::c("main_fg") + Fx::ub + help_text[i][1];
}
out += Fx::reset;
}
return (redraw ? Changed : retval);
}
//* Add menus here and update enum Menus in header
const auto menuFunc = vector{
ref(sizeError),
ref(signalChoose),
ref(signalSend),
ref(signalReturn),
ref(optionsMenu),
ref(helpMenu),
ref(mainMenu),
};
bitset<8> menuMask;
void process(string key) {
if (menuMask.none()) {
Menu::active = false;
Global::overlay.clear();
Global::overlay.shrink_to_fit();
Runner::pause_output = false;
bg.clear();
bg.shrink_to_fit();
currentMenu = -1;
Runner::run("all", true, true);
mouse_mappings.clear();
return;
}
if (currentMenu < 0 or not menuMask.test(currentMenu)) {
Menu::active = true;
redraw = true;
if (((menuMask.test(Main) or menuMask.test(Options) or menuMask.test(Help) or menuMask.test(SignalChoose))
and (Term::width < 80 or Term::height < 24))
or (Term::width < 50 or Term::height < 20)) {
menuMask.reset();
menuMask.set(SizeError);
}
for (const auto& i : iota(0, (int)menuMask.size())) {
if (menuMask.test(i)) currentMenu = i;
}
}
auto retCode = menuFunc.at(currentMenu)(key);
if (retCode == Closed) {
menuMask.reset(currentMenu);
mouse_mappings.clear();
bg.clear();
Runner::pause_output = false;
process();
}
else if (redraw) {
redraw = false;
Runner::run("all", true, true);
}
else if (retCode == Changed)
Runner::run("overlay");
else if (retCode == Switch) {
Runner::pause_output = false;
bg.clear();
redraw = true;
mouse_mappings.clear();
process();
}
}
void show(int menu, int signal) {
menuMask.set(menu);
signalToSend = signal;
process();
}
}