Merge branch 'aristocratos:main' into main

This commit is contained in:
kz6fittycent 2023-12-12 14:06:44 -06:00 committed by GitHub
commit 2973a76f2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 20430 additions and 1140 deletions

View File

@ -1,4 +1,4 @@
[*.{cpp,h,sh,md,cfg,sample}] [*.{cpp,h,hpp,sh,md,cfg,sample}]
indent_style = tab indent_style = tab
indent_size = 4 indent_size = 4

4
.github/FUNDING.yml vendored
View File

@ -3,10 +3,10 @@
github: aristocratos github: aristocratos
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: aristocratos
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] custom: https://paypal.me/aristocratos

View File

@ -0,0 +1,75 @@
name: Continuous Build FreeBSD
on:
workflow_dispatch:
push:
branches:
- main
tags-ignore:
- '*.*'
paths:
- 'src/**'
- '!src/linux/**'
- '!src/osx/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/continuous-build-freebsd.yml'
pull_request:
branches:
- main
paths:
- 'src/**'
- '!src/linux/**'
- '!src/osx/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/continuous-build-freebsd.yml'
jobs:
build-freebsd:
runs-on: ubuntu-22.04
timeout-minutes: 20
strategy:
matrix:
compiler: ["clang++", "g++"]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Compile
uses: vmactions/freebsd-vm@v1
with:
release: '14.0'
usesh: true
prepare: |
pkg install -y gmake gcc coreutils git
git config --global --add safe.directory /home/runner/work/btop/btop
run: |
CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true
GIT_HASH=$(git rev-parse --short "$GITHUB_SHA")
COMPILER=$(echo ${{ matrix.compiler }} | sed 's/clang++/llvm/' | sed 's/g++/gcc/')
mv bin/btop bin/btop-"$COMPILER"-"$GIT_HASH"
ls -alh bin
- uses: actions/upload-artifact@v3
with:
name: btop-x86_64-freebsd-14
path: 'bin/*'
if-no-files-found: error
build-freebsd-cmake:
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Compile
uses: vmactions/freebsd-vm@v1
with:
release: '14.0'
usesh: true
prepare: pkg install -y cmake git ninja
run: |
CXX=clang++ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBTOP_STATIC=ON
cmake --build build

View File

@ -13,7 +13,17 @@ on:
- '!src/freebsd/**' - '!src/freebsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build.yml' - '.github/workflows/continuous-build-linux.yml'
pull_request:
branches:
- main
paths:
- 'src/**'
- '!src/osx/**'
- '!src/freebsd/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/continuous-build-linux.yml'
jobs: jobs:
static-build: static-build:
@ -83,7 +93,9 @@ jobs:
run: git config --global --add safe.directory /__w/btop/btop run: git config --global --add safe.directory /__w/btop/btop
- name: Checkout source - name: Checkout source
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
submodules: recursive
- name: Fix - Stopping at filesystem boundary - name: Fix - Stopping at filesystem boundary
run: git init # [fix Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).] run: git init # [fix Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).]
@ -107,7 +119,7 @@ jobs:
cp bin/btop .artifacts/$FILENAME cp bin/btop .artifacts/$FILENAME
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: btop-${{ matrix.toolchain }} name: btop-${{ matrix.toolchain }}
path: '.artifacts/**' path: '.artifacts/**'

View File

@ -1,6 +1,7 @@
name: Continuous Build MacOS name: Continuous Build MacOS
on: on:
workflow_dispatch:
push: push:
branches: branches:
- main - main
@ -12,15 +13,26 @@ on:
- '!src/freebsd/**' - '!src/freebsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/*' - '.github/workflows/continuous-build-macos.yml'
pull_request:
branches:
- main
paths:
- 'src/**'
- '!src/linux/**'
- '!src/freebsd/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/continuous-build-macos.yml'
jobs: jobs:
build-osx: build-macos11:
runs-on: macos-11 runs-on: macos-11
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with:
submodules: recursive
- name: Compile - name: Compile
run: | run: |
make CXX=g++-11 ARCH=x86_64 STATIC=true STRIP=true make CXX=g++-11 ARCH=x86_64 STATIC=true STRIP=true
@ -28,7 +40,30 @@ jobs:
mv bin/btop bin/btop-x86_64-BigSur-$GIT_HASH mv bin/btop bin/btop-x86_64-BigSur-$GIT_HASH
ls -alh bin ls -alh bin
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v3
with: with:
name: btop-x86_64-macos-BigSur name: btop-x86_64-macos11-BigSur
path: 'bin/*'
build-macos12:
runs-on: macos-12
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Compile
run: |
make CXX=g++-12 ARCH=x86_64 STATIC=true STRIP=true
GIT_HASH=$(git rev-parse --short "$GITHUB_SHA")
mv bin/btop bin/btop-x86_64-Monterey-$GIT_HASH
ls -alh bin
- uses: actions/upload-artifact@v3
with:
name: btop-x86_64-macos12-Monterey
path: 'bin/*' path: 'bin/*'

32
.gitignore vendored
View File

@ -51,6 +51,34 @@ bin
btop btop
.*/ .*/
# Optional libraries
lib/rocm_smi_lib
#do not ignore .github directory # Don't ignore .github directory
!.github !.github/
# Ignore files created by Qt Creator
*.config
*.creator
*.creator.user
*.creator.user.*
*.cflags
*.cxxflags
*.files
*.includes
# CMake
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
# CLion
cmake-build-*

0
.gitmodules vendored Normal file
View File

View File

@ -1,3 +1,115 @@
## v1.3.0
* Added Gpu Support | @romner-set | PR #529
* Elementarish theme: color update according to Elementary palette | @stradicat | PR #660
* Add alternative key codes for Delete, Insert, Home, End | @ivanp7 | PR #659
* Fix scrollbar not clearing sometimes. | @DecklynKern | PR #643
* Add keybind for toggling memory display mode in PROC box | @rahulaggarwal965 | PR #623
* Minor string initialization improvement | @imwints | PR #636
* Made disks statvfs logic asynchronous. | @crestfallnatwork | PR #633
* Fix signal list on non-linux/weird linux platforms | @lvxnull | PR #630
* Add option to accumulate a child's resources in parent in tree-view | @imwints | PR #618
* Add CMake support for Linux | @imwints | PR #589
* Horizon theme | @SidVeld | PR #610
* Fix short conversion of 1000-1023 *iB | @scorpion-26 | #609
* Fix integer overflows in btop_collect.cpp | @dorrellmw | #546
* Support compiling with LLVM | @imwints | #510
* Fix getting zfs pool name with '.' char in freebsd | @jfouquart | #602
* [macos/freebsd] support gcc13 | @joske | #600
* FreeBSD swap info | @rrveex | #560
* Create adwaita.theme | @flipflop133 | #485
+ Various fixes by @imwints, @simplepad, @joske, @gwena, @cpalv, @iambeingtracked, @mattico, @NexAdn
## v1.2.13
* Makefile: VERBOSE=true flag for Makefile to display all compiler commands and fixed so already set CXXFLAGS and LDFLAGS are displayed.
* Makefile: Added autodetection for gcc12 to make compiling on macos Ventura easier.
* Changed: Reverted back to sysconf(_SC_NPROCESSORS_ONLN) for Cpu core count ant let the new dynamic update fix if cores are turned on later
* Fixed: Ignore disks that fails in statvfs64() to avoid slowdowns and possible crashes.
* Fixed: Moved up get_cpuHz() in the execution order to get better cpu clock reading.
* Added: proc tree view: if there's more than 40 width left, try to print full cmd, by @Superty
* Fixed: Show the first IP of the interface in NET box instead of the last, by @correabuscar
* Changed: Replace getnameinfo with inet_ntop [on Linux], by @correabuscar
* Fixed: Not picking up last username from /etc/passwd
* Fixed: Process nice value underflowing, issue #461
* Changed: Replace getnameinfo with inet_ntop [on FreeBSD], by @correabuscar
* Changed: Replace getnameinfo with inet_ntop [on macos], by @correabuscar
## v1.2.12
* Added: Dynamic updating of max number of CPU cores.
## v1.2.11
* Fixed: Number of cores wrongly detected for Ryzen in rare cases.
## v1.2.10
* Fixed: Process tree filtering not case insensitive
* Added: Paper theme, by @s6muel
* Fixed: Extra checks to avoid crash on trying to replace empty strings in tree mode
* Fixed: Crashing when cores are offline
* Fixed: Cpu::collect() core count counter...
* Changed: Using sysconf(_SC_NPROCESSORS_CONF) for number of cores instead of sysconf(_SC_NPROCESSORS_ONLN)
* Maintenance: Code cleanup, by @stefanos82
## v1.2.9
* Fixed: Memory values not clearing properly when not in graph mode in mem box
* Changed: kyli0x theme color update, by @kyli0x
* Added: Elementarish theme, by @dennismayr
* Added: key "?" to see help, by @mohi001
* Added: solarized_light theme, by @Fingerzam
* Changed: Made ZFS stats collection compatible with zfs_pools_only option, by @simplepad
* Changed: Rewrite of process sorting and tree generation including fixes for tree sorting and mouse support
* Added: Option to hide the small cpu graphs for processes
* Changed: Small graphs now show colors for each character
* Fixed: Getting selfpath on macos (fix for finding theme folder)
## v1.2.8 ## v1.2.8
* Added: Support for ZFS pool io stats monitoring, by @simplepad * Added: Support for ZFS pool io stats monitoring, by @simplepad

201
CMakeLists.txt Normal file
View File

@ -0,0 +1,201 @@
# SPDX-License-Identifier: Apache-2.0
#
# CMake configuration for btop
#
cmake_minimum_required(VERSION 3.20)
# Disable in-source builds since they would override the Makefile
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
message(FATAL_ERROR "In-source builds are not allowed")
endif()
project("btop"
VERSION 1.2.13
DESCRIPTION "A monitor of resources"
HOMEPAGE_URL "https://github.com/aristocratos/btop"
LANGUAGES CXX
)
# Make custom modules available
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
# When the build type is not set we can't fortify
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_COLOR_DIAGNOSTICS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Options
include(CMakeDependentOption)
option(BTOP_STATIC "Link btop statically" OFF)
option(BTOP_LTO "Enable LTO" ON)
option(BTOP_USE_MOLD "Use mold to link btop" OFF)
option(BTOP_PEDANTIC "Enable a bunch of additional warnings" OFF)
option(BTOP_WERROR "Compile with warnings as errors" OFF)
option(BTOP_GPU "Enable GPU support" ON)
cmake_dependent_option(BTOP_RSMI_STATIC "Link statically to ROCm SMI" OFF "BTOP_GPU" OFF)
if(BTOP_STATIC)
# Set this before calling find_package
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
endif()
include(CheckCXXCompilerFlag)
include(CheckIncludeFileCXX)
include(CheckIPOSupported)
check_include_file_cxx(ranges CXX_HAS_RANGES)
if(NOT CXX_HAS_RANGES)
message(FATAL_ERROR "The compiler doesn't support <ranges>")
endif()
add_executable(btop
src/btop.cpp
src/btop_config.cpp
src/btop_draw.cpp
src/btop_input.cpp
src/btop_menu.cpp
src/btop_shared.cpp
src/btop_theme.cpp
src/btop_tools.cpp
)
# NOTE: Checks can be simplified with CMake 3.25
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_sources(btop PRIVATE
src/osx/btop_collect.cpp
src/osx/sensors.cpp
src/osx/smc.cpp
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
target_sources(btop PRIVATE src/freebsd/btop_collect.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_sources(btop PRIVATE src/linux/btop_collect.cpp)
else()
message(FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not supported")
endif()
# Check for and enable LTO
check_ipo_supported(RESULT ipo_supported)
if(ipo_supported AND BTOP_LTO)
set_target_properties(btop PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON)
endif()
# TODO: enable more warnings in coordination with upstream
target_compile_options(btop PRIVATE
-Wall -Wextra -Wpedantic
-ftree-vectorize -fstack-clash-protection
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(btop PRIVATE
-Wheader-hygiene -Wgnu -Wthread-safety
)
endif()
if(BTOP_PEDANTIC)
target_compile_options(btop PRIVATE
-Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused
-Woverloaded-virtual -Wconversion -Wsign-conversion -Wdouble-promotion
-Wformat=2 -Wimplicit-fallthrough -Weffc++
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(btop PRIVATE
-Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wnull-dereference
-Wuseless-cast
)
endif()
endif()
if(BTOP_WERROR)
target_compile_options(btop PRIVATE -Werror)
endif()
check_cxx_compiler_flag(-fstack-protector CXX_HAS_FSTACK_PROTECTOR)
if(CXX_HAS_FSTACK_PROTECTOR)
target_compile_options(btop PRIVATE -fstack-protector)
endif()
check_cxx_compiler_flag(-fcf-protection CXX_HAS_FCF_PROTECTION)
if(CXX_HAS_FCF_PROTECTION)
target_compile_options(btop PRIVATE -fcf-protection)
endif()
target_compile_definitions(btop PRIVATE
_FILE_OFFSET_BITS=64
_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1
# Only has an effect with optimizations enabled
$<$<NOT:$<CONFIG:Debug>>:_FORTIFY_SOURCE=2>
)
# Enable GPU support
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND BTOP_GPU)
target_compile_definitions(btop PRIVATE GPU_SUPPORT)
if(BTOP_RSMI_STATIC)
# ROCm doesn't properly add it's folders to the module path
# if `CMAKE_MODULE_PATH` is already set
# We could also manully append ROCm's path here
set(_CMAKE_MODULE_PATH CMAKE_MODULE_PATH)
unset(CMAKE_MODULE_PATH)
# NOTE: This might be problematic in the future if other sub projects
# depend on this or if btop starts producing libraries
# Build a static ROCm library
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
add_subdirectory(lib/rocm_smi_lib EXCLUDE_FROM_ALL)
add_library(ROCm INTERFACE)
# Export ROCm's properties to a CMake target (which should've been done by ROCm :-/)
target_compile_definitions(ROCm INTERFACE RSMI_STATIC)
target_include_directories(ROCm INTERFACE lib/rocm_smi_lib/include)
target_link_libraries(ROCm INTERFACE rocm_smi64)
set(CMAKE_MODULE_PATH _CMAKE_MODULE_PATH)
target_link_libraries(btop PRIVATE ROCm)
endif()
endif()
target_include_directories(btop SYSTEM PRIVATE include)
# mold
if(BTOP_USE_MOLD)
target_link_options(btop PRIVATE -fuse-ld=mold)
endif()
if(BTOP_STATIC)
target_compile_definitions(btop PRIVATE STATIC_BUILD)
target_link_options(btop PRIVATE -static LINKER:--fatal-warnings)
endif()
# Add libraries
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(btop PRIVATE Threads::Threads)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_link_libraries(btop PRIVATE $<LINK_LIBRARY:FRAMEWORK,CoreFoundation)
target_link_libraries(btop PRIVATE $<LINK_LIBRARY:FRAMEWORK,IOKit)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
find_package(devstat REQUIRED)
find_package(kvm REQUIRED)
target_link_libraries(btop PRIVATE devstat::devstat kvm::kvm)
if(BTOP_STATIC)
find_package(elf REQUIRED)
target_link_libraries(btop PRIVATE elf::elf)
endif()
endif()
install(TARGETS btop RUNTIME)
install(FILES "btop.desktop" DESTINATION "share/applications")
install(FILES "Img/icon.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME "btop.png")
install(FILES "Img/icon.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "btop.svg")
install(DIRECTORY "themes" DESTINATION "share/btop")

211
Makefile
View File

@ -1,6 +1,6 @@
#* Btop++ makefile v1.5 #* Btop++ makefile v1.6
BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝ \033[1;3;38;5;240mMakefile v1.5\033[0m BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝ \033[1;3;38;5;240mMakefile v1.6\033[0m
override BTOP_VERSION := $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown") override BTOP_VERSION := $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown")
override TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0") override TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0")
@ -12,12 +12,12 @@ else
endif endif
ifneq ($(QUIET),true) ifneq ($(QUIET),true)
override PRE := info info-quiet
override QUIET := false override QUIET := false
else
override PRE := info-quiet
endif endif
OLDCXX := $(CXXFLAGS)
OLDLD := $(LDFLAGS)
PREFIX ?= /usr/local PREFIX ?= /usr/local
#? Detect PLATFORM and ARCH from uname/gcc if not set #? Detect PLATFORM and ARCH from uname/gcc if not set
@ -36,6 +36,70 @@ endif
override PLATFORM_LC := $(shell echo $(PLATFORM) | tr '[:upper:]' '[:lower:]') override PLATFORM_LC := $(shell echo $(PLATFORM) | tr '[:upper:]' '[:lower:]')
#? GPU Support
ifeq ($(PLATFORM_LC)$(ARCH),linuxx86_64)
ifneq ($(STATIC),true)
GPU_SUPPORT := true
endif
endif
ifneq ($(GPU_SUPPORT),true)
GPU_SUPPORT := false
endif
ifeq ($(GPU_SUPPORT),true)
override ADDFLAGS += -DGPU_SUPPORT
endif
#? Compiler and Linker
ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0)
override CXX_IS_CLANG := true
endif
override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1)
CLANG_WORKS = false
GCC_WORKS = false
#? Supported is Clang 16.0.0 and later
ifeq ($(CXX_IS_CLANG),true)
ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 16; echo $$?),0)
CLANG_WORKS := true
endif
endif
ifeq ($(CLANG_WORKS),false)
#? Try to find a newer GCC version
ifeq ($(shell command -v g++-13 >/dev/null; echo $$?),0)
CXX := g++-13
else ifeq ($(shell command -v g++13 >/dev/null; echo $$?),0)
CXX := g++13
else ifeq ($(shell command -v g++-12 >/dev/null; echo $$?),0)
CXX := g++-12
else ifeq ($(shell command -v g++12 >/dev/null; echo $$?),0)
CXX := g++12
else ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0)
CXX := g++-11
else ifeq ($(shell command -v g++11 >/dev/null; echo $$?),0)
CXX := g++11
else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0)
CXX := g++
else
GCC_NOT_FOUND := true
endif
ifndef GCC_NOT_FOUND
override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1)
ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0)
GCC_WORKS := true
endif
endif
endif
ifeq ($(CLANG_WORKS),false)
ifeq ($(GCC_WORKS),false)
$(error $(shell printf "\033[1;91mERROR: \033[97mCompiler too old. (Requires Clang 16.0.0, GCC 10.1.0)\033[0m"))
endif
endif
#? Any flags added to TESTFLAGS must not contain whitespace for the testing to work #? Any flags added to TESTFLAGS must not contain whitespace for the testing to work
override TESTFLAGS := -fexceptions -fstack-clash-protection -fcf-protection override TESTFLAGS := -fexceptions -fstack-clash-protection -fcf-protection
ifneq ($(PLATFORM) $(ARCH),macos arm64) ifneq ($(PLATFORM) $(ARCH),macos arm64)
@ -43,9 +107,17 @@ ifneq ($(PLATFORM) $(ARCH),macos arm64)
endif endif
ifeq ($(STATIC),true) ifeq ($(STATIC),true)
override ADDFLAGS += -static-libgcc -static-libstdc++ ifeq ($(CXX_IS_CLANG) $(CLANG_WORKS),true true)
ifneq ($(PLATFORM),macos) ifeq ($(shell $(CXX) -print-target-triple | grep gnu >/dev/null; echo $$?),0)
$(error $(shell printf "\033[1;91mERROR: \033[97m$(CXX) can't statically link glibc\033[0m"))
endif
else
override ADDFLAGS += -static-libgcc -static-libstdc++
endif
ifeq ($(PLATFORM_LC),linux)
override ADDFLAGS += -DSTATIC_BUILD -static -Wl,--fatal-warnings override ADDFLAGS += -DSTATIC_BUILD -static -Wl,--fatal-warnings
else ifeq ($(PLATFORM_LC),freebsd)
override ADDFLAGS += -DSTATIC_BUILD
endif endif
endif endif
@ -53,29 +125,10 @@ ifeq ($(STRIP),true)
override ADDFLAGS += -s override ADDFLAGS += -s
endif endif
#? Compiler and Linker ifeq ($(VERBOSE),true)
ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0) override VERBOSE := false
CXX := g++-11 else
else ifeq ($(shell command -v g++11 >/dev/null; echo $$?),0) override VERBOSE := true
CXX := g++11
else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0)
CXX := g++
endif
override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
#? Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10
ifeq ($(CXX),g++)
ifeq ($(shell g++ --version | grep clang >/dev/null 2>&1; echo $$?),0)
V_MAJOR := 0
else
V_MAJOR := $(shell echo $(CXX_VERSION) | cut -f1 -d".")
endif
ifneq ($(shell test $(V_MAJOR) -ge 11; echo $$?),0)
ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0)
override CXX := g++-11
override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
endif
endif
endif endif
#? Pull in platform specific source files and get thread count #? Pull in platform specific source files and get thread count
@ -87,7 +140,10 @@ else ifeq ($(PLATFORM_LC),freebsd)
PLATFORM_DIR := freebsd PLATFORM_DIR := freebsd
THREADS := $(shell getconf NPROCESSORS_ONLN 2>/dev/null || echo 1) THREADS := $(shell getconf NPROCESSORS_ONLN 2>/dev/null || echo 1)
SU_GROUP := wheel SU_GROUP := wheel
override ADDFLAGS += -lstdc++ -lm -lkvm -ldevstat -Wl,-rpath=/usr/local/lib/gcc11 override ADDFLAGS += -lm -lkvm -ldevstat -Wl,-rpath=/usr/local/lib/gcc$(CXX_VERSION_MAJOR)
ifneq ($(STATIC),true)
override ADDFLAGS += -lstdc++
endif
export MAKE = gmake export MAKE = gmake
else ifeq ($(PLATFORM_LC),macos) else ifeq ($(PLATFORM_LC),macos)
PLATFORM_DIR := osx PLATFORM_DIR := osx
@ -104,9 +160,16 @@ ifeq ($(THREADS),1)
override THREADS := auto override THREADS := auto
endif endif
#? LTO command line
ifeq ($(CLANG_WORKS),true)
LTO := thin
else
LTO := $(THREADS)
endif
#? The Directories, Source, Includes, Objects and Binary #? The Directories, Source, Includes, Objects and Binary
SRCDIR := src SRCDIR := src
INCDIR := include INCDIRS := include $(wildcard lib/**/include)
BUILDDIR := obj BUILDDIR := obj
TARGETDIR := bin TARGETDIR := bin
SRCEXT := cpp SRCEXT := cpp
@ -119,11 +182,11 @@ override GOODFLAGS := $(foreach flag,$(TESTFLAGS),$(strip $(shell echo "int main
#? Flags, Libraries and Includes #? Flags, Libraries and Includes
override REQFLAGS := -std=c++20 override REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -pedantic WARNFLAGS := -Wall -Wextra -pedantic
OPTFLAGS := -O2 -ftree-loop-vectorize -flto=$(THREADS) OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS $(GOODFLAGS) $(ADDFLAGS) LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS)
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR) INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR)
SU_USER := root SU_USER := root
ifdef DEBUG ifdef DEBUG
@ -154,24 +217,38 @@ endif
P := %% P := %%
#? Default Make ifeq ($(VERBOSE),true)
all: $(PRE) directories btop # Doesn't work with `&>`
override SUPPRESS := > /dev/null 2> /dev/null
else
override SUPPRESS :=
endif
#? Default Make
.ONESHELL:
all: | info rocm_smi info-quiet directories btop
ifneq ($(QUIET),true)
info: info:
@printf " $(BANNER)\n" @printf " $(BANNER)\n"
@printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n"
@printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n"
@printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" @printf "\033[1;95mGPU_SUPPORT \033[1;94m:| \033[0m$(GPU_SUPPORT)\n"
@printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n"
@printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n"
@printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n"
@printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n"
@printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n"
@printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m)\n" @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n"
@printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m)\n" @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n"
@printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n"
else
info:
@true
endif
info-quiet:
@sleep 0.1 2>/dev/null || true info-quiet: | info rocm_smi
@printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n" @printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n"
help: help:
@ -189,18 +266,22 @@ help:
#? Make the Directories #? Make the Directories
directories: directories:
@$(VERBOSE) || printf "mkdir -p $(TARGETDIR)\n"
@mkdir -p $(TARGETDIR) @mkdir -p $(TARGETDIR)
@$(VERBOSE) || printf "mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)\n"
@mkdir -p $(BUILDDIR)/$(PLATFORM_DIR) @mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)
#? Clean only Objects #? Clean only Objects
clean: clean:
@printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n"
@rm -rf $(BUILDDIR) @rm -rf $(BUILDDIR)
@cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true
#? Clean Objects and Binaries #? Clean Objects and Binaries
distclean: clean distclean: clean
@printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n"
@rm -rf $(TARGETDIR) @rm -rf $(TARGETDIR)
@rm -rf lib/rocm_smi_lib/build
install: install:
@printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n"
@ -246,22 +327,50 @@ uninstall:
#? Pull in dependency info for *existing* .o files #? Pull in dependency info for *existing* .o files
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
#? Compile rocm_smi
ifeq ($(GPU_SUPPORT)$(RSMI_STATIC),truetrue)
ROCM_DIR ?= lib/rocm_smi_lib
ROCM_BUILD_DIR := $(ROCM_DIR)/build
ifeq ($(DEBUG),true)
BUILD_TYPE := Debug
else
BUILD_TYPE := Release
endif
.ONESHELL:
rocm_smi:
@printf "\n\033[1;92mBuilding ROCm SMI static library\033[37m...\033[0m\n"
@TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(QUIET) || printf "\033[1;97mRunning CMake...\033[0m\n"
CXX=$(CXX) cmake -S $(ROCM_DIR) -B $(ROCM_BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DBUILD_SHARED_LIBS=OFF $(SUPPRESS) || { printf "\033[1;91mCMake failed, continuing build without statically linking ROCm SMI\033[37m...\033[0m\n"; exit 0; }
@$(QUIET) || printf "\n\033[1;97mBuilding and linking...\033[0m\n"
@cmake --build $(ROCM_BUILD_DIR) -j -t rocm_smi64 $(SUPPRESS) || { printf "\033[1;91mMake failed, continuing build without statically linking ROCm SMI\033[37m...\033[0m\n"; exit 0; }
@printf "\033[1;92m100$(P)\033[10D\033[5C-> \033[1;37m$(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a \033[1;93m(\033[1;97m$$(du -ah $(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a | cut -f1)iB\033[1;93m)\033[0m\n"
@printf "\033[1;92mROCm SMI build complete in \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n"
@$(eval override LDFLAGS += $(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a -DRSMI_STATIC) # TODO: this seems to execute every time, no matter if the compilation failed or succeeded
@$(eval override CXXFLAGS += -DRSMI_STATIC)
else
rocm_smi:
@true
endif
#? Link #? Link
.ONESHELL: .ONESHELL:
btop: $(OBJECTS) btop: $(OBJECTS) | rocm_smi directories
@sleep 0.2 2>/dev/null || true @sleep 0.2 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0") @TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(QUIET) || printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n" @$(QUIET) || printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n"
@$(VERBOSE) || printf "$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS)\n"
@$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) || exit 1 @$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) || exit 1
@printf "\033[1;92m100$(P) -> \033[1;37m$(TARGETDIR)/btop \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $(TARGETDIR)/btop | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n" @printf "\033[1;92m100$(P) -> \033[1;37m$(TARGETDIR)/btop \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $(TARGETDIR)/btop | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"
@printf "\n\033[1;92mBuild complete in \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n" @printf "\n\033[1;92mBuild complete in \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n"
#? Compile #? Compile
.ONESHELL: .ONESHELL:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories
@sleep 0.3 2>/dev/null || true @sleep 0.3 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0") @TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n" @$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n"
@$(VERBOSE) || printf "$(CXX) $(CXXFLAGS) $(INC) -MMD -c -o $@ $<\n"
@$(CXX) $(CXXFLAGS) $(INC) -MMD -c -o $@ $< || exit 1 @$(CXX) $(CXXFLAGS) $(INC) -MMD -c -o $@ $< || exit 1
@printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n" @printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"

477
README.md
View File

