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_size = 4

4
.github/FUNDING.yml vendored
View file

@ -3,10 +3,10 @@
github: aristocratos
patreon: # Replace with a single Patreon 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
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt 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/**'
- 'include/**'
- '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:
static-build:
@ -83,7 +93,9 @@ jobs:
run: git config --global --add safe.directory /__w/btop/btop
- name: Checkout source
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
submodules: recursive
- name: Fix - Stopping at filesystem boundary
run: git init # [fix Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).]
@ -107,7 +119,7 @@ jobs:
cp bin/btop .artifacts/$FILENAME
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: btop-${{ matrix.toolchain }}
path: '.artifacts/**'

View file

@ -1,6 +1,7 @@
name: Continuous Build MacOS
on:
workflow_dispatch:
push:
branches:
- main
@ -12,15 +13,26 @@ on:
- '!src/freebsd/**'
- 'include/**'
- '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:
build-osx:
build-macos11:
runs-on: macos-11
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Compile
run: |
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
ls -alh bin
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
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/*'

32
.gitignore vendored
View file

@ -51,6 +51,34 @@ bin
btop
.*/
# Optional libraries
lib/rocm_smi_lib
#do not ignore .github directory
!.github
# Don't ignore .github directory
!.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
* 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 TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0")
@ -12,12 +12,12 @@ else
endif
ifneq ($(QUIET),true)
override PRE := info info-quiet
override QUIET := false
else
override PRE := info-quiet
endif
OLDCXX := $(CXXFLAGS)
OLDLD := $(LDFLAGS)
PREFIX ?= /usr/local
#? Detect PLATFORM and ARCH from uname/gcc if not set
@ -36,6 +36,70 @@ endif
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
override TESTFLAGS := -fexceptions -fstack-clash-protection -fcf-protection
ifneq ($(PLATFORM) $(ARCH),macos arm64)
@ -43,9 +107,17 @@ ifneq ($(PLATFORM) $(ARCH),macos arm64)
endif
ifeq ($(STATIC),true)
override ADDFLAGS += -static-libgcc -static-libstdc++
ifneq ($(PLATFORM),macos)
ifeq ($(CXX_IS_CLANG) $(CLANG_WORKS),true true)
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
else ifeq ($(PLATFORM_LC),freebsd)
override ADDFLAGS += -DSTATIC_BUILD
endif
endif
@ -53,29 +125,10 @@ ifeq ($(STRIP),true)
override ADDFLAGS += -s
endif
#? Compiler and Linker
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++
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
ifeq ($(VERBOSE),true)
override VERBOSE := false
else
override VERBOSE := true
endif
#? Pull in platform specific source files and get thread count
@ -87,7 +140,10 @@ else ifeq ($(PLATFORM_LC),freebsd)
PLATFORM_DIR := freebsd
THREADS := $(shell getconf NPROCESSORS_ONLN 2>/dev/null || echo 1)
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
else ifeq ($(PLATFORM_LC),macos)
PLATFORM_DIR := osx
@ -104,9 +160,16 @@ ifeq ($(THREADS),1)
override THREADS := auto
endif
#? LTO command line
ifeq ($(CLANG_WORKS),true)
LTO := thin
else
LTO := $(THREADS)
endif
#? The Directories, Source, Includes, Objects and Binary
SRCDIR := src
INCDIR := include
INCDIRS := include $(wildcard lib/**/include)
BUILDDIR := obj
TARGETDIR := bin
SRCEXT := cpp
@ -119,11 +182,11 @@ override GOODFLAGS := $(foreach flag,$(TESTFLAGS),$(strip $(shell echo "int main
#? Flags, Libraries and Includes
override REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -pedantic
OPTFLAGS := -O2 -ftree-loop-vectorize -flto=$(THREADS)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS $(GOODFLAGS) $(ADDFLAGS)
OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS)
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR)
INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR)
SU_USER := root
ifdef DEBUG
@ -154,24 +217,38 @@ endif
P := %%
#? Default Make
all: $(PRE) directories btop
ifeq ($(VERBOSE),true)
# 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:
@printf " $(BANNER)\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;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n"
@printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n"
@printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n"
@printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n"
@printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n"
@printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\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;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m)\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;95mGPU_SUPPORT \033[1;94m:| \033[0m$(GPU_SUPPORT)\n"
@printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n"
@printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n"
@printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n"
@printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n"
@printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n"
@printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\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"
help:
@ -189,18 +266,22 @@ help:
#? Make the Directories
directories:
@$(VERBOSE) || printf "mkdir -p $(TARGETDIR)\n"
@mkdir -p $(TARGETDIR)
@$(VERBOSE) || printf "mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)\n"
@mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)
#? Clean only Objects
clean:
@printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n"
@rm -rf $(BUILDDIR)
@cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true
#? Clean Objects and Binaries
distclean: clean
@printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n"
@rm -rf $(TARGETDIR)
@rm -rf lib/rocm_smi_lib/build
install:
@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
-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
.ONESHELL:
btop: $(OBJECTS)
btop: $(OBJECTS) | rocm_smi directories
@sleep 0.2 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(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
@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"
#? Compile
.ONESHELL:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories
@sleep 0.3 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(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
@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>
![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)
![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow)
![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)
[![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 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
@ -28,28 +29,55 @@
* [Prerequisites](#prerequisites) (Read this if you are having issues!)
* [Screenshots](#screenshots)
* [Keybindings](#help-menu)
* [Installation Linux/OSX](#installation)
* [Installation Linux/macOS](#installation)
* [Compilation Linux](#compilation-linux)
* [Compilation OSX](#compilation-osx)
* [Compilation macOS](#compilation-macos-osx)
* [Compilation FreeBSD](#compilation-freebsd)
* [GPU compatibility](#gpu-compatibility)
* [Installing the snap](#installing-the-snap)
* [Configurability](#configurability)
* [License](#license)
## 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
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!
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
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.
Macos binaries + installer are included for both x86 and ARM64 (Apple Silicon) in the releases.
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.
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
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.
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.
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 [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:
**OSX**
**macOS / OSX**
```bash
# Install and use Homebrew or MacPorts package managers for easy dependency installation
brew install coreutils make gcc@11
@ -82,12 +110,12 @@ git checkout freebsd
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
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.
##### 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:
* 1.1.0 Mac OsX support
* 1.1.0 macOS [OSX] support
* 1.2.0 FreeBSD support
* 1.3.0 Support for GPU monitoring
* 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
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>
@ -142,9 +170,9 @@ C++ version and continuation of [bashtop](https://github.com/aristocratos/bashto
* Send any signal to selected process.
* UI menu for changing all config file options.
* Auto scaling graph for network usage.
* Shows IO activity and speeds for disks
* Shows IO activity and speeds for disks.
* Battery meter
* Selectable symbols for the graphs
* Selectable symbols for the graphs.
* Custom presets
* 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) .
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`.
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:**
``` bash
```bash
# use "make install PREFIX=/target/dir" to set target, default: /usr/local
# only use "sudo" when installing to a NON user owned directory
sudo make install
@ -246,7 +274,7 @@ Also needs a UTF8 locale and a font that covers:
* **Run setuid.sh or:**
``` bash
```bash
# 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
sudo make setuid
@ -256,7 +284,7 @@ Also needs a UTF8 locale and a font that covers:
* **Run uninstall.sh or:**
``` bash
```bash
sudo make uninstall
```
@ -274,6 +302,19 @@ Also needs a UTF8 locale and a font that covers:
sudo zypper in 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))**
@ -285,61 +326,98 @@ Also needs a UTF8 locale and a font that covers:
## 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).
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
sudo apt install coreutils sed git build-essential gcc-11 g++-11
Gpu support will not work when static linking glibc (or musl, etc.)!
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**
``` bash
```bash
git clone https://github.com/aristocratos/btop.git
cd btop
```
3. **Compile**
Append `STATIC=true` to `make` command for static compilation.
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
```bash
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`
Notice! Only use "sudo" when installing to a NON user owned directory.
``` bash
sudo make install
```
5. **(Optional) Set suid bit to make btop always run as root (or other user)**
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.
@ -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`
``` bash
sudo make setuid
```
* **Uninstall**
``` bash
```bash
sudo make uninstall
```
@ -375,57 +449,138 @@ Also needs a UTF8 locale and a font that covers:
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).
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`.
Install and use Homebrew or MacPorts package managers for easy dependency installation
1. **Install dependencies (example for Homebrew)**
``` bash
brew install coreutils make gcc@11
```bash
brew install coreutils make gcc@12
```
2. **Clone repository**
``` bash
```bash
git clone https://github.com/aristocratos/btop.git
cd btop
```
3. **Compile**
Append `STATIC=true` to `make` command for static compilation (only libgcc and libstdc++ will be static!).
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
```bash
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`
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.
@ -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`
``` bash
sudo gmake setuid
```
* **Uninstall**
``` bash
```bash
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.
<details>
<summary>
### With gmake
</summary>
1. **Install dependencies**
``` bash
```bash
sudo pkg install gmake gcc11 coreutils git
```
2. **Clone repository**
``` bash
```bash
git clone https://github.com/aristocratos/btop.git
cd btop
```
3. **Compile**
Append `STATIC=true` to `make` command for static compilation.
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
```bash
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**
```bash
sudo gmake install
```
Append `PREFIX=/target/dir` to set target, default: `/usr/local`
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)**
```bash
sudo gmake setuid
```
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.
Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel`
``` bash
sudo gmake setuid
```
* **Uninstall**
``` bash
```bash
sudo gmake uninstall
```
@ -545,6 +705,96 @@ Also needs a UTF8 locale and a font that covers:
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
[![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
sudo snap connect btop:removable-media
or
or
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.
#* 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"
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_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"
#* 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.
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.
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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -37,21 +37,34 @@ tab-size = 4
#include <mach-o/dyld.h>
#include <limits.h>
#endif
#if !defined(__clang__) && __GNUC__ < 11
#include <semaphore.h>
#else
#include <semaphore>
#endif
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_input.hpp>
#include <btop_theme.hpp>
#include <btop_draw.hpp>
#include <btop_menu.hpp>
#include "btop_shared.hpp"
#include "btop_tools.hpp"
#include "btop_config.hpp"
#include "btop_input.hpp"
#include "btop_theme.hpp"
#include "btop_draw.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 rng = std::ranges;
using namespace Tools;
using namespace std::chrono_literals;
using namespace std::literals;
namespace Global {
const vector<array<string, 2>> Banner_src = {
@ -62,7 +75,7 @@ namespace Global {
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
};
const string Version = "1.2.8";
const string Version = "1.2.13";
int coreCount;
string overlay;
@ -80,9 +93,9 @@ namespace Global {
string exit_error_msg;
atomic<bool> thread_exception (false);
bool debuginit = false;
bool debug = false;
bool utf_force = false;
bool debuginit{}; // defaults to false
bool debug{}; // defaults to false
bool utf_force{}; // defaults to false
uint64_t start_time;
@ -92,8 +105,8 @@ namespace Global {
atomic<bool> should_sleep (false);
atomic<bool> _runner_started (false);
bool arg_tty = false;
bool arg_low_color = false;
bool arg_tty{}; // defaults to false
bool arg_low_color{}; // defaults to false
int arg_preset = -1;
}
@ -102,22 +115,23 @@ void argumentParser(const int& argc, char **argv) {
for(int i = 1; i < argc; i++) {
const string argument = argv[i];
if (is_in(argument, "-h", "--help")) {
cout << "usage: btop [-h] [-v] [-/+t] [-p <id>] [--utf-force] [--debug]\n\n"
<< "optional arguments:\n"
<< " -h, --help show this help message and exit\n"
<< " -v, --version show version info and exit\n"
<< " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n"
<< " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
<< " +t, --tty_off force (OFF) tty mode\n"
<< " -p, --preset <id> start with preset, integer value between 0-9\n"
<< " --utf-force force start even if no UTF-8 locale was detected\n"
<< " --debug start in DEBUG mode: shows microsecond timer for information collect\n"
<< " and screen draw functions and sets loglevel to DEBUG\n"
<< endl;
fmt::println(
"usage: btop [-h] [-v] [-/+t] [-p <id>] [--utf-force] [--debug]\n\n"
"optional arguments:\n"
" -h, --help show this help message and exit\n"
" -v, --version show version info and exit\n"
" -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n"
" -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
" +t, --tty_off force (OFF) tty mode\n"
" -p, --preset <id> start with preset, integer value between 0-9\n"
" --utf-force force start even if no UTF-8 locale was detected\n"
" --debug start in DEBUG mode: shows microsecond timer for information collect\n"
" and screen draw functions and sets loglevel to DEBUG"
);
exit(0);
}
else if (is_in(argument, "-v", "--version")) {
cout << "btop version: " << Global::Version << endl;
fmt::println("btop version: {}", Global::Version);
exit(0);
}
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")) {
if (++i >= argc) {
cout << "ERROR: Preset option needs an argument." << endl;
fmt::println("ERROR: Preset option needs an argument.");
exit(1);
}
else if (const string val = argv[i]; isint(val) and val.size() == 1) {
Global::arg_preset = std::clamp(stoi(val), 0, 9);
}
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);
}
}
@ -149,8 +163,8 @@ void argumentParser(const int& argc, char **argv) {
else if (argument == "--debug")
Global::debug = true;
else {
cout << " Unknown argument: " << argument << "\n" <<
" Use -h or --help for help." << endl;
fmt::println(" Unknown argument: {}\n"
" Use -h or --help for help.", argument);
exit(1);
}
}
@ -169,8 +183,11 @@ void term_resize(bool force) {
if (force and refreshed) force = false;
}
else return;
static const array<string, 4> all_boxes = {"cpu", "mem", "net", "proc"};
#ifdef GPU_SUPPORT
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;
if (Runner::active) Runner::stop();
Term::refresh();
@ -178,30 +195,52 @@ void term_resize(bool force) {
auto boxes = Config::getS("shown_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);
if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) {
cout << Term::clear << Global::bg_black << Global::fg_white << Mv::to((Term::height / 2) - 2, (Term::width / 2) - 11)
<< "Terminal size too small:" << Mv::to((Term::height / 2) - 1, (Term::width / 2) - 10)
<< " Width = " << (Term::width < min_size.at(1) ? Global::fg_red : Global::fg_green) << Term::width
<< Global::fg_white << " Height = " << (Term::height < min_size.at(0) ? Global::fg_red : Global::fg_green) << Term::height
<< Mv::to((Term::height / 2) + 1, (Term::width / 2) - 12) << Global::fg_white
<< "Needed for current config:" << Mv::to((Term::height / 2) + 2, (Term::width / 2) - 10)
<< "Width = " << min_size.at(0) << " Height = " << min_size.at(1) << flush;
if (Term::width < minWidth or Term::height < minHeight) {
int width = Term::width, height = Term::height;
cout << fmt::format("{clear}{bg_black}{fg_white}"
"{mv1}Terminal size too small:"
"{mv2} Width = {fg_width}{width} {fg_white}Height = {fg_height}{height}"
"{mv3}{fg_white}Needed for current config:"
"{mv4}Width = {minWidth} Height = {minHeight}",
"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;
for (; not Term::refresh() and not got_key; got_key = Input::poll(10));
if (got_key) {
auto key = Input::get();
if (key == "q")
clean_quit(0);
else if (is_in(key, "1", "2", "3", "4")) {
Config::current_preset = -1;
Config::toggle_box(all_boxes.at(std::stoi(key) - 1));
boxes = Config::getS("shown_boxes");
else if (key.size() == 1 and isint(key)) {
auto intKey = stoi(key);
#ifdef GPU_SUPPORT
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);
minWidth = min_size.at(0), minHeight = min_size.at(1);
}
else if (not Term::refresh()) break;
}
@ -216,20 +255,25 @@ void clean_quit(int sig) {
Runner::stop();
if (Global::_runner_started) {
#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!");
pthread_cancel(Runner::runner_id);
}
#else
struct timespec ts;
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!");
pthread_cancel(Runner::runner_id);
}
#endif
}
#ifdef GPU_SUPPORT
Gpu::Nvml::shutdown();
Gpu::Rsmi::shutdown();
#endif
Config::write();
if (Term::initialized) {
@ -240,7 +284,7 @@ void clean_quit(int sig) {
if (not Global::exit_error_msg.empty()) {
sig = 1;
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));
@ -307,16 +351,15 @@ namespace Runner {
atomic<bool> stopping (false);
atomic<bool> waiting (false);
atomic<bool> redraw (false);
atomic<bool> coreNum_reset (false);
//* Setup semaphore for triggering thread to do work
#if __GNUC__ < 11
#include <semaphore.h>
#if !defined(__clang__) && __GNUC__ < 11
sem_t do_work;
inline void thread_sem_init() { sem_init(&do_work, 0, 0); }
inline void thread_wait() { sem_wait(&do_work); }
inline void thread_trigger() { sem_post(&do_work); }
#else
#include <semaphore>
std::binary_semaphore do_work(0);
inline void thread_sem_init() { ; }
inline void thread_wait() { do_work.acquire(); }
@ -328,8 +371,14 @@ namespace Runner {
pthread_mutex_t& pt_mutex;
public:
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() { if (status == 0) pthread_mutex_unlock(&pt_mutex); }
thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) {
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
@ -337,23 +386,27 @@ namespace Runner {
int status = -1;
public:
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() {
if (status == 0) status = seteuid(Global::real_uid);
if (status == 0)
status = seteuid(Global::real_uid);
}
};
string output;
string empty_bg;
bool pause_output = false;
bool pause_output{}; // defaults to false
sigset_t mask;
pthread_t runner_id;
pthread_mutex_t mtx;
enum debug_actions {
collect_begin,
collect_done,
draw_begin,
draw_begin_only,
draw_done
};
@ -365,6 +418,14 @@ namespace Runner {
string debug_bg;
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 {
vector<string> boxes;
bool no_update;
@ -381,6 +442,13 @@ namespace Runner {
case collect_begin:
debug_times[name].at(collect) = time_micros();
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:
debug_times[name].at(draw) = time_micros();
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 ----------------------------------
void * _runner(void * _) {
(void)_;
void * _runner(void *) {
//? Block some signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
sigemptyset(&mask);
// sigaddset(&mask, SIGINT);
// sigaddset(&mask, SIGTSTP);
sigaddset(&mask, SIGWINCH);
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
thread_lock pt_lck(mtx);
@ -438,7 +505,15 @@ namespace Runner {
//! DEBUG stats
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["total"] = {0, 0};
}
@ -447,6 +522,29 @@ namespace Runner {
//* Run collection and draw functions for all boxes
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
if (v_contains(conf.boxes, "cpu")) {
try {
@ -455,18 +553,43 @@ namespace Runner {
//? Start collect
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);
//? 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);
}
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
if (v_contains(conf.boxes, "mem")) {
try {
@ -483,7 +606,7 @@ namespace Runner {
if (Global::debug) debug_timer("mem", draw_done);
}
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);
}
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);
}
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) {
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;
Input::interrupt = true;
stopping = true;
@ -549,26 +673,53 @@ namespace Runner {
if (empty_bg.empty()) {
const int x = Term::width / 2 - 10, y = Term::height / 2 - 10;
output += Term::clear;
empty_bg += Draw::banner_gen(y, 0, true)
+ Mv::to(y+6, x) + Theme::c("title") + Fx::b + "No boxes shown!"
+ Mv::to(y+8, x) + Theme::c("hi_fg") + "1" + Theme::c("main_fg") + " | Show CPU box"
+ Mv::to(y+9, x) + Theme::c("hi_fg") + "2" + Theme::c("main_fg") + " | Show MEM box"
+ Mv::to(y+10, x) + Theme::c("hi_fg") + "3" + Theme::c("main_fg") + " | Show NET box"
+ Mv::to(y+11, x) + Theme::c("hi_fg") + "4" + Theme::c("main_fg") + " | Show PROC box"
+ Mv::to(y+12, x-2) + Theme::c("hi_fg") + "esc" + Theme::c("main_fg") + " | Show menu"
+ Mv::to(y+13, x) + Theme::c("hi_fg") + "q" + Theme::c("main_fg") + " | Quit";
empty_bg = fmt::format(
"{banner}"
"{mv1}{titleFg}{b}No boxes shown!"
"{mv2}{hiFg}1 {mainFg}| Show CPU box"
"{mv3}{hiFg}2 {mainFg}| Show MEM box"
"{mv4}{hiFg}3 {mainFg}| Show NET box"
"{mv5}{hiFg}4 {mainFg}| Show PROC box"
"{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;
}
//! DEBUG stats -->
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"}) {
#endif
if (not debug_times.contains(name)) debug_times[name] = {0,0};
const auto& [time_collect, time_draw] = debug_times.at(name);
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;
}
//* ----------------------------------------------- THREAD LOOP -----------------------------------------------
pthread_exit(NULL);
return {};
}
//? ------------------------------------------ Secondary thread end -----------------------------------------------
//* 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);
if (active) {
Logger::error("Stall in Runner thread, restarting!");
active = false;
// exit(1);
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!";
clean_quit(1);
}
@ -679,19 +830,19 @@ int main(int argc, char **argv) {
//? Setup paths for config, log and user themes
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");
break;
}
}
if (Config::conf_dir.empty()) {
cout << "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;
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.");
}
else {
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"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
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.");
}
else {
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
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")) {
Logger::debug("Using locale " + (string)std::setlocale(LC_ALL, ""));
}
else {
string found;
bool set_failure = false;
bool set_failure{}; // defaults to false
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);
if (std::setlocale(LC_ALL, found.c_str()) == NULL) {
if (std::setlocale(LC_ALL, found.c_str()) == nullptr) {
set_failure = true;
Logger::warning("Failed to set locale " + found + " continuing anyway.");
}
@ -769,7 +920,7 @@ int main(int argc, char **argv) {
for (auto& l : ssplit(loc, ';')) {
if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) {
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;
}
}
@ -790,10 +941,10 @@ int main(int argc, char **argv) {
if (cur_locale.empty()) {
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");
}
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");
}
else {
@ -848,7 +999,7 @@ int main(int argc, char **argv) {
Shared::init();
}
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);
}
@ -865,7 +1016,7 @@ int main(int argc, char **argv) {
//? Start runner thread
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!";
clean_quit(1);
}
@ -960,7 +1111,7 @@ int main(int argc, char **argv) {
}
}
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);
}

View file

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@ -17,18 +17,25 @@ tab-size = 4
*/
#include <array>
#include <ranges>
#include <atomic>
#include <fstream>
#include <ranges>
#include <string_view>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include <fmt/core.h>
#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 rng = std::ranges;
using namespace std::literals;
using namespace Tools;
//* 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"
"#* 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\""},
{"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."},
{"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_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
{"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
{"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."},
@ -98,14 +107,18 @@ namespace Config {
{"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"
"#* Select from a list of detected attributes from the options menu."},
{"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n"
"#* Select from a list of detected attributes from the options menu."},
#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_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."},
{"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"},
{"shown_boxes", "cpu mem net proc"},
{"graph_symbol", "braille"},
{"presets", "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"},
{"graph_symbol_cpu", "default"},
{"graph_symbol_gpu", "default"},
{"graph_symbol_mem", "default"},
{"graph_symbol_net", "default"},
{"graph_symbol_proc", "default"},
{"proc_sorting", "cpu lazy"},
{"cpu_graph_upper", "total"},
{"cpu_graph_lower", "total"},
{"cpu_graph_upper", "Auto"},
{"cpu_graph_lower", "Auto"},
{"cpu_sensor", "Auto"},
{"selected_battery", "Auto"},
{"cpu_core_map", ""},
@ -213,10 +241,19 @@ namespace Config {
{"proc_filter", ""},
{"proc_command", ""},
{"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},
{"truecolor", true},
{"rounded_corners", true},
@ -229,7 +266,7 @@ namespace Config {
{"proc_cpu_graphs", true},
{"proc_info_smaps", false},
{"proc_left", false},
{"proc_filter_kernel", false},
{"proc_filter_kernel", false},
{"cpu_invert_lower", true},
{"cpu_single_graph", false},
{"cpu_bottom", false},
@ -261,10 +298,15 @@ namespace Config {
{"lowcolor", false},
{"show_detailed", 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},
{"net_download", 100},
{"net_upload", 100},
@ -275,9 +317,9 @@ namespace Config {
{"proc_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);
if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end())
write_new = true;
@ -311,7 +353,7 @@ namespace Config {
validError = "Malformatted preset in config value presets!";
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!";
return false;
}
@ -327,7 +369,7 @@ namespace Config {
new_presets.push_back(preset);
}
preset_list = move(new_presets);
preset_list = std::move(new_presets);
return true;
}
@ -364,7 +406,7 @@ namespace Config {
string validError;
bool intValid(const string& name, const string& value) {
bool intValid(const std::string_view name, const string& value) {
int i_value;
try {
i_value = stoi(value);
@ -378,7 +420,7 @@ namespace Config {
return false;
}
catch (const std::exception& e) {
validError = (string)e.what();
validError = string{e.what()};
return false;
}
@ -394,7 +436,7 @@ namespace Config {
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))
validError = "Invalid log_level: " + value;
@ -402,11 +444,16 @@ namespace Config {
validError = "Invalid graph symbol identifier: " + 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))
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))
return false;
@ -451,7 +498,7 @@ namespace Config {
return false;
}
string getAsString(const string& name) {
string getAsString(const std::string_view name) {
if (bools.contains(name))
return (bools.at(name) ? "True" : "False");
else if (ints.contains(name))
@ -461,7 +508,7 @@ namespace Config {
return "";
}
void flip(const string& name) {
void flip(const std::string_view name) {
if (_locked(name)) {
if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name);
else boolsTmp.insert_or_assign(name, (not bools.at(name)));
@ -498,7 +545,7 @@ namespace Config {
boolsTmp.clear();
}
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);
}
@ -509,8 +556,15 @@ namespace Config {
auto new_boxes = ssplit(boxes);
for (auto& box : new_boxes) {
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;
}

View file

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@ -20,10 +20,13 @@ tab-size = 4
#include <string>
#include <vector>
#include <robin_hood.h>
#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
namespace Config {
@ -31,18 +34,25 @@ namespace Config {
extern std::filesystem::path conf_dir;
extern std::filesystem::path conf_file;
extern unordered_flat_map<string, string> strings;
extern unordered_flat_map<string, string> stringsTmp;
extern unordered_flat_map<string, bool> bools;
extern unordered_flat_map<string, bool> boolsTmp;
extern unordered_flat_map<string, int> ints;
extern unordered_flat_map<string, int> intsTmp;
extern unordered_flat_map<std::string_view, string> strings;
extern unordered_flat_map<std::string_view, string> stringsTmp;
extern unordered_flat_map<std::string_view, bool> bools;
extern unordered_flat_map<std::string_view, bool> boolsTmp;
extern unordered_flat_map<std::string_view, int> ints;
extern unordered_flat_map<std::string_view, int> intsTmp;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" };
const vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
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" };
#ifdef GPU_SUPPORT
const vector<string> show_gpu_values = { "Auto", "On", "Off" };
#endif
extern vector<string> current_boxes;
extern vector<string> preset_list;
extern vector<string> available_batteries;
@ -60,44 +70,44 @@ namespace Config {
//* Apply selected 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>
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>
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>
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;
bool intValid(const string& name, const string& value);
bool stringValid(const string& name, const string& value);
bool intValid(const std::string_view name, const string& value);
bool stringValid(const std::string_view name, const string& value);
//* Set config key <name> to bool <value>
inline void set(const string& name, const bool& value) {
inline void set(const std::string_view name, bool value) {
if (_locked(name)) boolsTmp.insert_or_assign(name, value);
else bools.at(name) = value;
}
//* Set config key <name> to int <value>
inline void set(const 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);
else ints.at(name) = 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);
else strings.at(name) = value;
}
//* Flip config key bool <name>
void flip(const string& name);
void flip(const std::string_view name);
//* Lock config and cache changes until unlocked
void lock();
@ -111,10 +121,3 @@ namespace Config {
//* Write the config file to disk
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 <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 {
const string h_line = "";
@ -62,8 +66,8 @@ namespace Draw {
//* An editable text field
class TextEdit {
size_t pos = 0;
size_t upos = 0;
size_t pos{}; // defaults to 0
size_t upos{}; // defaults to 0
bool numeric;
public:
string text;
@ -75,9 +79,11 @@ namespace Draw {
};
//* 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 Meter {
@ -87,7 +93,7 @@ namespace Draw {
array<string, 101> cache;
public:
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
string operator()(int value);
@ -109,18 +115,15 @@ namespace Draw {
public:
Graph();
Graph( int width,
int height,
const string& color_gradient,
const deque<long long>& data,
const string& symbol="default",
bool invert=false,
bool no_zero=false,
long long max_value=0,
long long offset=0);
Graph(int width, int height,
const string& color_gradient,
const deque<long long>& data,
const string& symbol="default",
bool invert=false, bool no_zero=false,
long long max_value=0, long long offset=0);
//* Add last value from back of <data> and return string representation of graph
string& operator()(const 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
string& operator()();
@ -134,4 +137,4 @@ namespace Proc {
extern Draw::TextEdit filter;
extern unordered_flat_map<size_t, Draw::Graph> p_graphs;
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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -21,17 +21,29 @@ tab-size = 4
#include <vector>
#include <thread>
#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 <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 std::literals; // for operator""s
namespace rng = std::ranges;
namespace Input {
@ -52,9 +64,13 @@ namespace Input {
{"[C", "right"},
{"OC", "right"},
{"[2~", "insert"},
{"[4h", "insert"},
{"[3~", "delete"},
{"[P", "delete"},
{"[H", "home"},
{"[1~", "home"},
{"[F", "end"},
{"[4~", "end"},
{"[5~", "page_up"},
{"[6~", "page_down"},
{"\t", "tab"},
@ -235,8 +251,8 @@ namespace Input {
void process(const string& key) {
if (key.empty()) return;
try {
auto& filtering = Config::getB("proc_filtering");
auto& vim_keys = Config::getB("vim_keys");
auto filtering = Config::getB("proc_filtering");
auto vim_keys = Config::getB("vim_keys");
auto help_key = (vim_keys ? "H" : "h");
auto kill_key = (vim_keys ? "K" : "k");
//? Global input actions
@ -257,11 +273,21 @@ namespace Input {
Menu::show(Menu::Menus::Options);
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);
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();
Runner::run("all", false, true);
return;
@ -294,12 +320,12 @@ namespace Input {
if (key == "enter" or key == "down") {
Config::set("proc_filter", Proc::filter.text);
Config::set("proc_filtering", false);
old_filter.clear();
if(key == "down"){
process("down");
return;
}
}
old_filter.clear();
if(key == "down"){
process("down");
return;
}
}
else if (key == "escape" or key == "mouse_click") {
Config::set("proc_filter", old_filter);
Config::set("proc_filtering", false);
@ -340,6 +366,9 @@ namespace Input {
else if (key == "c")
Config::flip("proc_per_core");
else if (key == "%")
Config::flip("proc_mem_bytes");
else if (key == "delete" and not Config::getS("proc_filter").empty())
Config::set("proc_filter", ""s);
@ -545,10 +574,9 @@ namespace Input {
}
}
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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -24,10 +24,15 @@ tab-size = 4
#include <robin_hood.h>
#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:
cin.sync_with_stdio(false);
cin.tie(NULL);
cin.tie(nullptr);
These will automatically be set when running Term::init() from btop_tools.cpp
*/
@ -65,4 +70,4 @@ namespace Input {
//* Process actions for input <key>
void process(const string& key);
}
}

View file

@ -19,44 +19,108 @@ tab-size = 4
#include <deque>
#include <robin_hood.h>
#include <array>
#include <ranges>
#include <signal.h>
#include <errno.h>
#include <cmath>
#include <filesystem>
#include <btop_menu.hpp>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_theme.hpp>
#include <btop_draw.hpp>
#include <btop_shared.hpp>
#include "btop_menu.hpp"
#include "btop_tools.hpp"
#include "btop_config.hpp"
#include "btop_theme.hpp"
#include "btop_draw.hpp"
#include "btop_shared.hpp"
using robin_hood::unordered_flat_map;
using std::array;
using std::ceil;
using std::max;
using std::min;
using std::ref;
using std::views::iota;
using 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;
namespace fs = std::filesystem;
namespace rng = std::ranges;
namespace Menu {
atomic<bool> active (false);
string bg;
bool redraw = true;
bool redraw{true};
int currentMenu = -1;
msgBox messageBox;
int signalToSend = 0;
int signalKillRet = 0;
int signalToSend{}; // defaults to 0
int signalKillRet{}; // defaults to 0
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",
"SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
"SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2",
"SIGPIPE", "SIGALRM", "SIGTERM", "16", "SIGCHLD",
"SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN",
"SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ",
"SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO",
"SIGPWR", "SIGSYS"
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT",
"SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP",
"SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGIO", "SIGPWR", "SIGSYS"
#endif
#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
"SIGINFO", "SIGUSR1", "SIGUSR2"
#else
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
"SIGTRAP", "SIGABRT", "7", "SIGFPE",
"SIGKILL", "10", "SIGSEGV", "12",
"SIGPIPE", "SIGALRM", "SIGTERM", "16",
"17", "18", "19", "20",
"21", "22", "23", "24",
"25", "26", "27", "28",
"29", "30", "31"
#endif
};
unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
@ -109,6 +173,7 @@ namespace Menu {
{"2", "Toggle MEM box."},
{"3", "Toggle NET box."},
{"4", "Toggle PROC box."},
{"5", "Toggle GPU box."},
{"d", "Toggle disks view in MEM box."},
{"F2, o", "Shows options."},
{"F1, ?, h", "Shows this window."},
@ -131,6 +196,7 @@ namespace Menu {
{"c", "Toggle per-core cpu usage of processes."},
{"r", "Reverse sorting order in processes box."},
{"e", "Toggle processes tree view."},
{"%", "Toggles memory display mode in processes box."},
{"Selected +, -", "Expand/collapse the selected process in tree view."},
{"Selected t", "Terminate selected process with SIGTERM - 15."},
{"Selected k", "Kill selected process with SIGKILL - 9."},
@ -197,7 +263,7 @@ namespace Menu {
"P=(0 or 1) for alternate positions.",
"G=graph symbol to use for box.",
"",
"Use withespace \" \" as separator between",
"Use whitespace \" \" as separator between",
"different presets.",
"",
"Example:",
@ -206,6 +272,9 @@ namespace Menu {
"Manually set which boxes to show.",
"",
"Available values are \"cpu mem net proc\".",
#ifdef GPU_SUPPORT
"Or \"gpu0\" through \"gpu5\" for GPU boxes.",
#endif
"Separate values with whitespace.",
"",
"Toggle between presets with key \"p\"."},
@ -308,23 +377,49 @@ namespace Menu {
{"cpu_graph_upper",
"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.",
"",
"\"total\" = Total cpu usage.",
"CPU:",
"\"total\" = Total cpu usage. (Auto)",
"\"user\" = User 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 lower graph.",
"",
"Sets the CPU stat shown in lower half of",
"Sets the CPU/GPU stat shown in lower half of",
"the CPU graph.",
"",
"CPU:",
"\"total\" = Total cpu usage.",
"\"user\" = User mode cpu usage.",
"\"system\" = Kernel mode cpu usage.",
"+ more depending on kernel."},
"+ more depending on kernel.",
#ifdef GPU_SUPPORT
"",
"GPU:",
"\"gpu-totals\" = GPU usage split/device. (Auto)",
"\"gpu-vram-totals\" = VRAM usage split by GPU.",
"\"gpu-pwr-totals\" = Power usage split by GPU.",
"\"gpu-average\" = Avg usage of all GPUs.",
"\"gpu-vram-total\" = VRAM usage of all GPUs.",
"\"gpu-pwr-total\" = Power usage of all GPUs.",
"Not all stats are supported on all devices."
#endif
},
{"cpu_invert_lower",
"Toggles orientation of the lower CPU graph.",
"",
@ -336,12 +431,24 @@ namespace Menu {
"to fit to box height.",
"",
"True or False."},
#ifdef GPU_SUPPORT
{"show_gpu_info",
"Show gpu info in cpu box.",
"",
"Toggles gpu stats in cpu box and the",
"gpu graph (if \"cpu_graph_lower\" is set to",
"\"Auto\").",
"",
"\"Auto\" to show when no gpu box is shown.",
"\"On\" to always show.",
"\"Off\" to never show."},
#endif
{"check_temp",
"Enable cpu temperature reporting.",
"",
"True or False."},
{"cpu_sensor",
"Cpu temperature sensor",
"Cpu temperature sensor.",
"",
"Select the sensor that corresponds to",
"your cpu temperature.",
@ -381,7 +488,7 @@ namespace Menu {
"Rankine, 0 = abosulte zero, 1 degree change",
"equals 1 degree change in Fahrenheit."},
{"show_cpu_freq",
"Show CPU frequency",
"Show CPU frequency.",
"",
"Can cause slowdowns on systems with many",
"cores and certain kernel versions."},
@ -397,6 +504,50 @@ namespace Menu {
"",
"True or False."},
},
#ifdef GPU_SUPPORT
{
{"nvml_measure_pcie_speeds",
"Measure PCIe throughput on NVIDIA cards.",
"",
"May impact performance on certain cards.",
"",
"True or False."},
{"graph_symbol_gpu",
"Graph symbol to use for graphs in gpu box.",
"",
"\"default\", \"braille\", \"block\" or \"tty\".",
"",
"\"default\" for the general default symbol.",},
{"gpu_mirror_graph",
"Horizontally mirror the GPU graph.",
"",
"True or False."},
{"custom_gpu_name0",
"Custom gpu0 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name1",
"Custom gpu1 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name2",
"Custom gpu2 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name3",
"Custom gpu3 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name4",
"Custom gpu4 model name in gpu stats box.",
"",
"Empty string to disable."},
{"custom_gpu_name5",
"Custom gpu5 model name in gpu stats box.",
"",
"Empty string to disable."},
},
#endif
{
{"mem_below_net",
"Mem box location.",
@ -583,6 +734,11 @@ namespace Menu {
"Set true to show processes grouped by",
"parents with lines drawn between parent",
"and child process."},
{"proc_aggregate",
"Aggregate child's resources in parent.",
"",
"In tree-view, include all child resources",
"with the parent even while expanded."},
{"proc_colors",
"Enable colors in process view.",
"",
@ -611,19 +767,19 @@ namespace Menu {
"Show cpu graph for each process.",
"",
"True or False"},
{"proc_filter_kernel",
"(Linux) Filter kernel processes from output.",
"",
"Set to 'True' to filter out internal",
"processes started by the Linux kernel."},
{"proc_filter_kernel",
"(Linux) Filter kernel processes from output.",
"",
"Set to 'True' to filter out internal",
"processes started by the Linux kernel."},
}
};
msgBox::msgBox() {}
msgBox::msgBox(int width, int boxtype, vector<string> content, string title)
: width(width), boxtype(boxtype) {
const auto& tty_mode = Config::getB("tty_mode");
const auto& rounded = Config::getB("rounded_corners");
auto tty_mode = Config::getB("tty_mode");
auto rounded = Config::getB("rounded_corners");
const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
@ -712,8 +868,11 @@ namespace Menu {
};
int signalChoose(const string& key) {
auto& s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
static int x = 0, y = 0, selected_signal = -1;
auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
static int x{}; // defaults to 0
static int y{}; // defaults to 0
static int selected_signal = -1;
if (bg.empty()) selected_signal = -1;
auto& out = Global::overlay;
int retval = Changed;
@ -839,7 +998,7 @@ namespace Menu {
}
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 (redraw) {
atomic_wait(Runner::active);
@ -912,10 +1071,11 @@ namespace Menu {
int mainMenu(const string& key) {
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_normal;
auto& tty_mode = Config::getB("tty_mode");
auto tty_mode = Config::getB("tty_mode");
if (bg.empty()) selected = 0;
int retval = Changed;
@ -969,7 +1129,6 @@ namespace Menu {
retval = NoChange;
}
if (retval == Changed) {
auto& out = Global::overlay;
out = bg + Fx::reset + Fx::b;
@ -986,14 +1145,23 @@ namespace Menu {
out += Fx::reset;
}
return (redraw ? Changed : retval);
}
int optionsMenu(const string& key) {
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 bool editing = false;
static int y{}; // defaults to 0
static int x{}; // defaults to 0
static int height{}; // defaults to 0
static int page{}; // defaults to 0
static int pages{}; // defaults to 0
static int selected{}; // defaults to 0
static int select_max{}; // defaults to 0
static int item_height{}; // defaults to 0
static int selected_cat{}; // defaults to 0
static int max_items{}; // defaults to 0
static int last_sel{}; // defaults to 0
static bool editing{}; // defaults to false
static Draw::TextEdit editor;
static string warnings;
static bitset<8> selPred;
@ -1011,9 +1179,13 @@ namespace Menu {
{"cpu_graph_lower", std::cref(Cpu::available_fields)},
{"cpu_sensor", std::cref(Cpu::available_sensors)},
{"selected_battery", std::cref(Config::available_batteries)},
#ifdef GPU_SUPPORT
{"show_gpu_info", std::cref(Config::show_gpu_values)},
{"graph_symbol_gpu", std::cref(Config::valid_graph_symbols_def)},
#endif
};
auto& tty_mode = Config::getB("tty_mode");
auto& vim_keys = Config::getB("vim_keys");
auto tty_mode = Config::getB("tty_mode");
auto vim_keys = Config::getB("vim_keys");
if (max_items == 0) {
for (const auto& cat : categories) {
if ((int)cat.size() > max_items) max_items = cat.size();
@ -1025,9 +1197,9 @@ namespace Menu {
Theme::updateThemes();
}
int retval = Changed;
bool recollect = false;
bool screen_redraw = false;
bool theme_refresh = false;
bool recollect{}; // defaults to false
bool screen_redraw{}; // defaults to false
bool theme_refresh{}; // defaults to false
//? Draw background if needed else process input
if (redraw) {
@ -1062,7 +1234,8 @@ namespace Menu {
const auto& option = categories[selected_cat][item_height * page + selected][0];
if (selPred.test(isString) and Config::stringValid(option, editor.text)) {
Config::set(option, editor.text);
if (option == "custom_cpu_name") 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")) {
screen_redraw = true;
Config::current_preset = -1;
@ -1143,7 +1316,7 @@ namespace Menu {
if (--selected_cat < 0) selected_cat = (int)categories.size() - 1;
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;
page = selected = 0;
}
@ -1195,7 +1368,7 @@ namespace Menu {
Logger::set(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;
}
else
@ -1241,11 +1414,19 @@ namespace Menu {
//? Category buttons
out += Mv::to(y+7, x+4);
#ifdef GPU_SUPPORT
for (int i = 0; const auto& m : {"general", "cpu", "gpu", "mem", "net", "proc"}) {
#else
for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) {
#endif
out += Fx::b + (i == selected_cat
? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']'
: Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ')
#ifdef GPU_SUPPORT
+ Mv::r(7);
#else
+ Mv::r(10);
#endif
if (string button_name = "select_cat_" + to_string(i + 1); not editing and not mouse_mappings.contains(button_name))
mouse_mappings[button_name] = {y+6, x+2 + 15*i, 3, 15};
i++;
@ -1304,11 +1485,11 @@ namespace Menu {
optionsMenu("");
}
if (screen_redraw) {
auto overlay_bkp = move(Global::overlay);
auto clock_bkp = move(Global::clock);
auto overlay_bkp = std::move(Global::overlay);
auto clock_bkp = std::move(Global::clock);
Draw::calcSizes();
Global::overlay = move(overlay_bkp);
Global::clock = move(clock_bkp);
Global::overlay = std::move(overlay_bkp);
Global::clock = std::move(clock_bkp);
recollect = true;
}
if (recollect) {
@ -1320,7 +1501,12 @@ namespace Menu {
}
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;
int retval = Changed;

View file

@ -23,9 +23,12 @@ tab-size = 4
#include <vector>
#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 {
@ -43,7 +46,12 @@ namespace Menu {
//? Strings in content vector is not checked for box width overflow
class msgBox {
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:
enum BoxTypes { OK, YES_NO, NO_YES };
enum msgReturn {

View file

@ -18,16 +18,28 @@ tab-size = 4
#include <ranges>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include "btop_config.hpp"
#include "btop_shared.hpp"
#include "btop_tools.hpp"
using std::string_literals::operator""s;
namespace rng = std::ranges;
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 {
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) {
switch (v_index(sort_vector, sorting)) {
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 (reverse) {
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();
bool filtering = false;
//? If filtering, include children of matching processes
if (not found and (should_filter or not filter.empty())) {
if (not s_contains(std::to_string(cur_proc.pid), filter)
and not s_contains(cur_proc.name, filter)
and not s_contains(cur_proc.cmd, filter)
and not s_contains(cur_proc.user, filter)) {
and not s_contains_ic(cur_proc.name, filter)
and not s_contains_ic(cur_proc.cmd, filter)
and not s_contains_ic(cur_proc.user, filter)) {
filtering = true;
cur_proc.filtered = true;
filter_found++;
@ -132,7 +145,7 @@ namespace Proc {
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(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 {
@ -140,12 +153,10 @@ namespace Proc {
}
//? Recursive iteration over all children
int children = 0;
for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) {
if (collapsed and not filtering) {
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);
@ -158,17 +169,24 @@ namespace Proc {
filter_found++;
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) {
return;
}
//? 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, " └─ ");
//? 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
#include <string>
#include <vector>
#include <filesystem>
#include <array>
#include <atomic>
#include <deque>
#include <robin_hood.h>
#include <array>
#include <ifaddrs.h>
#include <filesystem>
#include <string>
#include <tuple>
#include <vector>
#include <ifaddrs.h>
#include <robin_hood.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 banner_gen();
@ -55,11 +63,12 @@ namespace Runner {
extern atomic<bool> reading;
extern atomic<bool> stopping;
extern atomic<bool> redraw;
extern atomic<bool> coreNum_reset;
extern pthread_t runner_id;
extern bool pause_output;
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();
}
@ -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 {
extern string box;
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>> temp;
long long temp_max = 0;
array<float, 3> load_avg;
array<double, 3> load_avg;
};
//* 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
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
auto get_core_mapping() -> unordered_flat_map<int, int>;
@ -124,17 +218,21 @@ namespace Mem {
extern string box;
extern int x, y, width, height, min_width, min_height;
extern bool has_swap, shown, redraw;
const array<string, 4> mem_names = {"used", "available", "cached", "free"};
const array<string, 2> swap_names = {"swap_used", "swap_free"};
const array mem_names { "used"s, "available"s, "cached"s, "free"s };
const array swap_names { "swap_used"s, "swap_free"s };
extern int disk_ios;
struct disk_info {
std::filesystem::path dev;
string name;
string fstype = "";
std::filesystem::path stat = "";
int64_t total = 0, used = 0, free = 0;
int used_percent = 0, free_percent = 0;
string fstype{}; // defaults to ""
std::filesystem::path stat{}; // defaults to ""
int64_t total{}; // defaults to 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};
deque<long long> io_read = {};
deque<long long> io_write = {};
@ -156,10 +254,10 @@ namespace Mem {
uint64_t get_totalMem();
//* 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
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;
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 {
unordered_flat_map<string, deque<long long>> bandwidth = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, net_stat> stat = { {"download", {}}, {"upload", {}} };
string ipv4 = "", ipv6 = "";
bool connected = false;
string ipv4{}; // defaults to ""
string ipv6{}; // defaults to ""
bool connected{}; // defaults to false
};
extern unordered_flat_map<string, net_info> current_net;
//* 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
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 {
@ -232,25 +336,32 @@ namespace Proc {
//* Container for process information
struct proc_info {
size_t pid = 0;
string name = "", cmd = "";
string short_cmd = "";
size_t threads = 0;
int name_offset = 0;
string user = "";
uint64_t mem = 0;
double cpu_p = 0.0, cpu_c = 0.0;
size_t pid{}; // defaults to 0
string name{}; // defaults to ""
string cmd{}; // defaults to ""
string short_cmd{}; // defaults to ""
size_t threads{}; // defaults to 0
int name_offset{}; // defaults to 0
string user{}; // defaults to ""
uint64_t mem{}; // defaults to 0
double cpu_p{}; // defaults to = 0.0
double cpu_c{}; // defaults to = 0.0
char state = '0';
uint64_t p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0;
string prefix = "";
size_t depth = 0, tree_index = 0;
bool collapsed = false, filtered = false;
int64_t p_nice{}; // defaults to 0
uint64_t ppid{}; // defaults to 0
uint64_t cpu_s{}; // defaults to 0
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
struct detail_container {
size_t last_pid = 0;
bool skip_smaps = false;
size_t last_pid{}; // defaults to 0
bool skip_smaps{}; // defaults to false
proc_info entry;
string elapsed, parent, status, io_read, io_write, memory;
long long first_mem = -1;
@ -262,13 +373,13 @@ namespace Proc {
extern detail_container detailed;
//* 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
int selection(const string& cmd_key);
//* 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 {
std::reference_wrapper<proc_info> entry;
@ -276,11 +387,14 @@ namespace Proc {
};
//* 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
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
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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -17,20 +17,21 @@ tab-size = 4
*/
#include <cmath>
#include <vector>
#include <ranges>
#include <algorithm>
#include <fstream>
#include <unistd.h>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_theme.hpp>
#include "btop_tools.hpp"
#include "btop_config.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;
namespace rng = std::ranges;
namespace fs = std::filesystem;
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) {
hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) {
Logger::error("Invalid hex value: " + hexa);
return "";
for (auto& c : hexa) {
if (not isxdigit(c)) {
Logger::error("Invalid hex value: " + hexa);
return "";
}
}
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
@ -186,7 +189,7 @@ namespace Theme {
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;");
r = std::clamp(r, 0, 255);
g = std::clamp(g, 0, 255);
@ -200,14 +203,17 @@ namespace Theme {
array<int, 3> hex_to_dec(string hexa) {
if (hexa.size() > 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) {
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) {
return array<int, 3>{
return array{
stoi(hexa.substr(0, 2), nullptr, 16),
stoi(hexa.substr(2, 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) {
vector<string> t_rgb;
string depth;
const bool& t_to_256 = Config::getB("lowcolor");
bool t_to_256 = Config::getB("lowcolor");
colors.clear(); rgbs.clear();
for (const auto& [name, color] : Default_theme) {
if (name == "main_bg" and not Config::getB("theme_background")) {
@ -247,11 +253,11 @@ namespace Theme {
}
else if (not source.at(name).empty()) {
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) + "\"");
else {
} else {
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
void generateGradients() {
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
rgbs.insert({ { "proc_start", rgbs["main_fg"] },
{ "proc_mid", {-1, -1, -1} },
{ "proc_end", rgbs["inactive_fg"] },
{ "proc_color_start", rgbs["inactive_fg"] },
{ "proc_color_mid", {-1, -1, -1} },
{ "proc_color_end", rgbs["process_start"] },
rgbs.insert({
{ "proc_start", rgbs["main_fg"] },
{ "proc_mid", {-1, -1, -1} },
{ "proc_end", rgbs["inactive_fg"] },
{ "proc_color_start", rgbs["inactive_fg"] },
{ "proc_color_mid", {-1, -1, -1} },
{ "proc_color_end", rgbs["process_start"] },
});
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
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 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;
//? 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");
string section = "_start";
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);
if (i == split) {
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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -18,12 +18,16 @@ tab-size = 4
#pragma once
#include <string>
#include <robin_hood.h>
#include <array>
#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 {
extern std::filesystem::path theme_dir;
@ -36,13 +40,13 @@ namespace Theme {
//* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale
//* t_to_256: [true|false] convert 24bit value to 256 color value
//* 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
//* Args r: [0-255], g: [0-255], b: [0-255]
//* t_to_256: [true|false] convert 24bit value to 256 color value
//* 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
void updateThemes();
@ -63,4 +67,4 @@ namespace Theme {
//* 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); }
}
}

View file

@ -17,6 +17,7 @@ tab-size = 4
*/
#include <cmath>
#include <codecvt>
#include <iostream>
#include <fstream>
#include <ctime>
@ -24,18 +25,28 @@ tab-size = 4
#include <iomanip>
#include <utility>
#include <ranges>
#include <robin_hood.h>
#include <widechar_width.hpp>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include "robin_hood.h"
#include "widechar_width.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 rng = std::ranges;
@ -44,9 +55,9 @@ namespace rng = std::ranges;
//* Collection of escape codes and functions for terminal manipulation
namespace Term {
atomic<bool> initialized = false;
atomic<int> width = 0;
atomic<int> height = 0;
atomic<bool> initialized{}; // defaults to false
atomic<int> width{}; // defaults to 0
atomic<int> height{}; // defaults to 0
string current_tty;
namespace {
@ -69,7 +80,7 @@ namespace Term {
else settings.c_lflag &= ~(ICANON);
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
if (on) setlinebuf(stdin);
else setbuf(stdin, NULL);
else setbuf(stdin, nullptr);
return true;
}
}
@ -88,19 +99,31 @@ namespace Term {
}
auto get_min_size(const string& boxes) -> array<int, 2> {
const bool cpu = boxes.find("cpu") != string::npos;
const bool mem = boxes.find("mem") != string::npos;
const bool net = boxes.find("net") != string::npos;
const bool proc = boxes.find("proc") != string::npos;
int width = 0;
bool cpu = boxes.find("cpu") != string::npos;
bool mem = boxes.find("mem") != string::npos;
bool net = boxes.find("net") != string::npos;
bool proc = boxes.find("proc") != string::npos;
#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;
else if (net) width = Mem::min_width;
width += (proc ? Proc::min_width : 0);
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);
if (proc) height += Proc::min_height;
else height += (mem ? Mem::min_height : 0) + (net ? Net::min_height : 0);
#ifdef GPU_SUPPORT
height += Gpu::min_height*gpu;
#endif
return { width, height };
}
@ -110,15 +133,15 @@ namespace Term {
initialized = (bool)isatty(STDIN_FILENO);
if (initialized) {
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
cin.sync_with_stdio(false);
cout.sync_with_stdio(false);
//? Disable stream ties
cin.tie(NULL);
cout.tie(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
echo(false);
linebuffered(false);
refresh();
@ -141,7 +164,7 @@ namespace Term {
//? --------------------------------------------------- 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 {
// string uncolor(const string& s) {
// string out = s;
@ -195,8 +218,10 @@ namespace Tools {
return chars;
}
string uresize(string str, const size_t len, const bool wide) {
if (len < 1 or str.empty()) return "";
string uresize(string str, const size_t len, bool wide) {
if (len < 1 or str.empty())
return "";
if (wide) {
try {
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
@ -223,8 +248,10 @@ namespace Tools {
return str;
}
string luresize(string str, const size_t len, const bool wide) {
if (len < 1 or str.empty()) return "";
string luresize(string str, const size_t len, bool wide) {
if (len < 1 or str.empty())
return "";
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) {
x += 2;
@ -252,63 +279,82 @@ namespace Tools {
}
string ltrim(const string& str, const string& t_str) {
string_view str_v = str;
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
return (string)str_v;
std::string_view str_v{str};
while (str_v.starts_with(t_str))
str_v.remove_prefix(t_str.size());
return string{str_v};
}
string rtrim(const string& str, const string& t_str) {
string_view str_v = str;
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size());
return (string)str_v;
std::string_view str_v{str};
while (str_v.ends_with(t_str))
str_v.remove_suffix(t_str.size());
return string{str_v};
}
auto ssplit(const string& str, const char& delim) -> vector<string> {
vector<string> out;
for (const auto& s : str | rng::views::split(delim)
| 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);
}
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 (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), ' ');
}
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), ' ');
}
}
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 (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;
}
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;
}
}
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 (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), ' ');
}
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), ' ');
}
}
string trans(const string& str) {
string_view oldstr = str;
std::string_view oldstr{str};
string newstr;
newstr.reserve(str.size());
for (size_t pos; (pos = oldstr.find(' ')) != string::npos;) {
@ -318,7 +364,7 @@ namespace Tools {
newstr.append(Mv::r(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) {
@ -332,14 +378,37 @@ namespace Tools {
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;
const size_t mult = (bit) ? 8 : 1;
const 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"};
static const array<string, 11> megaUnits_bit = {"bit", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb", "Bb", "Gb"};
static const array<string, 11> megaUnits_byte = {"Byte", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "GB"};
bool mega = Config::getB("base_10_sizes");
// taking advantage of type deduction for array creation (since C++17)
// combined with string literals (operator""s)
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);
value *= 100 * mult;
@ -366,15 +435,29 @@ namespace Tools {
}
if (out.empty()) {
out = to_string(value);
if (not mega and out.size() == 4 and start > 0) { out.pop_back(); 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 (not mega and out.size() == 4 and start > 0) {
out.pop_back();
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) {
auto f_pos = out.find('.');
if (f_pos == 1 and out.size() > 3) out = to_string(round(stof(out) * 10) / 10).substr(0,3);
else if (f_pos != string::npos) out = to_string((int)round(stof(out)));
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
if (f_pos == 1 and out.size() > 3) {
out = to_string(round(stod(out) * 10) / 10).substr(0,3);
}
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]);
}
else out += " " + units[start];
@ -384,11 +467,19 @@ namespace Tools {
}
std::string operator*(const string& str, int64_t n) {
if (n < 1 or str.empty()) return "";
else if(n == 1) return str;
if (n < 1 or str.empty()) {
return "";
}
else if (n == 1) {
return str;
}
string new_str;
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;
}
@ -400,11 +491,11 @@ namespace Tools {
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();
}
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();
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);
}
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 (out.empty() ? fallback : out);
@ -447,15 +538,83 @@ namespace Tools {
string hostname() {
char host[HOST_NAME_MAX];
gethostname(host, HOST_NAME_MAX);
return (string)host;
return string{host};
}
string username() {
auto user = getenv("LOGNAME");
if (user == NULL or strlen(user) == 0) user = getenv("USER");
return (user != NULL ? user : "");
if (user == nullptr or strlen(user) == 0) user = getenv("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 {
@ -472,10 +631,14 @@ namespace Logger {
int status = -1;
public:
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() {
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);
}
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;
atomic_lock lck(busy, true);
lose_priv neutered{};
@ -492,19 +655,26 @@ namespace Logger {
if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {
auto old_log = logfile;
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) {
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";
}
else logfile.clear();
}
catch (const std::exception& e) {
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
#include <string>
#include <vector>
#include <algorithm> // for std::ranges::count_if
#include <array>
#include <atomic>
#include <regex>
#include <chrono>
#include <filesystem>
#include <ranges>
#include <chrono>
#include <regex>
#include <string>
#include <thread>
#include <tuple>
#include <vector>
#include <pthread.h>
#include <limits.h>
#ifndef HOST_NAME_MAX
@ -37,9 +38,19 @@ tab-size = 4
#define HOST_NAME_MAX 64
#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 ------------------------------------------------------
@ -80,19 +91,19 @@ namespace Fx {
//* Collection of escape codes and functions for cursor manipulation
namespace Mv {
//* 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
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
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
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
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
const string save = Fx::e + "s";
@ -140,19 +151,25 @@ namespace Term {
namespace Tools {
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 std::wstring& w_str);
//* 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; }));
}
//* 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)
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
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
inline bool s_contains_ic(const string& str, const string& find_val) {
auto it = std::search(
str.begin(), str.end(),
find_val.begin(), find_val.end(),
[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
);
auto it = std::search(
str.begin(), str.end(),
find_val.begin(), find_val.end(),
[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
);
return it != str.end();
}
@ -254,31 +271,35 @@ namespace Tools {
auto ssplit(const string& str, const char& delim = ' ') -> vector<string>;
//* 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
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
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
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
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
string trans(const string& str);
//* 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
//* bit=True or defaults to bytes
//* start=int to set 1024 multiplier starting unit
//* 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
std::string operator*(const string& str, int64_t n);
@ -301,25 +322,24 @@ namespace Tools {
#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
class atomic_lock {
atomic<bool>& atom;
bool not_true = false;
bool not_true{}; // defaults to false
public:
atomic_lock(atomic<bool>& atom, bool wait=false);
atomic_lock(atomic<bool>& atom, bool wait = false);
~atomic_lock();
};
//* 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.
auto celsius_to(const long long& celsius, const string& scale) -> tuple<long long, string>;
}
//* Simple logging implementation
@ -333,13 +353,56 @@ namespace Logger {
};
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"
void set(const string& level);
void log_write(const size_t level, const string& msg);
inline void error(const string msg) { log_write(1, msg); }
inline void warning(const string msg) { log_write(2, msg); }
inline void info(const string msg) { log_write(3, msg); }
inline void debug(const string msg) { log_write(4, msg); }
void log_write(const Level level, const string& msg);
inline void error(const string msg) { log_write(ERROR, msg); }
inline void warning(const string msg) { log_write(WARNING, msg); }
inline void info(const string msg) { log_write(INFO, 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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -19,13 +19,15 @@ tab-size = 4
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ifaddrs.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 <ifaddrs.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netdb.h>
#include <netinet/tcp_fsm.h>
#include <netinet/in.h> // for inet_ntop stuff
#include <pwd.h>
#include <sys/_timeval.h>
#include <sys/endian.h>
@ -57,9 +59,9 @@ tab-size = 4
#include <string>
#include <memory>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include "../btop_config.hpp"
#include "../btop_shared.hpp"
#include "../btop_tools.hpp"
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;
@ -72,7 +74,7 @@ using namespace Tools;
namespace Cpu {
vector<long long> core_old_totals;
vector<long long> core_old_idles;
vector<string> available_fields = {"total"};
vector<string> available_fields = {"Auto", "total"};
vector<string> available_sensors = {"Auto"};
cpu_info current_cpu;
bool got_sensors = false, cpu_temp_only = false;
@ -118,12 +120,12 @@ namespace Shared {
//? Shared global variables init
int mib[2];
mib[0] = CTL_HW;
mib[1] = HW_NCPU;
mib[1] = HW_NCPU;
int ncpu;
size_t len = sizeof(ncpu);
if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) {
size_t len = sizeof(ncpu);
if (sysctl(mib, 2, &ncpu, &len, nullptr, 0) == -1) {
Logger::warning("Could not determine number of cores, defaulting to 1.");
} else {
} else {
coreCount = ncpu;
}
@ -141,21 +143,21 @@ namespace Shared {
int64_t memsize = 0;
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");
}
totalMem = memsize;
struct timeval 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");
} else {
bootTime = result.tv_sec;
}
size = sizeof(kfscale);
if (sysctlbyname("kern.fscale", &kfscale, &size, NULL, 0) == -1) {
if (sysctlbyname("kern.fscale", &kfscale, &size, nullptr, 0) == -1) {
kfscale = 2048;
}
@ -181,6 +183,17 @@ namespace Shared {
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 Cpu {
@ -192,19 +205,19 @@ namespace Cpu {
const array<string, 10> time_names = {"user", "nice", "system", "idle"};
unordered_flat_map<string, long long> cpu_old = {
{"totals", 0},
{"idles", 0},
{"user", 0},
{"nice", 0},
{"system", 0},
{"idle", 0}
{"totals", 0},
{"idles", 0},
{"user", 0},
{"nice", 0},
{"system", 0},
{"idle", 0}
};
string get_cpuName() {
string name;
char buffer[1024];
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");
return name;
}
@ -251,13 +264,13 @@ namespace Cpu {
if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
int32_t 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");
} else {
got_sensors = true;
int 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...
current_cpu.temp_max = temp;
}
@ -271,7 +284,7 @@ namespace Cpu {
int found = 0;
bool got_package = false;
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;
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);
for (int i = 0; i < Shared::coreCount; i++) {
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;
if (not got_package) {
p_temp += temp;
@ -304,7 +317,7 @@ namespace Cpu {
unsigned int freq = 1;
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 std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz
@ -360,17 +373,17 @@ namespace Cpu {
uint32_t percent = -1;
size_t size = sizeof(percent);
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;
} else {
has_battery = true;
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;
}
int 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";
} else {
if (state == 2) {
@ -385,22 +398,18 @@ namespace Cpu {
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()))
return current_cpu;
auto &cpu = current_cpu;
double avg[3];
if (getloadavg(avg, sizeof(avg)) < 0) {
if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) {
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);
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");
}
long long global_totals = 0;
@ -534,35 +543,37 @@ namespace Mem {
// find all zpools in the system. Do this only at startup.
void get_zpools() {
std::regex toReplace("\\.");
PipeWrapper poolPipe = PipeWrapper("zpool list -H -o name", "r");
while (not std::feof(poolPipe())) {
char poolName[512];
size_t len = 512;
if (fgets(poolName, len, poolPipe())) {
poolName[strcspn(poolName, "\n")] = 0;
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
static struct statinfo cur;
long double etime = 0;
uint64_t total_bytes_read;
static struct statinfo cur;
long double etime = 0;
uint64_t total_bytes_read;
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);
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++) {
auto d = cur.dinfo->devices[i];
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
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);
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));
@ -574,18 +585,18 @@ namespace Mem {
}
// this code is for ZFS mounts
for (string poolName : Mem::zpools) {
for (const auto &poolName : Mem::zpools) {
char sysCtl[1024];
snprintf(sysCtl, sizeof(sysCtl), "sysctl kstat.zfs.%s.dataset | egrep \'dataset_name|nread|nwritten\'", poolName.c_str());
PipeWrapper f = PipeWrapper(sysCtl, "r");
if (f()) {
char buf[512];
size_t len = 512;
uint64_t nread = 0, nwritten = 0;
while (not std::feof(f())) {
uint64_t nread = 0, nwritten = 0;
if (fgets(buf, len, f())) {
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) {
// create entry if datasetname matches with anything in mapping
// 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()))
return current_mem;
auto &show_swap = Config::getB("show_swap");
auto &show_disks = Config::getB("show_disks");
auto &swap_disk = Config::getB("swap_disk");
auto show_swap = Config::getB("show_swap");
auto show_disks = Config::getB("show_disks");
auto swap_disk = Config::getB("swap_disk");
auto &mem = current_mem;
static const bool snapped = (getenv("BTOP_SNAPPED") != NULL);
static bool snapped = (getenv("BTOP_SNAPPED") != nullptr);
int mib[4];
u_int memActive, memWire, cachedMem, freeMem;
@ -626,12 +637,12 @@ namespace Mem {
len = 4; sysctlnametomib("vm.stats.vm.v_active_count", mib, &len);
len = sizeof(memActive);
sysctl(mib, 4, &(memActive), &len, NULL, 0);
sysctl(mib, 4, &(memActive), &len, nullptr, 0);
memActive *= Shared::pageSize;
len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", mib, &len);
len = sizeof(memWire);
sysctl(mib, 4, &(memWire), &len, NULL, 0);
sysctl(mib, 4, &(memWire), &len, nullptr, 0);
memWire *= Shared::pageSize;
mem.stats.at("used") = memWire + memActive;
@ -639,16 +650,30 @@ namespace Mem {
len = sizeof(cachedMem);
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;
mem.stats.at("cached") = cachedMem;
len = sizeof(freeMem);
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;
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) {
for (const auto &name : swap_names) {
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();
auto &disks_filter = Config::getS("disks_filter");
bool filter_exclude = false;
// auto &only_physical = Config::getB("only_physical");
// auto only_physical = Config::getB("only_physical");
auto &disks = mem.disks;
vector<string> filter;
if (not disks_filter.empty()) {
@ -688,7 +713,7 @@ namespace Mem {
for (int i = 0; i < count; i++) {
auto fstype = string(stfs[i].f_fstypename);
if (fstype == "autofs" || fstype == "devfs" || fstype == "linprocfs" || fstype == "procfs" || fstype == "tmpfs" || fstype == "linsysfs" ||
fstype == "fdesckfs") {
fstype == "fdesckfs") {
// in memory filesystems -> not useful to show
continue;
}
@ -803,11 +828,11 @@ namespace Net {
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 &config_iface = Config::getS("net_iface");
auto &net_sync = Config::getB("net_sync");
auto &net_auto = Config::getB("net_auto");
auto net_sync = Config::getB("net_sync");
auto net_auto = Config::getB("net_auto");
auto new_timestamp = time_ms();
if (not no_update and errors < 3) {
@ -820,45 +845,65 @@ namespace Net {
return empty_net;
}
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();
string ipv4, ipv6;
//? Iteration over all items in getifaddrs() list
for (auto *ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family;
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
if (not v_contains(interfaces, iface)) {
interfaces.push_back(iface);
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;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0};
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");
} else {
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");
} else {
char *lim = buf.get() + len;
char *next = NULL;
char *next = nullptr;
for (next = buf.get(); next < lim;) {
struct if_msghdr *ifm = (struct if_msghdr *)next;
next += ifm->ifm_msglen;
@ -942,7 +987,7 @@ namespace Net {
auto sorted_interfaces = interfaces;
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,
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();
//? Try to set to a connected interface
@ -964,9 +1009,9 @@ namespace Net {
for (const auto &dir : {"download", "upload"}) {
for (const auto &sel : {0, 1}) {
if (rescale or max_count[dir][sel] >= 5) {
const uint64_t 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
: net[selected_iface].stat[dir].speed);
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, 0ll) / 5
: net[selected_iface].stat[dir].speed);
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;
redraw = true;
@ -1035,7 +1080,7 @@ namespace Proc {
//? Process runtime : current time - start time (both in unix time - seconds since epoch)
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
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
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 &reverse = Config::getB("proc_reversed");
auto reverse = Config::getB("proc_reversed");
const auto &filter = Config::getS("proc_filter");
const auto &per_core = Config::getB("proc_per_core");
const auto &tree = Config::getB("proc_tree");
const auto &show_detailed = Config::getB("show_detailed");
auto per_core = Config::getB("proc_per_core");
auto tree = Config::getB("proc_tree");
auto show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid");
bool 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) {
current_sort = sorting;
current_rev = reverse;
@ -1101,7 +1135,7 @@ namespace Proc {
vector<array<long, CPUSTATES>> cpu_time(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");
}
cputimes = 0;
@ -1120,16 +1154,16 @@ namespace Proc {
should_filter = true;
found.clear();
struct timeval currentTime;
gettimeofday(&currentTime, NULL);
gettimeofday(&currentTime, nullptr);
const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000);
int count = 0;
char buf[_POSIX2_LINE_MAX];
kvm_openfiles_wrapper kd(NULL, _PATH_DEVNULL, NULL, O_RDONLY, buf);
char buf[_POSIX2_LINE_MAX];
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);
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;
if (pid < 1) continue;
found.push_back(pid);
@ -1147,7 +1181,7 @@ namespace Proc {
//? Get program name, command, username, parent pid, nice and status
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();
found.pop_back();
continue;
@ -1310,8 +1344,8 @@ namespace Tools {
struct timeval ts, currTime;
std::size_t len = sizeof(ts);
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) {
gettimeofday(&currTime, NULL);
if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) {
gettimeofday(&currTime, nullptr);
return currTime.tv_sec - ts.tv_sec;
}
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 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
distributed under the License is distributed on an "AS IS" BASIS,
@ -19,7 +19,6 @@ tab-size = 4
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <libproc.h>
#include <mach/mach.h>
#include <mach/mach_host.h>
@ -28,7 +27,12 @@ tab-size = 4
#include <mach/processor_info.h>
#include <mach/vm_statistics.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 <ifaddrs.h>
#include <net/if_dl.h>
#include <netdb.h>
#include <netinet/tcp_fsm.h>
@ -37,12 +41,10 @@ tab-size = 4
#include <sys/statvfs.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <netinet/in.h> // for inet_ntop
#include <unistd.h>
#include <stdexcept>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
#include <cmath>
#include <fstream>
#include <numeric>
@ -50,6 +52,10 @@ tab-size = 4
#include <regex>
#include <string>
#include "../btop_config.hpp"
#include "../btop_shared.hpp"
#include "../btop_tools.hpp"
#include "sensors.hpp"
#include "smc.hpp"
@ -64,10 +70,9 @@ using namespace Tools;
namespace Cpu {
vector<long long> core_old_totals;
vector<long long> core_old_idles;
vector<string> available_fields = {"total"};
vector<string> available_fields = {"Auto", "total"};
vector<string> available_sensors = {"Auto"};
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;
int core_offset = 0;
@ -123,7 +128,7 @@ namespace Shared {
}
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");
}
@ -149,7 +154,7 @@ namespace Shared {
int64_t memsize = 0;
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");
}
totalMem = memsize;
@ -158,7 +163,6 @@ namespace Shared {
arg_max = sysconf(_SC_ARG_MAX);
//? 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.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {});
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"};
unordered_flat_map<string, long long> cpu_old = {
{"totals", 0},
{"idles", 0},
{"user", 0},
{"nice", 0},
{"system", 0},
{"idle", 0}
{"totals", 0},
{"idles", 0},
{"user", 0},
{"nice", 0},
{"system", 0},
{"idle", 0}
};
string get_cpuName() {
string name;
char buffer[1024];
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");
return name;
}
@ -318,7 +322,7 @@ namespace Cpu {
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
return "";
}
@ -437,23 +441,19 @@ namespace Cpu {
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()))
return current_cpu;
auto &cpu = current_cpu;
double avg[3];
if (getloadavg(avg, sizeof(avg)) < 0) {
if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) {
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 i;
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{};
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. */
if (IOServiceGetMatchingServices(libtop_master_port,
IOServiceMatching("IOMediaBSDClient"), &drive_list)) {
IOServiceMatching("IOMediaBSDClient"), &drive_list)) {
Logger::error("Error in IOServiceGetMatchingServices()");
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()))
return current_mem;
auto &show_swap = Config::getB("show_swap");
auto &show_disks = Config::getB("show_disks");
auto &swap_disk = Config::getB("swap_disk");
auto show_swap = Config::getB("show_swap");
auto show_disks = Config::getB("show_disks");
auto swap_disk = Config::getB("swap_disk");
auto &mem = current_mem;
static const bool snapped = (getenv("BTOP_SNAPPED") != NULL);
static bool snapped = (getenv("BTOP_SNAPPED") != nullptr);
vm_statistics64 p;
mach_msg_type_number_t info_size = HOST_VM_INFO64_COUNT;
@ -685,7 +685,7 @@ namespace Mem {
struct xsw_usage swap;
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_free") = swap.xsu_avail;
mem.stats.at("swap_used") = swap.xsu_used;
@ -712,7 +712,7 @@ namespace Mem {
double uptime = system_uptime();
auto &disks_filter = Config::getS("disks_filter");
bool filter_exclude = false;
// auto &only_physical = Config::getB("only_physical");
// auto only_physical = Config::getB("only_physical");
auto &disks = mem.disks;
vector<string> filter;
if (not disks_filter.empty()) {
@ -842,11 +842,11 @@ namespace Net {
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 &config_iface = Config::getS("net_iface");
auto &net_sync = Config::getB("net_sync");
auto &net_auto = Config::getB("net_auto");
auto net_sync = Config::getB("net_sync");
auto net_auto = Config::getB("net_auto");
auto new_timestamp = time_ms();
if (not no_update and errors < 3) {
@ -859,45 +859,63 @@ namespace Net {
return empty_net;
}
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();
string ipv4, ipv6;
//? Iteration over all items in getifaddrs() list
for (auto *ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family;
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
if (not v_contains(interfaces, iface)) {
interfaces.push_back(iface);
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;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0};
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");
} else {
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");
} else {
char *lim = buf.get() + len;
char *next = NULL;
char *next = nullptr;
for (next = buf.get(); next < lim;) {
struct if_msghdr *ifm = (struct if_msghdr *)next;
next += ifm->ifm_msglen;
@ -981,7 +999,7 @@ namespace Net {
auto sorted_interfaces = interfaces;
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,
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();
//? Try to set to a connected interface
@ -1003,9 +1021,9 @@ namespace Net {
for (const auto &dir : {"download", "upload"}) {
for (const auto &sel : {0, 1}) {
if (rescale or max_count[dir][sel] >= 5) {
const uint64_t 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
: net[selected_iface].stat[dir].speed);
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, 0ll) / 5
: net[selected_iface].stat[dir].speed);
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;
redraw = true;
@ -1074,7 +1092,7 @@ namespace Proc {
//? Process runtime : current time - start time (both in unix time - seconds since epoch)
struct timeval currentTime;
gettimeofday(&currentTime, NULL);
gettimeofday(&currentTime, nullptr);
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);
@ -1106,17 +1124,17 @@ namespace 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 &reverse = Config::getB("proc_reversed");
auto reverse = Config::getB("proc_reversed");
const auto &filter = Config::getS("proc_filter");
const auto &per_core = Config::getB("proc_per_core");
const auto &tree = Config::getB("proc_tree");
const auto &show_detailed = Config::getB("show_detailed");
auto per_core = Config::getB("proc_per_core");
auto tree = Config::getB("proc_tree");
auto show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid");
bool 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) {
current_sort = sorting;
current_rev = reverse;
@ -1136,7 +1154,7 @@ namespace Proc {
{ //* Get CPU totals
natural_t cpu_count;
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{};
error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
if (error != KERN_SUCCESS) {
@ -1158,13 +1176,13 @@ namespace Proc {
size_t size = 0;
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");
}
uint64_t cpu_t = 0;
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);
for (size_t i = 0; i < count; i++) { //* iterate over all processes in kinfo_proc
struct kinfo_proc& kproc = processes.get()[i];
@ -1195,7 +1213,7 @@ namespace Proc {
std::unique_ptr<char[]> proc_chars(new char[Shared::arg_max]);
int mib[] = {CTL_KERN, KERN_PROCARGS2, (int)pid};
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;
memcpy(&argc, &proc_chars.get()[0], sizeof(argc));
std::string_view proc_args(proc_chars.get(), argmax);
@ -1358,8 +1376,8 @@ namespace Tools {
struct timeval ts, currTime;
std::size_t len = sizeof(ts);
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) {
gettimeofday(&currTime, NULL);
if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) {
gettimeofday(&currTime, nullptr);
return currTime.tv_sec - ts.tv_sec;
}
return 0.0;

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@ -104,7 +104,7 @@ namespace Cpu {
long long getTemp(int core);
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);
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
# 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]="#2b3339"
theme[main_bg]="#272e33"
# Main text color
theme[main_fg]="#d3c6aa"
@ -18,13 +16,13 @@ theme[title]="#d3c6aa"
theme[hi_fg]="#e67e80"
# Background color of selected items
theme[selected_bg]="#4b565c"
theme[selected_bg]="#374145"
# Foreground color of selected items
theme[selected_fg]="#dbbc7f"
# 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
theme[graph_text]="#d3c6aa"
@ -33,19 +31,19 @@ theme[graph_text]="#d3c6aa"
theme[proc_misc]="#a7c080"
# Cpu box outline color
theme[cpu_box]="#4b565c"
theme[cpu_box]="#374145"
# Memory/disks box outline color
theme[mem_box]="#4b565c"
theme[mem_box]="#374145"
# Net up/down box outline color
theme[net_box]="#4b565c"
theme[net_box]="#374145"
# Processes box outline color
theme[proc_box]="#4b565c"
theme[proc_box]="#374145"
# Box divider line and small boxes line color
theme[div_line]="#4b565c"
theme[div_line]="#374145"
# Temperature graph colors
theme[temp_start]="#a7c080"
@ -78,14 +76,14 @@ theme[used_mid]="#dbbc7f"
theme[used_end]="#f85552"
# Download graph colors
theme[download_start]="#8da101"
theme[download_start]="#a7c080"
theme[download_mid]="#83c092"
theme[download_end]="#a7c080"
theme[download_end]="#7fbbb3"
# Upload graph colors
theme[upload_start]="#f85552"
theme[upload_mid]="#dbbc7f"
theme[upload_end]="#a7c080"
theme[upload_start]="#dbbc7f"
theme[upload_mid]="#e69875"
theme[upload_end]="#e67e80"
# Process box color gradient for threads, mem and cpu usage
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>
# 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
theme[main_fg]="#f8f8f2"
theme[main_fg]="#e8f6f5"
# Title color for boxes
theme[title]="#f8f8f2"
theme[title]="#e8f6f5"
# Highlight color for keyboard shortcuts
theme[hi_fg]="#21d6c9"
@ -17,16 +17,16 @@ theme[hi_fg]="#21d6c9"
theme[selected_bg]="#1aaba0"
# Foreground color of selected item in processes box
theme[selected_fg]="#f8f8f2"
theme[selected_fg]="#e8f6f5"
# 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
theme[graph_text]="#21d6c9"
theme[graph_text]="#ba1a84"
# 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
theme[proc_misc]="#21d6c9"
@ -49,44 +49,44 @@ theme[div_line]="#80638e"
# Temperature graph colors
theme[temp_start]="#21d6c9"
theme[temp_mid]="#1aaba0"
theme[temp_end]="#497e7a"
theme[temp_end]="#5ec4bc"
# CPU graph colors
theme[cpu_start]="#21d6c9"
theme[cpu_mid]="#1aaba0"
theme[cpu_end]="#497e7a"
theme[cpu_end]="#5ec4bc"
# Mem/Disk free meter
theme[free_start]="#21d6c9"
theme[free_mid]="#1aaba0"
theme[free_end]="#497e7a"
theme[free_end]="#5ec4bc"
# Mem/Disk cached meter
theme[cached_start]="#21d6c9"
theme[cached_mid]="#1aaba0"
theme[cached_end]="#497e7a"
theme[cached_end]="#5ec4bc"
# Mem/Disk available meter
theme[available_start]="#21d6c9"
theme[available_mid]="#1aaba0"
theme[available_end]="#497e7a"
theme[available_end]="#5ec4bc"
# Mem/Disk used meter
theme[used_start]="#21d6c9"
theme[used_mid]="#1aaba0"
theme[used_end]="#497e7a"
theme[used_end]="#5ec4bc"
# Download graph colors
theme[download_start]="#21d6c9"
theme[download_mid]="#1aaba0"
theme[download_end]="#497e7a"
theme[download_end]="#5ec4bc"
# Upload graph colors
theme[upload_start]="#ec95ec"
theme[upload_mid]="#1aaba0"
theme[upload_end]="#497e7a"
theme[upload_end]="#5ec4bc"
# Process box color gradient for threads, mem and cpu usage
theme[process_start]="#21d6c9"
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"