@ -5,7 +5,7 @@
</a> </a>
![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux)
![OSX](https://img.shields.io/badge/-OSX-black?logo=apple) ![macOS](https://img.shields.io/badge/-OSX-black?logo=apple)
![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd) ![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd)
![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow) ![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow)
![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green) ![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green)
@ -15,7 +15,8 @@
[![Coffee](https://img.shields.io/badge/-Buy%20me%20a%20Coffee-grey?logo=Ko-fi)](https://ko-fi.com/aristocratos) [![Coffee](https://img.shields.io/badge/-Buy%20me%20a%20Coffee-grey?logo=Ko-fi)](https://ko-fi.com/aristocratos)
[![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop)
[![Continuous Build Linux](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml) [![Continuous Build Linux](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml)
[![Continuous Build MacOS](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml) [![Continuous Build macOS](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml)
[![Continuous Build FreeBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml)
## Index ## Index
@ -28,28 +29,55 @@
* [Prerequisites](#prerequisites) (Read this if you are having issues!) * [Prerequisites](#prerequisites) (Read this if you are having issues!)
* [Screenshots](#screenshots) * [Screenshots](#screenshots)
* [Keybindings](#help-menu) * [Keybindings](#help-menu)
* [Installation Linux/OSX](#installation) * [Installation Linux/macOS](#installation)
* [Compilation Linux](#compilation-linux) * [Compilation Linux](#compilation-linux)
* [Compilation OSX](#compilation-osx) * [Compilation macOS](#compilation-macos-osx)
* [Compilation FreeBSD](#compilation-freebsd) * [Compilation FreeBSD](#compilation-freebsd)
* [GPU compatibility](#gpu-compatibility)
* [Installing the snap](#installing-the-snap) * [Installing the snap](#installing-the-snap)
* [Configurability](#configurability) * [Configurability](#configurability)
* [License](#license) * [License](#license)
## News ## News
##### 25 November 2023
GPU monitoring added for Linux!
Compile from git main to try it out.
Use keys `5`, `6`, `7` and `0` to show/hide the gpu monitoring boxes. `5` = Gpu 1, `6` = Gpu 2, etc.
Gpu stats/graphs can also be displayed in the "Cpu box" (not as verbose), see the cpu options menu for info and configuration.
Note that the binaries provided on the release page (when released) and the continuous builds will not have gpu support enabled.
Because the GPU support relies on loading of dynamic gpu libraries, gpu support will not work when also static linking.
See [Compilation Linux](#compilation-linux) for more info on how to compile with gpu monitoring support.
Many thanks to [@romner-set](https://github.com/romner-set) who wrote the vast majority of the implementation for GPU support.
Big update with version bump to 1.3 coming soon.
##### 28 August 2022
[![btop4win](https://github.com/aristocratos/btop4win/raw/master/Img/logo.png)](https://github.com/aristocratos/btop4win)
First release of btop4win available at https://github.com/aristocratos/btop4win
##### 16 January 2022 ##### 16 January 2022
Release v1.2.0 with FreeBSD support. No release binaries for FreeBSD provided as of yet. Release v1.2.0 with FreeBSD support. No release binaries for FreeBSD provided as of yet.
Again a big thanks to [@joske](https://github.com/joske) for his porting efforts! Again a big thanks to [@joske](https://github.com/joske) for his porting efforts!
Since compatibility with Linux, MacOS and FreeBSD are done, the focus going forward will be on new features like GPU monitoring. Since compatibility with Linux, macOS and FreeBSD are done, the focus going forward will be on new features like GPU monitoring.
##### 13 November 2021 ##### 13 November 2021
Release v1.1.0 with OSX support. Binaries in [continuous-build-macos](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml) are only x86 for now. Release v1.1.0 with macOS support. Binaries in [continuous-build-macos](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml) are only x86 for now.
Macos binaries + installer are included for both x86 and ARM64 (Apple Silicon) in the releases. macOS binaries + installer are included for both x86 and ARM64 (Apple Silicon) in the releases.
Big thank you to [@joske](https://github.com/joske) who wrote the vast majority of the implementation! Big thank you to [@joske](https://github.com/joske) who wrote the vast majority of the implementation!
@ -58,12 +86,12 @@ Big thank you to [@joske](https://github.com/joske) who wrote the vast majority
##### 30 October 2021 ##### 30 October 2021
Work on the OSX and FreeBSD branches, both initiated and mostly worked on by [@joske](https://github.com/joske), will likely be completed in the coming weeks. Work on the OSX [macOS] and FreeBSD branches, both initiated and mostly worked on by [@joske](https://github.com/joske), will likely be completed in the coming weeks.
The OSX branch has some memory leaks that needs to be sorted out and both have some issues with the processes cpu usage calculation and other smaller issues that needs fixing. The OSX [macOS] branch has some memory leaks that needs to be sorted out and both have some issues with the processes cpu usage calculation and other smaller issues that needs fixing.
If you want to help out, test for bugs/fix bugs or just try out the branches: If you want to help out, test for bugs/fix bugs or just try out the branches:
**OSX** **macOS / OSX**
```bash ```bash
# Install and use Homebrew or MacPorts package managers for easy dependency installation # Install and use Homebrew or MacPorts package managers for easy dependency installation
brew install coreutils make gcc@11 brew install coreutils make gcc@11
@ -82,12 +110,12 @@ git checkout freebsd
gmake gmake
``` ```
Note that GNU make (`gmake`) is recommended but not required for OSX but it is required on FreeBSD. Note that GNU make (`gmake`) is recommended but not required for macOS/OSX but it is required on FreeBSD.
##### 6 October 2021 ##### 6 October 2021
OsX development have been started by [@joske](https://github.com/joske), big thanks :) macOS development have been started by [@joske](https://github.com/joske), big thanks :)
See branch [OSX](https://github.com/aristocratos/btop/tree/OSX) for current progress. See branch [OSX](https://github.com/aristocratos/btop/tree/OSX) for current progress.
##### 18 September 2021 ##### 18 September 2021
@ -102,7 +130,7 @@ Please report any bugs to the [Issues](https://github.com/aristocratos/btop/issu
The development plan right now: The development plan right now:
* 1.1.0 Mac OsX support * 1.1.0 macOS [OSX] support
* 1.2.0 FreeBSD support * 1.2.0 FreeBSD support
* 1.3.0 Support for GPU monitoring * 1.3.0 Support for GPU monitoring
* 1.X.0 Other platforms and features... * 1.X.0 Other platforms and features...
@ -112,7 +140,7 @@ Windows support is not in the plans as of now, but if anyone else wants to take
##### 5 May 2021 ##### 5 May 2021
This project is gonna take some time until it has complete feature parity with bpytop, since all system information gathering will have to be written from scratch without any external libraries. This project is gonna take some time until it has complete feature parity with bpytop, since all system information gathering will have to be written from scratch without any external libraries.
And will need some help in the form of code contributions to get complete support for BSD and OSX. And will need some help in the form of code contributions to get complete support for BSD and macOS/OSX.
</details> </details>
@ -142,9 +170,9 @@ C++ version and continuation of [bashtop](https://github.com/aristocratos/bashto
* Send any signal to selected process. * Send any signal to selected process.
* UI menu for changing all config file options. * UI menu for changing all config file options.
* Auto scaling graph for network usage. * Auto scaling graph for network usage.
* Shows IO activity and speeds for disks * Shows IO activity and speeds for disks.
* Battery meter * Battery meter
* Selectable symbols for the graphs * Selectable symbols for the graphs.
* Custom presets * Custom presets
* And more... * And more...
@ -152,7 +180,7 @@ C++ version and continuation of [bashtop](https://github.com/aristocratos/bashto
Btop++ uses the same theme files as bpytop and bashtop (some color values missing in bashtop themes) . Btop++ uses the same theme files as bpytop and bashtop (some color values missing in bashtop themes) .
See [themes](https://github.com/aristocratos/btop/tree/master/themes) folder for available themes. See [themes](https://github.com/aristocratos/btop/tree/main/themes) folder for available themes.
The `make install` command places the default themes in `[$PREFIX or /usr/local]/share/btop/themes`. The `make install` command places the default themes in `[$PREFIX or /usr/local]/share/btop/themes`.
User created themes should be placed in `$XDG_CONFIG_HOME/btop/themes` or `$HOME/.config/btop/themes`. User created themes should be placed in `$XDG_CONFIG_HOME/btop/themes` or `$HOME/.config/btop/themes`.
@ -234,7 +262,7 @@ Also needs a UTF8 locale and a font that covers:
* **Run install.sh or:** * **Run install.sh or:**
``` bash ```bash
# use "make install PREFIX=/target/dir" to set target, default: /usr/local # use "make install PREFIX=/target/dir" to set target, default: /usr/local
# only use "sudo" when installing to a NON user owned directory # only use "sudo" when installing to a NON user owned directory
sudo make install sudo make install
@ -246,7 +274,7 @@ Also needs a UTF8 locale and a font that covers:
* **Run setuid.sh or:** * **Run setuid.sh or:**
``` bash ```bash
# run after make install and use same PREFIX if any was used at install # run after make install and use same PREFIX if any was used at install
# set SU_USER and SU_GROUP to select user and group, default is root:root # set SU_USER and SU_GROUP to select user and group, default is root:root
sudo make setuid sudo make setuid
@ -256,7 +284,7 @@ Also needs a UTF8 locale and a font that covers:
* **Run uninstall.sh or:** * **Run uninstall.sh or:**
``` bash ```bash
sudo make uninstall sudo make uninstall
``` ```
@ -274,6 +302,19 @@ Also needs a UTF8 locale and a font that covers:
sudo zypper in btop sudo zypper in btop
``` ```
* For all other versions, see [openSUSE Software: btop](https://software.opensuse.org/package/btop) * For all other versions, see [openSUSE Software: btop](https://software.opensuse.org/package/btop)
* **Fedora**
```bash
sudo dnf install btop
```
* **RHEL/AlmaLinux 8+**
```bash
sudo dnf install epel-release
sudo dnf install btop
```
* **FreeBSD**
```sh
pkg install btop
```
**Binary release on Homebrew (macOS (x86_64 & ARM64) / Linux (x86_64))** **Binary release on Homebrew (macOS (x86_64 & ARM64) / Linux (x86_64))**
@ -285,61 +326,98 @@ Also needs a UTF8 locale and a font that covers:
## Compilation Linux ## Compilation Linux
Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). Needs GCC 10 / Clang 16 (or higher).
The makefile also needs GNU coreutils and `sed` (should already be installed on any modern distribution). The makefile also needs GNU coreutils and `sed` (should already be installed on any modern distribution).
For a `cmake` based build alternative see the [fork](https://github.com/jan-guenter/btop/tree/main) by @jan-guenter ### GPU compatibility
1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** Btop++ supports NVIDIA and AMD GPUs out of the box on Linux x86_64, provided you have the correct drivers and libraries.
Use gcc-10 g++-10 if gcc-11 isn't available Compatibility with Intel GPUs using generic DRM calls is planned, as is compatibility for FreeBSD and macOS.
``` bash Gpu support will not work when static linking glibc (or musl, etc.)!
sudo apt install coreutils sed git build-essential gcc-11 g++-11
For x86_64 Linux the flag `GPU_SUPPORT` is automatically set to `true`, to manually disable gpu support set the flag to false, like:
`make GPU_SUPPORT=false` (or `cmake -DBTOP_GPU=false` with CMake)
* **NVIDIA**
You must use an official NVIDIA driver, both the closed-source and [open-source](https://github.com/NVIDIA/open-gpu-kernel-modules) ones have been verified to work.
In addition to that you must also have the `nvidia-ml` dynamic library installed, which should be included with the driver package of your distribution.
* **AMD**
AMDGPU data is queried using the [ROCm SMI](https://github.com/RadeonOpenCompute/rocm_smi_lib) library, which may or may not be packaged for your distribution. If your distribution doesn't provide a package, btop++ is statically linked to ROCm SMI with the `RSMI_STATIC=true` make flag.
This flag expects the ROCm SMI source code in `lib/rocm_smi_lib`, and compilation will fail if it's not there. The latest tested version is 5.6.x, which can be obtained with the following command:
```bash
git clone https://github.com/RadeonOpenCompute/rocm_smi_lib.git --depth 1 -b rocm-5.6.x lib/rocm_smi_lib
``` ```
<details>
<summary>
### With Make
</summary>
1. **Install dependencies (example for Ubuntu 21.04 Hirsute)**
```bash
sudo apt install coreutils sed git build-essential gcc-11 g++-11
```
2. **Clone repository** 2. **Clone repository**
``` bash ```bash
git clone https://github.com/aristocratos/btop.git git clone https://github.com/aristocratos/btop.git
cd btop cd btop
``` ```
3. **Compile** 3. **Compile**
Append `STATIC=true` to `make` command for static compilation. ```bash
Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc.
Append `QUIET=true` for less verbose output.
Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag).
Append `ARCH=<architecture>` to manually set the target architecture.
If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system.
Use `ADDFLAGS` variable for appending flags to both compiler and linker.
For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
If `g++` is linked to an older version of gcc on your system specify the correct version by appending `CXX=g++-10` or `CXX=g++-11`.
``` bash
make make
``` ```
4. **Install** Options for make:
| Flag | Description |
|---------------------------------|-------------------------------------------------------------------------|
| `VERBOSE=true` | To display full compiler/linker commands |
| `STATIC=true` | For static compilation |
| `QUIET=true` | For less verbose output |
| `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) |
| `ARCH=<architecture>` | To manually set the target architecture |
| `GPU_SUPPORT=<true\|false>` | Enable/disable GPU support (Enabled by default on X86_64 Linux) |
| `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manualy set which compiler to use |
Example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc.
4. **Install**
```bash
sudo make install
```
Append `PREFIX=/target/dir` to set target, default: `/usr/local` Append `PREFIX=/target/dir` to set target, default: `/usr/local`
Notice! Only use "sudo" when installing to a NON user owned directory. Notice! Only use "sudo" when installing to a NON user owned directory.
``` bash 5. **(Optional) Set suid bit to make btop always run as root (or other user)**
sudo make install
```
5. **(Optional) Set suid bit to make btop always run as root (or other user)** ```bash
sudo make setuid
```
No need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems. No need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems.
@ -347,13 +425,9 @@ Also needs a UTF8 locale and a font that covers:
Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `root` Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `root`
``` bash
sudo make setuid
```
* **Uninstall** * **Uninstall**
``` bash ```bash
sudo make uninstall sudo make uninstall
``` ```
@ -375,57 +449,138 @@ Also needs a UTF8 locale and a font that covers:
make help make help
``` ```
## Compilation OSX </details>
<details>
<summary>
### With CMake (Community maintained)
</summary>
1. **Install build dependencies**
Requires Clang / GCC, CMake, Ninja and Git
For example, with Debian Bookworm:
```bash
sudo apt install cmake git g++ ninja-build
```
2. **Clone the repository**
```bash
git clone https://github.com/aristocratos/btop.git && cd btop
``````
3. **Compile**
```bash
# Configure
cmake -B build -G Ninja
# Build
cmake --build build
```
This will automatically build a release version of btop.
Some useful options to pass to the configure step:
| Configure flag | Description |
|---------------------------------|-------------------------------------------------------------------------|
| `-DBTOP_STATIC=<ON\|OFF>` | Enables static linking (OFF by default) |
| `-DBTOP_LTO=<ON\|OFF>` | Enables link time optimization (ON by default) |
| `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) |
| `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) |
| `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) |
| `-DBTOP_GPU=<ON\|OFF>` | Enable GPU support (ON by default) |
| `-DBTOP_RSMI_STATIC=<ON\|OFF>` | Build and link the ROCm SMI library statically (OFF by default) |
| `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) |
To force a compiler, run `CXX=<compiler> cmake -B build -G Ninja`
4. **Install**
```bash
cmake --install build
```
May require root privileges
5. **Uninstall**
CMake doesn't generate an uninstall target by default. To remove installed files, run
```
cat build/install_manifest.txt | xargs rm -irv
```
6. **Cleanup build directory**
```bash
cmake --build build -t clean
```
</details>
## Compilation macOS OSX
Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary).
GCC 12 needed for macOS Ventura. If you get linker errors on Ventura you'll need to upgrade your command line tools (Version 14.0) is bugged.
The makefile also needs GNU coreutils and `sed`. The makefile also needs GNU coreutils and `sed`.
Install and use Homebrew or MacPorts package managers for easy dependency installation Install and use Homebrew or MacPorts package managers for easy dependency installation
1. **Install dependencies (example for Homebrew)** 1. **Install dependencies (example for Homebrew)**
``` bash ```bash
brew install coreutils make gcc@11 brew install coreutils make gcc@12
``` ```
2. **Clone repository** 2. **Clone repository**
``` bash ```bash
git clone https://github.com/aristocratos/btop.git git clone https://github.com/aristocratos/btop.git
cd btop cd btop
``` ```
3. **Compile** 3. **Compile**
Append `STATIC=true` to `make` command for static compilation (only libgcc and libstdc++ will be static!). ```bash
Append `QUIET=true` for less verbose output.
Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag).
Append `ARCH=<architecture>` to manually set the target architecture.
If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system.
Use `ADDFLAGS` variable for appending flags to both compiler and linker.
For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
``` bash
gmake gmake
``` ```
4. **Install** Options for make:
| Flag | Description |
|---------------------------------|-------------------------------------------------------------------------|
| `VERBOSE=true` | To display full compiler/linker commands |
| `STATIC=true` | For static compilation (only libgcc and libstdc++) |
| `QUIET=true` | For less verbose output |
| `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) |
| `ARCH=<architecture>` | To manually set the target architecture |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manualy set which compiler to use |
Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
4. **Install**
```bash
sudo gmake install
```
Append `PREFIX=/target/dir` to set target, default: `/usr/local` Append `PREFIX=/target/dir` to set target, default: `/usr/local`
Notice! Only use "sudo" when installing to a NON user owned directory. Notice! Only use "sudo" when installing to a NON user owned directory.
``` bash 5. **(Recommended) Set suid bit to make btop always run as root (or other user)**
sudo gmake install
```
5. **(Recommended) Set suid bit to make btop always run as root (or other user)** ```bash
sudo gmake setuid
```
No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. No need for `sudo` to see information for non user owned processes and to enable signal sending to any process.
@ -433,13 +588,9 @@ Also needs a UTF8 locale and a font that covers:
Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel` Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel`
``` bash
sudo gmake setuid
```
* **Uninstall** * **Uninstall**
``` bash ```bash
sudo gmake uninstall sudo gmake uninstall
``` ```
@ -467,63 +618,72 @@ Also needs a UTF8 locale and a font that covers:
Note that GNU make (`gmake`) is required to compile on FreeBSD. Note that GNU make (`gmake`) is required to compile on FreeBSD.
<details>
<summary>
### With gmake
</summary>
1. **Install dependencies** 1. **Install dependencies**
``` bash ```bash
sudo pkg install gmake gcc11 coreutils git sudo pkg install gmake gcc11 coreutils git
``` ```
2. **Clone repository** 2. **Clone repository**
``` bash ```bash
git clone https://github.com/aristocratos/btop.git git clone https://github.com/aristocratos/btop.git
cd btop cd btop
``` ```
3. **Compile** 3. **Compile**
Append `STATIC=true` to `make` command for static compilation. ```bash
Append `QUIET=true` for less verbose output.
Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag).
Append `ARCH=<architecture>` to manually set the target architecture.
If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system.
Use `ADDFLAGS` variable for appending flags to both compiler and linker.
For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
``` bash
gmake gmake
``` ```
Options for make:
| Flag | Description |
|---------------------------------|-------------------------------------------------------------------------|
| `VERBOSE=true` | To display full compiler/linker commands |
| `STATIC=true` | For static compilation (only libgcc and libstdc++) |
| `QUIET=true` | For less verbose output |
| `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) |
| `ARCH=<architecture>` | To manually set the target architecture |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manualy set which compiler to use |
Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
4. **Install** 4. **Install**
```bash
sudo gmake install
```
Append `PREFIX=/target/dir` to set target, default: `/usr/local` Append `PREFIX=/target/dir` to set target, default: `/usr/local`
Notice! Only use "sudo" when installing to a NON user owned directory. Notice! Only use "sudo" when installing to a NON user owned directory.
``` bash
sudo gmake install
```
5. **(Recommended) Set suid bit to make btop always run as root (or other user)** 5. **(Recommended) Set suid bit to make btop always run as root (or other user)**
```bash
sudo gmake setuid
```
No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. No need for `sudo` to see information for non user owned processes and to enable signal sending to any process.
Run after make install and use same PREFIX if any was used at install. Run after make install and use same PREFIX if any was used at install.
Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel` Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel`
``` bash
sudo gmake setuid
```
* **Uninstall** * **Uninstall**
``` bash ```bash
sudo gmake uninstall sudo gmake uninstall
``` ```
@ -545,6 +705,96 @@ Also needs a UTF8 locale and a font that covers:
gmake help gmake help
``` ```
</details>
<details>
<summary>
### With CMake (Community maintained)
</summary>
1. **Install build dependencies**
Requires Clang / GCC, CMake, Ninja and Git
_**Note:** LLVM's libc++ shipped with FreeBSD 13 is too old and cannot compile btop._
FreeBSD 14 and later:
```bash
pkg install cmake ninja
```
FreeBSD 13:
```bash
pkg install cmake gcc13 ninja
```
2. **Clone the repository**
```bash
git clone https://github.com/aristocratos/btop.git && cd btop
``````
3. **Compile**
FreeBSD 14 and later:
```bash
# Configure
cmake -B build -G Ninja
# Build
cmake --build build
```
FreeBSD 13:
```bash
# Configure
CXX=g++13 cmake -B build -G Ninja
# Build
cmake --build build
```
This will automatically build a release version of btop.
Some useful options to pass to the configure step:
| Configure flag | Description |
|---------------------------------|-------------------------------------------------------------------------|
| `-DBTOP_STATIC=<ON\|OFF>` | Enables static linking (OFF by default) |
| `-DBTOP_LTO=<ON\|OFF>` | Enables link time optimization (ON by default) |
| `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) |
| `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) |
| `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) |
| `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) |
_**Note:** Static linking does not work with GCC._
To force a compiler, run `CXX=<compiler> cmake -B build -G Ninja`
4. **Install**
```bash
cmake --install build
```
May require root privileges
5. **Uninstall**
CMake doesn't generate an uninstall target by default. To remove installed files, run
```
cat build/install_manifest.txt | xargs rm -irv
```
6. **Cleanup build directory**
```bash
cmake --build build -t clean
```
</details>
## Installing the snap ## Installing the snap
[![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop)
@ -568,7 +818,7 @@ Also needs a UTF8 locale and a font that covers:
```bash ```bash
sudo snap connect btop:removable-media sudo snap connect btop:removable-media
or or
sudo snap connect btop-desktop:removable-media sudo snap connect btop-desktop:removable-media
``` ```
@ -599,7 +849,7 @@ force_tty = False
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 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. #* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
#* Use withespace " " as separator between different presets. #* Use whitespace " " as separator between different presets.
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty" #* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty" presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
@ -629,7 +879,7 @@ graph_symbol_net = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". # Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_proc = "default" graph_symbol_proc = "default"
#* Manually set which boxes to show. Available values are "cpu mem net proc", separate values with whitespace. #* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
shown_boxes = "proc cpu mem net" shown_boxes = "proc cpu mem net"
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs. #* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
@ -727,6 +977,9 @@ mem_graphs = True
#* Show mem box below net box instead of above. #* Show mem box below net box instead of above.
mem_below_net = False mem_below_net = False
#* Count ZFS ARC in cached and available memory.
zfs_arc_cached = True
#* If swap memory should be shown in memory box. #* If swap memory should be shown in memory box.
show_swap = True show_swap = True

View File

@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
#
# Find devstat, the Device Statistics Library
#
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
find_path(devstat_INCLUDE_DIR NAMES devstat.h)
find_library(devstat_LIBRARY NAMES devstat)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(devstat REQUIRED_VARS devstat_LIBRARY devstat_INCLUDE_DIR)
if(devstat_FOUND AND NOT TARGET devstat::devstat)
add_library(devstat::devstat UNKNOWN IMPORTED)
set_target_properties(devstat::devstat PROPERTIES
IMPORTED_LOCATION "${devstat_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${devstat_INCLUDE_DIR}"
)
endif()
mark_as_advanced(devstat_INCLUDE_DIR devstat_LIBRARY)
endif()

View File

@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
#
# Find libelf, the ELF Access Library
#
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
find_path(elf_INCLUDE_DIR NAMES libelf.h)
find_library(elf_LIBRARY NAMES elf)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(elf REQUIRED_VARS elf_LIBRARY elf_INCLUDE_DIR)
if(elf_FOUND AND NOT TARGET elf::elf)
add_library(elf::elf UNKNOWN IMPORTED)
set_target_properties(elf::elf PROPERTIES
IMPORTED_LOCATION "${elf_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${elf_INCLUDE_DIR}"
)
endif()
mark_as_advanced(elf_INCLUDE_DIR elf_LIBRARY)
endif()

View File

@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
#
# Find libkvm, the Kernel Data Access Library
#
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
find_path(kvm_INCLUDE_DIR NAMES kvm.h)
find_library(kvm_LIBRARY NAMES kvm)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(kvm REQUIRED_VARS kvm_LIBRARY kvm_INCLUDE_DIR)
if(kvm_FOUND AND NOT TARGET kvm::kvm)
add_library(kvm::kvm UNKNOWN IMPORTED)
set_target_properties(kvm::kvm PROPERTIES
IMPORTED_LOCATION "${kvm_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${kvm_INCLUDE_DIR}"
)
endif()
mark_as_advanced(kvm_INCLUDE_DIR kvm_LIBRARY)
endif()

27
include/fmt/LICENSE.rst Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.

234
include/fmt/args.h Normal file
View File

@ -0,0 +1,234 @@
// Formatting library for C++ - dynamic format arguments
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_ARGS_H_
#define FMT_ARGS_H_
#include <functional> // std::reference_wrapper
#include <memory> // std::unique_ptr
#include <vector>
#include "core.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> const T& unwrap(const T& v) { return v; }
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
return static_cast<const T&>(v);
}
class dynamic_arg_list {
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So storage_node_base is made a fake template.
template <typename = void> struct node {
virtual ~node() = default;
std::unique_ptr<node<>> next;
};
template <typename T> struct typed_node : node<> {
T value;
template <typename Arg>
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
template <typename Char>
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};
std::unique_ptr<node<>> head_;
public:
template <typename T, typename Arg> const T& push(const Arg& arg) {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value;
}
};
} // namespace detail
/**
\rst
A dynamic version of `fmt::format_arg_store`.
It's equipped with a storage to potentially temporary objects which lifetimes
could be shorter than the format arguments object.
It can be implicitly converted into `~fmt::basic_format_args` for passing
into type-erased formatting functions such as `~fmt::vformat`.
\endrst
*/
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
std::is_same<T, basic_string_view<char_type>>::value ||
std::is_same<T, detail::std_string_view<char_type>>::value ||
(mapped_type != detail::type::cstring_type &&
mapped_type != detail::type::string_type &&
mapped_type != detail::type::custom_type))
};
};
template <typename T>
using stored_type = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_;
std::vector<detail::named_arg_info<char_type>> named_info_;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
detail::dynamic_arg_list dynamic_args_;
friend class basic_format_args<Context>;
unsigned long long get_types() const {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
const basic_format_arg<Context>* data() const {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg));
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
constexpr dynamic_format_arg_store() = default;
/**
\rst
Adds an argument into the dynamic store for later passing to a formatting
function.
Note that custom types and string types (but not string views) are copied
into the store dynamically allocating memory if necessary.
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmt::vformat("{} and {} and {}", store);
\endrst
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
/**
\rst
Adds a reference to the argument into the dynamic store for later passing to
a formatting function.
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
char band[] = "Rolling Stones";
store.push_back(std::cref(band));
band[9] = 'c'; // Changing str affects the output.
std::string result = fmt::vformat("{}", store);
// result == "Rolling Scones"
\endrst
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert(
need_copy<T>::value,
"objects of built-in types and string views are always copied");
emplace_arg(arg.get());
}
/**
Adds named argument into the dynamic store for later passing to a formatting
function. ``std::reference_wrapper`` is supported to avoid copying of the
argument. The name is always copied into the store.
*/
template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
}
/** Erase all elements from the store */
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list();
}
/**
\rst
Reserves space to store at least *new_cap* arguments including
*new_cap_named* named arguments.
\endrst
*/
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
};
FMT_END_NAMESPACE
#endif // FMT_ARGS_H_

2268
include/fmt/chrono.h Normal file

File diff suppressed because it is too large Load Diff

633
include/fmt/color.h Normal file
View File

@ -0,0 +1,633 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COLOR_H_
#define FMT_COLOR_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
};
enum class emphasis : uint8_t {
bold = 1,
faint = 1 << 1,
italic = 1 << 2,
underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
};
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
FMT_BEGIN_DETAIL_NAMESPACE
// color is a struct of either a rgb color or a terminal color.
struct color_type {
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
}
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
: is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
FMT_END_DETAIL_NAMESPACE
/** A text style consisting of foreground and background colors and emphasis. */
class text_style {
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), set_background_color(), ems(em) {}
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
const text_style& rhs) {
return lhs |= rhs;
}
FMT_CONSTEXPR bool has_foreground() const noexcept {
return set_foreground_color;
}
FMT_CONSTEXPR bool has_background() const noexcept {
return set_background_color;
}
FMT_CONSTEXPR bool has_emphasis() const noexcept {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR detail::color_type get_foreground() const noexcept {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR detail::color_type get_background() const noexcept {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
FMT_CONSTEXPR emphasis get_emphasis() const noexcept {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems;
}
private:
FMT_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept;
friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
/** Creates a text style from the foreground (text) color. */
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept {
return text_style(true, foreground);
}
/** Creates a text style from the background color. */
FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept {
return text_style(false, background);
}
FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
return text_style(lhs) | rhs;
}
FMT_BEGIN_DETAIL_NAMESPACE
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {};
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0;
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR const Char* begin() const noexcept { return buffer; }
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept {
return buffer + std::char_traits<Char>::length(buffer);
}
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
};
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
detail::color_type foreground) noexcept {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
detail::color_type background) noexcept {
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
return ansi_color_escape<Char>(em);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename T> struct styled_arg {
const T& value;
text_style style;
};
template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf);
}
FMT_END_DETAIL_NAMESPACE
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
format_args args) {
// Legacy wide streams are not supported.
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
if (detail::is_utf8()) {
detail::print(f, string_view(buf.begin(), buf.size()));
return;
}
buf.push_back('\0');
int result = std::fputs(buf.data(), f);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
/**
\rst
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
vprint(f, ts, format_str,
fmt::make_format_args<buffer_context<char_t<S>>>(args...));
}
/**
\rst
Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting.
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(const text_style& ts, const S& format_str, const Args&... args) {
return print(stdout, ts, format_str, args...);
}
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const text_style& ts, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, detail::to_string_view(format_str), args);
return fmt::to_string(buf);
}
/**
\rst
Formats arguments and returns the result as a string using ANSI
escape sequences to specify text formatting.
**Example**::
#include <fmt/color.h>
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
"The answer is {}", 42);
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return fmt::vformat(ts, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf, out);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<char_t<S>>>(args...));
}
template <typename T, typename Char>
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
template <typename FormatContext>
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = std::copy(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = std::copy(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = std::copy(reset_color.begin(), reset_color.end(), out);
}
return out;
}
};
/**
\rst
Returns an argument that will be formatted using ANSI escape sequences,
to be used in a formatting function.
**Example**::
fmt::print("Elapsed time: {0:.2f} seconds",
fmt::styled(1.23, fmt::fg(fmt::color::green) |
fmt::bg(fmt::color::blue)));
\endrst
*/
template <typename T>
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
-> detail::styled_arg<remove_cvref_t<T>> {
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

605
include/fmt/compile.h Normal file
View File

@ -0,0 +1,605 @@
// Formatting library for C++ - experimental format string compilation
//
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename InputIt>
FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
counting_iterator it) {
return it + (end - begin);
}
template <typename OutputIt> class truncating_iterator_base {
protected:
OutputIt out_;
size_t limit_;
size_t count_ = 0;
truncating_iterator_base() : out_(), limit_(0) {}
truncating_iterator_base(OutputIt out, size_t limit)
: out_(out), limit_(limit) {}
public:
using iterator_category = std::output_iterator_tag;
using value_type = typename std::iterator_traits<OutputIt>::value_type;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
FMT_UNCHECKED_ITERATOR(truncating_iterator_base);
OutputIt base() const { return out_; }
size_t count() const { return count_; }
};
// An output iterator that truncates the output and counts the number of objects
// written to it.
template <typename OutputIt,
typename Enable = typename std::is_void<
typename std::iterator_traits<OutputIt>::value_type>::type>
class truncating_iterator;
template <typename OutputIt>
class truncating_iterator<OutputIt, std::false_type>
: public truncating_iterator_base<OutputIt> {
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
public:
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
truncating_iterator& operator++() {
if (this->count_++ < this->limit_) ++this->out_;
return *this;
}
truncating_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
value_type& operator*() const {
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
}
};
template <typename OutputIt>
class truncating_iterator<OutputIt, std::true_type>
: public truncating_iterator_base<OutputIt> {
public:
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
template <typename T> truncating_iterator& operator=(T val) {
if (this->count_++ < this->limit_) *this->out_++ = val;
return *this;
}
truncating_iterator& operator++() { return *this; }
truncating_iterator& operator++(int) { return *this; }
truncating_iterator& operator*() { return *this; }
};
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/**
\rst
Converts a string literal *s* into a format string that will be parsed at
compile time and converted into efficient formatting code. Requires C++17
``constexpr if`` compiler support.
**Example**::
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst
*/
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) \
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail>
const T& first(const T& value, const Tail&...) {
return value;
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
else
return detail::get<N - 1>(rest...);
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<Args...>(name);
}
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
};
template <int N, typename T>
using get_type = typename get_type_impl<N, T>::type;
template <typename T> struct is_compiled_format : std::false_type {};
template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
}
};
template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
size_t size) {
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
return write<Char>(out, get_arg_checked<T, N>(args...));
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
return true;
}
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
FMT_THROW(format_error("argument with specified name is not found"));
}
return out;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
formatter<T, Char> fmt;
template <typename OutputIt, typename... Args>
constexpr FMT_INLINE OutputIt format(OutputIt out,
const Args&... args) const {
const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
};
template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
return pos;
}
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) {
if constexpr (POS !=
basic_string_view<typename S::char_type>(format_str).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
return tail;
else
return make_concat(head, tail);
} else {
return head;
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
}
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S format_str) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
format_str);
} else if constexpr (c != ':') {
FMT_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
if constexpr (result.end >= str.size() || str[result.end] != '}') {
FMT_THROW(format_error("expected '}'"));
return 0;
} else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
format_str);
}
}
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing");
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(
format_str);
} else {
constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
format_str);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
if constexpr (arg_index >= 0) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str);
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
}
}
} else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
}
}
template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
constexpr auto compile(S format_str) {
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
format_str);
return result;
}
}
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
const auto& first = detail::first(args...);
if constexpr (detail::is_named_arg<
remove_cvref_t<decltype(first)>>::value) {
return fmt::to_string(first.value);
} else {
return fmt::to_string(first);
}
}
}
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmt::format(compiled, std::forward<Args>(args)...);
}
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmt::format_to(out, compiled, std::forward<Args>(args)...);
}
}
#endif
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const S& format_str, Args&&... args) {
auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n),
format_str, std::forward<Args>(args)...);
return {it.base(), it.count()};
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
const Args&... args) {
return fmt::format_to(detail::counting_iterator(), format_str, args...)
.count();
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& format_str, const Args&... args) {
memory_buffer buffer;
fmt::format_to(std::back_inserter(buffer), format_str, args...);
detail::print(f, {buffer.data(), buffer.size()});
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& format_str, const Args&... args) {
print(stdout, format_str, args...);
}
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
}
} // namespace literals
#endif
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_

2905
include/fmt/core.h Normal file

File diff suppressed because it is too large Load Diff

1662
include/fmt/format-inl.h Normal file

File diff suppressed because it is too large Load Diff

4731
include/fmt/format.h Normal file

File diff suppressed because it is too large Load Diff

451
include/fmt/os.h Normal file
View File

@ -0,0 +1,451 @@
// Formatting library for C++ - optional OS-specific functionality
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_OS_H_
#define FMT_OS_H_
#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <system_error> // std::system_error
#if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
#endif
#include "format.h"
#ifndef FMT_USE_FCNTL
// UWP doesn't provide _pipe.
# if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
# endif
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
# else
# define FMT_USE_FCNTL 0
# endif
#endif
#ifndef FMT_POSIX
# if defined(_WIN32) && !defined(__MINGW32__)
// Fix warnings about deprecated symbols.
# define FMT_POSIX(call) _##call
# else
# define FMT_POSIX(call) call
# endif
#endif
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
#ifdef FMT_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else
# define FMT_SYSTEM(call) ::call
# ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMT_POSIX_CALL(call) ::_##call
# else
# define FMT_POSIX_CALL(call) ::call
# endif
#endif
// Retries the expression while it evaluates to error_result and errno
// equals to EINTR.
#ifndef _WIN32
# define FMT_RETRY_VAL(result, expression, error_result) \
do { \
(result) = (expression); \
} while ((result) == (error_result) && errno == EINTR)
#else
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
#endif
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
/**
\rst
A reference to a null-terminated string. It can be constructed from a C
string or ``std::string``.
You can use one of the following type aliases for common character types:
+---------------+-----------------------------+
| Type | Definition |
+===============+=============================+
| cstring_view | basic_cstring_view<char> |
+---------------+-----------------------------+
| wcstring_view | basic_cstring_view<wchar_t> |
+---------------+-----------------------------+
This class is most useful as a parameter type to allow passing
different types of strings to a function, for example::
template <typename... Args>
std::string format(cstring_view format_str, const Args & ... args);
format("{}", 42);
format(std::string("{}"), 42);
\endrst
*/
template <typename Char> class basic_cstring_view {
private:
const Char* data_;
public:
/** Constructs a string reference object from a C string. */
basic_cstring_view(const Char* s) : data_(s) {}
/**
\rst
Constructs a string reference from an ``std::string`` object.
\endrst
*/
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
/** Returns the pointer to a C string. */
const Char* c_str() const { return data_; }
};
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
#ifdef _WIN32
FMT_API const std::error_category& system_category() noexcept;
FMT_BEGIN_DETAIL_NAMESPACE
FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
FMT_END_DETAIL_NAMESPACE
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args);
/**
\rst
Constructs a :class:`std::system_error` object with the description
of the form
.. parsed-literal::
*<message>*: *<system-message>*
where *<message>* is the formatted message and *<system-message>* is the
system message corresponding to the error code.
*error_code* is a Windows error code as given by ``GetLastError``.
If *error_code* is not a valid error code such as -1, the system message
will look like "error -1".
**Example**::
// This throws a system_error with the description
// cannot open file 'madeup': The system cannot find the file specified.
// or similar (system message may vary).
const char *filename = "madeup";
LPOFSTRUCT of = LPOFSTRUCT();
HFILE file = OpenFile(filename, &of, OF_READ);
if (file == HFILE_ERROR) {
throw fmt::windows_error(GetLastError(),
"cannot open file '{}'", filename);
}
\endrst
*/
template <typename... Args>
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
}
// Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors.
FMT_API void report_windows_error(int error_code, const char* message) noexcept;
#else
inline const std::error_category& system_category() noexcept {
return std::system_category();
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
FILE* file_;
friend class file;
explicit buffered_file(FILE* f) : file_(f) {}
public:
buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept;
public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
buffered_file& operator=(buffered_file&& other) {
close();
file_ = other.file_;
other.file_ = nullptr;
return *this;
}
// Opens a file.
FMT_API buffered_file(cstring_view filename, cstring_view mode);
// Closes the file.
FMT_API void close();
// Returns the pointer to a FILE object representing this file.
FILE* get() const noexcept { return file_; }
FMT_API int descriptor() const;
void vprint(string_view format_str, format_args args) {
fmt::vprint(file_, format_str, args);
}
template <typename... Args>
inline void print(string_view format_str, const Args&... args) {
vprint(format_str, fmt::make_format_args(args...));
}
};
#if FMT_USE_FCNTL
// A file. Closed file is represented by a file object with descriptor -1.
// Methods that are not declared with noexcept may throw
// fmt::system_error in case of failure. Note that some errors such as
// closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler.
class FMT_API file {
private:
int fd_; // File descriptor.
// Constructs a file object with a given descriptor.
explicit file(int fd) : fd_(fd) {}
public:
// Possible values for the oflag argument to the constructor.
enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
};
// Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag);
public:
file(const file&) = delete;
void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw.
file& operator=(file&& other) {
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
// Destroys the object closing the file it represents if any.
~file() noexcept;
// Returns the file descriptor.
int descriptor() const noexcept { return fd_; }
// Closes the file.
void close();
// Returns the file size. The size has signed type for consistency with
// stat::st_size.
long long size() const;
// Attempts to read count bytes from the file into the specified buffer.
size_t read(void* buffer, size_t count);
// Attempts to write count bytes from the specified buffer to the file.
size_t write(const void* buffer, size_t count);
// Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object.
static file dup(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd, std::error_code& ec) noexcept;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
static void pipe(file& read_end, file& write_end);
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
buffered_file fdopen(const char* mode);
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
};
// Returns the memory page size.
long getpagesize();
FMT_BEGIN_DETAIL_NAMESPACE
struct buffer_size {
buffer_size() = default;
size_t value = 0;
buffer_size operator=(size_t val) const {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
oflag = new_oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
// Intel has a bug that results in failure to deduce a constructor
// for empty parameter packs.
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
ostream_params(int new_oflag) : oflag(new_oflag) {}
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
# endif
};
class file_buffer final : public buffer<char> {
file file_;
FMT_API void grow(size_t) override;
public:
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other);
FMT_API ~file_buffer();
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
FMT_END_DETAIL_NAMESPACE
// Added {} below to work around default constructor error known to
// occur in Xcode versions 7.2.1 and 8.2.1.
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */
class FMT_API ostream {
private:
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() { buffer_.close(); }
/**
Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
fmt::make_format_args(args...));
}
};
/**
\rst
Opens a file for writing. Supported parameters passed in *params*:
* ``<integer>``: Flags passed to `open
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
(``file::WRONLY | file::CREATE | file::TRUNC`` by default)
* ``buffer_size=<integer>``: Output buffer size
**Example**::
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
\endrst
*/
template <typename... T>
inline ostream output_file(cstring_view path, T... params) {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_OS_H_

209
include/fmt/ostream.h Normal file
View File

@ -0,0 +1,209 @@
// Formatting library for C++ - std::ostream support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_
#include <fstream> // std::filebuf
#if defined(_WIN32) && defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
# include <__std_stream>
#endif
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
#if FMT_MSC_VERSION
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
template class file_access<file_access_tag, std::__stdoutbuf<char>,
&std::__stdoutbuf<char>::__file_>;
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
#endif
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
#if FMT_MSC_VERSION
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
FILE* c_file;
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
else
return false;
if (c_file) return write_console(c_file, data);
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#else
ignore_unused(os, data);
#endif
return false;
}
inline bool write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) {
return false;
}
// Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do {
unsigned_streamsize n = size <= max_size ? size : max_size;
os.write(buf_data, static_cast<std::streamsize>(n));
buf_data += n;
size -= n;
} while (size != 0);
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value,
locale_ref loc = locale_ref()) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>());
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view { const T& value; };
} // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
void set_debug_format() = delete;
template <typename T, typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value, ctx.locale());
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
};
using ostream_formatter = basic_ostream_formatter<char>;
template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename OutputIt>
auto format(detail::streamed_view<T> view,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
return basic_ostream_formatter<Char>::format(view.value, ctx);
}
};
/**
\rst
Returns a view that formats `value` via an ostream ``operator<<``.
**Example**::
fmt::print("Current thread id: {}\n",
fmt::streamed(std::this_thread::get_id()));
\endrst
*/
template <typename T>
auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
namespace detail {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
} // namespace detail
FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::write_buffer(os, buffer);
}
/**
\rst
Prints formatted data to the stream *os*.
**Example**::
fmt::print(cerr, "Don't {}!", "panic");
\endrst
*/
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmt::make_format_args(args...);
if (detail::is_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
}
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
}
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
}
FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_

667
include/fmt/printf.h Normal file
View File

@ -0,0 +1,667 @@
// Formatting library for C++ - legacy printf implementation
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_PRINTF_H_
#define FMT_PRINTF_H_
#include <algorithm> // std::max
#include <limits> // std::numeric_limits
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter { printf_formatter() = delete; };
template <typename Char> class basic_printf_context {
private:
detail::buffer_appender<Char> out_;
basic_format_args<basic_printf_context> args_;
public:
using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(detail::buffer_appender<Char> out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
auto out() -> detail::buffer_appender<Char> { return out_; }
void advance_to(detail::buffer_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
}
FMT_CONSTEXPR void on_error(const char* message) {
detail::error_handler().on_error(message);
}
};
FMT_BEGIN_DETAIL_NAMESPACE
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = max_value<int>();
return value <= max;
}
static auto fits_in_int(bool) -> bool { return true; }
};
template <> struct int_checker<true> {
template <typename T> static auto fits_in_int(T value) -> bool {
return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>();
}
static auto fits_in_int(int) -> bool { return true; }
};
struct printf_precision_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
throw_format_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> int {
throw_format_error("precision is not integer");
return 0;
}
};
// An argument visitor that returns true iff arg is a zero integer.
struct is_zero_int {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> bool {
return value == 0;
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> bool {
return false;
}
};
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
template <typename T, typename Context> class arg_converter {
private:
using char_type = typename Context::char_type;
basic_format_arg<Context>& arg_;
char_type type_;
public:
arg_converter(basic_format_arg<Context>& arg, char_type type)
: arg_(arg), type_(type) {}
void operator()(bool value) {
if (type_ != 's') operator()<bool>(value);
}
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
void operator()(U value) {
bool is_signed = type_ == 'd' || type_ == 'i';
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
auto n = static_cast<int>(static_cast<target_type>(value));
arg_ = detail::make_arg<Context>(n);
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else {
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
}
}
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
void operator()(U) {} // No conversion needed for non-integral types.
};
// Converts an integer argument to T for printf, if T is an integral type.
// If T is void, the argument is converted to corresponding signed or unsigned
// type depending on the type specifier: 'd' and 'i' - signed, other -
// unsigned).
template <typename T, typename Context, typename Char>
void convert_arg(basic_format_arg<Context>& arg, Char type) {
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
}
// Converts an integer argument to char for printf.
template <typename Context> class char_converter {
private:
basic_format_arg<Context>& arg_;
public:
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
auto c = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
void operator()(T) {} // No conversion needed for non-integral types.
};
// An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise.
template <typename Char> struct get_cstring {
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
auto operator()(const Char* s) -> const Char* { return s; }
};
// Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
private:
format_specs<Char>& specs_;
public:
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
}
unsigned int_max = max_value<int>();
if (width > int_max) throw_format_error("number is too big");
return static_cast<unsigned>(width);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> unsigned {
throw_format_error("width is not integer");
return 0;
}
};
// Workaround for a bug with the XL compiler when initializing
// printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The ``printf`` argument formatter.
template <typename Char>
class printf_arg_formatter : public arg_formatter<Char> {
private:
using base = arg_formatter<Char>;
using context_type = basic_printf_context<Char>;
context_type& context_;
void write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.type = presentation_type::none;
write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
}
public:
printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
base::operator()(value);
return;
}
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
write<Char>(this->out, static_cast<Char>(value), fmt_specs);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) {
base::operator()(value);
}
/** Formats a null-terminated C string. */
void operator()(const char* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
/** Formats a null-terminated wide C string. */
void operator()(const wchar_t* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
void operator()(basic_string_view<Char> value) { base::operator()(value); }
/** Formats a pointer. */
void operator()(const void* value) {
if (value)
base::operator()(value);
else
write_null_pointer();
}
/** Formats an argument of a custom (user-defined) type. */
void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({});
handle.format(parse_ctx, context_);
}
};
template <typename Char>
void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
specs.align = align::left;
break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill[0] = '0';
break;
case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space;
break;
case '#':
specs.alt = true;
break;
default:
return;
}
}
}
template <typename Char, typename GetArg>
auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
GetArg get_arg) -> int {
int arg_index = -1;
Char c = *it;
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
int value = parse_nonnegative_int(it, end, -1);
if (it != end && *it == '$') { // value is an argument index
++it;
arg_index = value != -1 ? value : max_value<int>();
} else {
if (c == '0') specs.fill[0] = '0';
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
if (value == -1) throw_format_error("number is too big");
specs.width = value;
return arg_index;
}
}
}
parse_flags(specs, it, end);
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) throw_format_error("number is too big");
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
detail::printf_width_handler<Char>(specs), get_arg(-1)));
}
}
return arg_index;
}
inline auto parse_printf_presentation_type(char c, type t)
-> presentation_type {
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'x':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none;
case 'a':
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
case 'A':
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none;
case 'f':
return in(t, float_set) ? pt::fixed_lower : pt::none;
case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none;
case 'g':
return in(t, float_set) ? pt::general_lower : pt::none;
case 'G':
return in(t, float_set) ? pt::general_upper : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
}
}
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using iterator = buffer_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
auto get_arg = [&](int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index);
};
const Char* start = parse_ctx.begin();
const Char* end = parse_ctx.end();
auto it = start;
while (it != end) {
if (!find<false, Char>(it, end, '%', it)) {
it = end; // find leaves it == nullptr if it doesn't find '%'.
break;
}
Char c = *it++;
if (it != end && *it == c) {
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
start = ++it;
continue;
}
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs<Char>();
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) throw_format_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
specs.precision = parse_nonnegative_int(it, end, 0);
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(printf_precision_handler(), get_arg(-1)));
} else {
specs.precision = 0;
}
}
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) {
// Ignore '0' for non-numeric types or if '-' present.
specs.fill[0] = ' ';
}
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv);
}
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
else
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
}
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
Char t = it != end ? *it : 0;
switch (c) {
case 'h':
if (t == 'h') {
++it;
t = it != end ? *it : 0;
convert_arg<signed char>(arg, t);
} else {
convert_arg<short>(arg, t);
}
break;
case 'l':
if (t == 'l') {
++it;
t = it != end ? *it : 0;
convert_arg<long long>(arg, t);
} else {
convert_arg<long>(arg, t);
}
break;
case 'j':
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L':
// printf produces garbage when 'L' is omitted for long double, no
// need to do the same.
break;
default:
--it;
convert_arg<void>(arg, c);
}
// Parse type.
if (it == end) throw_format_error("invalid format string");
char type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
switch (type) {
case 'i':
case 'u':
type = 'd';
break;
case 'c':
visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
break;
}
}
specs.type = parse_printf_presentation_type(type, arg.type());
if (specs.type == presentation_type::none)
throw_format_error("invalid format specifier");
start = it;
// Format argument.
visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
}
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
FMT_END_DETAIL_NAMESPACE
using printf_context = basic_printf_context<char>;
using wprintf_context = basic_printf_context<wchar_t>;
using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>;
/**
\rst
Constructs an `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::printf_args`.
\endrst
*/
template <typename... T>
inline auto make_printf_args(const T&... args)
-> format_arg_store<printf_context, T...> {
return {args...};
}
// DEPRECATED!
template <typename... T>
inline auto make_wprintf_args(const T&... args)
-> format_arg_store<wprintf_context, T...> {
return {args...};
}
template <typename Char>
inline auto vsprintf(
basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
return to_string(buf);
}
/**
\rst
Formats arguments and returns the result as a string.
**Example**::
std::string message = fmt::sprintf("The answer is %d", 42);
\endrst
*/
template <typename S, typename... T,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename Char>
inline auto vfprintf(
std::FILE* f, basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
size_t size = buf.size();
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
}
/**
\rst
Prints formatted data to the file *f*.
**Example**::
fmt::fprintf(stderr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename Char>
FMT_DEPRECATED inline auto vprintf(
basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int {
return vfprintf(stdout, fmt, args);
}
/**
\rst
Prints formatted data to ``stdout``.
**Example**::
fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst
*/
template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_wprintf_args(args...));
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_

732
include/fmt/ranges.h Normal file
View File

@ -0,0 +1,732 @@
// Formatting library for C++ - experimental range support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
//
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
// All Rights Reserved
// {fmt} support for ranges, containers and types tuple interface.
#ifndef FMT_RANGES_H_
#define FMT_RANGES_H_
#include <initializer_list>
#include <tuple>
#include <type_traits>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Range, typename OutputIt>
auto copy(const Range& range, OutputIt out) -> OutputIt {
for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it;
return out;
}
template <typename OutputIt>
auto copy(const char* str, OutputIt out) -> OutputIt {
while (*str) *out++ = *str++;
return out;
}
template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
// Returns true if T has a std::string-like interface, like std::string_view.
template <typename T> class is_std_string_like {
template <typename U>
static auto check(U* p)
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
template <typename> static void check(...);
public:
static constexpr const bool value =
is_string<T>::value ||
std::is_convertible<T, std_string_view<char>>::value ||
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Char>
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
template <typename T> class is_map {
template <typename U> static auto check(U*) -> typename U::mapped_type;
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
#endif
};
template <typename T> class is_set {
template <typename U> static auto check(U*) -> typename U::key_type;
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
#endif
};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
template <typename T, typename Enable = void>
struct has_member_fn_begin_end_t : std::false_type {};
template <typename T>
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};
// Member function overload
template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
// ADL overload. Only participates in overload resolution if member functions
// are not found.
template <typename T>
auto range_begin(T&& rng)
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(begin(static_cast<T&&>(rng)))> {
return begin(static_cast<T&&>(rng));
}
template <typename T>
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(end(static_cast<T&&>(rng)))> {
return end(static_cast<T&&>(rng));
}
template <typename T, typename Enable = void>
struct has_const_begin_end : std::false_type {};
template <typename T, typename Enable = void>
struct has_mutable_begin_end : std::false_type {};
template <typename T>
struct has_const_begin_end<
T,
void_t<
decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};
template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_end(std::declval<T>())),
// the extra int here is because older versions of MSVC don't
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
# undef FMT_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
// Check for integer_sequence
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
template <typename T, T... N>
using integer_sequence = std::integer_sequence<T, N...>;
template <size_t... N> using index_sequence = std::index_sequence<N...>;
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
#else
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
};
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
template <typename T, size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
template <size_t N>
using make_index_sequence = make_integer_sequence<size_t, N>;
#endif
template <typename T>
using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ {
public:
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... Is>
static std::true_type check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>);
static std::false_type check2(...);
template <std::size_t... Is>
static decltype(check2(
index_sequence<Is...>{},
integer_sequence<
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{})) check(index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
template <typename Tuple, typename F, size_t... Is>
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get;
// Using a free function get<Is>(Tuple) now.
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
ignore_unused(unused);
}
template <typename Tuple, typename F>
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
std::forward<Tuple>(t), std::forward<F>(f));
}
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
}
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>()));
};
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
using type = T&;
};
template <typename T>
using range_reference_type = typename range_reference_type_impl<T>::type;
#else
template <typename Range>
using range_reference_type =
decltype(*detail::range_begin(std::declval<Range&>()));
#endif
// We don't use the Range's value_type for anything, but we do need the Range's
// reference type, with cv-ref stripped.
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
// These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename T>
void operator()(const formatter<T, char_type>& f, const T& v) {
if (i > 0)
ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
}
int i;
FormatContext& ctx;
basic_string_view<char_type> separator;
};
} // namespace detail
template <typename T> struct is_tuple_like {
static constexpr const bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value =
detail::is_tuple_formattable_<T, C>::value;
};
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
fmt::is_tuple_formattable<Tuple, Char>::value>> {
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{};
public:
FMT_CONSTEXPR formatter() {}
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it != '}')
FMT_THROW(format_error("invalid format specifier"));
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
formatters_, value,
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return detail::copy_str<Char>(closing_bracket_, ctx.out());
}
};
template <typename T, typename Char> struct is_range {
static constexpr const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_convertible<T, detail::std_string_view<Char>>::value;
};
namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element>
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
std::declval<Element>()))>,
Char>;
template <typename R>
using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename T, typename Char, typename Enable = void>
struct range_formatter;
template <typename T, typename Char>
struct range_formatter<
T, Char,
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
is_formattable<T, Char>>::value>> {
private:
detail::range_formatter_type<Char, T> underlying_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '['>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
public:
FMT_CONSTEXPR range_formatter() {}
FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
return underlying_;
}
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it == 'n') {
set_brackets({}, {});
++it;
}
if (it != end && *it != '}') {
if (*it != ':') FMT_THROW(format_error("invalid format specifier"));
++it;
} else {
detail::maybe_set_debug_format(underlying_, true);
}
ctx.advance_to(it);
return underlying_.parse(ctx);
}
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
detail::range_mapper<buffer_context<Char>> mapper;
auto out = ctx.out();
out = detail::copy_str<Char>(opening_bracket_, out);
int i = 0;
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::copy_str<Char>(separator_, out);
ctx.advance_to(out);
out = underlying_.format(mapper.map(*it), ctx);
++i;
}
out = detail::copy_str<Char>(closing_bracket_, out);
return out;
}
};
enum class range_format { disabled, map, set, sequence, string, debug_string };
namespace detail {
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
std::is_same<uncvref_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence> {};
template <range_format K, typename R, typename Char, typename Enable = void>
struct range_default_formatter;
template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>;
template <range_format K, typename R, typename Char>
struct range_default_formatter<
K, R, Char,
enable_if_t<(K == range_format::sequence || K == range_format::map ||
K == range_format::set)>> {
using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> underlying_;
FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); }
FMT_CONSTEXPR void init(range_format_constant<range_format::set>) {
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
}
FMT_CONSTEXPR void init(range_format_constant<range_format::map>) {
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
underlying_.underlying().set_brackets({}, {});
underlying_.underlying().set_separator(
detail::string_literal<Char, ':', ' '>{});
}
FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(range_type& range, FormatContext& ctx) const
-> decltype(ctx.out()) {
return underlying_.format(range, ctx);
}
};
} // namespace detail
template <typename T, typename Char, typename Enable = void>
struct range_format_kind
: conditional_t<
is_range<T, Char>::value, detail::range_format_kind_<T>,
std::integral_constant<range_format, range_format::disabled>> {};
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value !=
range_format::disabled>
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>>
: detail::range_default_formatter<range_format_kind<R, Char>::value, R,
Char> {
};
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
}
private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
template <typename ParseContext>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
FMT_THROW(format_error("incompatible format specs for tuple elements"));
}
#endif
return end;
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (N > 1) {
out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
return out;
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<detail::is_container_adaptor_like<T>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMT_BEGIN_EXPORT
/**
\rst
Returns an object that formats `tuple` with elements separated by `sep`.
**Example**::
std::tuple<int, char> t = {1, 'a'};
fmt::print("{}", fmt::join(t, ", "));
// Output: "1, a"
\endrst
*/
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
return {tuple, sep};
}
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
return {tuple, sep};
}
/**
\rst
Returns an object that formats `initializer_list` with elements separated by
`sep`.
**Example**::
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
// Output: "1, 2, 3"
\endrst
*/
template <typename T>
auto join(std::initializer_list<T> list, string_view sep)
-> join_view<const T*, const T*> {
return join(std::begin(list), std::end(list), sep);
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_RANGES_H_

349
include/fmt/std.h Normal file
View File

@ -0,0 +1,349 @@
// Formatting library for C++ - formatters for standard library types
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include <cstdlib>
#include <exception>
#include <memory>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include "ostream.h"
#if FMT_HAS_INCLUDE(<version>)
# include <version>
#endif
// Checking FMT_CPLUSPLUS for warning suppression in MSVC.
#if FMT_CPLUSPLUS >= 201703L
# if FMT_HAS_INCLUDE(<filesystem>)
# include <filesystem>
# endif
# if FMT_HAS_INCLUDE(<variant>)
# include <variant>
# endif
# if FMT_HAS_INCLUDE(<optional>)
# include <optional>
# endif
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif
#ifdef __cpp_lib_filesystem
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p) {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
# ifdef _WIN32
template <>
inline void write_escaped_path<char>(memory_buffer& quoted,
const std::filesystem::path& p) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
// Convert UTF-16 to UTF-8.
if (!to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}))
FMT_THROW(std::runtime_error("invalid utf16"));
}
# endif
template <>
inline void write_escaped_path<std::filesystem::path::value_type>(
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
const std::filesystem::path& p) {
write_escaped_string<std::filesystem::path::value_type>(
std::back_inserter(quoted), p.native());
}
} // namespace detail
FMT_EXPORT
template <typename Char>
struct formatter<std::filesystem::path, Char>
: formatter<basic_string_view<Char>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto out = formatter<basic_string_view<Char>>::parse(ctx);
this->set_debug_format(false);
return out;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
typename FormatContext::iterator {
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p);
return formatter<basic_string_view<Char>>::format(
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
}
};
FMT_END_NAMESPACE
#endif
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(std::optional<T> const& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, optional);
ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#ifdef __cpp_lib_variant
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write<Char>(out, "monostate");
return out;
}
};
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
template <typename Char, typename OutputIt, typename T>
auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (is_string<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
else if constexpr (std::is_same_v<T, Char>)
return write_escaped_char(out, v);
else
return write<Char>(out, v);
}
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
};
FMT_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const Variant& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
try {
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
} catch (const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_variant
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
FMT_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = true;
}
return it;
}
template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);
const std::type_info& ti = typeid(ex);
#ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
out = detail::write_bytes(out, demangled_name_view, spec);
#elif FMT_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
#else
out = detail::write_bytes(out, string_view(ti.name()), spec);
#endif
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, Char(' '));
out = detail::write_bytes(out, string_view(ex.what()), spec);
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

258
include/fmt/xchar.h Normal file
View File

@ -0,0 +1,258 @@
// Formatting library for C++ - optional wchar_t and exotic character support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_XCHAR_H_
#define FMT_XCHAR_H_
#include <cwchar>
#include "format.h"
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
loc_value value, const format_specs<wchar_t>& specs,
locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_context = buffer_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<detail::char8_type> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
template <typename... T>
constexpr format_arg_store<wformat_context, T...> make_wformat_args(
const T&... args) {
return {args...};
}
inline namespace literals {
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
return {s};
}
#endif
} // namespace literals
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
-> join_view<It, Sentinel, wchar_t> {
return {begin, end, sep};
}
template <typename Range>
auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
template <typename T>
auto join(std::initializer_list<T> list, wstring_view sep)
-> join_view<const T*, const T*, wchar_t> {
return join(std::begin(list), std::end(list), sep);
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, format_str, args);
return to_string(buf);
}
template <typename... T>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
}
// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename Locale, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(
const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), args);
}
template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, T&&... args)
-> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
return vformat_to(out, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(
OutputIt out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <
typename OutputIt, typename Locale, typename S, typename... T,
typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...));
return buf.count();
}
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
auto buf = wmemory_buffer();
detail::vformat_to(buf, fmt, args);
buf.push_back(L'\0');
if (std::fputws(buf.data(), f) == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
inline void vprint(wstring_view fmt, wformat_args args) {
vprint(stdout, fmt, args);
}
template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename... T>
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/**
Converts *value* to ``std::wstring`` using the default format for type *T*.
*/
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_XCHAR_H_

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -37,21 +37,34 @@ tab-size = 4
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
#include <limits.h> #include <limits.h>
#endif #endif
#if !defined(__clang__) && __GNUC__ < 11
#include <semaphore.h>
#else
#include <semaphore>
#endif
#include <btop_shared.hpp> #include "btop_shared.hpp"
#include <btop_tools.hpp> #include "btop_tools.hpp"
#include <btop_config.hpp> #include "btop_config.hpp"
#include <btop_input.hpp> #include "btop_input.hpp"
#include <btop_theme.hpp> #include "btop_theme.hpp"
#include <btop_draw.hpp> #include "btop_draw.hpp"
#include <btop_menu.hpp> #include "btop_menu.hpp"
using std::atomic;
using std::cout;
using std::flush;
using std::min;
using std::string;
using std::string_view;
using std::to_string;
using std::vector;
using std::string, std::string_view, std::vector, std::atomic, std::endl, std::cout, std::min, std::flush, std::endl;
using std::string_literals::operator""s, std::to_string;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools; using namespace Tools;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace std::literals;
namespace Global { namespace Global {
const vector<array<string, 2>> Banner_src = { const vector<array<string, 2>> Banner_src = {
@ -62,7 +75,7 @@ namespace Global {
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
}; };
const string Version = "1.2.8"; const string Version = "1.2.13";
int coreCount; int coreCount;
string overlay; string overlay;
@ -80,9 +93,9 @@ namespace Global {
string exit_error_msg; string exit_error_msg;
atomic<bool> thread_exception (false); atomic<bool> thread_exception (false);
bool debuginit = false; bool debuginit{}; // defaults to false
bool debug = false; bool debug{}; // defaults to false
bool utf_force = false; bool utf_force{}; // defaults to false
uint64_t start_time; uint64_t start_time;
@ -92,8 +105,8 @@ namespace Global {
atomic<bool> should_sleep (false); atomic<bool> should_sleep (false);
atomic<bool> _runner_started (false); atomic<bool> _runner_started (false);
bool arg_tty = false; bool arg_tty{}; // defaults to false
bool arg_low_color = false; bool arg_low_color{}; // defaults to false
int arg_preset = -1; int arg_preset = -1;
} }
@ -102,22 +115,23 @@ void argumentParser(const int& argc, char **argv) {
for(int i = 1; i < argc; i++) { for(int i = 1; i < argc; i++) {
const string argument = argv[i]; const string argument = argv[i];
if (is_in(argument, "-h", "--help")) { if (is_in(argument, "-h", "--help")) {
cout << "usage: btop [-h] [-v] [-/+t] [-p <id>] [--utf-force] [--debug]\n\n" fmt::println(
<< "optional arguments:\n" "usage: btop [-h] [-v] [-/+t] [-p <id>] [--utf-force] [--debug]\n\n"
<< " -h, --help show this help message and exit\n" "optional arguments:\n"
<< " -v, --version show version info and exit\n" " -h, --help show this help message and exit\n"
<< " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n" " -v, --version show version info and exit\n"
<< " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n" " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n"
<< " +t, --tty_off force (OFF) tty mode\n" " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
<< " -p, --preset <id> start with preset, integer value between 0-9\n" " +t, --tty_off force (OFF) tty mode\n"
<< " --utf-force force start even if no UTF-8 locale was detected\n" " -p, --preset <id> start with preset, integer value between 0-9\n"
<< " --debug start in DEBUG mode: shows microsecond timer for information collect\n" " --utf-force force start even if no UTF-8 locale was detected\n"
<< " and screen draw functions and sets loglevel to DEBUG\n" " --debug start in DEBUG mode: shows microsecond timer for information collect\n"
<< endl; " and screen draw functions and sets loglevel to DEBUG"
);
exit(0); exit(0);
} }
else if (is_in(argument, "-v", "--version")) { else if (is_in(argument, "-v", "--version")) {
cout << "btop version: " << Global::Version << endl; fmt::println("btop version: {}", Global::Version);
exit(0); exit(0);
} }
else if (is_in(argument, "-lc", "--low-color")) { else if (is_in(argument, "-lc", "--low-color")) {
@ -133,14 +147,14 @@ void argumentParser(const int& argc, char **argv) {
} }
else if (is_in(argument, "-p", "--preset")) { else if (is_in(argument, "-p", "--preset")) {
if (++i >= argc) { if (++i >= argc) {
cout << "ERROR: Preset option needs an argument." << endl; fmt::println("ERROR: Preset option needs an argument.");
exit(1); exit(1);
} }
else if (const string val = argv[i]; isint(val) and val.size() == 1) { else if (const string val = argv[i]; isint(val) and val.size() == 1) {
Global::arg_preset = std::clamp(stoi(val), 0, 9); Global::arg_preset = std::clamp(stoi(val), 0, 9);
} }
else { else {
cout << "ERROR: Preset option only accepts an integer value between 0-9." << endl; fmt::println("ERROR: Preset option only accepts an integer value between 0-9.");
exit(1); exit(1);
} }
} }
@ -149,8 +163,8 @@ void argumentParser(const int& argc, char **argv) {
else if (argument == "--debug") else if (argument == "--debug")
Global::debug = true; Global::debug = true;
else { else {
cout << " Unknown argument: " << argument << "\n" << fmt::println(" Unknown argument: {}\n"
" Use -h or --help for help." << endl; " Use -h or --help for help.", argument);
exit(1); exit(1);
} }
} }
@ -169,8 +183,11 @@ void term_resize(bool force) {
if (force and refreshed) force = false; if (force and refreshed) force = false;
} }
else return; else return;
#ifdef GPU_SUPPORT
static const array<string, 4> all_boxes = {"cpu", "mem", "net", "proc"}; static const array<string, 10> all_boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"};
#else
static const array<string, 5> all_boxes = {"", "cpu", "mem", "net", "proc"};
#endif
Global::resized = true; Global::resized = true;
if (Runner::active) Runner::stop(); if (Runner::active) Runner::stop();
Term::refresh(); Term::refresh();
@ -178,30 +195,52 @@ void term_resize(bool force) {
auto boxes = Config::getS("shown_boxes"); auto boxes = Config::getS("shown_boxes");
auto min_size = Term::get_min_size(boxes); auto min_size = Term::get_min_size(boxes);
auto minWidth = min_size.at(0), minHeight = min_size.at(1);
while (not force or (Term::width < min_size.at(0) or Term::height < min_size.at(1))) { while (not force or (Term::width < minWidth or Term::height < minHeight)) {
sleep_ms(100); sleep_ms(100);
if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) { if (Term::width < minWidth or Term::height < minHeight) {
cout << Term::clear << Global::bg_black << Global::fg_white << Mv::to((Term::height / 2) - 2, (Term::width / 2) - 11) int width = Term::width, height = Term::height;
<< "Terminal size too small:" << Mv::to((Term::height / 2) - 1, (Term::width / 2) - 10) cout << fmt::format("{clear}{bg_black}{fg_white}"
<< " Width = " << (Term::width < min_size.at(1) ? Global::fg_red : Global::fg_green) << Term::width "{mv1}Terminal size too small:"
<< Global::fg_white << " Height = " << (Term::height < min_size.at(0) ? Global::fg_red : Global::fg_green) << Term::height "{mv2} Width = {fg_width}{width} {fg_white}Height = {fg_height}{height}"
<< Mv::to((Term::height / 2) + 1, (Term::width / 2) - 12) << Global::fg_white "{mv3}{fg_white}Needed for current config:"
<< "Needed for current config:" << Mv::to((Term::height / 2) + 2, (Term::width / 2) - 10) "{mv4}Width = {minWidth} Height = {minHeight}",
<< "Width = " << min_size.at(0) << " Height = " << min_size.at(1) << flush; "clear"_a = Term::clear, "bg_black"_a = Global::bg_black, "fg_white"_a = Global::fg_white,
"mv1"_a = Mv::to((height / 2) - 2, (width / 2) - 11),
"mv2"_a = Mv::to((height / 2) - 1, (width / 2) - 10),
"fg_width"_a = (width < minWidth ? Global::fg_red : Global::fg_green),
"width"_a = width,
"fg_height"_a = (height < minHeight ? Global::fg_red : Global::fg_green),
"height"_a = height,
"mv3"_a = Mv::to((height / 2) + 1, (width / 2) - 12),
"mv4"_a = Mv::to((height / 2) + 2, (width / 2) - 10),
"minWidth"_a = minWidth,
"minHeight"_a = minHeight
) << std::flush;
bool got_key = false; bool got_key = false;
for (; not Term::refresh() and not got_key; got_key = Input::poll(10)); for (; not Term::refresh() and not got_key; got_key = Input::poll(10));
if (got_key) { if (got_key) {
auto key = Input::get(); auto key = Input::get();
if (key == "q") if (key == "q")
clean_quit(0); clean_quit(0);
else if (is_in(key, "1", "2", "3", "4")) { else if (key.size() == 1 and isint(key)) {
Config::current_preset = -1; auto intKey = stoi(key);
Config::toggle_box(all_boxes.at(std::stoi(key) - 1)); #ifdef GPU_SUPPORT
boxes = Config::getS("shown_boxes"); if ((intKey == 0 and Gpu::gpu_names.size() >= 5) or (intKey >= 5 and std::cmp_greater_equal(Gpu::gpu_names.size(), intKey - 4))) {
#else
if (intKey > 0 and intKey < 5) {
#endif
auto box = all_boxes.at(intKey);
Config::current_preset = -1;
Config::toggle_box(box);
boxes = Config::getS("shown_boxes");
}
} }
} }
min_size = Term::get_min_size(boxes); min_size = Term::get_min_size(boxes);
minWidth = min_size.at(0), minHeight = min_size.at(1);
} }
else if (not Term::refresh()) break; else if (not Term::refresh()) break;
} }
@ -216,20 +255,25 @@ void clean_quit(int sig) {
Runner::stop(); Runner::stop();
if (Global::_runner_started) { if (Global::_runner_started) {
#ifdef __APPLE__ #ifdef __APPLE__
if (pthread_join(Runner::runner_id, NULL) != 0) { if (pthread_join(Runner::runner_id, nullptr) != 0) {
Logger::warning("Failed to join _runner thread on exit!"); Logger::warning("Failed to join _runner thread on exit!");
pthread_cancel(Runner::runner_id); pthread_cancel(Runner::runner_id);
} }
#else #else
struct timespec ts; struct timespec ts;
ts.tv_sec = 5; ts.tv_sec = 5;
if (pthread_timedjoin_np(Runner::runner_id, NULL, &ts) != 0) { if (pthread_timedjoin_np(Runner::runner_id, nullptr, &ts) != 0) {
Logger::warning("Failed to join _runner thread on exit!"); Logger::warning("Failed to join _runner thread on exit!");
pthread_cancel(Runner::runner_id); pthread_cancel(Runner::runner_id);
} }
#endif #endif
} }
#ifdef GPU_SUPPORT
Gpu::Nvml::shutdown();
Gpu::Rsmi::shutdown();
#endif
Config::write(); Config::write();
if (Term::initialized) { if (Term::initialized) {
@ -240,7 +284,7 @@ void clean_quit(int sig) {
if (not Global::exit_error_msg.empty()) { if (not Global::exit_error_msg.empty()) {
sig = 1; sig = 1;
Logger::error(Global::exit_error_msg); Logger::error(Global::exit_error_msg);
std::cerr << Global::fg_red << "ERROR: " << Global::fg_white << Global::exit_error_msg << Fx::reset << endl; fmt::println(std::cerr, "{}ERROR: {}{}{}", Global::fg_red, Global::fg_white, Global::exit_error_msg, Fx::reset);
} }
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time)); Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
@ -307,16 +351,15 @@ namespace Runner {
atomic<bool> stopping (false); atomic<bool> stopping (false);
atomic<bool> waiting (false); atomic<bool> waiting (false);
atomic<bool> redraw (false); atomic<bool> redraw (false);
atomic<bool> coreNum_reset (false);
//* Setup semaphore for triggering thread to do work //* Setup semaphore for triggering thread to do work
#if __GNUC__ < 11 #if !defined(__clang__) && __GNUC__ < 11
#include <semaphore.h>
sem_t do_work; sem_t do_work;
inline void thread_sem_init() { sem_init(&do_work, 0, 0); } inline void thread_sem_init() { sem_init(&do_work, 0, 0); }
inline void thread_wait() { sem_wait(&do_work); } inline void thread_wait() { sem_wait(&do_work); }
inline void thread_trigger() { sem_post(&do_work); } inline void thread_trigger() { sem_post(&do_work); }
#else #else
#include <semaphore>
std::binary_semaphore do_work(0); std::binary_semaphore do_work(0);
inline void thread_sem_init() { ; } inline void thread_sem_init() { ; }
inline void thread_wait() { do_work.acquire(); } inline void thread_wait() { do_work.acquire(); }
@ -328,8 +371,14 @@ namespace Runner {
pthread_mutex_t& pt_mutex; pthread_mutex_t& pt_mutex;
public: public:
int status; int status;
thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { pthread_mutex_init(&pt_mutex, NULL); status = pthread_mutex_lock(&pt_mutex); } thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) {
~thread_lock() { if (status == 0) pthread_mutex_unlock(&pt_mutex); } pthread_mutex_init(&pt_mutex, nullptr);
status = pthread_mutex_lock(&pt_mutex);
}
~thread_lock() {
if (status == 0)
pthread_mutex_unlock(&pt_mutex);
}
}; };
//* Wrapper for raising priviliges when using SUID bit //* Wrapper for raising priviliges when using SUID bit
@ -337,23 +386,27 @@ namespace Runner {
int status = -1; int status = -1;
public: public:
gain_priv() { gain_priv() {
if (Global::real_uid != Global::set_uid) this->status = seteuid(Global::set_uid); if (Global::real_uid != Global::set_uid)
this->status = seteuid(Global::set_uid);
} }
~gain_priv() { ~gain_priv() {
if (status == 0) status = seteuid(Global::real_uid); if (status == 0)
status = seteuid(Global::real_uid);
} }
}; };
string output; string output;
string empty_bg; string empty_bg;
bool pause_output = false; bool pause_output{}; // defaults to false
sigset_t mask; sigset_t mask;
pthread_t runner_id; pthread_t runner_id;
pthread_mutex_t mtx; pthread_mutex_t mtx;
enum debug_actions { enum debug_actions {
collect_begin, collect_begin,
collect_done,
draw_begin, draw_begin,
draw_begin_only,
draw_done draw_done
}; };
@ -365,6 +418,14 @@ namespace Runner {
string debug_bg; string debug_bg;
unordered_flat_map<string, array<uint64_t, 2>> debug_times; unordered_flat_map<string, array<uint64_t, 2>> debug_times;
class MyNumPunct : public std::numpunct<char>
{
protected:
virtual char do_thousands_sep() const { return '\''; }
virtual std::string do_grouping() const { return "\03"; }
};
struct runner_conf { struct runner_conf {
vector<string> boxes; vector<string> boxes;
bool no_update; bool no_update;
@ -381,6 +442,13 @@ namespace Runner {
case collect_begin: case collect_begin:
debug_times[name].at(collect) = time_micros(); debug_times[name].at(collect) = time_micros();
return; return;
case collect_done:
debug_times[name].at(collect) = time_micros() - debug_times[name].at(collect);
debug_times["total"].at(collect) += debug_times[name].at(collect);
return;
case draw_begin_only:
debug_times[name].at(draw) = time_micros();
return;
case draw_begin: case draw_begin:
debug_times[name].at(draw) = time_micros(); debug_times[name].at(draw) = time_micros();
debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect); debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect);
@ -394,15 +462,14 @@ namespace Runner {
} }
//? ------------------------------- Secondary thread: async launcher and drawing ---------------------------------- //? ------------------------------- Secondary thread: async launcher and drawing ----------------------------------
void * _runner(void * _) { void * _runner(void *) {
(void)_;
//? Block some signals in this thread to avoid deadlock from any signal handlers trying to stop this thread //? Block some signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
sigemptyset(&mask); sigemptyset(&mask);
// sigaddset(&mask, SIGINT); // sigaddset(&mask, SIGINT);
// sigaddset(&mask, SIGTSTP); // sigaddset(&mask, SIGTSTP);
sigaddset(&mask, SIGWINCH); sigaddset(&mask, SIGWINCH);
sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGTERM);
pthread_sigmask(SIG_BLOCK, &mask, NULL); pthread_sigmask(SIG_BLOCK, &mask, nullptr);
//? pthread_mutex_lock to lock thread and monitor health from main thread //? pthread_mutex_lock to lock thread and monitor health from main thread
thread_lock pt_lck(mtx); thread_lock pt_lck(mtx);
@ -438,7 +505,15 @@ namespace Runner {
//! DEBUG stats //! DEBUG stats
if (Global::debug) { if (Global::debug) {
if (debug_bg.empty() or redraw) Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug"); if (debug_bg.empty() or redraw)
Runner::debug_bg = Draw::createBox(2, 2, 33,
#ifdef GPU_SUPPORT
9,
#else
8,
#endif
"", true, "μs");
debug_times.clear(); debug_times.clear();
debug_times["total"] = {0, 0}; debug_times["total"] = {0, 0};
} }
@ -447,6 +522,29 @@ namespace Runner {
//* Run collection and draw functions for all boxes //* Run collection and draw functions for all boxes
try { try {
#ifdef GPU_SUPPORT
//? GPU data collection
const bool gpu_in_cpu_panel = Gpu::gpu_names.size() > 0 and (
Config::getS("cpu_graph_lower").starts_with("gpu-") or Config::getS("cpu_graph_upper").starts_with("gpu-")
or (Gpu::shown == 0 and Config::getS("show_gpu_info") != "Off")
);
vector<unsigned int> gpu_panels = {};
for (auto& box : conf.boxes)
if (box.starts_with("gpu"))
gpu_panels.push_back(box.back()-'0');
vector<Gpu::gpu_info> gpus;
if (gpu_in_cpu_panel or not gpu_panels.empty()) {
if (Global::debug) debug_timer("gpu", collect_begin);
gpus = Gpu::collect(conf.no_update);
if (Global::debug) debug_timer("gpu", collect_done);
}
auto& gpus_ref = gpus;
#else
vector<Gpu::gpu_info> gpus_ref{};
#endif
//? CPU //? CPU
if (v_contains(conf.boxes, "cpu")) { if (v_contains(conf.boxes, "cpu")) {
try { try {
@ -455,18 +553,43 @@ namespace Runner {
//? Start collect //? Start collect
auto cpu = Cpu::collect(conf.no_update); auto cpu = Cpu::collect(conf.no_update);
if (coreNum_reset) {
coreNum_reset = false;
Cpu::core_mapping = Cpu::get_core_mapping();
Global::resized = true;
Input::interrupt = true;
continue;
}
if (Global::debug) debug_timer("cpu", draw_begin); if (Global::debug) debug_timer("cpu", draw_begin);
//? Draw box //? Draw box
if (not pause_output) output += Cpu::draw(cpu, conf.force_redraw, conf.no_update); if (not pause_output) output += Cpu::draw(cpu, gpus_ref, conf.force_redraw, conf.no_update);
if (Global::debug) debug_timer("cpu", draw_done); if (Global::debug) debug_timer("cpu", draw_done);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
throw std::runtime_error("Cpu:: -> " + (string)e.what()); throw std::runtime_error("Cpu:: -> " + string{e.what()});
} }
} }
#ifdef GPU_SUPPORT
//? GPU
if (not gpu_panels.empty() and not gpus_ref.empty()) {
try {
if (Global::debug) debug_timer("gpu", draw_begin_only);
//? Draw box
if (not pause_output)
for (unsigned long i = 0; i < gpu_panels.size(); ++i)
output += Gpu::draw(gpus_ref[gpu_panels[i]], i, conf.force_redraw, conf.no_update);
if (Global::debug) debug_timer("gpu", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Gpu:: -> " + string{e.what()});
}
}
#endif
//? MEM //? MEM
if (v_contains(conf.boxes, "mem")) { if (v_contains(conf.boxes, "mem")) {
try { try {
@ -483,7 +606,7 @@ namespace Runner {
if (Global::debug) debug_timer("mem", draw_done); if (Global::debug) debug_timer("mem", draw_done);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
throw std::runtime_error("Mem:: -> " + (string)e.what()); throw std::runtime_error("Mem:: -> " + string{e.what()});
} }
} }
@ -503,7 +626,7 @@ namespace Runner {
if (Global::debug) debug_timer("net", draw_done); if (Global::debug) debug_timer("net", draw_done);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
throw std::runtime_error("Net:: -> " + (string)e.what()); throw std::runtime_error("Net:: -> " + string{e.what()});
} }
} }
@ -523,12 +646,13 @@ namespace Runner {
if (Global::debug) debug_timer("proc", draw_done); if (Global::debug) debug_timer("proc", draw_done);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
throw std::runtime_error("Proc:: -> " + (string)e.what()); throw std::runtime_error("Proc:: -> " + string{e.what()});
} }
} }
} }
catch (const std::exception& e) { catch (const std::exception& e) {
Global::exit_error_msg = "Exception in runner thread -> " + (string)e.what(); Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()};
Global::thread_exception = true; Global::thread_exception = true;
Input::interrupt = true; Input::interrupt = true;
stopping = true; stopping = true;
@ -549,26 +673,53 @@ namespace Runner {
if (empty_bg.empty()) { if (empty_bg.empty()) {
const int x = Term::width / 2 - 10, y = Term::height / 2 - 10; const int x = Term::width / 2 - 10, y = Term::height / 2 - 10;
output += Term::clear; output += Term::clear;
empty_bg += Draw::banner_gen(y, 0, true) empty_bg = fmt::format(
+ Mv::to(y+6, x) + Theme::c("title") + Fx::b + "No boxes shown!" "{banner}"
+ Mv::to(y+8, x) + Theme::c("hi_fg") + "1" + Theme::c("main_fg") + " | Show CPU box" "{mv1}{titleFg}{b}No boxes shown!"
+ Mv::to(y+9, x) + Theme::c("hi_fg") + "2" + Theme::c("main_fg") + " | Show MEM box" "{mv2}{hiFg}1 {mainFg}| Show CPU box"
+ Mv::to(y+10, x) + Theme::c("hi_fg") + "3" + Theme::c("main_fg") + " | Show NET box" "{mv3}{hiFg}2 {mainFg}| Show MEM box"
+ Mv::to(y+11, x) + Theme::c("hi_fg") + "4" + Theme::c("main_fg") + " | Show PROC box" "{mv4}{hiFg}3 {mainFg}| Show NET box"
+ Mv::to(y+12, x-2) + Theme::c("hi_fg") + "esc" + Theme::c("main_fg") + " | Show menu" "{mv5}{hiFg}4 {mainFg}| Show PROC box"
+ Mv::to(y+13, x) + Theme::c("hi_fg") + "q" + Theme::c("main_fg") + " | Quit"; "{mv6}{hiFg}5-0 {mainFg}| Show GPU boxes"
"{mv7}{hiFg}esc {mainFg}| Show menu"
"{mv8}{hiFg}q {mainFg}| Quit",
"banner"_a = Draw::banner_gen(y, 0, true),
"titleFg"_a = Theme::c("title"), "b"_a = Fx::b, "hiFg"_a = Theme::c("hi_fg"), "mainFg"_a = Theme::c("main_fg"),
"mv1"_a = Mv::to(y+6, x),
"mv2"_a = Mv::to(y+8, x),
"mv3"_a = Mv::to(y+9, x),
"mv4"_a = Mv::to(y+10, x),
"mv5"_a = Mv::to(y+11, x),
"mv6"_a = Mv::to(y+12, x-2),
"mv7"_a = Mv::to(y+13, x-2),
"mv8"_a = Mv::to(y+14, x)
);
} }
output += empty_bg; output += empty_bg;
} }
//! DEBUG stats --> //! DEBUG stats -->
if (Global::debug and not Menu::active) { if (Global::debug and not Menu::active) {
output += debug_bg + Theme::c("title") + Fx::b + ljust(" Box", 9) + ljust("Collect μs", 12, true) + ljust("Draw μs", 9, true) + Theme::c("main_fg") + Fx::ub; output += fmt::format("{pre}{box:5.5} {collect:>12.12} {draw:>12.12}{post}",
"pre"_a = debug_bg + Theme::c("title") + Fx::b,
"box"_a = "box", "collect"_a = "collect", "draw"_a = "draw",
"post"_a = Theme::c("main_fg") + Fx::ub
);
static auto loc = std::locale(std::locale::classic(), new MyNumPunct);
#ifdef GPU_SUPPORT
for (const string name : {"cpu", "mem", "net", "proc", "gpu", "total"}) {
#else
for (const string name : {"cpu", "mem", "net", "proc", "total"}) { for (const string name : {"cpu", "mem", "net", "proc", "total"}) {
#endif
if (not debug_times.contains(name)) debug_times[name] = {0,0}; if (not debug_times.contains(name)) debug_times[name] = {0,0};
const auto& [time_collect, time_draw] = debug_times.at(name); const auto& [time_collect, time_draw] = debug_times.at(name);
if (name == "total") output += Fx::b; if (name == "total") output += Fx::b;
output += Mv::l(29) + Mv::d(1) + ljust(name, 8) + ljust(to_string(time_collect), 12) + ljust(to_string(time_draw), 9); output += fmt::format(loc, "{mvLD}{name:5.5} {collect:12L} {draw:12L}",
"mvLD"_a = Mv::l(31) + Mv::d(1),
"name"_a = name,
"collect"_a = time_collect,
"draw"_a = time_draw
);
} }
} }
@ -579,19 +730,19 @@ namespace Runner {
<< Term::sync_end << flush; << Term::sync_end << flush;
} }
//* ----------------------------------------------- THREAD LOOP ----------------------------------------------- //* ----------------------------------------------- THREAD LOOP -----------------------------------------------
pthread_exit(NULL); return {};
} }
//? ------------------------------------------ Secondary thread end ----------------------------------------------- //? ------------------------------------------ Secondary thread end -----------------------------------------------
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values
void run(const string& box, const bool no_update, const bool force_redraw) { void run(const string& box, bool no_update, bool force_redraw) {
atomic_wait_for(active, true, 5000); atomic_wait_for(active, true, 5000);
if (active) { if (active) {
Logger::error("Stall in Runner thread, restarting!"); Logger::error("Stall in Runner thread, restarting!");
active = false; active = false;
// exit(1); // exit(1);
pthread_cancel(Runner::runner_id); pthread_cancel(Runner::runner_id);
if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) { if (pthread_create(&Runner::runner_id, nullptr, &Runner::_runner, nullptr) != 0) {
Global::exit_error_msg = "Failed to re-create _runner thread!"; Global::exit_error_msg = "Failed to re-create _runner thread!";
clean_quit(1); clean_quit(1);
} }
@ -679,19 +830,19 @@ int main(int argc, char **argv) {
//? Setup paths for config, log and user themes //? Setup paths for config, log and user themes
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
if (std::getenv(env) != NULL and access(std::getenv(env), W_OK) != -1) { if (std::getenv(env) != nullptr and access(std::getenv(env), W_OK) != -1) {
Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
break; break;
} }
} }
if (Config::conf_dir.empty()) { if (Config::conf_dir.empty()) {
cout << "WARNING: Could not get path user HOME folder.\n" fmt::println("WARNING: Could not get path user HOME folder.\n"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl; "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this.");
} }
else { else {
if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) { if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) {
cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n" fmt::println("WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl; "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this.");
} }
else { else {
Config::conf_file = Config::conf_dir / "btop.conf"; Config::conf_file = Config::conf_dir / "btop.conf";
@ -746,17 +897,17 @@ int main(int argc, char **argv) {
} }
//? Try to find and set a UTF-8 locale //? Try to find and set a UTF-8 locale
if (std::setlocale(LC_ALL, "") != NULL and not s_contains((string)std::setlocale(LC_ALL, ""), ";") if (std::setlocale(LC_ALL, "") != nullptr and not s_contains((string)std::setlocale(LC_ALL, ""), ";")
and str_to_upper(s_replace((string)std::setlocale(LC_ALL, ""), "-", "")).ends_with("UTF8")) { and str_to_upper(s_replace((string)std::setlocale(LC_ALL, ""), "-", "")).ends_with("UTF8")) {
Logger::debug("Using locale " + (string)std::setlocale(LC_ALL, "")); Logger::debug("Using locale " + (string)std::setlocale(LC_ALL, ""));
} }
else { else {
string found; string found;
bool set_failure = false; bool set_failure{}; // defaults to false
for (const auto loc_env : array{"LANG", "LC_ALL"}) { for (const auto loc_env : array{"LANG", "LC_ALL"}) {
if (std::getenv(loc_env) != NULL and str_to_upper(s_replace((string)std::getenv(loc_env), "-", "")).ends_with("UTF8")) { if (std::getenv(loc_env) != nullptr and str_to_upper(s_replace((string)std::getenv(loc_env), "-", "")).ends_with("UTF8")) {
found = std::getenv(loc_env); found = std::getenv(loc_env);
if (std::setlocale(LC_ALL, found.c_str()) == NULL) { if (std::setlocale(LC_ALL, found.c_str()) == nullptr) {
set_failure = true; set_failure = true;
Logger::warning("Failed to set locale " + found + " continuing anyway."); Logger::warning("Failed to set locale " + found + " continuing anyway.");
} }
@ -769,7 +920,7 @@ int main(int argc, char **argv) {
for (auto& l : ssplit(loc, ';')) { for (auto& l : ssplit(loc, ';')) {
if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) { if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) {
found = l.substr(l.find('=') + 1); found = l.substr(l.find('=') + 1);
if (std::setlocale(LC_ALL, found.c_str()) != NULL) { if (std::setlocale(LC_ALL, found.c_str()) != nullptr) {
break; break;
} }
} }
@ -790,10 +941,10 @@ int main(int argc, char **argv) {
if (cur_locale.empty()) { if (cur_locale.empty()) {
Logger::warning("No UTF-8 locale detected! Some symbols might not display correctly."); Logger::warning("No UTF-8 locale detected! Some symbols might not display correctly.");
} }
else if (std::setlocale(LC_ALL, string(cur_locale + ".UTF-8").c_str()) != NULL) { else if (std::setlocale(LC_ALL, string(cur_locale + ".UTF-8").c_str()) != nullptr) {
Logger::debug("Setting LC_ALL=" + cur_locale + ".UTF-8"); Logger::debug("Setting LC_ALL=" + cur_locale + ".UTF-8");
} }
else if(std::setlocale(LC_ALL, "en_US.UTF-8") != NULL) { else if(std::setlocale(LC_ALL, "en_US.UTF-8") != nullptr) {
Logger::debug("Setting LC_ALL=en_US.UTF-8"); Logger::debug("Setting LC_ALL=en_US.UTF-8");
} }
else { else {
@ -848,7 +999,7 @@ int main(int argc, char **argv) {
Shared::init(); Shared::init();
} }
catch (const std::exception& e) { catch (const std::exception& e) {
Global::exit_error_msg = "Exception in Shared::init() -> " + (string)e.what(); Global::exit_error_msg = "Exception in Shared::init() -> " + string{e.what()};
clean_quit(1); clean_quit(1);
} }
@ -865,7 +1016,7 @@ int main(int argc, char **argv) {
//? Start runner thread //? Start runner thread
Runner::thread_sem_init(); Runner::thread_sem_init();
if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) { if (pthread_create(&Runner::runner_id, nullptr, &Runner::_runner, nullptr) != 0) {
Global::exit_error_msg = "Failed to create _runner thread!"; Global::exit_error_msg = "Failed to create _runner thread!";
clean_quit(1); clean_quit(1);
} }
@ -960,7 +1111,7 @@ int main(int argc, char **argv) {
} }
} }
catch (const std::exception& e) { catch (const std::exception& e) {
Global::exit_error_msg = "Exception in main loop -> " + (string)e.what(); Global::exit_error_msg = "Exception in main loop -> " + string{e.what()};
clean_quit(1); clean_quit(1);
} }

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -17,18 +17,25 @@ tab-size = 4
*/ */
#include <array> #include <array>
#include <ranges>
#include <atomic> #include <atomic>
#include <fstream> #include <fstream>
#include <ranges>
#include <string_view> #include <string_view>
#include <btop_config.hpp> #include <fmt/core.h>
#include <btop_shared.hpp>
#include <btop_tools.hpp> #include "btop_config.hpp"
#include "btop_shared.hpp"
#include "btop_tools.hpp"
using std::array;
using std::atomic;
using std::string_view;
using std::array, std::atomic, std::string_view, std::string_literals::operator""s;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace rng = std::ranges; namespace rng = std::ranges;
using namespace std::literals;
using namespace Tools; using namespace Tools;
//* Functions and variables for reading and writing the btop config file //* Functions and variables for reading and writing the btop config file
@ -51,7 +58,7 @@ namespace Config {
{"presets", "#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.\n" {"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" "#* 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 withespace \" \" as separator between different presets.\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\""}, "#* 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" {"vim_keys", "#* Set to True to enable \"h,j,k,l,g,G\" keys for directional control in lists.\n"
@ -66,14 +73,16 @@ namespace Config {
"#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."}, "#* 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_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
#ifdef GPU_SUPPORT
{"graph_symbol_gpu", "# Graph symbol to use for graphs in gpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
#endif
{"graph_symbol_mem", "# 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_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\"."}, {"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."}, {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\" and \"gpu0\" through \"gpu5\", separate values with whitespace."},
{"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."}, {"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."},
@ -98,14 +107,18 @@ namespace Config {
{"proc_left", "#* Show proc box on left side of screen instead of right."}, {"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_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" {"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."}, "#* 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" {"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."}, "#* Select from a list of detected attributes from the options menu."},
#ifdef GPU_SUPPORT
{"show_gpu_info", "#* If gpu info should be shown in the cpu box. Available values = \"Auto\", \"On\" and \"Off\"."},
#endif
{"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."}, {"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_single_graph", "#* Set to True to completely disable the lower CPU graph."},
@ -185,21 +198,36 @@ namespace Config {
{"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."}, {"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" {"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."} "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."},
#ifdef GPU_SUPPORT
{"nvml_measure_pcie_speeds",
"#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards."},
{"gpu_mirror_graph", "#* Horizontally mirror the GPU graph."},
{"custom_gpu_name0", "#* Custom gpu0 model name, empty string to disable."},
{"custom_gpu_name1", "#* Custom gpu1 model name, empty string to disable."},
{"custom_gpu_name2", "#* Custom gpu2 model name, empty string to disable."},
{"custom_gpu_name3", "#* Custom gpu3 model name, empty string to disable."},
{"custom_gpu_name4", "#* Custom gpu4 model name, empty string to disable."},
{"custom_gpu_name5", "#* Custom gpu5 model name, empty string to disable."},
#endif
}; };
unordered_flat_map<string, string> strings = { unordered_flat_map<std::string_view, string> strings = {
{"color_theme", "Default"}, {"color_theme", "Default"},
{"shown_boxes", "cpu mem net proc"}, {"shown_boxes", "cpu mem net proc"},
{"graph_symbol", "braille"}, {"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"}, {"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_cpu", "default"},
{"graph_symbol_gpu", "default"},
{"graph_symbol_mem", "default"}, {"graph_symbol_mem", "default"},
{"graph_symbol_net", "default"}, {"graph_symbol_net", "default"},
{"graph_symbol_proc", "default"}, {"graph_symbol_proc", "default"},
{"proc_sorting", "cpu lazy"}, {"proc_sorting", "cpu lazy"},
{"cpu_graph_upper", "total"}, {"cpu_graph_upper", "Auto"},
{"cpu_graph_lower", "total"}, {"cpu_graph_lower", "Auto"},
{"cpu_sensor", "Auto"}, {"cpu_sensor", "Auto"},
{"selected_battery", "Auto"}, {"selected_battery", "Auto"},
{"cpu_core_map", ""}, {"cpu_core_map", ""},
@ -213,10 +241,19 @@ namespace Config {
{"proc_filter", ""}, {"proc_filter", ""},
{"proc_command", ""}, {"proc_command", ""},
{"selected_name", ""}, {"selected_name", ""},
#ifdef GPU_SUPPORT
{"custom_gpu_name0", ""},
{"custom_gpu_name1", ""},
{"custom_gpu_name2", ""},
{"custom_gpu_name3", ""},
{"custom_gpu_name4", ""},
{"custom_gpu_name5", ""},
{"show_gpu_info", "Auto"}
#endif
}; };
unordered_flat_map<string, string> stringsTmp; unordered_flat_map<std::string_view, string> stringsTmp;
unordered_flat_map<string, bool> bools = { unordered_flat_map<std::string_view, bool> bools = {
{"theme_background", true}, {"theme_background", true},
{"truecolor", true}, {"truecolor", true},
{"rounded_corners", true}, {"rounded_corners", true},
@ -229,7 +266,7 @@ namespace Config {
{"proc_cpu_graphs", true}, {"proc_cpu_graphs", true},
{"proc_info_smaps", false}, {"proc_info_smaps", false},
{"proc_left", false}, {"proc_left", false},
{"proc_filter_kernel", false}, {"proc_filter_kernel", false},
{"cpu_invert_lower", true}, {"cpu_invert_lower", true},
{"cpu_single_graph", false}, {"cpu_single_graph", false},
{"cpu_bottom", false}, {"cpu_bottom", false},
@ -261,10 +298,15 @@ namespace Config {
{"lowcolor", false}, {"lowcolor", false},
{"show_detailed", false}, {"show_detailed", false},
{"proc_filtering", false}, {"proc_filtering", false},
{"proc_aggregate", false},
#ifdef GPU_SUPPORT
{"nvml_measure_pcie_speeds", true},
{"gpu_mirror_graph", true},
#endif
}; };
unordered_flat_map<string, bool> boolsTmp; unordered_flat_map<std::string_view, bool> boolsTmp;
unordered_flat_map<string, int> ints = { unordered_flat_map<std::string_view, int> ints = {
{"update_ms", 2000}, {"update_ms", 2000},
{"net_download", 100}, {"net_download", 100},
{"net_upload", 100}, {"net_upload", 100},
@ -275,9 +317,9 @@ namespace Config {
{"proc_selected", 0}, {"proc_selected", 0},
{"proc_last_selected", 0}, {"proc_last_selected", 0},
}; };
unordered_flat_map<string, int> intsTmp; unordered_flat_map<std::string_view, int> intsTmp;
bool _locked(const string& name) { bool _locked(const std::string_view name) {
atomic_wait(writelock, true); atomic_wait(writelock, true);
if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end()) if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end())
write_new = true; write_new = true;
@ -311,7 +353,7 @@ namespace Config {
validError = "Malformatted preset in config value presets!"; validError = "Malformatted preset in config value presets!";
return false; return false;
} }
if (not is_in(vals.at(0), "cpu", "mem", "net", "proc")) { if (not is_in(vals.at(0), "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4", "gpu5")) {
validError = "Invalid box name in config value presets!"; validError = "Invalid box name in config value presets!";
return false; return false;
} }
@ -327,7 +369,7 @@ namespace Config {
new_presets.push_back(preset); new_presets.push_back(preset);
} }
preset_list = move(new_presets); preset_list = std::move(new_presets);
return true; return true;
} }
@ -364,7 +406,7 @@ namespace Config {
string validError; string validError;
bool intValid(const string& name, const string& value) { bool intValid(const std::string_view name, const string& value) {
int i_value; int i_value;
try { try {
i_value = stoi(value); i_value = stoi(value);
@ -378,7 +420,7 @@ namespace Config {
return false; return false;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
validError = (string)e.what(); validError = string{e.what()};
return false; return false;
} }
@ -394,7 +436,7 @@ namespace Config {
return false; return false;
} }
bool stringValid(const string& name, const string& value) { bool stringValid(const std::string_view name, const string& value) {
if (name == "log_level" and not v_contains(Logger::log_levels, value)) if (name == "log_level" and not v_contains(Logger::log_levels, value))
validError = "Invalid log_level: " + value; validError = "Invalid log_level: " + value;
@ -402,11 +444,16 @@ namespace Config {
validError = "Invalid graph symbol identifier: " + value; validError = "Invalid graph symbol identifier: " + value;
else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value))) else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value)))
validError = "Invalid graph symbol identifier for" + name + ": " + value; validError = fmt::format("Invalid graph symbol identifier for {}: {}", name, value);
else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) else if (name == "shown_boxes" and not value.empty() and not check_boxes(value))
validError = "Invalid box name(s) in shown_boxes!"; validError = "Invalid box name(s) in shown_boxes!";
#ifdef GPU_SUPPORT
else if (name == "show_gpu_info" and not v_contains(show_gpu_values, value))
validError = "Invalid value for show_gpu_info: " + value;
#endif
else if (name == "presets" and not presetsValid(value)) else if (name == "presets" and not presetsValid(value))
return false; return false;
@ -451,7 +498,7 @@ namespace Config {
return false; return false;
} }
string getAsString(const string& name) { string getAsString(const std::string_view name) {
if (bools.contains(name)) if (bools.contains(name))
return (bools.at(name) ? "True" : "False"); return (bools.at(name) ? "True" : "False");
else if (ints.contains(name)) else if (ints.contains(name))
@ -461,7 +508,7 @@ namespace Config {
return ""; return "";
} }
void flip(const string& name) { void flip(const std::string_view name) {
if (_locked(name)) { if (_locked(name)) {
if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name); if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name);
else boolsTmp.insert_or_assign(name, (not bools.at(name))); else boolsTmp.insert_or_assign(name, (not bools.at(name)));
@ -498,7 +545,7 @@ namespace Config {
boolsTmp.clear(); boolsTmp.clear();
} }
catch (const std::exception& e) { catch (const std::exception& e) {
Global::exit_error_msg = "Exception during Config::unlock() : " + (string)e.what(); Global::exit_error_msg = "Exception during Config::unlock() : " + string{e.what()};
clean_quit(1); clean_quit(1);
} }
@ -509,8 +556,15 @@ namespace Config {
auto new_boxes = ssplit(boxes); auto new_boxes = ssplit(boxes);
for (auto& box : new_boxes) { for (auto& box : new_boxes) {
if (not v_contains(valid_boxes, box)) return false; if (not v_contains(valid_boxes, box)) return false;
#ifdef GPU_SUPPORT
if (box.starts_with("gpu")) {
size_t gpu_num = stoi(box.substr(3));
if (gpu_num == 0) gpu_num = 5;
if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false;
}
#endif
} }
current_boxes = move(new_boxes); current_boxes = std::move(new_boxes);
return true; return true;
} }

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -20,10 +20,13 @@ tab-size = 4
#include <string> #include <string>
#include <vector> #include <vector>
#include <robin_hood.h>
#include <filesystem> #include <filesystem>
using std::string, std::vector, robin_hood::unordered_flat_map; #include <robin_hood.h>
using std::string;
using std::vector;
using robin_hood::unordered_flat_map;
//* Functions and variables for reading and writing the btop config file //* Functions and variables for reading and writing the btop config file
namespace Config { namespace Config {
@ -31,18 +34,25 @@ namespace Config {
extern std::filesystem::path conf_dir; extern std::filesystem::path conf_dir;
extern std::filesystem::path conf_file; extern std::filesystem::path conf_file;
extern unordered_flat_map<string, string> strings; extern unordered_flat_map<std::string_view, string> strings;
extern unordered_flat_map<string, string> stringsTmp; extern unordered_flat_map<std::string_view, string> stringsTmp;
extern unordered_flat_map<string, bool> bools; extern unordered_flat_map<std::string_view, bool> bools;
extern unordered_flat_map<string, bool> boolsTmp; extern unordered_flat_map<std::string_view, bool> boolsTmp;
extern unordered_flat_map<string, int> ints; extern unordered_flat_map<std::string_view, int> ints;
extern unordered_flat_map<string, int> intsTmp; extern unordered_flat_map<std::string_view, int> intsTmp;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" }; const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" }; const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" };
const vector<string> valid_boxes = { "cpu", "mem", "net", "proc" }; const vector<string> valid_boxes = {
"cpu", "mem", "net", "proc"
#ifdef GPU_SUPPORT
,"gpu0", "gpu1", "gpu2", "gpu3", "gpu4", "gpu5"
#endif
};
const vector<string> temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" }; const vector<string> temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" };
#ifdef GPU_SUPPORT
const vector<string> show_gpu_values = { "Auto", "On", "Off" };
#endif
extern vector<string> current_boxes; extern vector<string> current_boxes;
extern vector<string> preset_list; extern vector<string> preset_list;
extern vector<string> available_batteries; extern vector<string> available_batteries;
@ -60,44 +70,44 @@ namespace Config {
//* Apply selected preset //* Apply selected preset
void apply_preset(const string& preset); void apply_preset(const string& preset);
bool _locked(const string& name); bool _locked(const std::string_view name);
//* Return bool for config key <name> //* Return bool for config key <name>
inline const bool& getB(const string& name) { return bools.at(name); } inline bool getB(const std::string_view name) { return bools.at(name); }
//* Return integer for config key <name> //* Return integer for config key <name>
inline const int& getI(const string& name) { return ints.at(name); } inline const int& getI(const std::string_view name) { return ints.at(name); }
//* Return string for config key <name> //* Return string for config key <name>
inline const string& getS(const string& name) { return strings.at(name); } inline const string& getS(const std::string_view name) { return strings.at(name); }
string getAsString(const string& name); string getAsString(const std::string_view name);
extern string validError; extern string validError;
bool intValid(const string& name, const string& value); bool intValid(const std::string_view name, const string& value);
bool stringValid(const string& name, const string& value); bool stringValid(const std::string_view name, const string& value);
//* Set config key <name> to bool <value> //* Set config key <name> to bool <value>
inline void set(const string& name, const bool& value) { inline void set(const std::string_view name, bool value) {
if (_locked(name)) boolsTmp.insert_or_assign(name, value); if (_locked(name)) boolsTmp.insert_or_assign(name, value);
else bools.at(name) = value; else bools.at(name) = value;
} }
//* Set config key <name> to int <value> //* Set config key <name> to int <value>
inline void set(const string& name, const int& value) { inline void set(const std::string_view name, const int& value) {
if (_locked(name)) intsTmp.insert_or_assign(name, value); if (_locked(name)) intsTmp.insert_or_assign(name, value);
else ints.at(name) = value; else ints.at(name) = value;
} }
//* Set config key <name> to string <value> //* Set config key <name> to string <value>
inline void set(const string& name, const string& value) { inline void set(const std::string_view name, const string& value) {
if (_locked(name)) stringsTmp.insert_or_assign(name, value); if (_locked(name)) stringsTmp.insert_or_assign(name, value);
else strings.at(name) = value; else strings.at(name) = value;
} }
//* Flip config key bool <name> //* Flip config key bool <name>
void flip(const string& name); void flip(const std::string_view name);
//* Lock config and cache changes until unlocked //* Lock config and cache changes until unlocked
void lock(); void lock();
@ -111,10 +121,3 @@ namespace Config {
//* Write the config file to disk //* Write the config file to disk
void write(); void write();
} }

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,11 @@ tab-size = 4
#include <robin_hood.h> #include <robin_hood.h>
#include <deque> #include <deque>
using std::string, std::array, std::vector, robin_hood::unordered_flat_map, std::deque; using robin_hood::unordered_flat_map;
using std::array;
using std::deque;
using std::string;
using std::vector;
namespace Symbols { namespace Symbols {
const string h_line = ""; const string h_line = "";
@ -62,8 +66,8 @@ namespace Draw {
//* An editable text field //* An editable text field
class TextEdit { class TextEdit {
size_t pos = 0; size_t pos{}; // defaults to 0
size_t upos = 0; size_t upos{}; // defaults to 0
bool numeric; bool numeric;
public: public:
string text; string text;
@ -75,9 +79,11 @@ namespace Draw {
}; };
//* Create a box and return as a string //* Create a box and return as a string
string createBox(const int x, const int y, const int width, const int height, string line_color="", const bool fill=false, const string title="", const string title2="", const int num=0); string createBox(const int x, const int y, const int width,
const int height, string line_color = "", bool fill = false,
const string title = "", const string title2 = "", const int num = 0);
bool update_clock(bool force=false); bool update_clock(bool force = false);
//* Class holding a percentage meter //* Class holding a percentage meter
class Meter { class Meter {
@ -87,7 +93,7 @@ namespace Draw {
array<string, 101> cache; array<string, 101> cache;
public: public:
Meter(); Meter();
Meter(const int width, const string& color_gradient, const bool invert = false); Meter(const int width, const string& color_gradient, bool invert = false);
//* Return a string representation of the meter with given value //* Return a string representation of the meter with given value
string operator()(int value); string operator()(int value);
@ -109,18 +115,15 @@ namespace Draw {
public: public:
Graph(); Graph();
Graph( int width, Graph(int width, int height,
int height, const string& color_gradient,
const string& color_gradient, const deque<long long>& data,
const deque<long long>& data, const string& symbol="default",
const string& symbol="default", bool invert=false, bool no_zero=false,
bool invert=false, long long max_value=0, long long offset=0);
bool no_zero=false,
long long max_value=0,
long long offset=0);
//* Add last value from back of <data> and return string representation of graph //* Add last value from back of <data> and return string representation of graph
string& operator()(const deque<long long>& data, const bool data_same=false); string& operator()(const deque<long long>& data, bool data_same=false);
//* Return string representation of graph //* Return string representation of graph
string& operator()(); string& operator()();
@ -134,4 +137,4 @@ namespace Proc {
extern Draw::TextEdit filter; extern Draw::TextEdit filter;
extern unordered_flat_map<size_t, Draw::Graph> p_graphs; extern unordered_flat_map<size_t, Draw::Graph> p_graphs;
extern unordered_flat_map<size_t, int> p_counters; extern unordered_flat_map<size_t, int> p_counters;
} }

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -21,17 +21,29 @@ tab-size = 4
#include <vector> #include <vector>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <btop_input.hpp>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_menu.hpp>
#include <btop_draw.hpp>
#include <signal.h> #include <signal.h>
#include <utility>
#include "btop_input.hpp"
#include "btop_tools.hpp"
#include "btop_config.hpp"
#include "btop_shared.hpp"
#include "btop_menu.hpp"
#include "btop_draw.hpp"
#include "btop_input.hpp"
#include "btop_tools.hpp"
#include "btop_config.hpp"
#include "btop_shared.hpp"
#include "btop_menu.hpp"
#include "btop_draw.hpp"
using std::cin;
using std::cin, std::vector, std::string_literals::operator""s;
using namespace Tools; using namespace Tools;
using namespace std::literals; // for operator""s
namespace rng = std::ranges; namespace rng = std::ranges;
namespace Input { namespace Input {
@ -52,9 +64,13 @@ namespace Input {
{"[C", "right"}, {"[C", "right"},
{"OC", "right"}, {"OC", "right"},
{"[2~", "insert"}, {"[2~", "insert"},
{"[4h", "insert"},
{"[3~", "delete"}, {"[3~", "delete"},
{"[P", "delete"},
{"[H", "home"}, {"[H", "home"},
{"[1~", "home"},
{"[F", "end"}, {"[F", "end"},
{"[4~", "end"},
{"[5~", "page_up"}, {"[5~", "page_up"},
{"[6~", "page_down"}, {"[6~", "page_down"},
{"\t", "tab"}, {"\t", "tab"},
@ -235,8 +251,8 @@ namespace Input {
void process(const string& key) { void process(const string& key) {
if (key.empty()) return; if (key.empty()) return;
try { try {
auto& filtering = Config::getB("proc_filtering"); auto filtering = Config::getB("proc_filtering");
auto& vim_keys = Config::getB("vim_keys"); auto vim_keys = Config::getB("vim_keys");
auto help_key = (vim_keys ? "H" : "h"); auto help_key = (vim_keys ? "H" : "h");
auto kill_key = (vim_keys ? "K" : "k"); auto kill_key = (vim_keys ? "K" : "k");
//? Global input actions //? Global input actions
@ -257,11 +273,21 @@ namespace Input {
Menu::show(Menu::Menus::Options); Menu::show(Menu::Menus::Options);
return; return;
} }
else if (is_in(key, "1", "2", "3", "4")) { else if (key.size() == 1 and isint(key)) {
auto intKey = stoi(key);
#ifdef GPU_SUPPORT
static const array<string, 10> boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"};
if ((intKey == 0 and Gpu::gpu_names.size() < 5) or (intKey >= 5 and std::cmp_less(Gpu::gpu_names.size(), intKey - 4)))
return;
#else
static const array<string, 10> boxes = {"", "cpu", "mem", "net", "proc"};
if (intKey == 0 or intKey > 4)
return;
#endif
atomic_wait(Runner::active); atomic_wait(Runner::active);
Config::current_preset = -1; Config::current_preset = -1;
static const array<string, 4> boxes = {"cpu", "mem", "net", "proc"};
Config::toggle_box(boxes.at(std::stoi(key) - 1)); Config::toggle_box(boxes.at(intKey));
Draw::calcSizes(); Draw::calcSizes();
Runner::run("all", false, true); Runner::run("all", false, true);
return; return;
@ -294,12 +320,12 @@ namespace Input {
if (key == "enter" or key == "down") { if (key == "enter" or key == "down") {
Config::set("proc_filter", Proc::filter.text); Config::set("proc_filter", Proc::filter.text);
Config::set("proc_filtering", false); Config::set("proc_filtering", false);
old_filter.clear(); old_filter.clear();
if(key == "down"){ if(key == "down"){
process("down"); process("down");
return; return;
} }
} }
else if (key == "escape" or key == "mouse_click") { else if (key == "escape" or key == "mouse_click") {
Config::set("proc_filter", old_filter); Config::set("proc_filter", old_filter);
Config::set("proc_filtering", false); Config::set("proc_filtering", false);
@ -340,6 +366,9 @@ namespace Input {
else if (key == "c") else if (key == "c")
Config::flip("proc_per_core"); Config::flip("proc_per_core");
else if (key == "%")
Config::flip("proc_mem_bytes");
else if (key == "delete" and not Config::getS("proc_filter").empty()) else if (key == "delete" and not Config::getS("proc_filter").empty())
Config::set("proc_filter", ""s); Config::set("proc_filter", ""s);
@ -545,10 +574,9 @@ namespace Input {
} }
} }
catch (const std::exception& e) { catch (const std::exception& e) {
throw std::runtime_error("Input::process(\"" + key + "\") : " + (string)e.what()); throw std::runtime_error("Input::process(\"" + key + "\") : " + string{e.what()});
} }
} }
} }

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -24,10 +24,15 @@ tab-size = 4
#include <robin_hood.h> #include <robin_hood.h>
#include <deque> #include <deque>
using robin_hood::unordered_flat_map, std::array, std::string, std::atomic, std::deque; using robin_hood::unordered_flat_map;
using std::array;
using std::atomic;
using std::deque;
using std::string;
/* The input functions relies on the following std::cin options being set: /* The input functions relies on the following std::cin options being set:
cin.sync_with_stdio(false); cin.sync_with_stdio(false);
cin.tie(NULL); cin.tie(nullptr);
These will automatically be set when running Term::init() from btop_tools.cpp These will automatically be set when running Term::init() from btop_tools.cpp
*/ */
@ -65,4 +70,4 @@ namespace Input {
//* Process actions for input <key> //* Process actions for input <key>
void process(const string& key); void process(const string& key);
} }

View File

@ -19,44 +19,108 @@ tab-size = 4
#include <deque> #include <deque>
#include <robin_hood.h> #include <robin_hood.h>
#include <array> #include <array>
#include <ranges>
#include <signal.h> #include <signal.h>
#include <errno.h> #include <errno.h>
#include <cmath> #include <cmath>
#include <filesystem> #include <filesystem>
#include <btop_menu.hpp> #include "btop_menu.hpp"
#include <btop_tools.hpp> #include "btop_tools.hpp"
#include <btop_config.hpp> #include "btop_config.hpp"
#include <btop_theme.hpp> #include "btop_theme.hpp"
#include <btop_draw.hpp> #include "btop_draw.hpp"
#include <btop_shared.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 std::deque, robin_hood::unordered_flat_map, std::array, std::views::iota, std::ref, std::max, std::min, std::ceil, std::clamp;
using namespace Tools; using namespace Tools;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace rng = std::ranges;
namespace Menu { namespace Menu {
atomic<bool> active (false); atomic<bool> active (false);
string bg; string bg;
bool redraw = true; bool redraw{true};
int currentMenu = -1; int currentMenu = -1;
msgBox messageBox; msgBox messageBox;
int signalToSend = 0; int signalToSend{}; // defaults to 0
int signalKillRet = 0; int signalKillRet{}; // defaults to 0
const array<string, 32> P_Signals = { const array<string, 32> P_Signals = {
"0", "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", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
"SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2",
"SIGPIPE", "SIGALRM", "SIGTERM", "16", "SIGCHLD", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT",
"SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP",
"SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
"SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGPWR", "SIGSYS" "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; unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
@ -109,6 +173,7 @@ namespace Menu {
{"2", "Toggle MEM box."}, {"2", "Toggle MEM box."},
{"3", "Toggle NET box."}, {"3", "Toggle NET box."},
{"4", "Toggle PROC box."}, {"4", "Toggle PROC box."},
{"5", "Toggle GPU box."},
{"d", "Toggle disks view in MEM box."}, {"d", "Toggle disks view in MEM box."},
{"F2, o", "Shows options."}, {"F2, o", "Shows options."},
{"F1, ?, h", "Shows this window."}, {"F1, ?, h", "Shows this window."},
@ -131,6 +196,7 @@ namespace Menu {
{"c", "Toggle per-core cpu usage of processes."}, {"c", "Toggle per-core cpu usage of processes."},
{"r", "Reverse sorting order in processes box."}, {"r", "Reverse sorting order in processes box."},
{"e", "Toggle processes tree view."}, {"e", "Toggle processes tree view."},
{"%", "Toggles memory display mode in processes box."},
{"Selected +, -", "Expand/collapse the selected process in tree view."}, {"Selected +, -", "Expand/collapse the selected process in tree view."},
{"Selected t", "Terminate selected process with SIGTERM - 15."}, {"Selected t", "Terminate selected process with SIGTERM - 15."},
{"Selected k", "Kill selected process with SIGKILL - 9."}, {"Selected k", "Kill selected process with SIGKILL - 9."},
@ -197,7 +263,7 @@ namespace Menu {
"P=(0 or 1) for alternate positions.", "P=(0 or 1) for alternate positions.",
"G=graph symbol to use for box.", "G=graph symbol to use for box.",
"", "",
"Use withespace \" \" as separator between", "Use whitespace \" \" as separator between",
"different presets.", "different presets.",
"", "",
"Example:", "Example:",
@ -206,6 +272,9 @@ namespace Menu {
"Manually set which boxes to show.", "Manually set which boxes to show.",
"", "",
"Available values are \"cpu mem net proc\".", "Available values are \"cpu mem net proc\".",
#ifdef GPU_SUPPORT
"Or \"gpu0\" through \"gpu5\" for GPU boxes.",
#endif
"Separate values with whitespace.", "Separate values with whitespace.",
"", "",
"Toggle between presets with key \"p\"."}, "Toggle between presets with key \"p\"."},
@ -308,23 +377,49 @@ namespace Menu {
{"cpu_graph_upper", {"cpu_graph_upper",
"Cpu upper graph.", "Cpu upper graph.",
"", "",
"Sets the CPU stat shown in upper half of", "Sets the CPU/GPU stat shown in upper half of",
"the CPU graph.", "the CPU graph.",
"", "",
"\"total\" = Total cpu usage.", "CPU:",
"\"total\" = Total cpu usage. (Auto)",
"\"user\" = User mode cpu usage.", "\"user\" = User mode cpu usage.",
"\"system\" = Kernel mode cpu usage.", "\"system\" = Kernel mode cpu usage.",
"+ more depending on kernel."}, "+ 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_graph_lower",
"Cpu lower graph.", "Cpu lower graph.",
"", "",
"Sets the CPU stat shown in lower half of", "Sets the CPU/GPU stat shown in lower half of",
"the CPU graph.", "the CPU graph.",
"", "",
"CPU:",
"\"total\" = Total cpu usage.", "\"total\" = Total cpu usage.",
"\"user\" = User mode cpu usage.", "\"user\" = User mode cpu usage.",
"\"system\" = Kernel mode cpu usage.", "\"system\" = Kernel mode cpu usage.",
"+ more depending on kernel."}, "+ 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", {"cpu_invert_lower",
"Toggles orientation of the lower CPU graph.", "Toggles orientation of the lower CPU graph.",
"", "",
@ -336,12 +431,24 @@ namespace Menu {
"to fit to box height.", "to fit to box height.",
"", "",
"True or False."}, "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", {"check_temp",
"Enable cpu temperature reporting.", "Enable cpu temperature reporting.",
"", "",
"True or False."}, "True or False."},
{"cpu_sensor", {"cpu_sensor",
"Cpu temperature sensor", "Cpu temperature sensor.",
"", "",
"Select the sensor that corresponds to", "Select the sensor that corresponds to",
"your cpu temperature.", "your cpu temperature.",
@ -381,7 +488,7 @@ namespace Menu {
"Rankine, 0 = abosulte zero, 1 degree change", "Rankine, 0 = abosulte zero, 1 degree change",
"equals 1 degree change in Fahrenheit."}, "equals 1 degree change in Fahrenheit."},
{"show_cpu_freq", {"show_cpu_freq",
"Show CPU frequency", "Show CPU frequency.",
"", "",
"Can cause slowdowns on systems with many", "Can cause slowdowns on systems with many",
"cores and certain kernel versions."}, "cores and certain kernel versions."},
@ -397,6 +504,50 @@ namespace Menu {
"", "",
"True or False."}, "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_below_net",
"Mem box location.", "Mem box location.",
@ -583,6 +734,11 @@ namespace Menu {
"Set true to show processes grouped by", "Set true to show processes grouped by",
"parents with lines drawn between parent", "parents with lines drawn between parent",
"and child process."}, "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", {"proc_colors",
"Enable colors in process view.", "Enable colors in process view.",
"", "",
@ -611,19 +767,19 @@ namespace Menu {
"Show cpu graph for each process.", "Show cpu graph for each process.",
"", "",
"True or False"}, "True or False"},
{"proc_filter_kernel", {"proc_filter_kernel",
"(Linux) Filter kernel processes from output.", "(Linux) Filter kernel processes from output.",
"", "",
"Set to 'True' to filter out internal", "Set to 'True' to filter out internal",
"processes started by the Linux kernel."}, "processes started by the Linux kernel."},
} }
}; };
msgBox::msgBox() {} msgBox::msgBox() {}
msgBox::msgBox(int width, int boxtype, vector<string> content, string title) msgBox::msgBox(int width, int boxtype, vector<string> content, string title)
: width(width), boxtype(boxtype) { : width(width), boxtype(boxtype) {
const auto& tty_mode = Config::getB("tty_mode"); auto tty_mode = Config::getB("tty_mode");
const auto& rounded = Config::getB("rounded_corners"); auto rounded = Config::getB("rounded_corners");
const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up); 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& 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& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
@ -712,8 +868,11 @@ namespace Menu {
}; };
int signalChoose(const string& key) { 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")); auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
static int x = 0, y = 0, selected_signal = -1; static int x{}; // defaults to 0
static int y{}; // defaults to 0
static int selected_signal = -1;
if (bg.empty()) selected_signal = -1; if (bg.empty()) selected_signal = -1;
auto& out = Global::overlay; auto& out = Global::overlay;
int retval = Changed; int retval = Changed;
@ -839,7 +998,7 @@ namespace Menu {
} }
int signalSend(const string& key) { 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")); 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 (s_pid == 0) return Closed;
if (redraw) { if (redraw) {
atomic_wait(Runner::active); atomic_wait(Runner::active);
@ -912,10 +1071,11 @@ namespace Menu {
int mainMenu(const string& key) { int mainMenu(const string& key) {
enum MenuItems { Options, Help, Quit }; enum MenuItems { Options, Help, Quit };
static int y = 0, selected = 0; static int y{}; // defaults to 0
static int selected{}; // defaults to 0
static vector<string> colors_selected; static vector<string> colors_selected;
static vector<string> colors_normal; static vector<string> colors_normal;
auto& tty_mode = Config::getB("tty_mode"); auto tty_mode = Config::getB("tty_mode");
if (bg.empty()) selected = 0; if (bg.empty()) selected = 0;
int retval = Changed; int retval = Changed;
@ -969,7 +1129,6 @@ namespace Menu {
retval = NoChange; retval = NoChange;
} }
if (retval == Changed) { if (retval == Changed) {
auto& out = Global::overlay; auto& out = Global::overlay;
out = bg + Fx::reset + Fx::b; out = bg + Fx::reset + Fx::b;
@ -986,14 +1145,23 @@ namespace Menu {
out += Fx::reset; out += Fx::reset;
} }
return (redraw ? Changed : retval); return (redraw ? Changed : retval);
} }
int optionsMenu(const string& key) { int optionsMenu(const string& key) {
enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable}; enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable};
static int y = 0, x = 0, height = 0, page = 0, pages = 0, selected = 0, select_max = 0, item_height = 0, selected_cat = 0, max_items = 0, last_sel = 0; static int y{}; // defaults to 0
static bool editing = false; 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 Draw::TextEdit editor;
static string warnings; static string warnings;
static bitset<8> selPred; static bitset<8> selPred;
@ -1011,9 +1179,13 @@ namespace Menu {
{"cpu_graph_lower", std::cref(Cpu::available_fields)}, {"cpu_graph_lower", std::cref(Cpu::available_fields)},
{"cpu_sensor", std::cref(Cpu::available_sensors)}, {"cpu_sensor", std::cref(Cpu::available_sensors)},
{"selected_battery", std::cref(Config::available_batteries)}, {"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 tty_mode = Config::getB("tty_mode");
auto& vim_keys = Config::getB("vim_keys"); auto vim_keys = Config::getB("vim_keys");
if (max_items == 0) { if (max_items == 0) {
for (const auto& cat : categories) { for (const auto& cat : categories) {
if ((int)cat.size() > max_items) max_items = cat.size(); if ((int)cat.size() > max_items) max_items = cat.size();
@ -1025,9 +1197,9 @@ namespace Menu {
Theme::updateThemes(); Theme::updateThemes();
} }
int retval = Changed; int retval = Changed;
bool recollect = false; bool recollect{}; // defaults to false
bool screen_redraw = false; bool screen_redraw{}; // defaults to false
bool theme_refresh = false; bool theme_refresh{}; // defaults to false
//? Draw background if needed else process input //? Draw background if needed else process input
if (redraw) { if (redraw) {
@ -1062,7 +1234,8 @@ namespace Menu {
const auto& option = categories[selected_cat][item_height * page + selected][0]; const auto& option = categories[selected_cat][item_height * page + selected][0];
if (selPred.test(isString) and Config::stringValid(option, editor.text)) { if (selPred.test(isString) and Config::stringValid(option, editor.text)) {
Config::set(option, editor.text); Config::set(option, editor.text);
if (option == "custom_cpu_name") screen_redraw = true; if (option == "custom_cpu_name" or option.starts_with("custom_gpu_name"))
screen_redraw = true;
else if (is_in(option, "shown_boxes", "presets")) { else if (is_in(option, "shown_boxes", "presets")) {
screen_redraw = true; screen_redraw = true;
Config::current_preset = -1; Config::current_preset = -1;
@ -1143,7 +1316,7 @@ namespace Menu {
if (--selected_cat < 0) selected_cat = (int)categories.size() - 1; if (--selected_cat < 0) selected_cat = (int)categories.size() - 1;
page = selected = 0; page = selected = 0;
} }
else if (is_in(key, "1", "2", "3", "4", "5") or key.starts_with("select_cat_")) { else if (is_in(key, "1", "2", "3", "4", "5", "6") or key.starts_with("select_cat_")) {
selected_cat = key.back() - '0' - 1; selected_cat = key.back() - '0' - 1;
page = selected = 0; page = selected = 0;
} }
@ -1195,7 +1368,7 @@ namespace Menu {
Logger::set(optList.at(i)); Logger::set(optList.at(i));
Logger::info("Logger set to " + optList.at(i)); Logger::info("Logger set to " + optList.at(i));
} }
else if (is_in(option, "proc_sorting", "cpu_sensor") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) 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; screen_redraw = true;
} }
else else
@ -1241,11 +1414,19 @@ namespace Menu {
//? Category buttons //? Category buttons
out += Mv::to(y+7, x+4); 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"}) { for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) {
#endif
out += Fx::b + (i == selected_cat out += Fx::b + (i == selected_cat
? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']' ? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']'
: Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ') : Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ')
#ifdef GPU_SUPPORT
+ Mv::r(7);
#else
+ Mv::r(10); + Mv::r(10);
#endif
if (string button_name = "select_cat_" + to_string(i + 1); not editing and not mouse_mappings.contains(button_name)) 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}; mouse_mappings[button_name] = {y+6, x+2 + 15*i, 3, 15};
i++; i++;
@ -1304,11 +1485,11 @@ namespace Menu {
optionsMenu(""); optionsMenu("");
} }
if (screen_redraw) { if (screen_redraw) {
auto overlay_bkp = move(Global::overlay); auto overlay_bkp = std::move(Global::overlay);
auto clock_bkp = move(Global::clock); auto clock_bkp = std::move(Global::clock);
Draw::calcSizes(); Draw::calcSizes();
Global::overlay = move(overlay_bkp); Global::overlay = std::move(overlay_bkp);
Global::clock = move(clock_bkp); Global::clock = std::move(clock_bkp);
recollect = true; recollect = true;
} }
if (recollect) { if (recollect) {
@ -1320,7 +1501,12 @@ namespace Menu {
} }
int helpMenu(const string& key) { int helpMenu(const string& key) {
static int y = 0, x = 0, height = 0, page = 0, pages = 0; 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; if (bg.empty()) page = 0;
int retval = Changed; int retval = Changed;

View File

@ -23,9 +23,12 @@ tab-size = 4
#include <vector> #include <vector>
#include <bitset> #include <bitset>
#include <btop_input.hpp> #include "btop_input.hpp"
using std::string, std::atomic, std::vector, std::bitset; using std::atomic;
using std::bitset;
using std::string;
using std::vector;
namespace Menu { namespace Menu {
@ -43,7 +46,12 @@ namespace Menu {
//? Strings in content vector is not checked for box width overflow //? Strings in content vector is not checked for box width overflow
class msgBox { class msgBox {
string box_contents, button_left, button_right; string box_contents, button_left, button_right;
int height = 0, width = 0, boxtype = 0, selected = 0, x = 0, y = 0; int height{}; // defaults to 0
int width{}; // defaults to 0
int boxtype{}; // defaults to 0
int selected{}; // defaults to 0
int x{}; // defaults to 0
int y{}; // defaults to 0
public: public:
enum BoxTypes { OK, YES_NO, NO_YES }; enum BoxTypes { OK, YES_NO, NO_YES };
enum msgReturn { enum msgReturn {

View File

@ -18,16 +18,28 @@ tab-size = 4
#include <ranges> #include <ranges>
#include <btop_shared.hpp> #include "btop_config.hpp"
#include <btop_tools.hpp> #include "btop_shared.hpp"
#include "btop_tools.hpp"
using std::string_literals::operator""s;
namespace rng = std::ranges; namespace rng = std::ranges;
using namespace Tools; using namespace Tools;
#ifdef GPU_SUPPORT
namespace Gpu {
vector<string> gpu_names;
vector<int> gpu_b_height_offsets;
unordered_flat_map<string, deque<long long>> shared_gpu_percent = {
{"gpu-average", {}},
{"gpu-vram-total", {}},
{"gpu-pwr-total", {}},
};
long long gpu_pwr_total_max;
}
#endif
namespace Proc { namespace Proc {
void proc_sorter(vector<proc_info>& proc_vec, const string& sorting, const bool reverse, const bool tree) { void proc_sorter(vector<proc_info>& proc_vec, const string& sorting, bool reverse, bool tree) {
if (reverse) { if (reverse) {
switch (v_index(sort_vector, sorting)) { switch (v_index(sort_vector, sorting)) {
case 0: rng::stable_sort(proc_vec, rng::less{}, &proc_info::pid); break; case 0: rng::stable_sort(proc_vec, rng::less{}, &proc_info::pid); break;
@ -71,7 +83,7 @@ namespace Proc {
} }
} }
void tree_sort(vector<tree_proc>& proc_vec, const string& sorting, const bool reverse, int& c_index, const int index_max, const bool collapsed) { void tree_sort(vector<tree_proc>& proc_vec, const string& sorting, bool reverse, int& c_index, const int index_max, bool collapsed) {
if (proc_vec.size() > 1) { if (proc_vec.size() > 1) {
if (reverse) { if (reverse) {
switch (v_index(sort_vector, sorting)) { switch (v_index(sort_vector, sorting)) {
@ -99,16 +111,17 @@ namespace Proc {
} }
} }
void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<tree_proc>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found, const bool no_update, const bool should_filter) { void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<tree_proc>& out_procs,
int cur_depth, bool collapsed, const string& filter, bool found, bool no_update, bool should_filter) {
auto cur_pos = out_procs.size(); auto cur_pos = out_procs.size();
bool filtering = false; bool filtering = false;
//? If filtering, include children of matching processes //? If filtering, include children of matching processes
if (not found and (should_filter or not filter.empty())) { if (not found and (should_filter or not filter.empty())) {
if (not s_contains(std::to_string(cur_proc.pid), filter) if (not s_contains(std::to_string(cur_proc.pid), filter)
and not s_contains(cur_proc.name, filter) and not s_contains_ic(cur_proc.name, filter)
and not s_contains(cur_proc.cmd, filter) and not s_contains_ic(cur_proc.cmd, filter)
and not s_contains(cur_proc.user, filter)) { and not s_contains_ic(cur_proc.user, filter)) {
filtering = true; filtering = true;
cur_proc.filtered = true; cur_proc.filtered = true;
filter_found++; filter_found++;
@ -132,7 +145,7 @@ namespace Proc {
std::string_view cmd_view = cur_proc.cmd; std::string_view cmd_view = cur_proc.cmd;
cmd_view = cmd_view.substr((size_t)0, std::min(cmd_view.find(' '), cmd_view.size())); cmd_view = cmd_view.substr((size_t)0, std::min(cmd_view.find(' '), cmd_view.size()));
cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size())); cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
cur_proc.short_cmd = (string)cmd_view; cur_proc.short_cmd = string{cmd_view};
} }
} }
else { else {
@ -140,12 +153,10 @@ namespace Proc {
} }
//? Recursive iteration over all children //? Recursive iteration over all children
int children = 0;
for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) {
if (collapsed and not filtering) { if (collapsed and not filtering) {
cur_proc.filtered = true; cur_proc.filtered = true;
} }
children++;
_tree_gen(p, in_procs, out_procs.back().children, cur_depth + 1, (collapsed or cur_proc.collapsed), filter, found, no_update, should_filter); _tree_gen(p, in_procs, out_procs.back().children, cur_depth + 1, (collapsed or cur_proc.collapsed), filter, found, no_update, should_filter);
@ -158,17 +169,24 @@ namespace Proc {
filter_found++; filter_found++;
p.filtered = true; p.filtered = true;
} }
else if (Config::getB("proc_aggregate")) {
cur_proc.cpu_p += p.cpu_p;
cur_proc.cpu_c += p.cpu_c;
cur_proc.mem += p.mem;
cur_proc.threads += p.threads;
}
} }
if (collapsed or filtering) { if (collapsed or filtering) {
return; return;
} }
//? Add tree terminator symbol if it's the last child in a sub-tree //? Add tree terminator symbol if it's the last child in a sub-tree
if (children > 0 and out_procs.back().children.back().entry.get().prefix.size() >= 8 and not out_procs.back().children.back().entry.get().prefix.ends_with("]─")) if (out_procs.back().children.size() > 0 and out_procs.back().children.back().entry.get().prefix.size() >= 8 and not out_procs.back().children.back().entry.get().prefix.ends_with("]─"))
out_procs.back().children.back().entry.get().prefix.replace(out_procs.back().children.back().entry.get().prefix.size() - 8, 8, " └─ "); out_procs.back().children.back().entry.get().prefix.replace(out_procs.back().children.back().entry.get().prefix.size() - 8, 8, " └─ ");
//? Add collapse/expand symbols if process have any children //? Add collapse/expand symbols if process have any children
out_procs.at(cur_pos).entry.get().prefix = ""s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); out_procs.at(cur_pos).entry.get().prefix = ""s * cur_depth + (out_procs.at(cur_pos).children.size() > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ ");
} }
} }

View File

@ -18,18 +18,26 @@ tab-size = 4
#pragma once #pragma once
#include <string> #include <array>
#include <vector>
#include <filesystem>
#include <atomic> #include <atomic>
#include <deque> #include <deque>
#include <robin_hood.h> #include <filesystem>
#include <array> #include <string>
#include <ifaddrs.h>
#include <tuple> #include <tuple>
#include <vector>
#include <ifaddrs.h>
#include <robin_hood.h>
#include <unistd.h> #include <unistd.h>
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array, std::tuple; using robin_hood::unordered_flat_map;
using std::array;
using std::atomic;
using std::deque;
using std::string;
using std::tuple;
using std::vector;
using namespace std::literals; // for operator""s
void term_resize(bool force=false); void term_resize(bool force=false);
void banner_gen(); void banner_gen();
@ -55,11 +63,12 @@ namespace Runner {
extern atomic<bool> reading; extern atomic<bool> reading;
extern atomic<bool> stopping; extern atomic<bool> stopping;
extern atomic<bool> redraw; extern atomic<bool> redraw;
extern atomic<bool> coreNum_reset;
extern pthread_t runner_id; extern pthread_t runner_id;
extern bool pause_output; extern bool pause_output;
extern string debug_bg; extern string debug_bg;
void run(const string& box="", const bool no_update=false, const bool force_redraw=false); void run(const string& box="", bool no_update = false, bool force_redraw = false);
void stop(); void stop();
} }
@ -77,6 +86,91 @@ namespace Shared {
} }
namespace Gpu {
#ifdef GPU_SUPPORT
extern vector<string> box;
extern int width, height, min_width, min_height;
extern vector<int> x_vec, y_vec;
extern vector<bool> redraw;
extern int shown;
extern vector<char> shown_panels;
extern vector<string> gpu_names;
extern vector<int> gpu_b_height_offsets;
extern long long gpu_pwr_total_max;
extern unordered_flat_map<string, deque<long long>> shared_gpu_percent; // averages, power/vram total
const array mem_names { "used"s, "free"s };
//* Container for process information // TODO
/*struct proc_info {
unsigned int pid;
unsigned long long mem;
};*/
//* Container for supported Gpu::*::collect() functions
struct gpu_info_supported {
bool gpu_utilization = true,
mem_utilization = true,
gpu_clock = true,
mem_clock = true,
pwr_usage = true,
pwr_state = true,
temp_info = true,
mem_total = true,
mem_used = true,
pcie_txrx = true;
};
//* Per-device container for GPU info
struct gpu_info {
unordered_flat_map<string, deque<long long>> gpu_percent = {
{"gpu-totals", {}},
{"gpu-vram-totals", {}},
{"gpu-pwr-totals", {}},
};
unsigned int gpu_clock_speed; // MHz
long long pwr_usage; // mW
long long pwr_max_usage = 255000;
long long pwr_state;
deque<long long> temp = {0};
long long temp_max = 110;
long long mem_total = 0;
long long mem_used = 0;
deque<long long> mem_utilization_percent = {0}; // TODO: properly handle GPUs that can't report some stats
long long mem_clock_speed = 0; // MHz
long long pcie_tx = 0; // KB/s
long long pcie_rx = 0;
gpu_info_supported supported_functions;
// vector<proc_info> graphics_processes = {}; // TODO
// vector<proc_info> compute_processes = {};
};
namespace Nvml {
extern bool shutdown();
}
namespace Rsmi {
extern bool shutdown();
}
//* Collect gpu stats and temperatures
auto collect(bool no_update = false) -> vector<gpu_info>&;
//* Draw contents of gpu box using <gpus> as source
string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same);
#else
struct gpu_info {
bool supported = false;
};
#endif
}
namespace Cpu { namespace Cpu {
extern string box; extern string box;
extern int x, y, width, height, min_width, min_height; extern int x, y, width, height, min_width, min_height;
@ -103,14 +197,14 @@ namespace Cpu {
vector<deque<long long>> core_percent; vector<deque<long long>> core_percent;
vector<deque<long long>> temp; vector<deque<long long>> temp;
long long temp_max = 0; long long temp_max = 0;
array<float, 3> load_avg; array<double, 3> load_avg;
}; };
//* Collect cpu stats and temperatures //* Collect cpu stats and temperatures
auto collect(const bool no_update=false) -> cpu_info&; auto collect(bool no_update = false) -> cpu_info&;
//* Draw contents of cpu box using <cpu> as source //* Draw contents of cpu box using <cpu> as source
string draw(const cpu_info& cpu, const bool force_redraw=false, const bool data_same=false); string draw(const cpu_info& cpu, const vector<Gpu::gpu_info>& gpu, bool force_redraw = false, bool data_same = false);
//* Parse /proc/cpu info for mapping of core ids //* Parse /proc/cpu info for mapping of core ids
auto get_core_mapping() -> unordered_flat_map<int, int>; auto get_core_mapping() -> unordered_flat_map<int, int>;
@ -124,17 +218,21 @@ namespace Mem {
extern string box; extern string box;
extern int x, y, width, height, min_width, min_height; extern int x, y, width, height, min_width, min_height;
extern bool has_swap, shown, redraw; extern bool has_swap, shown, redraw;
const array<string, 4> mem_names = {"used", "available", "cached", "free"}; const array mem_names { "used"s, "available"s, "cached"s, "free"s };
const array<string, 2> swap_names = {"swap_used", "swap_free"}; const array swap_names { "swap_used"s, "swap_free"s };
extern int disk_ios; extern int disk_ios;
struct disk_info { struct disk_info {
std::filesystem::path dev; std::filesystem::path dev;
string name; string name;
string fstype = ""; string fstype{}; // defaults to ""
std::filesystem::path stat = ""; std::filesystem::path stat{}; // defaults to ""
int64_t total = 0, used = 0, free = 0; int64_t total{}; // defaults to 0
int used_percent = 0, free_percent = 0; int64_t used{}; // defaults to 0
int64_t free{}; // defaults to 0
int used_percent{}; // defaults to 0
int free_percent{}; // defaults to 0
array<int64_t, 3> old_io = {0, 0, 0}; array<int64_t, 3> old_io = {0, 0, 0};
deque<long long> io_read = {}; deque<long long> io_read = {};
deque<long long> io_write = {}; deque<long long> io_write = {};
@ -156,10 +254,10 @@ namespace Mem {
uint64_t get_totalMem(); uint64_t get_totalMem();
//* Collect mem & disks stats //* Collect mem & disks stats
auto collect(const bool no_update=false) -> mem_info&; auto collect(bool no_update = false) -> mem_info&;
//* Draw contents of mem box using <mem> as source //* Draw contents of mem box using <mem> as source
string draw(const mem_info& mem, const bool force_redraw=false, const bool data_same=false); string draw(const mem_info& mem, bool force_redraw = false, bool data_same = false);
} }
@ -173,23 +271,29 @@ namespace Net {
extern unordered_flat_map<string, uint64_t> graph_max; extern unordered_flat_map<string, uint64_t> graph_max;
struct net_stat { struct net_stat {
uint64_t speed = 0, top = 0, total = 0, last = 0, offset = 0, rollover = 0; uint64_t speed{}; // defaults to 0
uint64_t top{}; // defaults to 0
uint64_t total{}; // defaults to 0
uint64_t last{}; // defaults to 0
uint64_t offset{}; // defaults to 0
uint64_t rollover{}; // defaults to 0
}; };
struct net_info { struct net_info {
unordered_flat_map<string, deque<long long>> bandwidth = { {"download", {}}, {"upload", {}} }; unordered_flat_map<string, deque<long long>> bandwidth = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, net_stat> stat = { {"download", {}}, {"upload", {}} }; unordered_flat_map<string, net_stat> stat = { {"download", {}}, {"upload", {}} };
string ipv4 = "", ipv6 = ""; string ipv4{}; // defaults to ""
bool connected = false; string ipv6{}; // defaults to ""
bool connected{}; // defaults to false
}; };
extern unordered_flat_map<string, net_info> current_net; extern unordered_flat_map<string, net_info> current_net;
//* Collect net upload/download stats //* Collect net upload/download stats
auto collect(const bool no_update=false) -> net_info&; auto collect(bool no_update=false) -> net_info&;
//* Draw contents of net box using <net> as source //* Draw contents of net box using <net> as source
string draw(const net_info& net, const bool force_redraw=false, const bool data_same=false); string draw(const net_info& net, bool force_redraw = false, bool data_same = false);
} }
namespace Proc { namespace Proc {
@ -232,25 +336,32 @@ namespace Proc {
//* Container for process information //* Container for process information
struct proc_info { struct proc_info {
size_t pid = 0; size_t pid{}; // defaults to 0
string name = "", cmd = ""; string name{}; // defaults to ""
string short_cmd = ""; string cmd{}; // defaults to ""
size_t threads = 0; string short_cmd{}; // defaults to ""
int name_offset = 0; size_t threads{}; // defaults to 0
string user = ""; int name_offset{}; // defaults to 0
uint64_t mem = 0; string user{}; // defaults to ""
double cpu_p = 0.0, cpu_c = 0.0; uint64_t mem{}; // defaults to 0
double cpu_p{}; // defaults to = 0.0
double cpu_c{}; // defaults to = 0.0
char state = '0'; char state = '0';
uint64_t p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0; int64_t p_nice{}; // defaults to 0
string prefix = ""; uint64_t ppid{}; // defaults to 0
size_t depth = 0, tree_index = 0; uint64_t cpu_s{}; // defaults to 0
bool collapsed = false, filtered = false; uint64_t cpu_t{}; // defaults to 0
string prefix{}; // defaults to ""
size_t depth{}; // defaults to 0
size_t tree_index{}; // defaults to 0
bool collapsed{}; // defaults to false
bool filtered{}; // defaults to false
}; };
//* Container for process info box //* Container for process info box
struct detail_container { struct detail_container {
size_t last_pid = 0; size_t last_pid{}; // defaults to 0
bool skip_smaps = false; bool skip_smaps{}; // defaults to false
proc_info entry; proc_info entry;
string elapsed, parent, status, io_read, io_write, memory; string elapsed, parent, status, io_read, io_write, memory;
long long first_mem = -1; long long first_mem = -1;
@ -262,13 +373,13 @@ namespace Proc {
extern detail_container detailed; extern detail_container detailed;
//* Collect and sort process information from /proc //* Collect and sort process information from /proc
auto collect(const bool no_update=false) -> vector<proc_info>&; auto collect(bool no_update = false) -> vector<proc_info>&;
//* Update current selection and view, returns -1 if no change otherwise the current selection //* Update current selection and view, returns -1 if no change otherwise the current selection
int selection(const string& cmd_key); int selection(const string& cmd_key);
//* Draw contents of proc box using <plist> as data source //* Draw contents of proc box using <plist> as data source
string draw(const vector<proc_info>& plist, const bool force_redraw=false, const bool data_same=false); string draw(const vector<proc_info>& plist, bool force_redraw = false, bool data_same = false);
struct tree_proc { struct tree_proc {
std::reference_wrapper<proc_info> entry; std::reference_wrapper<proc_info> entry;
@ -276,11 +387,14 @@ namespace Proc {
}; };
//* Sort vector of proc_info's //* Sort vector of proc_info's
void proc_sorter(vector<proc_info>& proc_vec, const string& sorting, const bool reverse, const bool tree = false); void proc_sorter(vector<proc_info>& proc_vec, const string& sorting, bool reverse, bool tree = false);
//* Recursive sort of process tree //* Recursive sort of process tree
void tree_sort(vector<tree_proc>& proc_vec, const string& sorting, const bool reverse, int& c_index, const int index_max, const bool collapsed = false); void tree_sort(vector<tree_proc>& proc_vec, const string& sorting,
bool reverse, int& c_index, const int index_max, bool collapsed = false);
//* Generate process tree list //* Generate process tree list
void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<tree_proc>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false); void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<tree_proc>& out_procs,
int cur_depth, bool collapsed, const string& filter,
bool found = false, bool no_update = false, bool should_filter = false);
} }

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -17,20 +17,21 @@ tab-size = 4
*/ */
#include <cmath> #include <cmath>
#include <vector>
#include <ranges>
#include <algorithm>
#include <fstream> #include <fstream>
#include <unistd.h> #include <unistd.h>
#include <btop_tools.hpp> #include "btop_tools.hpp"
#include <btop_config.hpp> #include "btop_config.hpp"
#include <btop_theme.hpp> #include "btop_theme.hpp"
using std::round;
using std::stoi;
using std::to_string;
using std::vector;
using std::views::iota;
using std::round, std::vector, std::stoi, std::views::iota,
std::clamp, std::max, std::min, std::ceil, std::to_string;
using namespace Tools; using namespace Tools;
namespace rng = std::ranges;
namespace fs = std::filesystem; namespace fs = std::filesystem;
string Term::fg, Term::bg; string Term::fg, Term::bg;
@ -149,12 +150,14 @@ namespace Theme {
} }
} }
string hex_to_color(string hexa, const bool& t_to_256, const string& depth) { string hex_to_color(string hexa, bool t_to_256, const string& depth) {
if (hexa.size() > 1) { if (hexa.size() > 1) {
hexa.erase(0, 1); hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) { for (auto& c : hexa) {
Logger::error("Invalid hex value: " + hexa); if (not isxdigit(c)) {
return ""; Logger::error("Invalid hex value: " + hexa);
return "";
}
} }
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;"); string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
@ -186,7 +189,7 @@ namespace Theme {
return ""; return "";
} }
string dec_to_color(int r, int g, int b, const bool& t_to_256, const string& depth) { string dec_to_color(int r, int g, int b, bool t_to_256, const string& depth) {
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;"); string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
r = std::clamp(r, 0, 255); r = std::clamp(r, 0, 255);
g = std::clamp(g, 0, 255); g = std::clamp(g, 0, 255);
@ -200,14 +203,17 @@ namespace Theme {
array<int, 3> hex_to_dec(string hexa) { array<int, 3> hex_to_dec(string hexa) {
if (hexa.size() > 1) { if (hexa.size() > 1) {
hexa.erase(0, 1); hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) return array<int, 3>{-1, -1, -1}; for (auto& c : hexa) {
if (not isxdigit(c))
return array{-1, -1, -1};
}
if (hexa.size() == 2) { if (hexa.size() == 2) {
int h_int = stoi(hexa, nullptr, 16); int h_int = stoi(hexa, nullptr, 16);
return array<int, 3>{h_int, h_int, h_int}; return array{h_int, h_int, h_int};
} }
else if (hexa.size() == 6) { else if (hexa.size() == 6) {
return array<int, 3>{ return array{
stoi(hexa.substr(0, 2), nullptr, 16), stoi(hexa.substr(0, 2), nullptr, 16),
stoi(hexa.substr(2, 2), nullptr, 16), stoi(hexa.substr(2, 2), nullptr, 16),
stoi(hexa.substr(4, 2), nullptr, 16) stoi(hexa.substr(4, 2), nullptr, 16)
@ -221,7 +227,7 @@ namespace Theme {
void generateColors(const unordered_flat_map<string, string>& source) { void generateColors(const unordered_flat_map<string, string>& source) {
vector<string> t_rgb; vector<string> t_rgb;
string depth; string depth;
const bool& t_to_256 = Config::getB("lowcolor"); bool t_to_256 = Config::getB("lowcolor");
colors.clear(); rgbs.clear(); colors.clear(); rgbs.clear();
for (const auto& [name, color] : Default_theme) { for (const auto& [name, color] : Default_theme) {
if (name == "main_bg" and not Config::getB("theme_background")) { if (name == "main_bg" and not Config::getB("theme_background")) {
@ -247,11 +253,11 @@ namespace Theme {
} }
else if (not source.at(name).empty()) { else if (not source.at(name).empty()) {
t_rgb = ssplit(source.at(name)); t_rgb = ssplit(source.at(name));
if (t_rgb.size() != 3) if (t_rgb.size() != 3) {
Logger::error("Invalid RGB decimal value: \"" + source.at(name) + "\""); Logger::error("Invalid RGB decimal value: \"" + source.at(name) + "\"");
else { } else {
colors[name] = dec_to_color(stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2]), t_to_256, depth); colors[name] = dec_to_color(stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2]), t_to_256, depth);
rgbs[name] = array<int, 3>{stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2])}; rgbs[name] = array{stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2])};
} }
} }
@ -284,15 +290,16 @@ namespace Theme {
//* Generate color gradients from two or three colors, 101 values indexed 0-100 //* Generate color gradients from two or three colors, 101 values indexed 0-100
void generateGradients() { void generateGradients() {
gradients.clear(); gradients.clear();
const bool& t_to_256 = Config::getB("lowcolor"); bool t_to_256 = Config::getB("lowcolor");
//? Insert values for processes greyscale gradient and processes color gradient //? Insert values for processes greyscale gradient and processes color gradient
rgbs.insert({ { "proc_start", rgbs["main_fg"] }, rgbs.insert({
{ "proc_mid", {-1, -1, -1} }, { "proc_start", rgbs["main_fg"] },
{ "proc_end", rgbs["inactive_fg"] }, { "proc_mid", {-1, -1, -1} },
{ "proc_color_start", rgbs["inactive_fg"] }, { "proc_end", rgbs["inactive_fg"] },
{ "proc_color_mid", {-1, -1, -1} }, { "proc_color_start", rgbs["inactive_fg"] },
{ "proc_color_end", rgbs["process_start"] }, { "proc_color_mid", {-1, -1, -1} },
{ "proc_color_end", rgbs["process_start"] },
}); });
for (const auto& [name, source_arr] : rgbs) { for (const auto& [name, source_arr] : rgbs) {
@ -315,10 +322,10 @@ namespace Theme {
//? Split iteration in two passes of 50 + 51 instead of one pass of 101 if gradient has start, mid and end values defined //? Split iteration in two passes of 50 + 51 instead of one pass of 101 if gradient has start, mid and end values defined
int current_range = (input_colors[1][0] >= 0) ? 50 : 100; int current_range = (input_colors[1][0] >= 0) ? 50 : 100;
for (const int& rgb : iota(0, 3)) { for (int rgb : iota(0, 3)) {
int start = 0, offset = 0; int start = 0, offset = 0;
int end = (current_range == 50) ? 1 : 2; int end = (current_range == 50) ? 1 : 2;
for (const int& i : iota(0, 101)) { for (int i : iota(0, 101)) {
output_colors[i][rgb] = input_colors[start][rgb] + (i - offset) * (input_colors[end][rgb] - input_colors[start][rgb]) / current_range; output_colors[i][rgb] = input_colors[start][rgb] + (i - offset) * (input_colors[end][rgb] - input_colors[start][rgb]) / current_range;
//? Switch source arrays from start->mid to mid->end at 50 passes if mid is defined //? Switch source arrays from start->mid to mid->end at 50 passes if mid is defined
@ -353,7 +360,7 @@ namespace Theme {
const string base_name = rtrim(c.first, "_start"); const string base_name = rtrim(c.first, "_start");
string section = "_start"; string section = "_start";
int split = colors.at(base_name + "_mid").empty() ? 50 : 33; int split = colors.at(base_name + "_mid").empty() ? 50 : 33;
for (const int& i : iota(0, 101)) { for (int i : iota(0, 101)) {
gradients[base_name][i] = colors.at(base_name + section); gradients[base_name][i] = colors.at(base_name + section);
if (i == split) { if (i == split) {
section = (split == 33) ? "_mid" : "_end"; section = (split == 33) ? "_mid" : "_end";

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -18,12 +18,16 @@ tab-size = 4
#pragma once #pragma once
#include <string>
#include <robin_hood.h>
#include <array> #include <array>
#include <filesystem> #include <filesystem>
#include <string>
#include <vector>
#include <robin_hood.h>
using std::string, robin_hood::unordered_flat_map, std::array; using std::array;
using std::string;
using std::vector;
using robin_hood::unordered_flat_map;
namespace Theme { namespace Theme {
extern std::filesystem::path theme_dir; extern std::filesystem::path theme_dir;
@ -36,13 +40,13 @@ namespace Theme {
//* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale //* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale
//* t_to_256: [true|false] convert 24bit value to 256 color value //* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color //* depth: ["fg"|"bg"] for either a foreground color or a background color
string hex_to_color(string hexa, const bool& t_to_256=false, const string& depth="fg"); string hex_to_color(string hexa, bool t_to_256=false, const string& depth="fg");
//* Generate escape sequence for 24-bit or 256 color and return as a string //* Generate escape sequence for 24-bit or 256 color and return as a string
//* Args r: [0-255], g: [0-255], b: [0-255] //* Args r: [0-255], g: [0-255], b: [0-255]
//* t_to_256: [true|false] convert 24bit value to 256 color value //* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color //* depth: ["fg"|"bg"] for either a foreground color or a background color
string dec_to_color(int r, int g, int b, const bool& t_to_256=false, const string& depth="fg"); string dec_to_color(int r, int g, int b, bool t_to_256=false, const string& depth="fg");
//* Update list of paths for available themes //* Update list of paths for available themes
void updateThemes(); void updateThemes();
@ -63,4 +67,4 @@ namespace Theme {
//* Return array of red, green and blue in decimal for color <name> //* Return array of red, green and blue in decimal for color <name>
inline const std::array<int, 3>& dec(string name) { return rgbs.at(name); } inline const std::array<int, 3>& dec(string name) { return rgbs.at(name); }
} }

View File

@ -17,6 +17,7 @@ tab-size = 4
*/ */
#include <cmath> #include <cmath>
#include <codecvt>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <ctime> #include <ctime>
@ -24,18 +25,28 @@ tab-size = 4
#include <iomanip> #include <iomanip>
#include <utility> #include <utility>
#include <ranges> #include <ranges>
#include <robin_hood.h>
#include <widechar_width.hpp>
#include <unistd.h> #include <unistd.h>
#include <termios.h> #include <termios.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <btop_shared.hpp> #include "robin_hood.h"
#include <btop_tools.hpp> #include "widechar_width.hpp"
#include <btop_config.hpp> #include "btop_shared.hpp"
#include "btop_tools.hpp"
#include "btop_config.hpp"
using std::cin;
using std::cout;
using std::floor;
using std::flush;
using std::max;
using std::string_view;
using std::to_string;
using robin_hood::unordered_flat_map;
using namespace std::literals; // to use operator""s
using std::string_view, std::max, std::floor, std::to_string, std::cin, std::cout, std::flush, robin_hood::unordered_flat_map;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace rng = std::ranges; namespace rng = std::ranges;
@ -44,9 +55,9 @@ namespace rng = std::ranges;
//* Collection of escape codes and functions for terminal manipulation //* Collection of escape codes and functions for terminal manipulation
namespace Term { namespace Term {
atomic<bool> initialized = false; atomic<bool> initialized{}; // defaults to false
atomic<int> width = 0; atomic<int> width{}; // defaults to 0
atomic<int> height = 0; atomic<int> height{}; // defaults to 0
string current_tty; string current_tty;
namespace { namespace {
@ -69,7 +80,7 @@ namespace Term {
else settings.c_lflag &= ~(ICANON); else settings.c_lflag &= ~(ICANON);
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false; if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
if (on) setlinebuf(stdin); if (on) setlinebuf(stdin);
else setbuf(stdin, NULL); else setbuf(stdin, nullptr);
return true; return true;
} }
} }
@ -88,19 +99,31 @@ namespace Term {
} }
auto get_min_size(const string& boxes) -> array<int, 2> { auto get_min_size(const string& boxes) -> array<int, 2> {
const bool cpu = boxes.find("cpu") != string::npos; bool cpu = boxes.find("cpu") != string::npos;
const bool mem = boxes.find("mem") != string::npos; bool mem = boxes.find("mem") != string::npos;
const bool net = boxes.find("net") != string::npos; bool net = boxes.find("net") != string::npos;
const bool proc = boxes.find("proc") != string::npos; bool proc = boxes.find("proc") != string::npos;
int width = 0; #ifdef GPU_SUPPORT
int gpu = 0;
if (not Gpu::gpu_names.empty())
for (char i = '0'; i <= '5'; ++i)
gpu += (boxes.find(std::string("gpu") + i) != string::npos);
#endif
int width = 0;
if (mem) width = Mem::min_width; if (mem) width = Mem::min_width;
else if (net) width = Mem::min_width; else if (net) width = Mem::min_width;
width += (proc ? Proc::min_width : 0); width += (proc ? Proc::min_width : 0);
if (cpu and width < Cpu::min_width) width = Cpu::min_width; if (cpu and width < Cpu::min_width) width = Cpu::min_width;
#ifdef GPU_SUPPORT
if (gpu != 0 and width < Gpu::min_width) width = Gpu::min_width;
#endif
int height = (cpu ? Cpu::min_height : 0); int height = (cpu ? Cpu::min_height : 0);
if (proc) height += Proc::min_height; if (proc) height += Proc::min_height;
else height += (mem ? Mem::min_height : 0) + (net ? Net::min_height : 0); else height += (mem ? Mem::min_height : 0) + (net ? Net::min_height : 0);
#ifdef GPU_SUPPORT
height += Gpu::min_height*gpu;
#endif
return { width, height }; return { width, height };
} }
@ -110,15 +133,15 @@ namespace Term {
initialized = (bool)isatty(STDIN_FILENO); initialized = (bool)isatty(STDIN_FILENO);
if (initialized) { if (initialized) {
tcgetattr(STDIN_FILENO, &initial_settings); tcgetattr(STDIN_FILENO, &initial_settings);
current_tty = (ttyname(STDIN_FILENO) != NULL ? (string)ttyname(STDIN_FILENO) : "unknown"); current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast<string>(ttyname(STDIN_FILENO)) : "unknown");
//? Disable stream sync //? Disable stream sync
cin.sync_with_stdio(false); cin.sync_with_stdio(false);
cout.sync_with_stdio(false); cout.sync_with_stdio(false);
//? Disable stream ties //? Disable stream ties
cin.tie(NULL); cin.tie(nullptr);
cout.tie(NULL); cout.tie(nullptr);
echo(false); echo(false);
linebuffered(false); linebuffered(false);
refresh(); refresh();
@ -141,7 +164,7 @@ namespace Term {
//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
// ! Dsiabled due to issue when compiling with musl, reverted back to using regex // ! Disabled due to issue when compiling with musl, reverted back to using regex
// namespace Fx { // namespace Fx {
// string uncolor(const string& s) { // string uncolor(const string& s) {
// string out = s; // string out = s;
@ -195,8 +218,10 @@ namespace Tools {
return chars; return chars;
} }
string uresize(string str, const size_t len, const bool wide) { string uresize(string str, const size_t len, bool wide) {
if (len < 1 or str.empty()) return ""; if (len < 1 or str.empty())
return "";
if (wide) { if (wide) {
try { try {
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv; std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
@ -223,8 +248,10 @@ namespace Tools {
return str; return str;
} }
string luresize(string str, const size_t len, const bool wide) { string luresize(string str, const size_t len, bool wide) {
if (len < 1 or str.empty()) return ""; if (len < 1 or str.empty())
return "";
for (size_t x = 0, last_pos = 0, i = str.size() - 1; i > 0 ; i--) { for (size_t x = 0, last_pos = 0, i = str.size() - 1; i > 0 ; i--) {
if (wide and static_cast<unsigned char>(str.at(i)) > 0xef) { if (wide and static_cast<unsigned char>(str.at(i)) > 0xef) {
x += 2; x += 2;
@ -252,63 +279,82 @@ namespace Tools {
} }
string ltrim(const string& str, const string& t_str) { string ltrim(const string& str, const string& t_str) {
string_view str_v = str; std::string_view str_v{str};
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size()); while (str_v.starts_with(t_str))
return (string)str_v; str_v.remove_prefix(t_str.size());
return string{str_v};
} }
string rtrim(const string& str, const string& t_str) { string rtrim(const string& str, const string& t_str) {
string_view str_v = str; std::string_view str_v{str};
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size()); while (str_v.ends_with(t_str))
return (string)str_v; str_v.remove_suffix(t_str.size());
return string{str_v};
} }
auto ssplit(const string& str, const char& delim) -> vector<string> { auto ssplit(const string& str, const char& delim) -> vector<string> {
vector<string> out; vector<string> out;
for (const auto& s : str | rng::views::split(delim) for (const auto& s : str | rng::views::split(delim)
| rng::views::transform([](auto &&rng) { | rng::views::transform([](auto &&rng) {
return string_view(&*rng.begin(), rng::distance(rng)); return std::string_view(&*rng.begin(), rng::distance(rng));
})) { })) {
if (not s.empty()) out.emplace_back(s); if (not s.empty()) out.emplace_back(s);
} }
return out; return out;
} }
string ljust(string str, const size_t x, const bool utf, const bool wide, const bool limit) { string ljust(string str, const size_t x, bool utf, bool wide, bool limit) {
if (utf) { if (utf) {
if (limit and ulen(str, wide) > x) return uresize(str, x, wide); if (limit and ulen(str, wide) > x)
return uresize(str, x, wide);
return str + string(max((int)(x - ulen(str)), 0), ' '); return str + string(max((int)(x - ulen(str)), 0), ' ');
} }
else { else {
if (limit and str.size() > x) { str.resize(x); return str; } if (limit and str.size() > x) {
str.resize(x);
return str;
}
return str + string(max((int)(x - str.size()), 0), ' '); return str + string(max((int)(x - str.size()), 0), ' ');
} }
} }
string rjust(string str, const size_t x, const bool utf, const bool wide, const bool limit) { string rjust(string str, const size_t x, bool utf, bool wide, bool limit) {
if (utf) { if (utf) {
if (limit and ulen(str, wide) > x) return uresize(str, x, wide); if (limit and ulen(str, wide) > x)
return uresize(str, x, wide);
return string(max((int)(x - ulen(str)), 0), ' ') + str; return string(max((int)(x - ulen(str)), 0), ' ') + str;
} }
else { else {
if (limit and str.size() > x) { str.resize(x); return str; }; if (limit and str.size() > x) {
str.resize(x);
return str;
};
return string(max((int)(x - str.size()), 0), ' ') + str; return string(max((int)(x - str.size()), 0), ' ') + str;
} }
} }
string cjust(string str, const size_t x, const bool utf, const bool wide, const bool limit) { string cjust(string str, const size_t x, bool utf, bool wide, bool limit) {
if (utf) { if (utf) {
if (limit and ulen(str, wide) > x) return uresize(str, x, wide); if (limit and ulen(str, wide) > x)
return uresize(str, x, wide);
return string(max((int)ceil((double)(x - ulen(str)) / 2), 0), ' ') + str + string(max((int)floor((double)(x - ulen(str)) / 2), 0), ' '); return string(max((int)ceil((double)(x - ulen(str)) / 2), 0), ' ') + str + string(max((int)floor((double)(x - ulen(str)) / 2), 0), ' ');
} }
else { else {
if (limit and str.size() > x) { str.resize(x); return str; } if (limit and str.size() > x) {
str.resize(x);
return str;
}
return string(max((int)ceil((double)(x - str.size()) / 2), 0), ' ') + str + string(max((int)floor((double)(x - str.size()) / 2), 0), ' '); return string(max((int)ceil((double)(x - str.size()) / 2), 0), ' ') + str + string(max((int)floor((double)(x - str.size()) / 2), 0), ' ');
} }
} }
string trans(const string& str) { string trans(const string& str) {
string_view oldstr = str; std::string_view oldstr{str};
string newstr; string newstr;
newstr.reserve(str.size()); newstr.reserve(str.size());
for (size_t pos; (pos = oldstr.find(' ')) != string::npos;) { for (size_t pos; (pos = oldstr.find(' ')) != string::npos;) {
@ -318,7 +364,7 @@ namespace Tools {
newstr.append(Mv::r(x)); newstr.append(Mv::r(x));
oldstr.remove_prefix(pos + x); oldstr.remove_prefix(pos + x);
} }
return (newstr.empty()) ? str : newstr + (string)oldstr; return (newstr.empty()) ? str : newstr + string{oldstr};
} }
string sec_to_dhms(size_t seconds, bool no_days, bool no_seconds) { string sec_to_dhms(size_t seconds, bool no_days, bool no_seconds) {
@ -332,14 +378,37 @@ namespace Tools {
return out; return out;
} }
string floating_humanizer(uint64_t value, const bool shorten, size_t start, const bool bit, const bool per_second) { string floating_humanizer(uint64_t value, bool shorten, size_t start, bool bit, bool per_second) {
string out; string out;
const size_t mult = (bit) ? 8 : 1; const size_t mult = (bit) ? 8 : 1;
const bool mega = Config::getB("base_10_sizes"); bool mega = Config::getB("base_10_sizes");
static const array<string, 11> mebiUnits_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"};
static const array<string, 11> mebiUnits_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"}; // taking advantage of type deduction for array creation (since C++17)
static const array<string, 11> megaUnits_bit = {"bit", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb", "Bb", "Gb"}; // combined with string literals (operator""s)
static const array<string, 11> megaUnits_byte = {"Byte", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "GB"}; static const array mebiUnits_bit {
"bit"s, "Kib"s, "Mib"s,
"Gib"s, "Tib"s, "Pib"s,
"Eib"s, "Zib"s, "Yib"s,
"Bib"s, "GEb"s
};
static const array mebiUnits_byte {
"Byte"s, "KiB"s, "MiB"s,
"GiB"s, "TiB"s, "PiB"s,
"EiB"s, "ZiB"s, "YiB"s,
"BiB"s, "GEB"s
};
static const array megaUnits_bit {
"bit"s, "Kb"s, "Mb"s,
"Gb"s, "Tb"s, "Pb"s,
"Eb"s, "Zb"s, "Yb"s,
"Bb"s, "Gb"s
};
static const array megaUnits_byte {
"Byte"s, "KB"s, "MB"s,
"GB"s, "TB"s, "PB"s,
"EB"s, "ZB"s, "YB"s,
"BB"s, "GB"s
};
const auto& units = (bit) ? ( mega ? megaUnits_bit : mebiUnits_bit) : ( mega ? megaUnits_byte : mebiUnits_byte); const auto& units = (bit) ? ( mega ? megaUnits_bit : mebiUnits_bit) : ( mega ? megaUnits_byte : mebiUnits_byte);
value *= 100 * mult; value *= 100 * mult;
@ -366,15 +435,29 @@ namespace Tools {
} }
if (out.empty()) { if (out.empty()) {
out = to_string(value); out = to_string(value);
if (not mega and out.size() == 4 and start > 0) { out.pop_back(); out.insert(2, ".");} if (not mega and out.size() == 4 and start > 0) {
else if (out.size() == 3 and start > 0) out.insert(1, "."); out.pop_back();
else if (out.size() >= 2) out.resize(out.size() - 2); out.insert(2, ".");
}
else if (out.size() == 3 and start > 0) {
out.insert(1, ".");
}
else if (out.size() >= 2) {
out.resize(out.size() - 2);
}
} }
if (shorten) { if (shorten) {
auto f_pos = out.find('.'); auto f_pos = out.find('.');
if (f_pos == 1 and out.size() > 3) out = to_string(round(stof(out) * 10) / 10).substr(0,3); if (f_pos == 1 and out.size() > 3) {
else if (f_pos != string::npos) out = to_string((int)round(stof(out))); out = to_string(round(stod(out) * 10) / 10).substr(0,3);
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;} }
else if (f_pos != string::npos) {
out = to_string((int)round(stod(out)));
}
if (out.size() > 3) {
out = to_string((int)(out[0] - '0')) + ".0";
start++;
}
out.push_back(units[start][0]); out.push_back(units[start][0]);
} }
else out += " " + units[start]; else out += " " + units[start];
@ -384,11 +467,19 @@ namespace Tools {
} }
std::string operator*(const string& str, int64_t n) { std::string operator*(const string& str, int64_t n) {
if (n < 1 or str.empty()) return ""; if (n < 1 or str.empty()) {
else if(n == 1) return str; return "";
}
else if (n == 1) {
return str;
}
string new_str; string new_str;
new_str.reserve(str.size() * n); new_str.reserve(str.size() * n);
for (; n > 0; n--) new_str.append(str);
for (; n > 0; n--)
new_str.append(str);
return new_str; return new_str;
} }
@ -400,11 +491,11 @@ namespace Tools {
return ss.str(); return ss.str();
} }
void atomic_wait(const atomic<bool>& atom, const bool old) noexcept { void atomic_wait(const atomic<bool>& atom, bool old) noexcept {
while (atom.load(std::memory_order_relaxed) == old ) busy_wait(); while (atom.load(std::memory_order_relaxed) == old ) busy_wait();
} }
void atomic_wait_for(const atomic<bool>& atom, const bool old, const uint64_t wait_ms) noexcept { void atomic_wait_for(const atomic<bool>& atom, bool old, const uint64_t wait_ms) noexcept {
const uint64_t start_time = time_ms(); const uint64_t start_time = time_ms();
while (atom.load(std::memory_order_relaxed) == old and (time_ms() - start_time < wait_ms)) sleep_ms(1); while (atom.load(std::memory_order_relaxed) == old and (time_ms() - start_time < wait_ms)) sleep_ms(1);
} }
@ -426,7 +517,7 @@ namespace Tools {
for (string readstr; getline(file, readstr); out += readstr); for (string readstr; getline(file, readstr); out += readstr);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
Logger::error("readfile() : Exception when reading " + (string)path + " : " + e.what()); Logger::error("readfile() : Exception when reading " + string{path} + " : " + e.what());
return fallback; return fallback;
} }
return (out.empty() ? fallback : out); return (out.empty() ? fallback : out);
@ -447,15 +538,83 @@ namespace Tools {
string hostname() { string hostname() {
char host[HOST_NAME_MAX]; char host[HOST_NAME_MAX];
gethostname(host, HOST_NAME_MAX); gethostname(host, HOST_NAME_MAX);
return (string)host; return string{host};
} }
string username() { string username() {
auto user = getenv("LOGNAME"); auto user = getenv("LOGNAME");
if (user == NULL or strlen(user) == 0) user = getenv("USER"); if (user == nullptr or strlen(user) == 0) user = getenv("USER");
return (user != NULL ? user : ""); return (user != nullptr ? user : "");
} }
DebugTimer::DebugTimer(const string name, bool start, bool delayed_report) : name(name), delayed_report(delayed_report) {
if (start)
this->start();
}
DebugTimer::~DebugTimer() {
if (running)
this->stop(true);
this->force_report();
}
void DebugTimer::start() {
if (running) return;
running = true;
start_time = time_micros();
}
void DebugTimer::stop(bool report) {
if (not running) return;
running = false;
elapsed_time = time_micros() - start_time;
if (report) this->report();
}
void DebugTimer::reset(bool restart) {
running = false;
start_time = 0;
elapsed_time = 0;
if (restart) this->start();
}
void DebugTimer::stop_rename_reset(const string &new_name, bool report, bool restart) {
this->stop(report);
name = new_name;
this->reset(restart);
}
void DebugTimer::report() {
string report_line;
if (start_time == 0 and elapsed_time == 0)
report_line = fmt::format("DebugTimer::report() warning -> Timer [{}] has not been started!", name);
else if (running)
report_line = fmt::format(custom_locale, "Timer [{}] (running) currently at {:L} μs", name, time_micros() - start_time);
else
report_line = fmt::format(custom_locale, "Timer [{}] took {:L} μs", name, elapsed_time);
if (delayed_report)
report_buffer.emplace_back(report_line);
else
Logger::log_write(log_level, report_line);
}
void DebugTimer::force_report() {
if (report_buffer.empty()) return;
for (const auto& line : report_buffer)
Logger::log_write(log_level, line);
report_buffer.clear();
}
uint64_t DebugTimer::elapsed() {
if (running)
return time_micros() - start_time;
return elapsed_time;
}
bool DebugTimer::is_running() {
return running;
}
} }
namespace Logger { namespace Logger {
@ -472,10 +631,14 @@ namespace Logger {
int status = -1; int status = -1;
public: public:
lose_priv() { lose_priv() {
if (geteuid() != Global::real_uid) this->status = seteuid(Global::real_uid); if (geteuid() != Global::real_uid) {
this->status = seteuid(Global::real_uid);
}
} }
~lose_priv() { ~lose_priv() {
if (status == 0) status = seteuid(Global::set_uid); if (status == 0) {
status = seteuid(Global::set_uid);
}
} }
}; };
@ -483,7 +646,7 @@ namespace Logger {
loglevel = v_index(log_levels, level); loglevel = v_index(log_levels, level);
} }
void log_write(const size_t level, const string& msg) { void log_write(const Level level, const string& msg) {
if (loglevel < level or logfile.empty()) return; if (loglevel < level or logfile.empty()) return;
atomic_lock lck(busy, true); atomic_lock lck(busy, true);
lose_priv neutered{}; lose_priv neutered{};
@ -492,19 +655,26 @@ namespace Logger {
if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) { if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {
auto old_log = logfile; auto old_log = logfile;
old_log += ".1"; old_log += ".1";
if (fs::exists(old_log)) fs::remove(old_log, ec);
if (not ec) fs::rename(logfile, old_log, ec); if (fs::exists(old_log))
fs::remove(old_log, ec);
if (not ec)
fs::rename(logfile, old_log, ec);
} }
if (not ec) { if (not ec) {
std::ofstream lwrite(logfile, std::ios::app); std::ofstream lwrite(logfile, std::ios::app);
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";} if (first) {
first = false;
lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";
}
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n"; lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
} }
else logfile.clear(); else logfile.clear();
} }
catch (const std::exception& e) { catch (const std::exception& e) {
logfile.clear(); logfile.clear();
throw std::runtime_error("Exception in Logger::log_write() : " + (string)e.what()); throw std::runtime_error("Exception in Logger::log_write() : " + string{e.what()});
} }
} }
} }

View File

@ -18,16 +18,17 @@ tab-size = 4
#pragma once #pragma once
#include <string> #include <algorithm> // for std::ranges::count_if
#include <vector>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <regex> #include <chrono>
#include <filesystem> #include <filesystem>
#include <ranges> #include <ranges>
#include <chrono> #include <regex>
#include <string>
#include <thread> #include <thread>
#include <tuple> #include <tuple>
#include <vector>
#include <pthread.h> #include <pthread.h>
#include <limits.h> #include <limits.h>
#ifndef HOST_NAME_MAX #ifndef HOST_NAME_MAX
@ -37,9 +38,19 @@ tab-size = 4
#define HOST_NAME_MAX 64 #define HOST_NAME_MAX 64
#endif #endif
#endif #endif
#define FMT_HEADER_ONLY
#include "fmt/core.h"
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
using std::string, std::vector, std::atomic, std::to_string, std::tuple, std::array; using std::array;
using std::atomic;
using std::string;
using std::to_string;
using std::tuple;
using std::vector;
using namespace fmt::literals;
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------ //? ------------------------------------------------- NAMESPACES ------------------------------------------------------
@ -80,19 +91,19 @@ namespace Fx {
//* Collection of escape codes and functions for cursor manipulation //* Collection of escape codes and functions for cursor manipulation
namespace Mv { namespace Mv {
//* Move cursor to <line>, <column> //* Move cursor to <line>, <column>
inline string to(const int& line, const int& col) { return Fx::e + to_string(line) + ';' + to_string(col) + 'f'; } inline string to(int line, int col) { return Fx::e + to_string(line) + ';' + to_string(col) + 'f'; }
//* Move cursor right <x> columns //* Move cursor right <x> columns
inline string r(const int& x) { return Fx::e + to_string(x) + 'C'; } inline string r(int x) { return Fx::e + to_string(x) + 'C'; }
//* Move cursor left <x> columns //* Move cursor left <x> columns
inline string l(const int& x) { return Fx::e + to_string(x) + 'D'; } inline string l(int x) { return Fx::e + to_string(x) + 'D'; }
//* Move cursor up x lines //* Move cursor up x lines
inline string u(const int& x) { return Fx::e + to_string(x) + 'A'; } inline string u(int x) { return Fx::e + to_string(x) + 'A'; }
//* Move cursor down x lines //* Move cursor down x lines
inline string d(const int& x) { return Fx::e + to_string(x) + 'B'; } inline string d(int x) { return Fx::e + to_string(x) + 'B'; }
//* Save cursor position //* Save cursor position
const string save = Fx::e + "s"; const string save = Fx::e + "s";
@ -140,19 +151,25 @@ namespace Term {
namespace Tools { namespace Tools {
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max(); constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
class MyNumPunct : public std::numpunct<char> {
protected:
virtual char do_thousands_sep() const { return '\''; }
virtual std::string do_grouping() const { return "\03"; }
};
size_t wide_ulen(const string& str); size_t wide_ulen(const string& str);
size_t wide_ulen(const std::wstring& w_str); size_t wide_ulen(const std::wstring& w_str);
//* Return number of UTF8 characters in a string (wide=true for column size needed on terminal) //* Return number of UTF8 characters in a string (wide=true for column size needed on terminal)
inline size_t ulen(const string& str, const bool wide=false) { inline size_t ulen(const string& str, bool wide = false) {
return (wide ? wide_ulen(str) : std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; })); return (wide ? wide_ulen(str) : std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; }));
} }
//* Resize a string consisting of UTF8 characters (only reduces size) //* Resize a string consisting of UTF8 characters (only reduces size)
string uresize(const string str, const size_t len, const bool wide=false); string uresize(const string str, const size_t len, bool wide = false);
//* Resize a string consisting of UTF8 characters from left (only reduces size) //* Resize a string consisting of UTF8 characters from left (only reduces size)
string luresize(const string str, const size_t len, const bool wide=false); string luresize(const string str, const size_t len, bool wide = false);
//* Replace <from> in <str> with <to> and return new string //* Replace <from> in <str> with <to> and return new string
string s_replace(const string& str, const string& from, const string& to); string s_replace(const string& str, const string& from, const string& to);
@ -189,11 +206,11 @@ namespace Tools {
//* Check if string <str> contains string <find_val>, while ignoring case //* Check if string <str> contains string <find_val>, while ignoring case
inline bool s_contains_ic(const string& str, const string& find_val) { inline bool s_contains_ic(const string& str, const string& find_val) {
auto it = std::search( auto it = std::search(
str.begin(), str.end(), str.begin(), str.end(),
find_val.begin(), find_val.end(), find_val.begin(), find_val.end(),
[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); } [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
); );
return it != str.end(); return it != str.end();
} }
@ -254,31 +271,35 @@ namespace Tools {
auto ssplit(const string& str, const char& delim = ' ') -> vector<string>; auto ssplit(const string& str, const char& delim = ' ') -> vector<string>;
//* Put current thread to sleep for <ms> milliseconds //* Put current thread to sleep for <ms> milliseconds
inline void sleep_ms(const size_t& ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } inline void sleep_ms(const size_t& ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
//* Put current thread to sleep for <micros> microseconds //* Put current thread to sleep for <micros> microseconds
inline void sleep_micros(const size_t& micros) { std::this_thread::sleep_for(std::chrono::microseconds(micros)); } inline void sleep_micros(const size_t& micros) {
std::this_thread::sleep_for(std::chrono::microseconds(micros));
}
//* Left justify string <str> if <x> is greater than <str> length, limit return size to <x> by default //* Left justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
string ljust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true); string ljust(string str, const size_t x, bool utf = false, bool wide = false, bool limit = true);
//* Right justify string <str> if <x> is greater than <str> length, limit return size to <x> by default //* Right justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
string rjust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true); string rjust(string str, const size_t x, bool utf = false, bool wide = false, bool limit = true);
//* Center justify string <str> if <x> is greater than <str> length, limit return size to <x> by default //* Center justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
string cjust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true); string cjust(string str, const size_t x, bool utf = false, bool wide = false, bool limit = true);
//* Replace whitespaces " " with escape code for move right //* Replace whitespaces " " with escape code for move right
string trans(const string& str); string trans(const string& str);
//* Convert seconds to format "<days>d <hours>:<minutes>:<seconds>" and return string //* Convert seconds to format "<days>d <hours>:<minutes>:<seconds>" and return string
string sec_to_dhms(size_t seconds, bool no_days=false, bool no_seconds=false); string sec_to_dhms(size_t seconds, bool no_days = false, bool no_seconds = false);
//* Scales up in steps of 1024 to highest positive value unit and returns string with unit suffixed //* Scales up in steps of 1024 to highest positive value unit and returns string with unit suffixed
//* bit=True or defaults to bytes //* bit=True or defaults to bytes
//* start=int to set 1024 multiplier starting unit //* start=int to set 1024 multiplier starting unit
//* short=True always returns 0 decimals and shortens unit to 1 character //* short=True always returns 0 decimals and shortens unit to 1 character
string floating_humanizer(uint64_t value, const bool shorten=false, size_t start=0, const bool bit=false, const bool per_second=false); string floating_humanizer(uint64_t value, bool shorten = false, size_t start = 0, bool bit = false, bool per_second = false);
//* Add std::string operator * : Repeat string <str> <n> number of times //* Add std::string operator * : Repeat string <str> <n> number of times
std::string operator*(const string& str, int64_t n); std::string operator*(const string& str, int64_t n);
@ -301,25 +322,24 @@ namespace Tools {
#endif #endif
} }
void atomic_wait(const atomic<bool>& atom, const bool old=true) noexcept; void atomic_wait(const atomic<bool>& atom, bool old = true) noexcept;
void atomic_wait_for(const atomic<bool>& atom, const bool old=true, const uint64_t wait_ms=0) noexcept; void atomic_wait_for(const atomic<bool>& atom, bool old = true, const uint64_t wait_ms = 0) noexcept;
//* Sets atomic<bool> to true on construct, sets to false on destruct //* Sets atomic<bool> to true on construct, sets to false on destruct
class atomic_lock { class atomic_lock {
atomic<bool>& atom; atomic<bool>& atom;
bool not_true = false; bool not_true{}; // defaults to false
public: public:
atomic_lock(atomic<bool>& atom, bool wait=false); atomic_lock(atomic<bool>& atom, bool wait = false);
~atomic_lock(); ~atomic_lock();
}; };
//* Read a complete file and return as a string //* Read a complete file and return as a string
string readfile(const std::filesystem::path& path, const string& fallback=""); string readfile(const std::filesystem::path& path, const string& fallback = "");
//* Convert a celsius value to celsius, fahrenheit, kelvin or rankin and return tuple with new value and unit. //* Convert a celsius value to celsius, fahrenheit, kelvin or rankin and return tuple with new value and unit.
auto celsius_to(const long long& celsius, const string& scale) -> tuple<long long, string>; auto celsius_to(const long long& celsius, const string& scale) -> tuple<long long, string>;
} }
//* Simple logging implementation //* Simple logging implementation
@ -333,13 +353,56 @@ namespace Logger {
}; };
extern std::filesystem::path logfile; extern std::filesystem::path logfile;
enum Level : size_t {
DISABLED = 0,
ERROR = 1,
WARNING = 2,
INFO = 3,
DEBUG = 4,
};
//* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
void set(const string& level); void set(const string& level);
void log_write(const size_t level, const string& msg); void log_write(const Level level, const string& msg);
inline void error(const string msg) { log_write(1, msg); } inline void error(const string msg) { log_write(ERROR, msg); }
inline void warning(const string msg) { log_write(2, msg); } inline void warning(const string msg) { log_write(WARNING, msg); }
inline void info(const string msg) { log_write(3, msg); } inline void info(const string msg) { log_write(INFO, msg); }
inline void debug(const string msg) { log_write(4, msg); } inline void debug(const string msg) { log_write(DEBUG, msg); }
} }
namespace Tools {
//* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running
//* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called
//* Usage example: Tools::DebugTimer timer(name:"myTimer", [start:true], [delayed_report:true]) // Create timer and start
//* timer.stop(); // Stop timer and report elapsed time
//* timer.stop_rename_reset("myTimer2"); // Stop timer, report elapsed time, rename timer, reset and restart
class DebugTimer {
uint64_t start_time{};
uint64_t elapsed_time{};
bool running{};
std::locale custom_locale = std::locale(std::locale::classic(), new Tools::MyNumPunct);
vector<string> report_buffer{};
public:
string name{};
bool delayed_report{};
Logger::Level log_level = Logger::DEBUG;
DebugTimer() = default;
DebugTimer(const string name, bool start = true, bool delayed_report = true);
~DebugTimer();
void start();
void stop(bool report = true);
void reset(bool restart = true);
//* Stops and reports (default), renames timer then resets and restarts (default)
void stop_rename_reset(const string& new_name, bool report = true, bool restart = true);
void report();
void force_report();
uint64_t elapsed();
bool is_running();
};
}

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -19,13 +19,15 @@ tab-size = 4
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <ifaddrs.h>
#include <libproc.h> #include <libproc.h>
// man 3 getifaddrs: "BUGS: If both <net/if.h> and <ifaddrs.h> are being included, <net/if.h> must be included before <ifaddrs.h>"
#include <net/if.h> #include <net/if.h>
#include <ifaddrs.h>
#include <net/if_dl.h> #include <net/if_dl.h>
#include <net/route.h> #include <net/route.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/tcp_fsm.h> #include <netinet/tcp_fsm.h>
#include <netinet/in.h> // for inet_ntop stuff
#include <pwd.h> #include <pwd.h>
#include <sys/_timeval.h> #include <sys/_timeval.h>
#include <sys/endian.h> #include <sys/endian.h>
@ -57,9 +59,9 @@ tab-size = 4
#include <string> #include <string>
#include <memory> #include <memory>
#include <btop_config.hpp> #include "../btop_config.hpp"
#include <btop_shared.hpp> #include "../btop_shared.hpp"
#include <btop_tools.hpp> #include "../btop_tools.hpp"
using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min; using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min;
@ -72,7 +74,7 @@ using namespace Tools;
namespace Cpu { namespace Cpu {
vector<long long> core_old_totals; vector<long long> core_old_totals;
vector<long long> core_old_idles; vector<long long> core_old_idles;
vector<string> available_fields = {"total"}; vector<string> available_fields = {"Auto", "total"};
vector<string> available_sensors = {"Auto"}; vector<string> available_sensors = {"Auto"};
cpu_info current_cpu; cpu_info current_cpu;
bool got_sensors = false, cpu_temp_only = false; bool got_sensors = false, cpu_temp_only = false;
@ -118,12 +120,12 @@ namespace Shared {
//? Shared global variables init //? Shared global variables init
int mib[2]; int mib[2];
mib[0] = CTL_HW; mib[0] = CTL_HW;
mib[1] = HW_NCPU; mib[1] = HW_NCPU;
int ncpu; int ncpu;
size_t len = sizeof(ncpu); size_t len = sizeof(ncpu);
if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { if (sysctl(mib, 2, &ncpu, &len, nullptr, 0) == -1) {
Logger::warning("Could not determine number of cores, defaulting to 1."); Logger::warning("Could not determine number of cores, defaulting to 1.");
} else { } else {
coreCount = ncpu; coreCount = ncpu;
} }
@ -141,21 +143,21 @@ namespace Shared {
int64_t memsize = 0; int64_t memsize = 0;
size_t size = sizeof(memsize); size_t size = sizeof(memsize);
if (sysctlbyname("hw.physmem", &memsize, &size, NULL, 0) < 0) { if (sysctlbyname("hw.physmem", &memsize, &size, nullptr, 0) < 0) {
Logger::warning("Could not get memory size"); Logger::warning("Could not get memory size");
} }
totalMem = memsize; totalMem = memsize;
struct timeval result; struct timeval result;
size = sizeof(result); size = sizeof(result);
if (sysctlbyname("kern.boottime", &result, &size, NULL, 0) < 0) { if (sysctlbyname("kern.boottime", &result, &size, nullptr, 0) < 0) {
Logger::warning("Could not get boot time"); Logger::warning("Could not get boot time");
} else { } else {
bootTime = result.tv_sec; bootTime = result.tv_sec;
} }
size = sizeof(kfscale); size = sizeof(kfscale);
if (sysctlbyname("kern.fscale", &kfscale, &size, NULL, 0) == -1) { if (sysctlbyname("kern.fscale", &kfscale, &size, nullptr, 0) == -1) {
kfscale = 2048; kfscale = 2048;
} }
@ -181,6 +183,17 @@ namespace Shared {
Mem::get_zpools(); Mem::get_zpools();
} }
//* RAII wrapper for kvm_openfiles
class kvm_openfiles_wrapper {
kvm_t* kd = nullptr;
public:
kvm_openfiles_wrapper(const char* execf, const char* coref, const char* swapf, int flags, char* err) {
this->kd = kvm_openfiles(execf, coref, swapf, flags, err);
}
~kvm_openfiles_wrapper() { kvm_close(kd); }
auto operator()() -> kvm_t* { return kd; }
};
} // namespace Shared } // namespace Shared
namespace Cpu { namespace Cpu {
@ -192,19 +205,19 @@ namespace Cpu {
const array<string, 10> time_names = {"user", "nice", "system", "idle"}; const array<string, 10> time_names = {"user", "nice", "system", "idle"};
unordered_flat_map<string, long long> cpu_old = { unordered_flat_map<string, long long> cpu_old = {
{"totals", 0}, {"totals", 0},
{"idles", 0}, {"idles", 0},
{"user", 0}, {"user", 0},
{"nice", 0}, {"nice", 0},
{"system", 0}, {"system", 0},
{"idle", 0} {"idle", 0}
}; };
string get_cpuName() { string get_cpuName() {
string name; string name;
char buffer[1024]; char buffer[1024];
size_t size = sizeof(buffer); size_t size = sizeof(buffer);
if (sysctlbyname("hw.model", &buffer, &size, NULL, 0) < 0) { if (sysctlbyname("hw.model", &buffer, &size, nullptr, 0) < 0) {
Logger::error("Failed to get CPU name"); Logger::error("Failed to get CPU name");
return name; return name;
} }
@ -251,13 +264,13 @@ namespace Cpu {
if (Config::getB("show_coretemp") and Config::getB("check_temp")) { if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
int32_t temp; int32_t temp;
size_t size = sizeof(temp); size_t size = sizeof(temp);
if (sysctlbyname("dev.cpu.0.temperature", &temp, &size, NULL, 0) < 0) { if (sysctlbyname("dev.cpu.0.temperature", &temp, &size, nullptr, 0) < 0) {
Logger::warning("Could not get temp sensor - maybe you need to load the coretemp module"); Logger::warning("Could not get temp sensor - maybe you need to load the coretemp module");
} else { } else {
got_sensors = true; got_sensors = true;
int temp; int temp;
size_t size = sizeof(temp); size_t size = sizeof(temp);
sysctlbyname("dev.cpu.0.coretemp.tjmax", &temp, &size, NULL, 0); //asuming the max temp is same for all cores sysctlbyname("dev.cpu.0.coretemp.tjmax", &temp, &size, nullptr, 0); //asuming the max temp is same for all cores
temp = (temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero... temp = (temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero...
current_cpu.temp_max = temp; current_cpu.temp_max = temp;
} }
@ -271,7 +284,7 @@ namespace Cpu {
int found = 0; int found = 0;
bool got_package = false; bool got_package = false;
size_t size = sizeof(p_temp); size_t size = sizeof(p_temp);
if (sysctlbyname("hw.acpi.thermal.tz0.temperature", &p_temp, &size, NULL, 0) >= 0) { if (sysctlbyname("hw.acpi.thermal.tz0.temperature", &p_temp, &size, nullptr, 0) >= 0) {
got_package = true; got_package = true;
p_temp = (p_temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero... p_temp = (p_temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero...
} }
@ -279,7 +292,7 @@ namespace Cpu {
size = sizeof(temp); size = sizeof(temp);
for (int i = 0; i < Shared::coreCount; i++) { for (int i = 0; i < Shared::coreCount; i++) {
string s = "dev.cpu." + std::to_string(i) + ".temperature"; string s = "dev.cpu." + std::to_string(i) + ".temperature";
if (sysctlbyname(s.c_str(), &temp, &size, NULL, 0) >= 0) { if (sysctlbyname(s.c_str(), &temp, &size, nullptr, 0) >= 0) {
temp = (temp - 2732) / 10; temp = (temp - 2732) / 10;
if (not got_package) { if (not got_package) {
p_temp += temp; p_temp += temp;
@ -304,7 +317,7 @@ namespace Cpu {
unsigned int freq = 1; unsigned int freq = 1;
size_t size = sizeof(freq); size_t size = sizeof(freq);
if (sysctlbyname("dev.cpu.0.freq", &freq, &size, NULL, 0) < 0) { if (sysctlbyname("dev.cpu.0.freq", &freq, &size, nullptr, 0) < 0) {
return ""; return "";
} }
return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz
@ -360,17 +373,17 @@ namespace Cpu {
uint32_t percent = -1; uint32_t percent = -1;
size_t size = sizeof(percent); size_t size = sizeof(percent);
string status = "discharging"; string status = "discharging";
if (sysctlbyname("hw.acpi.battery.life", &percent, &size, NULL, 0) < 0) { if (sysctlbyname("hw.acpi.battery.life", &percent, &size, nullptr, 0) < 0) {
has_battery = false; has_battery = false;
} else { } else {
has_battery = true; has_battery = true;
size_t size = sizeof(seconds); size_t size = sizeof(seconds);
if (sysctlbyname("hw.acpi.battery.time", &seconds, &size, NULL, 0) < 0) { if (sysctlbyname("hw.acpi.battery.time", &seconds, &size, nullptr, 0) < 0) {
seconds = 0; seconds = 0;
} }
int state; int state;
size = sizeof(state); size = sizeof(state);
if (sysctlbyname("hw.acpi.battery.state", &state, &size, NULL, 0) < 0) { if (sysctlbyname("hw.acpi.battery.state", &state, &size, nullptr, 0) < 0) {
status = "unknown"; status = "unknown";
} else { } else {
if (state == 2) { if (state == 2) {
@ -385,22 +398,18 @@ namespace Cpu {
return {percent, seconds, status}; return {percent, seconds, status};
} }
auto collect(const bool no_update) -> cpu_info & { auto collect(bool no_update) -> cpu_info & {
if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty()))
return current_cpu; return current_cpu;
auto &cpu = current_cpu; auto &cpu = current_cpu;
double avg[3]; if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) {
if (getloadavg(avg, sizeof(avg)) < 0) {
Logger::error("failed to get load averages"); Logger::error("failed to get load averages");
} }
cpu.load_avg = { (float)avg[0], (float)avg[1], (float)avg[2]};
vector<array<long, CPUSTATES>> cpu_time(Shared::coreCount); vector<array<long, CPUSTATES>> cpu_time(Shared::coreCount);
size_t size = sizeof(long) * CPUSTATES * Shared::coreCount; size_t size = sizeof(long) * CPUSTATES * Shared::coreCount;
if (sysctlbyname("kern.cp_times", &cpu_time[0], &size, NULL, 0) == -1) { if (sysctlbyname("kern.cp_times", &cpu_time[0], &size, nullptr, 0) == -1) {
Logger::error("failed to get CPU times"); Logger::error("failed to get CPU times");
} }
long long global_totals = 0; long long global_totals = 0;
@ -534,35 +543,37 @@ namespace Mem {
// find all zpools in the system. Do this only at startup. // find all zpools in the system. Do this only at startup.
void get_zpools() { void get_zpools() {
std::regex toReplace("\\.");
PipeWrapper poolPipe = PipeWrapper("zpool list -H -o name", "r"); PipeWrapper poolPipe = PipeWrapper("zpool list -H -o name", "r");
while (not std::feof(poolPipe())) { while (not std::feof(poolPipe())) {
char poolName[512]; char poolName[512];
size_t len = 512; size_t len = 512;
if (fgets(poolName, len, poolPipe())) { if (fgets(poolName, len, poolPipe())) {
poolName[strcspn(poolName, "\n")] = 0; poolName[strcspn(poolName, "\n")] = 0;
Logger::debug("zpool found: " + string(poolName)); Logger::debug("zpool found: " + string(poolName));
Mem::zpools.push_back(poolName); Mem::zpools.push_back(std::regex_replace(poolName, toReplace, "%25"));
} }
} }
} }
void collect_disk(unordered_flat_map<string, disk_info> &disks, unordered_flat_map<string, string> &mapping) { void collect_disk(unordered_flat_map<string, disk_info> &disks, unordered_flat_map<string, string> &mapping) {
// this bit is for 'regular' mounts // this bit is for 'regular' mounts
static struct statinfo cur; static struct statinfo cur;
long double etime = 0; long double etime = 0;
uint64_t total_bytes_read; uint64_t total_bytes_read;
uint64_t total_bytes_write; uint64_t total_bytes_write;
static std::unique_ptr<struct devinfo, decltype(std::free)*> curDevInfo (reinterpret_cast<struct devinfo*>(std::calloc(1, sizeof(struct devinfo))), std::free); static std::unique_ptr<struct devinfo, decltype(std::free)*> curDevInfo (reinterpret_cast<struct devinfo*>(std::calloc(1, sizeof(struct devinfo))), std::free);
cur.dinfo = curDevInfo.get(); cur.dinfo = curDevInfo.get();
if (devstat_getdevs(NULL, &cur) != -1) { if (devstat_getdevs(nullptr, &cur) != -1) {
for (int i = 0; i < cur.dinfo->numdevs; i++) { for (int i = 0; i < cur.dinfo->numdevs; i++) {
auto d = cur.dinfo->devices[i]; auto d = cur.dinfo->devices[i];
string devStatName = "/dev/" + string(d.device_name) + std::to_string(d.unit_number); string devStatName = "/dev/" + string(d.device_name) + std::to_string(d.unit_number);
for (auto& [ignored, disk] : disks) { // find matching mountpoints - could be multiple as d.device_name is only ada (and d.unit_number is the device number), while the disk.dev is like /dev/ada0s1 for (auto& [ignored, disk] : disks) { // find matching mountpoints - could be multiple as d.device_name is only ada (and d.unit_number is the device number), while the disk.dev is like /dev/ada0s1
if (disk.dev.string().rfind(devStatName, 0) == 0) { if (disk.dev.string().rfind(devStatName, 0) == 0) {
devstat_compute_statistics(&d, NULL, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE); devstat_compute_statistics(&d, nullptr, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE);
assign_values(disk, total_bytes_read, total_bytes_write); assign_values(disk, total_bytes_read, total_bytes_write);
string mountpoint = mapping.at(disk.dev); string mountpoint = mapping.at(disk.dev);
Logger::debug("dev " + devStatName + " -> " + mountpoint + " read=" + std::to_string(total_bytes_read) + " write=" + std::to_string(total_bytes_write)); Logger::debug("dev " + devStatName + " -> " + mountpoint + " read=" + std::to_string(total_bytes_read) + " write=" + std::to_string(total_bytes_write));
@ -574,18 +585,18 @@ namespace Mem {
} }
// this code is for ZFS mounts // this code is for ZFS mounts
for (string poolName : Mem::zpools) { for (const auto &poolName : Mem::zpools) {
char sysCtl[1024]; char sysCtl[1024];
snprintf(sysCtl, sizeof(sysCtl), "sysctl kstat.zfs.%s.dataset | egrep \'dataset_name|nread|nwritten\'", poolName.c_str()); snprintf(sysCtl, sizeof(sysCtl), "sysctl kstat.zfs.%s.dataset | egrep \'dataset_name|nread|nwritten\'", poolName.c_str());
PipeWrapper f = PipeWrapper(sysCtl, "r"); PipeWrapper f = PipeWrapper(sysCtl, "r");
if (f()) { if (f()) {
char buf[512]; char buf[512];
size_t len = 512; size_t len = 512;
uint64_t nread = 0, nwritten = 0;
while (not std::feof(f())) { while (not std::feof(f())) {
uint64_t nread = 0, nwritten = 0;
if (fgets(buf, len, f())) { if (fgets(buf, len, f())) {
char *name = std::strtok(buf, ": \n"); char *name = std::strtok(buf, ": \n");
char *value = std::strtok(NULL, ": \n"); char *value = std::strtok(nullptr, ": \n");
if (string(name).find("dataset_name") != string::npos) { if (string(name).find("dataset_name") != string::npos) {
// create entry if datasetname matches with anything in mapping // create entry if datasetname matches with anything in mapping
// relies on the fact that the dataset name is last value in the list // relies on the fact that the dataset name is last value in the list
@ -608,17 +619,17 @@ namespace Mem {
} }
} }
} }
auto collect(const bool no_update) -> mem_info & { auto collect(bool no_update) -> mem_info & {
if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty()))
return current_mem; return current_mem;
auto &show_swap = Config::getB("show_swap"); auto show_swap = Config::getB("show_swap");
auto &show_disks = Config::getB("show_disks"); auto show_disks = Config::getB("show_disks");
auto &swap_disk = Config::getB("swap_disk"); auto swap_disk = Config::getB("swap_disk");
auto &mem = current_mem; auto &mem = current_mem;
static const bool snapped = (getenv("BTOP_SNAPPED") != NULL); static bool snapped = (getenv("BTOP_SNAPPED") != nullptr);
int mib[4]; int mib[4];
u_int memActive, memWire, cachedMem, freeMem; u_int memActive, memWire, cachedMem, freeMem;
@ -626,12 +637,12 @@ namespace Mem {
len = 4; sysctlnametomib("vm.stats.vm.v_active_count", mib, &len); len = 4; sysctlnametomib("vm.stats.vm.v_active_count", mib, &len);
len = sizeof(memActive); len = sizeof(memActive);
sysctl(mib, 4, &(memActive), &len, NULL, 0); sysctl(mib, 4, &(memActive), &len, nullptr, 0);
memActive *= Shared::pageSize; memActive *= Shared::pageSize;
len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", mib, &len); len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", mib, &len);
len = sizeof(memWire); len = sizeof(memWire);
sysctl(mib, 4, &(memWire), &len, NULL, 0); sysctl(mib, 4, &(memWire), &len, nullptr, 0);
memWire *= Shared::pageSize; memWire *= Shared::pageSize;
mem.stats.at("used") = memWire + memActive; mem.stats.at("used") = memWire + memActive;
@ -639,16 +650,30 @@ namespace Mem {
len = sizeof(cachedMem); len = sizeof(cachedMem);
len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", mib, &len); len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", mib, &len);
sysctl(mib, 4, &(cachedMem), &len, NULL, 0); sysctl(mib, 4, &(cachedMem), &len, nullptr, 0);
cachedMem *= Shared::pageSize; cachedMem *= Shared::pageSize;
mem.stats.at("cached") = cachedMem; mem.stats.at("cached") = cachedMem;
len = sizeof(freeMem); len = sizeof(freeMem);
len = 4; sysctlnametomib("vm.stats.vm.v_free_count", mib, &len); len = 4; sysctlnametomib("vm.stats.vm.v_free_count", mib, &len);
sysctl(mib, 4, &(freeMem), &len, NULL, 0); sysctl(mib, 4, &(freeMem), &len, nullptr, 0);
freeMem *= Shared::pageSize; freeMem *= Shared::pageSize;
mem.stats.at("free") = freeMem; mem.stats.at("free") = freeMem;
if (show_swap) {
char buf[_POSIX2_LINE_MAX];
Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf);
struct kvm_swap swap[16];
int nswap = kvm_getswapinfo(kd(), swap, 16, 0);
int totalSwap = 0, usedSwap = 0;
for (int i = 0; i < nswap; i++) {
totalSwap += swap[i].ksw_total;
usedSwap += swap[i].ksw_used;
}
mem.stats.at("swap_total") = totalSwap * Shared::pageSize;
mem.stats.at("swap_used") = usedSwap * Shared::pageSize;
}
if (show_swap and mem.stats.at("swap_total") > 0) { if (show_swap and mem.stats.at("swap_total") > 0) {
for (const auto &name : swap_names) { for (const auto &name : swap_names) {
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total"))); mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total")));
@ -670,7 +695,7 @@ namespace Mem {
double uptime = system_uptime(); double uptime = system_uptime();
auto &disks_filter = Config::getS("disks_filter"); auto &disks_filter = Config::getS("disks_filter");
bool filter_exclude = false; bool filter_exclude = false;
// auto &only_physical = Config::getB("only_physical"); // auto only_physical = Config::getB("only_physical");
auto &disks = mem.disks; auto &disks = mem.disks;
vector<string> filter; vector<string> filter;
if (not disks_filter.empty()) { if (not disks_filter.empty()) {
@ -688,7 +713,7 @@ namespace Mem {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
auto fstype = string(stfs[i].f_fstypename); auto fstype = string(stfs[i].f_fstypename);
if (fstype == "autofs" || fstype == "devfs" || fstype == "linprocfs" || fstype == "procfs" || fstype == "tmpfs" || fstype == "linsysfs" || if (fstype == "autofs" || fstype == "devfs" || fstype == "linprocfs" || fstype == "procfs" || fstype == "tmpfs" || fstype == "linsysfs" ||
fstype == "fdesckfs") { fstype == "fdesckfs") {
// in memory filesystems -> not useful to show // in memory filesystems -> not useful to show
continue; continue;
} }
@ -803,11 +828,11 @@ namespace Net {
auto operator()() -> struct ifaddrs * { return ifaddr; } auto operator()() -> struct ifaddrs * { return ifaddr; }
}; };
auto collect(const bool no_update) -> net_info & { auto collect(bool no_update) -> net_info & {
auto &net = current_net; auto &net = current_net;
auto &config_iface = Config::getS("net_iface"); auto &config_iface = Config::getS("net_iface");
auto &net_sync = Config::getB("net_sync"); auto net_sync = Config::getB("net_sync");
auto &net_auto = Config::getB("net_auto"); auto net_auto = Config::getB("net_auto");
auto new_timestamp = time_ms(); auto new_timestamp = time_ms();
if (not no_update and errors < 3) { if (not no_update and errors < 3) {
@ -820,45 +845,65 @@ namespace Net {
return empty_net; return empty_net;
} }
int family = 0; int family = 0;
char ip[NI_MAXHOST]; static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); // 46 >= 16, compile-time assurance.
enum { IPBUFFER_MAXSIZE = INET6_ADDRSTRLEN }; // manually using the known biggest value, guarded by the above static_assert
char ip[IPBUFFER_MAXSIZE];
interfaces.clear(); interfaces.clear();
string ipv4, ipv6; string ipv4, ipv6;
//? Iteration over all items in getifaddrs() list //? Iteration over all items in getifaddrs() list
for (auto *ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) { for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue; if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family; family = ifa->ifa_addr->sa_family;
const auto &iface = ifa->ifa_name; const auto &iface = ifa->ifa_name;
//? Get IPv4 address
if (family == AF_INET) {
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
net[iface].ipv4 = ip;
}
//? Get IPv6 address
// else if (family == AF_INET6) {
// if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
// net[iface].ipv6 = ip;
// }
//? Update available interfaces vector and get status of interface //? Update available interfaces vector and get status of interface
if (not v_contains(interfaces, iface)) { if (not v_contains(interfaces, iface)) {
interfaces.push_back(iface); interfaces.push_back(iface);
net[iface].connected = (ifa->ifa_flags & IFF_RUNNING); net[iface].connected = (ifa->ifa_flags & IFF_RUNNING);
// An interface can have more than one IP of the same family associated with it,
// but we pick only the first one to show in the NET box.
// Note: Interfaces without any IPv4 and IPv6 set are still valid and monitorable!
net[iface].ipv4.clear();
net[iface].ipv6.clear();
} }
//? Get IPv4 address
if (family == AF_INET) {
if (net[iface].ipv4.empty()) {
if (nullptr != inet_ntop(family, &(reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr)->sin_addr), ip, IPBUFFER_MAXSIZE)) {
net[iface].ipv4 = ip;
} else {
int errsv = errno;
Logger::error("Net::collect() -> Failed to convert IPv4 to string for iface " + string(iface) + ", errno: " + strerror(errsv));
}
}
}
//? Get IPv6 address
else if (family == AF_INET6) {
if (net[iface].ipv6.empty()) {
if (nullptr != inet_ntop(family, &(reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr)->sin6_addr), ip, IPBUFFER_MAXSIZE)) {
net[iface].ipv6 = ip;
} else {
int errsv = errno;
Logger::error("Net::collect() -> Failed to convert IPv6 to string for iface " + string(iface) + ", errno: " + strerror(errsv));
}
}
} //else, ignoring family==AF_LINK (see man 3 getifaddrs)
} }
unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats; unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0};
size_t len; size_t len;
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) {
Logger::error("failed getting network interfaces"); Logger::error("failed getting network interfaces");
} else { } else {
std::unique_ptr<char[]> buf(new char[len]); std::unique_ptr<char[]> buf(new char[len]);
if (sysctl(mib, 6, buf.get(), &len, NULL, 0) < 0) { if (sysctl(mib, 6, buf.get(), &len, nullptr, 0) < 0) {
Logger::error("failed getting network interfaces"); Logger::error("failed getting network interfaces");
} else { } else {
char *lim = buf.get() + len; char *lim = buf.get() + len;
char *next = NULL; char *next = nullptr;
for (next = buf.get(); next < lim;) { for (next = buf.get(); next < lim;) {
struct if_msghdr *ifm = (struct if_msghdr *)next; struct if_msghdr *ifm = (struct if_msghdr *)next;
next += ifm->ifm_msglen; next += ifm->ifm_msglen;
@ -942,7 +987,7 @@ namespace Net {
auto sorted_interfaces = interfaces; auto sorted_interfaces = interfaces;
rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) { rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) {
return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total, return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total,
net.at(b).stat["download"].total + net.at(b).stat["upload"].total); net.at(b).stat["download"].total + net.at(b).stat["upload"].total);
}); });
selected_iface.clear(); selected_iface.clear();
//? Try to set to a connected interface //? Try to set to a connected interface
@ -964,9 +1009,9 @@ namespace Net {
for (const auto &dir : {"download", "upload"}) { for (const auto &dir : {"download", "upload"}) {
for (const auto &sel : {0, 1}) { for (const auto &sel : {0, 1}) {
if (rescale or max_count[dir][sel] >= 5) { if (rescale or max_count[dir][sel] >= 5) {
const uint64_t avg_speed = (net[selected_iface].bandwidth[dir].size() > 5 const long long avg_speed = (net[selected_iface].bandwidth[dir].size() > 5
? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0) / 5 ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0ll) / 5
: net[selected_iface].stat[dir].speed); : net[selected_iface].stat[dir].speed);
graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10); graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10);
max_count[dir][0] = max_count[dir][1] = 0; max_count[dir][0] = max_count[dir][1] = 0;
redraw = true; redraw = true;
@ -1035,7 +1080,7 @@ namespace Proc {
//? Process runtime : current time - start time (both in unix time - seconds since epoch) //? Process runtime : current time - start time (both in unix time - seconds since epoch)
struct timeval currentTime; struct timeval currentTime;
gettimeofday(&currentTime, NULL); gettimeofday(&currentTime, nullptr);
detailed.elapsed = sec_to_dhms(currentTime.tv_sec - detailed.entry.cpu_s); // only interested in second granularity, so ignoring tc_usec detailed.elapsed = sec_to_dhms(currentTime.tv_sec - detailed.entry.cpu_s); // only interested in second granularity, so ignoring tc_usec
if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3); if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
@ -1066,29 +1111,18 @@ namespace Proc {
// } // }
} }
//* RAII wrapper for kvm_openfiles
class kvm_openfiles_wrapper {
kvm_t* kd = NULL;
public:
kvm_openfiles_wrapper(const char* execf, const char* coref, const char* swapf, int flags, char* err) {
this->kd = kvm_openfiles(execf, coref, swapf, flags, err);
}
~kvm_openfiles_wrapper() { kvm_close(kd); }
auto operator()() -> kvm_t* { return kd; }
};
//* Collects and sorts process information from /proc //* Collects and sorts process information from /proc
auto collect(const bool no_update) -> vector<proc_info> & { auto collect(bool no_update) -> vector<proc_info> & {
const auto &sorting = Config::getS("proc_sorting"); const auto &sorting = Config::getS("proc_sorting");
const auto &reverse = Config::getB("proc_reversed"); auto reverse = Config::getB("proc_reversed");
const auto &filter = Config::getS("proc_filter"); const auto &filter = Config::getS("proc_filter");
const auto &per_core = Config::getB("proc_per_core"); auto per_core = Config::getB("proc_per_core");
const auto &tree = Config::getB("proc_tree"); auto tree = Config::getB("proc_tree");
const auto &show_detailed = Config::getB("show_detailed"); auto show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid"); const size_t detailed_pid = Config::getI("detailed_pid");
bool should_filter = current_filter != filter; bool should_filter = current_filter != filter;
if (should_filter) current_filter = filter; if (should_filter) current_filter = filter;
const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter); bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
if (sorted_change) { if (sorted_change) {
current_sort = sorting; current_sort = sorting;
current_rev = reverse; current_rev = reverse;
@ -1101,7 +1135,7 @@ namespace Proc {
vector<array<long, CPUSTATES>> cpu_time(Shared::coreCount); vector<array<long, CPUSTATES>> cpu_time(Shared::coreCount);
size_t size = sizeof(long) * CPUSTATES * Shared::coreCount; size_t size = sizeof(long) * CPUSTATES * Shared::coreCount;
if (sysctlbyname("kern.cp_times", &cpu_time[0], &size, NULL, 0) == -1) { if (sysctlbyname("kern.cp_times", &cpu_time[0], &size, nullptr, 0) == -1) {
Logger::error("failed to get CPU times"); Logger::error("failed to get CPU times");
} }
cputimes = 0; cputimes = 0;
@ -1120,16 +1154,16 @@ namespace Proc {
should_filter = true; should_filter = true;
found.clear(); found.clear();
struct timeval currentTime; struct timeval currentTime;
gettimeofday(&currentTime, NULL); gettimeofday(&currentTime, nullptr);
const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000); const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000);
int count = 0; int count = 0;
char buf[_POSIX2_LINE_MAX]; char buf[_POSIX2_LINE_MAX];
kvm_openfiles_wrapper kd(NULL, _PATH_DEVNULL, NULL, O_RDONLY, buf); Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf);
const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_PROC, 0, &count); const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_PROC, 0, &count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
const struct kinfo_proc* kproc = &kprocs[i]; const struct kinfo_proc* kproc = &kprocs[i];
const size_t pid = (size_t)kproc->ki_pid; const size_t pid = (size_t)kproc->ki_pid;
if (pid < 1) continue; if (pid < 1) continue;
found.push_back(pid); found.push_back(pid);
@ -1147,7 +1181,7 @@ namespace Proc {
//? Get program name, command, username, parent pid, nice and status //? Get program name, command, username, parent pid, nice and status
if (no_cache) { if (no_cache) {
if (kproc->ki_comm == NULL or kproc->ki_comm == "idle"s) { if (string(kproc->ki_comm) == "idle"s) {
current_procs.pop_back(); current_procs.pop_back();
found.pop_back(); found.pop_back();
continue; continue;
@ -1310,8 +1344,8 @@ namespace Tools {
struct timeval ts, currTime; struct timeval ts, currTime;
std::size_t len = sizeof(ts); std::size_t len = sizeof(ts);
int mib[2] = {CTL_KERN, KERN_BOOTTIME}; int mib[2] = {CTL_KERN, KERN_BOOTTIME};
if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) { if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) {
gettimeofday(&currTime, NULL); gettimeofday(&currTime, nullptr);
return currTime.tv_sec - ts.tv_sec; return currTime.tv_sec - ts.tv_sec;
} }
return 0.0; return 0.0;

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -19,7 +19,6 @@ tab-size = 4
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h> #include <IOKit/IOKitLib.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ifaddrs.h>
#include <libproc.h> #include <libproc.h>
#include <mach/mach.h> #include <mach/mach.h>
#include <mach/mach_host.h> #include <mach/mach_host.h>
@ -28,7 +27,12 @@ tab-size = 4
#include <mach/processor_info.h> #include <mach/processor_info.h>
#include <mach/vm_statistics.h> #include <mach/vm_statistics.h>
#include <mach/mach_time.h> #include <mach/mach_time.h>
// BUGS
// If both <net/if.h> and <ifaddrs.h> are being included, <net/if.h> must be
// included before <ifaddrs.h>.
// from: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/getifaddrs.3.html
#include <net/if.h> #include <net/if.h>
#include <ifaddrs.h>
#include <net/if_dl.h> #include <net/if_dl.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/tcp_fsm.h> #include <netinet/tcp_fsm.h>
@ -37,12 +41,10 @@ tab-size = 4
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <sys/types.h> #include <sys/types.h>
#include <netinet/in.h> // for inet_ntop
#include <unistd.h> #include <unistd.h>
#include <stdexcept> #include <stdexcept>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include <cmath> #include <cmath>
#include <fstream> #include <fstream>
#include <numeric> #include <numeric>
@ -50,6 +52,10 @@ tab-size = 4
#include <regex> #include <regex>
#include <string> #include <string>
#include "../btop_config.hpp"
#include "../btop_shared.hpp"
#include "../btop_tools.hpp"
#include "sensors.hpp" #include "sensors.hpp"
#include "smc.hpp" #include "smc.hpp"
@ -64,10 +70,9 @@ using namespace Tools;
namespace Cpu { namespace Cpu {
vector<long long> core_old_totals; vector<long long> core_old_totals;
vector<long long> core_old_idles; vector<long long> core_old_idles;
vector<string> available_fields = {"total"}; vector<string> available_fields = {"Auto", "total"};
vector<string> available_sensors = {"Auto"}; vector<string> available_sensors = {"Auto"};
cpu_info current_cpu; cpu_info current_cpu;
fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq";
bool got_sensors = false, cpu_temp_only = false; bool got_sensors = false, cpu_temp_only = false;
int core_offset = 0; int core_offset = 0;
@ -123,7 +128,7 @@ namespace Shared {
} }
size_t physicalCoreCountSize = sizeof(physicalCoreCount); size_t physicalCoreCountSize = sizeof(physicalCoreCount);
if (sysctlbyname("hw.physicalcpu", &physicalCoreCount, &physicalCoreCountSize, NULL, 0) < 0) { if (sysctlbyname("hw.physicalcpu", &physicalCoreCount, &physicalCoreCountSize, nullptr, 0) < 0) {
Logger::error("Could not get physical core count"); Logger::error("Could not get physical core count");
} }
@ -149,7 +154,7 @@ namespace Shared {
int64_t memsize = 0; int64_t memsize = 0;
size_t size = sizeof(memsize); size_t size = sizeof(memsize);
if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) < 0) { if (sysctlbyname("hw.memsize", &memsize, &size, nullptr, 0) < 0) {
Logger::warning("Could not get memory size"); Logger::warning("Could not get memory size");
} }
totalMem = memsize; totalMem = memsize;
@ -158,7 +163,6 @@ namespace Shared {
arg_max = sysconf(_SC_ARG_MAX); arg_max = sysconf(_SC_ARG_MAX);
//? Init for namespace Cpu //? Init for namespace Cpu
if (not fs::exists(Cpu::freq_path) or access(Cpu::freq_path.c_str(), R_OK) == -1) Cpu::freq_path.clear();
Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {}); Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {});
Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {});
Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0);
@ -188,19 +192,19 @@ namespace Cpu {
const array<string, 10> time_names = {"user", "nice", "system", "idle"}; const array<string, 10> time_names = {"user", "nice", "system", "idle"};
unordered_flat_map<string, long long> cpu_old = { unordered_flat_map<string, long long> cpu_old = {
{"totals", 0}, {"totals", 0},
{"idles", 0}, {"idles", 0},
{"user", 0}, {"user", 0},
{"nice", 0}, {"nice", 0},
{"system", 0}, {"system", 0},
{"idle", 0} {"idle", 0}
}; };
string get_cpuName() { string get_cpuName() {
string name; string name;
char buffer[1024]; char buffer[1024];
size_t size = sizeof(buffer); size_t size = sizeof(buffer);
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, NULL, 0) < 0) { if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, nullptr, 0) < 0) {
Logger::error("Failed to get CPU name"); Logger::error("Failed to get CPU name");
return name; return name;
} }
@ -318,7 +322,7 @@ namespace Cpu {
int mib[] = {CTL_HW, HW_CPU_FREQ}; int mib[] = {CTL_HW, HW_CPU_FREQ};
if (sysctl(mib, 2, &freq, &size, NULL, 0) < 0) { if (sysctl(mib, 2, &freq, &size, nullptr, 0) < 0) {
// this fails on Apple Silicon macs. Apparently you're not allowed to know // this fails on Apple Silicon macs. Apparently you're not allowed to know
return ""; return "";
} }
@ -437,23 +441,19 @@ namespace Cpu {
return {percent, seconds, status}; return {percent, seconds, status};
} }
auto collect(const bool no_update) -> cpu_info & { auto collect(bool no_update) -> cpu_info & {
if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty()))
return current_cpu; return current_cpu;
auto &cpu = current_cpu; auto &cpu = current_cpu;
double avg[3]; if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) {
if (getloadavg(avg, sizeof(avg)) < 0) {
Logger::error("failed to get load averages"); Logger::error("failed to get load averages");
} }
cpu.load_avg = { (float)avg[0], (float)avg[1], (float)avg[2]};
natural_t cpu_count; natural_t cpu_count;
natural_t i; natural_t i;
kern_return_t error; kern_return_t error;
processor_cpu_load_info_data_t *cpu_load_info = NULL; processor_cpu_load_info_data_t *cpu_load_info = nullptr;
MachProcessorInfo info{}; MachProcessorInfo info{};
error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count); error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
@ -602,7 +602,7 @@ namespace Mem {
} }
/* Get the list of all drive objects. */ /* Get the list of all drive objects. */
if (IOServiceGetMatchingServices(libtop_master_port, if (IOServiceGetMatchingServices(libtop_master_port,
IOServiceMatching("IOMediaBSDClient"), &drive_list)) { IOServiceMatching("IOMediaBSDClient"), &drive_list)) {
Logger::error("Error in IOServiceGetMatchingServices()"); Logger::error("Error in IOServiceGetMatchingServices()");
return; return;
} }
@ -662,15 +662,15 @@ namespace Mem {
} }
} }
auto collect(const bool no_update) -> mem_info & { auto collect(bool no_update) -> mem_info & {
if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty()))
return current_mem; return current_mem;
auto &show_swap = Config::getB("show_swap"); auto show_swap = Config::getB("show_swap");
auto &show_disks = Config::getB("show_disks"); auto show_disks = Config::getB("show_disks");
auto &swap_disk = Config::getB("swap_disk"); auto swap_disk = Config::getB("swap_disk");
auto &mem = current_mem; auto &mem = current_mem;
static const bool snapped = (getenv("BTOP_SNAPPED") != NULL); static bool snapped = (getenv("BTOP_SNAPPED") != nullptr);
vm_statistics64 p; vm_statistics64 p;
mach_msg_type_number_t info_size = HOST_VM_INFO64_COUNT; mach_msg_type_number_t info_size = HOST_VM_INFO64_COUNT;
@ -685,7 +685,7 @@ namespace Mem {
struct xsw_usage swap; struct xsw_usage swap;
size_t len = sizeof(struct xsw_usage); size_t len = sizeof(struct xsw_usage);
if (sysctl(mib, 2, &swap, &len, NULL, 0) == 0) { if (sysctl(mib, 2, &swap, &len, nullptr, 0) == 0) {
mem.stats.at("swap_total") = swap.xsu_total; mem.stats.at("swap_total") = swap.xsu_total;
mem.stats.at("swap_free") = swap.xsu_avail; mem.stats.at("swap_free") = swap.xsu_avail;
mem.stats.at("swap_used") = swap.xsu_used; mem.stats.at("swap_used") = swap.xsu_used;
@ -712,7 +712,7 @@ namespace Mem {
double uptime = system_uptime(); double uptime = system_uptime();
auto &disks_filter = Config::getS("disks_filter"); auto &disks_filter = Config::getS("disks_filter");
bool filter_exclude = false; bool filter_exclude = false;
// auto &only_physical = Config::getB("only_physical"); // auto only_physical = Config::getB("only_physical");
auto &disks = mem.disks; auto &disks = mem.disks;
vector<string> filter; vector<string> filter;
if (not disks_filter.empty()) { if (not disks_filter.empty()) {
@ -842,11 +842,11 @@ namespace Net {
auto operator()() -> struct ifaddrs * { return ifaddr; } auto operator()() -> struct ifaddrs * { return ifaddr; }
}; };
auto collect(const bool no_update) -> net_info & { auto collect(bool no_update) -> net_info & {
auto &net = current_net; auto &net = current_net;
auto &config_iface = Config::getS("net_iface"); auto &config_iface = Config::getS("net_iface");
auto &net_sync = Config::getB("net_sync"); auto net_sync = Config::getB("net_sync");
auto &net_auto = Config::getB("net_auto"); auto net_auto = Config::getB("net_auto");
auto new_timestamp = time_ms(); auto new_timestamp = time_ms();
if (not no_update and errors < 3) { if (not no_update and errors < 3) {
@ -859,45 +859,63 @@ namespace Net {
return empty_net; return empty_net;
} }
int family = 0; int family = 0;
char ip[NI_MAXHOST]; static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); // 46 >= 16, compile-time assurance.
enum { IPBUFFER_MAXSIZE = INET6_ADDRSTRLEN }; // manually using the known biggest value, guarded by the above static_assert
char ip[IPBUFFER_MAXSIZE];
interfaces.clear(); interfaces.clear();
string ipv4, ipv6; string ipv4, ipv6;
//? Iteration over all items in getifaddrs() list //? Iteration over all items in getifaddrs() list
for (auto *ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) { for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue; if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family; family = ifa->ifa_addr->sa_family;
const auto &iface = ifa->ifa_name; const auto &iface = ifa->ifa_name;
//? Get IPv4 address
if (family == AF_INET) {
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
net[iface].ipv4 = ip;
}
//? Get IPv6 address
else if (family == AF_INET6) {
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
net[iface].ipv6 = ip;
}
//? Update available interfaces vector and get status of interface //? Update available interfaces vector and get status of interface
if (not v_contains(interfaces, iface)) { if (not v_contains(interfaces, iface)) {
interfaces.push_back(iface); interfaces.push_back(iface);
net[iface].connected = (ifa->ifa_flags & IFF_RUNNING); net[iface].connected = (ifa->ifa_flags & IFF_RUNNING);
// An interface can have more than one IP of the same family associated with it,
// but we pick only the first one to show in the NET box.
// Note: Interfaces without any IPv4 and IPv6 set are still valid and monitorable!
net[iface].ipv4.clear();
net[iface].ipv6.clear();
} }
//? Get IPv4 address
if (family == AF_INET) {
if (net[iface].ipv4.empty()) {
if (nullptr != inet_ntop(family, &(reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr)->sin_addr), ip, IPBUFFER_MAXSIZE)) {
net[iface].ipv4 = ip;
} else {
int errsv = errno;
Logger::error("Net::collect() -> Failed to convert IPv4 to string for iface " + string(iface) + ", errno: " + strerror(errsv));
}
}
}
//? Get IPv6 address
else if (family == AF_INET6) {
if (net[iface].ipv6.empty()) {
if (nullptr != inet_ntop(family, &(reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr)->sin6_addr), ip, IPBUFFER_MAXSIZE)) {
net[iface].ipv6 = ip;
} else {
int errsv = errno;
Logger::error("Net::collect() -> Failed to convert IPv6 to string for iface " + string(iface) + ", errno: " + strerror(errsv));
}
}
} // else, ignoring family==AF_LINK (see man 3 getifaddrs)
} }
unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats; unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0};
size_t len; size_t len;
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) {
Logger::error("failed getting network interfaces"); Logger::error("failed getting network interfaces");
} else { } else {
std::unique_ptr<char[]> buf(new char[len]); std::unique_ptr<char[]> buf(new char[len]);
if (sysctl(mib, 6, buf.get(), &len, NULL, 0) < 0) { if (sysctl(mib, 6, buf.get(), &len, nullptr, 0) < 0) {
Logger::error("failed getting network interfaces"); Logger::error("failed getting network interfaces");
} else { } else {
char *lim = buf.get() + len; char *lim = buf.get() + len;
char *next = NULL; char *next = nullptr;
for (next = buf.get(); next < lim;) { for (next = buf.get(); next < lim;) {
struct if_msghdr *ifm = (struct if_msghdr *)next; struct if_msghdr *ifm = (struct if_msghdr *)next;
next += ifm->ifm_msglen; next += ifm->ifm_msglen;
@ -981,7 +999,7 @@ namespace Net {
auto sorted_interfaces = interfaces; auto sorted_interfaces = interfaces;
rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) { rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) {
return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total, return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total,
net.at(b).stat["download"].total + net.at(b).stat["upload"].total); net.at(b).stat["download"].total + net.at(b).stat["upload"].total);
}); });
selected_iface.clear(); selected_iface.clear();
//? Try to set to a connected interface //? Try to set to a connected interface
@ -1003,9 +1021,9 @@ namespace Net {
for (const auto &dir : {"download", "upload"}) { for (const auto &dir : {"download", "upload"}) {
for (const auto &sel : {0, 1}) { for (const auto &sel : {0, 1}) {
if (rescale or max_count[dir][sel] >= 5) { if (rescale or max_count[dir][sel] >= 5) {
const uint64_t avg_speed = (net[selected_iface].bandwidth[dir].size() > 5 const long long avg_speed = (net[selected_iface].bandwidth[dir].size() > 5
? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0) / 5 ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0ll) / 5
: net[selected_iface].stat[dir].speed); : net[selected_iface].stat[dir].speed);
graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10); graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10);
max_count[dir][0] = max_count[dir][1] = 0; max_count[dir][0] = max_count[dir][1] = 0;
redraw = true; redraw = true;
@ -1074,7 +1092,7 @@ namespace Proc {
//? Process runtime : current time - start time (both in unix time - seconds since epoch) //? Process runtime : current time - start time (both in unix time - seconds since epoch)
struct timeval currentTime; struct timeval currentTime;
gettimeofday(&currentTime, NULL); gettimeofday(&currentTime, nullptr);
detailed.elapsed = sec_to_dhms(currentTime.tv_sec - (detailed.entry.cpu_s / 1'000'000)); detailed.elapsed = sec_to_dhms(currentTime.tv_sec - (detailed.entry.cpu_s / 1'000'000));
if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3); if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
@ -1106,17 +1124,17 @@ namespace Proc {
} }
//* Collects and sorts process information from /proc //* Collects and sorts process information from /proc
auto collect(const bool no_update) -> vector<proc_info> & { auto collect(bool no_update) -> vector<proc_info> & {
const auto &sorting = Config::getS("proc_sorting"); const auto &sorting = Config::getS("proc_sorting");
const auto &reverse = Config::getB("proc_reversed"); auto reverse = Config::getB("proc_reversed");
const auto &filter = Config::getS("proc_filter"); const auto &filter = Config::getS("proc_filter");
const auto &per_core = Config::getB("proc_per_core"); auto per_core = Config::getB("proc_per_core");
const auto &tree = Config::getB("proc_tree"); auto tree = Config::getB("proc_tree");
const auto &show_detailed = Config::getB("show_detailed"); auto show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid"); const size_t detailed_pid = Config::getI("detailed_pid");
bool should_filter = current_filter != filter; bool should_filter = current_filter != filter;
if (should_filter) current_filter = filter; if (should_filter) current_filter = filter;
const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter); bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
if (sorted_change) { if (sorted_change) {
current_sort = sorting; current_sort = sorting;
current_rev = reverse; current_rev = reverse;
@ -1136,7 +1154,7 @@ namespace Proc {
{ //* Get CPU totals { //* Get CPU totals
natural_t cpu_count; natural_t cpu_count;
kern_return_t error; kern_return_t error;
processor_cpu_load_info_data_t *cpu_load_info = NULL; processor_cpu_load_info_data_t *cpu_load_info = nullptr;
MachProcessorInfo info{}; MachProcessorInfo info{};
error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count); error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
if (error != KERN_SUCCESS) { if (error != KERN_SUCCESS) {
@ -1158,13 +1176,13 @@ namespace Proc {
size_t size = 0; size_t size = 0;
const auto timeNow = time_micros(); const auto timeNow = time_micros();
if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) { if (sysctl(mib, 4, nullptr, &size, nullptr, 0) < 0 || size == 0) {
Logger::error("Unable to get size of kproc_infos"); Logger::error("Unable to get size of kproc_infos");
} }
uint64_t cpu_t = 0; uint64_t cpu_t = 0;
std::unique_ptr<kinfo_proc[]> processes(new kinfo_proc[size / sizeof(kinfo_proc)]); std::unique_ptr<kinfo_proc[]> processes(new kinfo_proc[size / sizeof(kinfo_proc)]);
if (sysctl(mib, 4, processes.get(), &size, NULL, 0) == 0) { if (sysctl(mib, 4, processes.get(), &size, nullptr, 0) == 0) {
size_t count = size / sizeof(struct kinfo_proc); size_t count = size / sizeof(struct kinfo_proc);
for (size_t i = 0; i < count; i++) { //* iterate over all processes in kinfo_proc for (size_t i = 0; i < count; i++) { //* iterate over all processes in kinfo_proc
struct kinfo_proc& kproc = processes.get()[i]; struct kinfo_proc& kproc = processes.get()[i];
@ -1195,7 +1213,7 @@ namespace Proc {
std::unique_ptr<char[]> proc_chars(new char[Shared::arg_max]); std::unique_ptr<char[]> proc_chars(new char[Shared::arg_max]);
int mib[] = {CTL_KERN, KERN_PROCARGS2, (int)pid}; int mib[] = {CTL_KERN, KERN_PROCARGS2, (int)pid};
size_t argmax = Shared::arg_max; size_t argmax = Shared::arg_max;
if (sysctl(mib, 3, proc_chars.get(), &argmax, NULL, 0) == 0) { if (sysctl(mib, 3, proc_chars.get(), &argmax, nullptr, 0) == 0) {
int argc = 0; int argc = 0;
memcpy(&argc, &proc_chars.get()[0], sizeof(argc)); memcpy(&argc, &proc_chars.get()[0], sizeof(argc));
std::string_view proc_args(proc_chars.get(), argmax); std::string_view proc_args(proc_chars.get(), argmax);
@ -1358,8 +1376,8 @@ namespace Tools {
struct timeval ts, currTime; struct timeval ts, currTime;
std::size_t len = sizeof(ts); std::size_t len = sizeof(ts);
int mib[2] = {CTL_KERN, KERN_BOOTTIME}; int mib[2] = {CTL_KERN, KERN_BOOTTIME};
if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) { if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) {
gettimeofday(&currTime, NULL); gettimeofday(&currTime, nullptr);
return currTime.tv_sec - ts.tv_sec; return currTime.tv_sec - ts.tv_sec;
} }
return 0.0; return 0.0;

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -74,8 +74,8 @@ double getValue(IOHIDServiceClientRef sc) {
long long Cpu::ThermalSensors::getSensors() { long long Cpu::ThermalSensors::getSensors() {
CFDictionaryRef thermalSensors = matching(0xff00, 5); // 65280_10 = FF00_16 CFDictionaryRef thermalSensors = matching(0xff00, 5); // 65280_10 = FF00_16
// thermalSensors's PrimaryUsagePage should be 0xff00 for M1 chip, instead of 0xff05 // thermalSensors's PrimaryUsagePage should be 0xff00 for M1 chip, instead of 0xff05
// can be checked by ioreg -lfx // can be checked by ioreg -lfx
IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault); IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
IOHIDEventSystemClientSetMatching(system, thermalSensors); IOHIDEventSystemClientSetMatching(system, thermalSensors);
CFArrayRef matchingsrvs = IOHIDEventSystemClientCopyServices(system); CFArrayRef matchingsrvs = IOHIDEventSystemClientCopyServices(system);

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -18,6 +18,9 @@ tab-size = 4
#include "smc.hpp" #include "smc.hpp"
static constexpr size_t MaxIndexCount = sizeof("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") - 1;
static constexpr const char *KeyIndexes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static UInt32 _strtoul(char *str, int size, int base) { static UInt32 _strtoul(char *str, int size, int base) {
UInt32 total = 0; UInt32 total = 0;
int i; int i;
@ -34,20 +37,18 @@ static UInt32 _strtoul(char *str, int size, int base) {
static void _ultostr(char *str, UInt32 val) { static void _ultostr(char *str, UInt32 val) {
str[0] = '\0'; str[0] = '\0';
sprintf(str, "%c%c%c%c", snprintf(str, 5, "%c%c%c%c",
(unsigned int)val >> 24, (unsigned int)val >> 24,
(unsigned int)val >> 16, (unsigned int)val >> 16,
(unsigned int)val >> 8, (unsigned int)val >> 8,
(unsigned int)val); (unsigned int)val);
} }
namespace Cpu { namespace Cpu {
SMCConnection::SMCConnection() { SMCConnection::SMCConnection() {
IOMasterPort(kIOMasterPortDefault, &masterPort);
CFMutableDictionaryRef matchingDictionary = IOServiceMatching("AppleSMC"); CFMutableDictionaryRef matchingDictionary = IOServiceMatching("AppleSMC");
result = IOServiceGetMatchingServices(masterPort, matchingDictionary, &iterator); result = IOServiceGetMatchingServices(0, matchingDictionary, &iterator);
if (result != kIOReturnSuccess) { if (result != kIOReturnSuccess) {
throw std::runtime_error("failed to get AppleSMC"); throw std::runtime_error("failed to get AppleSMC");
} }
@ -65,7 +66,7 @@ namespace Cpu {
} }
} }
SMCConnection::~SMCConnection() { SMCConnection::~SMCConnection() {
IOServiceClose(conn); IOServiceClose(conn);
} }
long long SMCConnection::getSMCTemp(char *key) { long long SMCConnection::getSMCTemp(char *key) {
@ -92,12 +93,15 @@ namespace Cpu {
long long SMCConnection::getTemp(int core) { long long SMCConnection::getTemp(int core) {
char key[] = SMC_KEY_CPU_TEMP; char key[] = SMC_KEY_CPU_TEMP;
if (core >= 0) { if (core >= 0) {
snprintf(key, 5, "TC%1dc", core); if ((size_t)core > MaxIndexCount) {
return -1;
}
snprintf(key, 5, "TC%1cc", KeyIndexes[core]);
} }
long long result = getSMCTemp(key); long long result = getSMCTemp(key);
if (result == -1) { if (result == -1) {
// try again with C // try again with C
snprintf(key, 5, "TC%1dC", core); snprintf(key, 5, "TC%1dC", KeyIndexes[core]);
result = getSMCTemp(key); result = getSMCTemp(key);
} }
return result; return result;
@ -132,7 +136,7 @@ namespace Cpu {
return kIOReturnSuccess; return kIOReturnSuccess;
} }
kern_return_t SMCConnection::SMCCall(int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure) { kern_return_t SMCConnection::SMCCall(int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure) {
size_t structureInputSize; size_t structureInputSize;
size_t structureOutputSize; size_t structureOutputSize;
@ -141,10 +145,10 @@ namespace Cpu {
structureOutputSize = sizeof(SMCKeyData_t); structureOutputSize = sizeof(SMCKeyData_t);
return IOConnectCallStructMethod(conn, index, return IOConnectCallStructMethod(conn, index,
// inputStructure // inputStructure
inputStructure, structureInputSize, inputStructure, structureInputSize,
// ouputStructure // ouputStructure
outputStructure, &structureOutputSize); outputStructure, &structureOutputSize);
} }
} // namespace Cpu } // namespace Cpu

View File

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -104,7 +104,7 @@ namespace Cpu {
long long getTemp(int core); long long getTemp(int core);
private: private:
kern_return_t SMCReadKey(UInt32Char_t key, SMCVal_t *val); kern_return_t SMCReadKey(UInt32Char_t key, SMCVal_t *val);
long long getSMCTemp(char *key); long long getSMCTemp(char *key);
kern_return_t SMCCall(int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure); kern_return_t SMCCall(int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure);

89
themes/adwaita.theme Normal file
View File

@ -0,0 +1,89 @@
#Bashtop Adwaita theme
#by flipflop133
# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255"
# example for white: "#FFFFFF", "#ff" or "255 255 255".
# All graphs and meters can be gradients
# For single color graphs leave "mid" and "end" variable empty.
# Use "start" and "end" variables for two color gradient
# Use "start", "mid" and "end" for three color gradient
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#f6f5f4"
# Main text color
theme[main_fg]="#2e3436"
# Title color for boxes
theme[title]="#2e3436"
# Higlight color for keyboard shortcuts
theme[hi_fg]="#1a5fb4"
# Background color of selected item in processes box
theme[selected_bg]="#1c71d8"
# Foreground color of selected item in processes box
theme[selected_fg]="#ffffff"
# Color of inactive/disabled text
theme[inactive_fg]="#5e5c64"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#1a5fb4"
# Cpu box outline color
theme[cpu_box]="#2e3436"
# Memory/disks box outline color
theme[mem_box]="#3d3c14"
# Net up/down box outline color
theme[net_box]="#2e3436"
# Processes box outline color
theme[proc_box]="#2e3436"
# Box divider line and small boxes line color
theme[div_line]="#2e3436"
# Temperature graph colors
theme[temp_start]="#1a5fb4"
theme[temp_mid]="#1a5fb4"
theme[temp_end]="#c01c28"
# CPU graph colors
theme[cpu_start]="#1a5fb4"
theme[cpu_mid]="#1a5fb4"
theme[cpu_end]="#c01c28"
# Mem/Disk free meter
theme[free_start]="#1a5fb4"
theme[free_mid]="#1a5fb4"
theme[free_end]="#c01c28"
# Mem/Disk cached meter
theme[cached_start]="#1a5fb4"
theme[cached_mid]="#1a5fb4"
theme[cached_end]="#c01c28"
# Mem/Disk available meter
theme[available_start]="#1a5fb4"
theme[available_mid]="#1a5fb4"
theme[available_end]="#c01c28"
# Mem/Disk used meter
theme[used_start]="#1a5fb4"
theme[used_mid]="#1a5fb4"
theme[used_end]="#c01c28"
# Download graph colors
theme[download_start]="#1a5fb4"
theme[download_mid]="#1a5fb4"
theme[download_end]="#c01c28"
# Upload graph colors
theme[upload_start]="#1a5fb4"
theme[upload_mid]="#1a5fb4"
theme[upload_end]="#c01c28"

82
themes/elementarish.theme Normal file
View File

@ -0,0 +1,82 @@
# Theme: Elementarish
# (inspired by Elementary OS)
# By: Dennis Mayr
# Main bg
theme[main_bg]="#333333"
# Main text color
theme[main_fg]="#eee8d5"
# Title color for boxes
theme[title]="#eee8d5"
# Higlight color for keyboard shortcuts
theme[hi_fg]="#d1302c"
# Background color of selected item in processes box
theme[selected_bg]="#268ad0"
# Foreground color of selected item in processes box
theme[selected_fg]="#eee8d5"
# Color of inactive/disabled text
theme[inactive_fg]="#657b83"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#268ad0"
# Cpu box outline color
theme[cpu_box]="#657b83"
# Memory/disks box outline color
theme[mem_box]="#657b83"
# Net up/down box outline color
theme[net_box]="#657b83"
# Processes box outline color
theme[proc_box]="#657b83"
# Box divider line and small boxes line color
theme[div_line]="#657b83"
# Temperature graph colors
theme[temp_start]="#859900"
theme[temp_mid]="#b28602"
theme[temp_end]="#d1302c"
# CPU graph colors
theme[cpu_start]="#859900"
theme[cpu_mid]="#b28602"
theme[cpu_end]="#d1302c"
# Mem/Disk free meter
theme[free_start]="#268ad0"
theme[free_mid]="#6c71c4"
theme[free_end]="#2a9d95"
# Mem/Disk cached meter
theme[cached_start]="#268ad0"
theme[cached_mid]="#6c71c4"
theme[cached_end]="#d1302c"
# Mem/Disk available meter
theme[available_start]="#268ad0"
theme[available_mid]="#6c71c4"
theme[available_end]="#d1302c"
# Mem/Disk used meter
theme[used_start]="#859900"
theme[used_mid]="#b28602"
theme[used_end]="#d1302c"
# Download graph colors
theme[download_start]="#268ad0"
theme[download_mid]="#6c71c4"
theme[download_end]="#d1302c"
# Upload graph colors
theme[upload_start]="#268ad0"
theme[upload_mid]="#6c71c4"
theme[upload_end]="#d1302c"

View File

@ -1,12 +1,10 @@
# Btop everforest dark hard theme by u/archontop.
# All graphs and meters can be gradients # All graphs and meters can be gradients
# For single color graphs leave "mid" and "end" variable empty. # For single color graphs leave "mid" and "end" variable empty.
# Use "start" and "end" variables for two color gradient # Use "start" and "end" variables for two color gradient
# Use "start", "mid" and "end" for three color gradient # Use "start", "mid" and "end" for three color gradient
# Main background, empty for terminal default, need to be empty if you want transparent background # Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#2b3339" theme[main_bg]="#272e33"
# Main text color # Main text color
theme[main_fg]="#d3c6aa" theme[main_fg]="#d3c6aa"
@ -18,13 +16,13 @@ theme[title]="#d3c6aa"
theme[hi_fg]="#e67e80" theme[hi_fg]="#e67e80"
# Background color of selected items # Background color of selected items
theme[selected_bg]="#4b565c" theme[selected_bg]="#374145"
# Foreground color of selected items # Foreground color of selected items
theme[selected_fg]="#dbbc7f" theme[selected_fg]="#dbbc7f"
# Color of inactive/disabled text # Color of inactive/disabled text
theme[inactive_fg]="#2b3339" theme[inactive_fg]="#272e33"
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling # Color of text appearing on top of graphs, i.e uptime and current network graph scaling
theme[graph_text]="#d3c6aa" theme[graph_text]="#d3c6aa"
@ -33,19 +31,19 @@ theme[graph_text]="#d3c6aa"
theme[proc_misc]="#a7c080" theme[proc_misc]="#a7c080"
# Cpu box outline color # Cpu box outline color
theme[cpu_box]="#4b565c" theme[cpu_box]="#374145"
# Memory/disks box outline color # Memory/disks box outline color
theme[mem_box]="#4b565c" theme[mem_box]="#374145"
# Net up/down box outline color # Net up/down box outline color
theme[net_box]="#4b565c" theme[net_box]="#374145"
# Processes box outline color # Processes box outline color
theme[proc_box]="#4b565c" theme[proc_box]="#374145"
# Box divider line and small boxes line color # Box divider line and small boxes line color
theme[div_line]="#4b565c" theme[div_line]="#374145"
# Temperature graph colors # Temperature graph colors
theme[temp_start]="#a7c080" theme[temp_start]="#a7c080"
@ -78,14 +76,14 @@ theme[used_mid]="#dbbc7f"
theme[used_end]="#f85552" theme[used_end]="#f85552"
# Download graph colors # Download graph colors
theme[download_start]="#8da101" theme[download_start]="#a7c080"
theme[download_mid]="#83c092" theme[download_mid]="#83c092"
theme[download_end]="#a7c080" theme[download_end]="#7fbbb3"
# Upload graph colors # Upload graph colors
theme[upload_start]="#f85552" theme[upload_start]="#dbbc7f"
theme[upload_mid]="#dbbc7f" theme[upload_mid]="#e69875"
theme[upload_end]="#a7c080" theme[upload_end]="#e67e80"
# Process box color gradient for threads, mem and cpu usage # Process box color gradient for threads, mem and cpu usage
theme[process_start]="#a7c080" theme[process_start]="#a7c080"

86
themes/horizon.theme Normal file
View File

@ -0,0 +1,86 @@
# All graphs and meters can be gradients
# For single color graphs leave "mid" and "end" variable empty.
# Use "start" and "end" variables for two color gradient
# Use "start", "mid" and "end" for three color gradient
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#1C1E26"
# Main text color
theme[main_fg]="#f8f8f2"
# Title color for boxes
theme[title]="#f8f8f2"
# Highlight color for keyboard shortcuts
theme[hi_fg]="#B877DB"
# Background color of selected items
theme[selected_bg]="#282b37"
# Foreground color of selected items
theme[selected_fg]="#f8f8f2"
# Color of inactive/disabled text
theme[inactive_fg]="#272e33"
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling
theme[graph_text]="#f8f8f2"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#27D796"
# Cpu box outline color
theme[cpu_box]="#B877DB"
# Memory/disks box outline color
theme[mem_box]="#27D796"
# Net up/down box outline color
theme[net_box]="#E95678"
# Processes box outline color
theme[proc_box]="#25B2BC"
# Box divider line and small boxes line color
theme[div_line]="#272e33"
# Temperature graph colors
theme[temp_start]="#27D796"
theme[temp_mid]="#FAC29A"
theme[temp_end]="#E95678"
# CPU graph colors
theme[cpu_start]="#27D796"
theme[cpu_mid]="#FAC29A"
theme[cpu_end]="#E95678"
# Mem/Disk free meter
theme[free_start]="#E95678"
theme[free_mid]="#FAC29A"
theme[free_end]="#27D796"
# Mem/Disk cached meter
theme[cached_start]="#27D796"
theme[cached_mid]="#FAC29A"
theme[cached_end]="#E95678"
# Mem/Disk available meter
theme[available_start]="#27D796"
theme[available_mid]="#FAC29A"
theme[available_end]="#E95678"
# Mem/Disk used meter
theme[used_start]="#27D796"
theme[used_mid]="#FAC29A"
theme[used_end]="#E95678"
# Download graph colors
theme[download_start]="#27D796"
theme[download_mid]="#FAC29A"
theme[download_end]="#E95678"
# Upload graph colors
theme[upload_start]="#27D796"
theme[upload_mid]="#FAC29A"
theme[upload_end]="#E95678"

View File

@ -2,13 +2,13 @@
#by Kyli0x <kyli0x@protonmail.ch> #by Kyli0x <kyli0x@protonmail.ch>
# Main background, empty for terminal default, need to be empty if you want transparent background # Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#23252e" theme[main_bg]="#222222"
# Main text color # Main text color
theme[main_fg]="#f8f8f2" theme[main_fg]="#e8f6f5"
# Title color for boxes # Title color for boxes
theme[title]="#f8f8f2" theme[title]="#e8f6f5"
# Highlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#21d6c9" theme[hi_fg]="#21d6c9"
@ -17,16 +17,16 @@ theme[hi_fg]="#21d6c9"
theme[selected_bg]="#1aaba0" theme[selected_bg]="#1aaba0"
# Foreground color of selected item in processes box # Foreground color of selected item in processes box
theme[selected_fg]="#f8f8f2" theme[selected_fg]="#e8f6f5"
# Color of inactive/disabled text # Color of inactive/disabled text
theme[inactive_fg]="#497e7a" theme[inactive_fg]="#5ec4bc"
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling # Color of text appearing on top of graphs, i.e uptime and current network graph scaling
theme[graph_text]="#21d6c9" theme[graph_text]="#ba1a84"
# Background color of the percentage meters # Background color of the percentage meters
theme[meter_bg]="#80638e" theme[meter_bg]="#5ec4bc"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text # Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#21d6c9" theme[proc_misc]="#21d6c9"
@ -49,44 +49,44 @@ theme[div_line]="#80638e"
# Temperature graph colors # Temperature graph colors
theme[temp_start]="#21d6c9" theme[temp_start]="#21d6c9"
theme[temp_mid]="#1aaba0" theme[temp_mid]="#1aaba0"
theme[temp_end]="#497e7a" theme[temp_end]="#5ec4bc"
# CPU graph colors # CPU graph colors
theme[cpu_start]="#21d6c9" theme[cpu_start]="#21d6c9"
theme[cpu_mid]="#1aaba0" theme[cpu_mid]="#1aaba0"
theme[cpu_end]="#497e7a" theme[cpu_end]="#5ec4bc"
# Mem/Disk free meter # Mem/Disk free meter
theme[free_start]="#21d6c9" theme[free_start]="#21d6c9"
theme[free_mid]="#1aaba0" theme[free_mid]="#1aaba0"
theme[free_end]="#497e7a" theme[free_end]="#5ec4bc"
# Mem/Disk cached meter # Mem/Disk cached meter
theme[cached_start]="#21d6c9" theme[cached_start]="#21d6c9"
theme[cached_mid]="#1aaba0" theme[cached_mid]="#1aaba0"
theme[cached_end]="#497e7a" theme[cached_end]="#5ec4bc"
# Mem/Disk available meter # Mem/Disk available meter
theme[available_start]="#21d6c9" theme[available_start]="#21d6c9"
theme[available_mid]="#1aaba0" theme[available_mid]="#1aaba0"
theme[available_end]="#497e7a" theme[available_end]="#5ec4bc"
# Mem/Disk used meter # Mem/Disk used meter
theme[used_start]="#21d6c9" theme[used_start]="#21d6c9"
theme[used_mid]="#1aaba0" theme[used_mid]="#1aaba0"
theme[used_end]="#497e7a" theme[used_end]="#5ec4bc"
# Download graph colors # Download graph colors
theme[download_start]="#21d6c9" theme[download_start]="#21d6c9"
theme[download_mid]="#1aaba0" theme[download_mid]="#1aaba0"
theme[download_end]="#497e7a" theme[download_end]="#5ec4bc"
# Upload graph colors # Upload graph colors
theme[upload_start]="#ec95ec" theme[upload_start]="#ec95ec"
theme[upload_mid]="#1aaba0" theme[upload_mid]="#1aaba0"
theme[upload_end]="#497e7a" theme[upload_end]="#5ec4bc"
# Process box color gradient for threads, mem and cpu usage # Process box color gradient for threads, mem and cpu usage
theme[process_start]="#21d6c9" theme[process_start]="#21d6c9"
theme[process_mid]="#1aaba0" theme[process_mid]="#1aaba0"
theme[process_end]="#d486d4" theme[process_end]="#ba1a84"

83
themes/paper.theme Normal file
View File

@ -0,0 +1,83 @@
# Bashtop Paper theme
# c/o @s6muel
# inspired by @yorickpeterse's vim-paper theme at https://gitlab.com/yorickpeterse/vim-paper
#
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#F2EEDE"
# Main text color
theme[main_fg]="#00"
# Title color for boxes
theme[title]="#00"
# Higlight color for keyboard shortcuts
theme[hi_fg]="#CC3E28"
# Background color of selected item in processes box
theme[selected_bg]="#D8D5C7"
# Foreground color of selected item in processes box
theme[selected_fg]="#00"
# Color of inactive/disabled text
theme[inactive_fg]="#d8d5c7"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#00"
# Cpu box outline color
theme[cpu_box]="#00"
# Memory/disks box outline color
theme[mem_box]="#00"
# Net up/down box outline color
theme[net_box]="#00"
# Processes box outline color
theme[proc_box]="#00"
# Box divider line and small boxes line color
theme[div_line]="#00"
# Temperature graph colors
theme[temp_start]="#55"
theme[temp_mid]="#00"
theme[temp_end]="#CC3E28"
# CPU graph colors
theme[cpu_start]="#55"
theme[cpu_mid]="#00"
theme[cpu_end]="#CC3E28"
# Mem/Disk free meter
theme[free_start]="#216609"
theme[free_mid]=""
theme[free_end]="#216609"
# Mem/Disk cached meter
theme[cached_start]="#1e6fcc"
theme[cached_mid]=""
theme[cached_end]="#1e6fcc"
# Mem/Disk available meter
theme[available_start]="#216609"
theme[available_mid]=""
theme[available_end]="#216609"
# Mem/Disk used meter
theme[used_start]="#CC3E28"
theme[used_mid]=""
theme[used_end]="#CC3E28"
# Download graph colors
theme[download_start]="#55"
theme[download_mid]="#00"
theme[download_end]="#CC3E28"
# Upload graph colors
theme[upload_start]="#55"
theme[upload_mid]="#00"
theme[upload_end]="#CC3E28"

View File

@ -0,0 +1,89 @@
#solarized_light theme
#modified from solarized_dark theme
# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255"
# example for white: "#FFFFFF", "#ff" or "255 255 255".
# All graphs and meters can be gradients
# For single color graphs leave "mid" and "end" variable empty.
# Use "start" and "end" variables for two color gradient
# Use "start", "mid" and "end" for three color gradient
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#fdf6e3"
# Main text color
theme[main_fg]="#586e75"
# Title color for boxes
theme[title]="#002b36"
# Higlight color for keyboard shortcuts
theme[hi_fg]="#b58900"
# Background color of selected items
theme[selected_bg]="#eee8d5"
# Foreground color of selected items
theme[selected_fg]="#b58900"
# Color of inactive/disabled text
theme[inactive_fg]="#eee8d5"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#d33682"
# Cpu box outline color
theme[cpu_box]="#93a1a1"
# Memory/disks box outline color
theme[mem_box]="#93a1a1"
# Net up/down box outline color
theme[net_box]="#93a1a1"
# Processes box outline color
theme[proc_box]="#93a1a1"
# Box divider line and small boxes line color
theme[div_line]="#93a1a1"
# Temperature graph colors
theme[temp_start]="#268bd2"
theme[temp_mid]="#ccb5f7"
theme[temp_end]="#fc5378"
# CPU graph colors
theme[cpu_start]="#adc700"
theme[cpu_mid]="#d6a200"
theme[cpu_end]="#e65317"
# Mem/Disk free meter
theme[free_start]="#4e5900"
theme[free_mid]=""
theme[free_end]="#bad600"
# Mem/Disk cached meter
theme[cached_start]="#114061"
theme[cached_mid]=""
theme[cached_end]="#268bd2"
# Mem/Disk available meter
theme[available_start]="#705500"
theme[available_mid]=""
theme[available_end]="#edb400"
# Mem/Disk used meter
theme[used_start]="#6e1718"
theme[used_mid]=""
theme[used_end]="#e02f30"
# Download graph colors
theme[download_start]="#3d4070"
theme[download_mid]="#6c71c4"
theme[download_end]="#a3a8f7"
# Upload graph colors
theme[upload_start]="#701c45"
theme[upload_mid]="#d33682"
theme[upload_end]="#f56caf"