btop/src/btop_draw.cpp
aristocratos 934a9e3cf2 Squashed commit of the following:
commit 285fb215d1
Author: aristocratos <gnmjpl@gmail.com>
Date:   Thu Dec 28 13:10:18 2023 +0100

    Proc::draw() -> Use std::erase_if() instead of for loops

commit 2fba934cde
Author: aristocratos <gnmjpl@gmail.com>
Date:   Wed Dec 27 00:54:28 2023 +0100

    Fixed leftover code in GPU init logging false errors

commit ad14554f32
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 26 19:32:43 2023 +0100

    Try alternative names for GPU libraries during GPU init

commit a8fda16bf6
Merge: e15e0b7 2796af3
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 26 19:19:14 2023 +0100

    Merge pull request #696 from aristocratos/map_safety

commit 2796af3f37
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 26 19:18:37 2023 +0100

    Document DEBUG flag for Makefile

commit f484326bf2
Merge: b4eb397 e15e0b7
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 26 19:11:26 2023 +0100

    Merge branch 'main' into map_safety

commit b4eb397fc6
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 10:52:52 2023 +0100

    Fix errors

commit 3c04a7a380
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 10:41:15 2023 +0100

    Added more checks and debug logging

commit 8b81c4a4ec
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 03:28:35 2023 +0100

    Return const refs

commit f836233b64
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 02:49:24 2023 +0100

    Remove robin_hood.h

commit 3a8ceacaf8
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 02:37:32 2023 +0100

    Fix call to compact and missing utility include

commit e15e0b7188
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 02:27:38 2023 +0100

    Revert "Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback"

    This reverts commit 6c87ab6196.

commit ced47a960f
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 02:26:13 2023 +0100

    Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback

commit 6c87ab6196
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Dec 25 02:16:15 2023 +0100

    Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback

commit a2325371d4
Merge: aab2e8c b598f02
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sun Dec 17 19:56:31 2023 +0100

    Merge pull request #690 from aristocratos/osx-fix

commit b598f02468
Merge: b1fe377 aab2e8c
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sun Dec 17 12:06:39 2023 +0100

    Merge branch 'main' into osx-fix

commit aab2e8cc55
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sun Dec 17 12:03:47 2023 +0100

    Fixed test-snap-can-build.yml

commit b1fe3779e1
Merge: 7805242 2d15c41
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sun Dec 17 11:56:14 2023 +0100

    Merge branch 'main' into osx-fix

commit 2d15c41555
Merge: fe699b4 2d3e453
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sun Dec 17 11:54:49 2023 +0100

    Merge pull request #684 from kz6fittycent/main

commit 2d3e453ed5
Merge: 0a38864 fe699b4
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Fri Dec 15 12:02:11 2023 -0600

    Merge branch 'main' into main

commit 0a388647cc
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Fri Dec 15 12:01:45 2023 -0600

    Update test-snap-can-build.yml

    whoops

commit 49f425f356
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Fri Dec 15 12:00:48 2023 -0600

    Update test-snap-can-build.yml

    https://github.com/aristocratos/btop/pull/684#issuecomment-1852801811

commit 780524267f
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Fri Dec 15 09:02:57 2023 +0100

    conditional compile on Big Sur and up

commit fe699b4333
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 12 23:20:09 2023 +0100

    Version bump to 1.3.0 in preparation for upcoming release

commit 2d2df23198
Merge: d7b581e b71538e
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 12 23:19:31 2023 +0100

    Merge branch 'main' of github.com:aristocratos/btop

commit d7b581eda4
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 12 23:17:36 2023 +0100

    Updated changes

commit b71538eabe
Merge: a017056 730af5d
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 23:07:39 2023 +0100

    Merge pull request #666 from muneebmahmed/macos-clang

commit 730af5d4e1
Merge: 0246b1b a017056
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 23:05:52 2023 +0100

    Merge branch 'main' into macos-clang

commit a017056ea0
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 12 23:05:07 2023 +0100

    Added swap to ignore for statvfs() since it will always fail

commit e770cccaf8
Author: aristocratos <gnmjpl@gmail.com>
Date:   Tue Dec 12 22:55:48 2023 +0100

    Added try->catch for get_zfs_stat_file() to avoid fs error

commit 0246b1b971
Author: Muneeb Ahmed <32603485+muneebmahmed@users.noreply.github.com>
Date:   Mon Nov 20 12:18:40 2023 -0800

    Enable macos clang

    Apple clang uses different versioning from LLVM, so 15.0.0 is compatible

commit 6282f36f8f
Merge: cfd20a3 be73160
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 22:06:02 2023 +0100

    Merge pull request #675 from imwints/cmake

commit be731600f1
Merge: f4b14ce cfd20a3
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 22:01:21 2023 +0100

    Merge branch 'main' into cmake

commit 450b59b657
Merge: 875f08b cfd20a3
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 21:55:27 2023 +0100

    Merge branch 'main' into main

commit cfd20a374b
Merge: 14e664e b6a8696
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 21:48:55 2023 +0100

    Merge pull request #677 from imwints/cpu-model

commit b6a86962e2
Merge: 8096433 14e664e
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 21:47:01 2023 +0100

    Merge branch 'main' into cpu-model

commit 14e664e756
Merge: 0d35746 5902484
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Tue Dec 12 21:41:55 2023 +0100

    Merge pull request #679 from masiboss/main

commit 875f08ba5e
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Tue Dec 12 14:27:16 2023 -0600

    Update snapcraft.yaml - opengl

    - Testing opengl plug

commit 3ee4b18e57
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Tue Dec 12 14:14:59 2023 -0600

    Update bug_report.md

      - added snap info for bug reports to delineate

commit 2973a76f2b
Merge: fb782a2 0d35746
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Tue Dec 12 14:06:44 2023 -0600

    Merge branch 'aristocratos:main' into main

commit fb782a2ab3
Author: kz6fittycent <jimmy.tigert@gmail.com>
Date:   Tue Dec 12 14:02:27 2023 -0600

    Create test-snap-can-build.yml

commit 5902484f39
Author: masiboss <32394683+masiboss@users.noreply.github.com>
Date:   Thu Dec 7 21:42:11 2023 +0100

    simplify removal of "Apple"

commit 5beb9e12e5
Author: masiboss <32394683+masiboss@users.noreply.github.com>
Date:   Thu Dec 7 20:56:40 2023 +0100

    in case apple decides to add another suffix to the cpu name

commit 1b2f11b412
Author: masiboss <32394683+masiboss@users.noreply.github.com>
Date:   Thu Dec 7 20:49:34 2023 +0100

    cut less of cpu name if frequency is not shown

commit bcf4ad8ab6
Author: masiboss <32394683+masiboss@users.noreply.github.com>
Date:   Thu Dec 7 19:50:12 2023 +0100

    fix array out of bounds on regular m chip

commit aeefcacbc9
Author: masiboss <32394683+masiboss@users.noreply.github.com>
Date:   Thu Dec 7 19:34:11 2023 +0100

    fix cpu version not included

commit 23698940df
Author: masiboss <32394683+masiboss@users.noreply.github.com>
Date:   Thu Dec 7 19:23:58 2023 +0100

    strip "Apple" from name of Apple silicon chips

commit 8096433736
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Tue Dec 5 02:34:24 2023 +0100

    Fix printed model name for older Intel CPU

commit f4b14ce97e
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Tue Dec 5 01:00:14 2023 +0100

    Add CMake compile instructions for macOS

commit 97b35d9720
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Sat Dec 2 00:35:13 2023 +0100

    Add cmake workflow for all platforms

commit e35538fa29
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Sat Dec 2 00:34:32 2023 +0100

    Patch RPATH on FreeBSD, support OSX and format

commit 0d357468b5
Merge: ebc46ca 00f58b6
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Fri Dec 1 22:52:51 2023 +0100

    Merge pull request #674 from imwints/bsd-workflow

    Provide FreeBSD static release binaries

commit 00f58b6228
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Thu Nov 30 23:07:52 2023 +0100

    Provide FreeBSD static release binaries

    Bumps vmaction@freesdb-vm to version 1 which runs on Linux and doesn't
    hang all the time. Also uses clang for full static compilation

commit ebc46ca12c
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Mon Nov 27 18:21:42 2023 +0100

    Clean up compile instructions

commit d1384c9341
Merge: 2b0cc37 6f12e35
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Mon Nov 27 17:53:39 2023 +0100

    Merge pull request #671 from imwints/cmake-gpu

    Bring GPU support to CMake and improve how Make handles the ROCm library build

commit 6f12e3555d
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Mon Nov 27 00:33:11 2023 +0100

    Properly invoke CMake to build ROCm

    * Build an optimized library by default
    * Only build the library target
    * ROCm is build with debug symbols when `make DEBUG=true`
    * Enable LTO
    * Use the more generic CMake build command instead of calling make
      directly, this always uses all cores by default and makes it easier to
      switch to another generator e.g. Ninja
    * Use a variable to store the ROCm source directory. The directory can
      be changed with `make ROCM_DIR=<dir>`
    * The static library is now directly linked by CMake and not created off
      of the object files from a shared library build
    * The C++ compiler used to compile btop is now used to compile ROCm to
      avoid name mangling when `CXX` from the environment and `make CXX=`
      differ
    * CMake is invoked from btop's root directory

commit 0585bc9cfb
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Mon Nov 27 00:31:10 2023 +0100

    Suppress all output from ROCm build

    Similar to including external include files with `-isystem`, ignore
    output from ROCm build since these warnings aren't a concern here

commit 831be262b0
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Mon Nov 27 00:29:14 2023 +0100

    Remove ROCm object files with `make clean/distclean`

commit 2f59e61d87
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Sun Nov 26 22:56:58 2023 +0100

    Add GPU options for cmake based builds

commit 7588d96dd4
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Sun Nov 26 21:40:29 2023 +0100

    Add check for <ranges> header

commit ebbb769a6a
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Sun Nov 26 21:39:13 2023 +0100

    Move calls to find_package to where they're required

commit ed0fa34a9d
Author: Steffen Winter <steffen.winter@proton.me>
Date:   Sun Nov 26 19:39:29 2023 +0100

    Bump required CMake version

commit 2b0cc37632
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Nov 25 23:11:54 2023 +0100

    Update compile instructions for Gpu support

commit 359c67136b
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat Nov 25 22:49:26 2023 +0100

    Update changelog

commit 5b01235315
Merge: 0267eba 0bb8599
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Nov 25 21:57:32 2023 +0100

    Merge pull request #529 from romner-set/main

    Add GPU monitoring support

commit 0bb8599a96
Merge: 94d4502 0267eba
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Sat Nov 25 21:51:09 2023 +0100

    Merge branch 'main' into main

commit 94d4502901
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat Nov 25 21:48:50 2023 +0100

    Readme update and Makfile fixes.

commit 19bcff894b
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat Nov 25 21:01:11 2023 +0100

    Squashed commit of the following:

    commit 0267eba2bb
    Merge: 50bbab0 e81cf2b
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Wed Nov 15 21:43:18 2023 +0100

        Merge pull request #659 from ivanp7/patch-1

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

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

        Merge pull request #660 from stradicat/feature/elementarish

        Elementarish theme: color update according to Elementary palette

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

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

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

        Elementarish theme: color update according to Elementary palette

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

        Elementarish theme: color update according to Elementary palette

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

        Add alternative key codes for Insert, Home, End

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

        Add alternative Delete key code

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

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

        Merge pull request #649 from nobounce/workflow-timeout

        Set FreeBSD workflow timeout

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

        Set FreeBSD workflow timeout

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

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

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

        Merge pull request #643 from DecklynKern/main

        Fix scrollbar not clearing sometimes.

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

        Fix scrollbar not clearing sometimes.

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

        Merge pull request #623 from rahulaggarwal965/main

        Add keybind for toggling memory display mode in PROC box

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

        Add keybind for toggling memory display mode in PROC box

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

        Merge pull request #635 from lvxnull/editorconfig

        Add hpp files to .editorconfig

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

        Add hpp files to .editorconfig

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

        Merge pull request #636 from nobounce/performance-iili

        Minor string initialization improvement

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

        Minor string initialization improvement

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

        Merge pull request #633 from crestfallnatwork/main

        [fix] Made disks statvfs logic asynchronous.

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

        fixed bug where updated disks stats overrided disk io data

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

        fixed bug where updated disks stats overrided disk io data

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

        made disks stat logic async

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

        Merge pull request #630 from lvxnull/signal-list

        Fix signal list on non-linux/weird linux platforms

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

        Fix signal list on non-linux/weird linux platforms

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

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

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

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

        Make process thread count better readable when wider than 5 digits

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

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

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

        Merge pull request #589 from nobounce/cmake

        Add CMake support for Linux

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

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

        Horizon theme

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

        Add CMake support

        Linux is completly supported

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

        MacOS was not tested

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

        Add horizon theme

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

        Merge pull request #609 from scorpion-26/byteconv

        Fix short conversion of 1000-1023 *iB

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

        Fix short conversion of 1000-1023*iB

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

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

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

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

        Fix for FreeBSD github action not failing "correctly"...

commit b87772611c
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat Nov 25 20:44:45 2023 +0100

    Added definition GPU_SUPPORT to toggle GPU related code

commit 0267eba2bb
Merge: 50bbab0 e81cf2b
Author: Jakob P. Liljenberg <admin@qvantnet.com>
Date:   Wed Nov 15 21:43:18 2023 +0100

    Merge pull request #659 from ivanp7/patch-1

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

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

    Merge pull request #660 from stradicat/feature/elementarish

    Elementarish theme: color update according to Elementary palette

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

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

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

    Elementarish theme: color update according to Elementary palette

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

    Elementarish theme: color update according to Elementary palette

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

    Add alternative key codes for Insert, Home, End

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

    Add alternative Delete key code

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

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

    Merge pull request #649 from nobounce/workflow-timeout

    Set FreeBSD workflow timeout

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

    Set FreeBSD workflow timeout

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

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

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

    Merge pull request #643 from DecklynKern/main

    Fix scrollbar not clearing sometimes.

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

    Fix scrollbar not clearing sometimes.

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

    Merge pull request #623 from rahulaggarwal965/main

    Add keybind for toggling memory display mode in PROC box

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

    Add keybind for toggling memory display mode in PROC box

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

    Merge pull request #635 from lvxnull/editorconfig

    Add hpp files to .editorconfig

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

    Add hpp files to .editorconfig

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

    Merge pull request #636 from nobounce/performance-iili

    Minor string initialization improvement

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

    Minor string initialization improvement

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

    Merge pull request #633 from crestfallnatwork/main

    [fix] Made disks statvfs logic asynchronous.

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

    fixed bug where updated disks stats overrided disk io data

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

    fixed bug where updated disks stats overrided disk io data

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

    made disks stat logic async

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

    Merge pull request #630 from lvxnull/signal-list

    Fix signal list on non-linux/weird linux platforms

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

    Fix signal list on non-linux/weird linux platforms

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

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

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

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

    Make process thread count better readable when wider than 5 digits

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

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

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

    Merge pull request #589 from nobounce/cmake

    Add CMake support for Linux

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

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

    Horizon theme

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

    Add CMake support

    Linux is completly supported

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

    MacOS was not tested

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

    Add horizon theme

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

    Merge pull request #609 from scorpion-26/byteconv

    Fix short conversion of 1000-1023 *iB

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

    Fix short conversion of 1000-1023*iB

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

commit 975525d38f
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun Aug 27 12:34:46 2023 +0200

    Fix: Cpu gpu stats always shown when show_gpu_info is On and sizing issues

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

    Quickfixes for MacOS and FreeBSD compilation.

commit 7290109f80
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun Aug 27 00:58:30 2023 +0200

    Merge fix

commit 283d463242
Merge: efddad4 c296ac1
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun Aug 27 00:56:22 2023 +0200

    Merge branch 'main' into pr/romner-set/529

commit efddad42dc
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun Aug 27 00:39:57 2023 +0200

    Changed: cpu_graph_lower Auto defaults to cpu_graph_upper when show_gpu_info is Off

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

    Added show_gpu_info setting and Auto options for cpu graphs

commit b3970ee19c
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat Aug 26 20:52:59 2023 +0200

    Fixed: Key 5-0 gpu box toggle

commit bd5d697830
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat Aug 26 20:29:43 2023 +0200

    Squashed commit of the following:

    commit c296ac13cd
    Merge: 9a1e760 091c30a
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Sat Aug 26 19:29:57 2023 +0200

        Merge pull request #590 from nobounce/dangling-reference-config

        Convert parameters and config keys to std::string_view

    commit 9a1e760a66
    Merge: 9c8af4d 22e64ca
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Sat Aug 26 19:20:18 2023 +0200

        Merge pull request #602 from jfouquart/main

        Fix getting zfs pool name with '.' char in freebsd

    commit 9c8af4df43
    Merge: 8a49d8c 2217cbe
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Sat Aug 26 19:18:55 2023 +0200

        Merge pull request #601 from joske/cleanup

        [macos] don't check /sys on macos

    commit 8a49d8cf45
    Merge: 1556388 008fcd8
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Sat Aug 26 19:18:07 2023 +0200

        Merge pull request #600 from joske/makefile

        [macos/freebsd] support gcc13

    commit 1556388c83
    Merge: 1b126f5 d17e1a2
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Sat Aug 26 19:14:00 2023 +0200

        Merge pull request #599 from joske/main

        [macos] fix temp sensor on system with many cores

    commit d17e1a2dac
    Author: Jos Dehaes <jos.dehaes@gmail.com>
    Date:   Fri Aug 25 16:18:39 2023 +0200

        fix some warnings

    commit 4d8aa6b118
    Author: Jos Dehaes <jos.dehaes@gmail.com>
    Date:   Fri Aug 25 15:52:58 2023 +0200

        fix core check

    commit 22e64caaff
    Author: Jonathan Fouquart <jfouquart@hotmail.fr>
    Date:   Fri Aug 25 09:37:49 2023 +0200

        Fix getting zfs pool name with '.' char in freebsd

    commit 2217cbe143
    Author: Jos Dehaes <jos.dehaes@gmail.com>
    Date:   Wed Aug 23 16:01:04 2023 +0200

        [macos] don't check /sys on macos

    commit 008fcd889e
    Author: Jos Dehaes <jos.dehaes@gmail.com>
    Date:   Wed Aug 23 16:05:00 2023 +0200

        also add g++13

    commit 0fdca5eb03
    Author: Jos Dehaes <jos.dehaes@gmail.com>
    Date:   Wed Aug 23 15:54:07 2023 +0200

        support gcc13

    commit dcbdb7360d
    Author: Jos Dehaes <jos.dehaes@gmail.com>
    Date:   Wed Aug 23 15:46:47 2023 +0200

        [macos] fix temp sensor on system with many cores

    commit 1b126f55e3
    Author: aristocratos <gnmjpl@gmail.com>
    Date:   Fri Aug 4 01:08:27 2023 +0200

        Update Makefile for partial static compilation on freebsd

    commit c8ec6bbb00
    Author: aristocratos <gnmjpl@gmail.com>
    Date:   Thu Aug 3 23:08:33 2023 +0200

        Fix freebsd nullptr changes and makefile for gcc12 and newer

    commit 8a33aab588
    Merge: 94e5c02 e4abcef
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Sun Jul 30 13:21:48 2023 +0200

        Merge pull request #539 from nobounce/replace-NULL-nullptr

        Modernize using nullptr.

    commit 94e5c02d11
    Author: aristocratos <gnmjpl@gmail.com>
    Date:   Thu Jul 27 20:51:21 2023 +0200

        Better text editing

    commit 091c30ab2b
    Author: nobounce <steffen.winter@proton.me>
    Date:   Thu Jul 27 14:17:54 2023 +0200

        Convert parameters and config keys to std::string_view

        Using std::string_view instead of std::string& silences a new warning
        from GCC 13, -Wdangling-reference

        Also switch return type of `getI` from int& to int, trivial types are
        cheaper to copy by value

    commit e4abcefbf9
    Author: nobounce <steffen.winter@proton.me>
    Date:   Wed Jul 26 16:19:17 2023 +0200

        Use nullptr instead of NULL.

        See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
        TLDR: NULL is of type int and relies on proper implicit pointer
        conversion which may lead to issues when using overloaded functions

        It is also considered a 'best practise' for modern C++ and
        conveys the programmers intention more precisly.

    commit d53307f14c
    Author: nobounce <steffen.winter@proton.me>
    Date:   Sun Jul 23 19:53:36 2023 +0200

        Fix path to Linux CI file in itself

        The CI file has a list of dependent files including itself. The path was
        not updated when the CI was split into different files

    commit 594f42b9eb
    Merge: aca2e4b 53d6eba
    Author: Jakob P. Liljenberg <admin@qvantnet.com>
    Date:   Wed Jul 26 15:38:01 2023 +0200

        Merge pull request #584 from nobounce/nb/fix-ci-path

        Fix path to Linux CI file in itself

    commit aca2e4be75
    Author: aristocratos <gnmjpl@gmail.com>
    Date:   Wed Jul 26 14:38:48 2023 +0200

        Fix whitespace indent -> tab indent

    commit 33faa01910
    Author: aristocratos <gnmjpl@gmail.com>
    Date:   Wed Jul 26 14:34:15 2023 +0200

        Revert fmt submodule to static fmt folder in include

    commit 53d6ebabc0
    Author: nobounce <steffen.winter@proton.me>
    Date:   Sun Jul 23 19:53:36 2023 +0200

        Fix path to Linux CI file in itself

        The CI file has a list of dependent files including itself. The path was
        not updated when the CI was split into different files

commit 346c9e479b
Author: romner <roman@skotnica.com>
Date:   Wed Jul 19 16:53:58 2023 +0200

    Fix GPU text overflow in CPU panel, again

commit 3a5e5fd5d3
Author: romner <roman@skotnica.com>
Date:   Wed Jul 19 16:22:45 2023 +0200

    Improve 0-10 key input

commit 972b2b6a01
Author: romner <roman@skotnica.com>
Date:   Wed Jul 19 15:54:35 2023 +0200

    Fix available boxes in menu & config description

commit 1f73453aec
Author: romner <roman@skotnica.com>
Date:   Wed Jul 19 15:34:23 2023 +0200

    Fix crashes when trying to open nth GPU box with only n-1 GPUs in the system

commit 46c6be0a29
Author: romner <roman@skotnica.com>
Date:   Sun Jul 16 17:19:09 2023 +0200

    Fix GPU horizontal text overflow in CPU panel

commit 85fb28cee6
Author: romner <roman@skotnica.com>
Date:   Fri Jul 14 02:39:44 2023 +0200

    Fix RSMI_STATIC=true and add GPU section to README.md

commit 3fad8a6fde
Author: romner-set <roman@skotnica.com>
Date:   Mon Jun 26 13:10:31 2023 +0200

    Add GPU options

commit 746f716a02
Author: romner-set <roman@skotnica.com>
Date:   Fri Jun 16 11:11:57 2023 +0200

    Remove lib/rocm_smi_lib and add instructions for obtaining it to README

commit d8ebbe1181
Author: romner <roman@skotnica.com>
Date:   Thu Jun 8 20:24:01 2023 +0200

    Join NVML PCIe threads only if PCIe TX/RX is supported by GPU

commit be10989151
Author: romner <roman@skotnica.com>
Date:   Tue Jun 6 19:47:07 2023 +0200

    Parallelize NVML PCIe TX/RX data collection

commit 85892a9fe3
Author: aristocratos <gnmjpl@gmail.com>
Date:   Mon Jun 5 21:59:26 2023 +0200

    Fix type: ulong -> size_t and compare std::cmp_less

commit 85a10f0305
Author: romner <roman@skotnica.com>
Date:   Fri Jun 2 16:14:24 2023 +0200

    Fix ROCm SMI makefile flags

commit cd6979277d
Author: romner <roman@skotnica.com>
Date:   Fri Jun 2 15:44:44 2023 +0200

    Fix error when ROCm SMI static compilation fails

commit daaa45324f
Author: romner <roman@skotnica.com>
Date:   Fri Jun 2 15:34:12 2023 +0200

    Load ROCm SMI dynamically by default, optionally statically compile and link

commit 093edfe948
Author: aristocratos <gnmjpl@gmail.com>
Date:   Thu Jun 1 19:49:00 2023 +0200

    Minor changes in wording...

commit b9a4d31fa4
Author: aristocratos <gnmjpl@gmail.com>
Date:   Thu Jun 1 19:37:53 2023 +0200

    Fix Makefile dependency order and layout

commit a0163ce220
Author: romner <roman@skotnica.com>
Date:   Thu Jun 1 16:42:02 2023 +0200

    Statically link ROCm SMI

commit b2df0696fd
Author: romner-set <roman@skotnica.com>
Date:   Thu Jun 1 03:41:56 2023 +0200

    Dynamically load NVML

commit 547f17dda3
Author: romner-set <roman@skotnica.com>
Date:   Tue May 30 18:24:50 2023 +0200

    Add more GPU graph types to the CPU panel

commit 842c761a73
Author: romner-set <roman@skotnica.com>
Date:   Mon May 22 09:46:20 2023 +0200

    Fix crash when all GPU panels are open but the CPU panel is closed

commit 8c96bd51e9
Author: romner <roman@skotnica.com>
Date:   Sun May 21 20:34:47 2023 +0200

    Handle GPUs which cannot report certain stats in GPU panel

commit 414d7eb94c
Author: romner <roman@skotnica.com>
Date:   Sun May 21 18:02:50 2023 +0200

    Handle GPUs which cannot report certain stats in btop_collect.cpp and CPU panel

commit 005de97e6d
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun May 21 13:58:11 2023 +0200

    Add missing fmt prefixes

commit 1fee2bc08b
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sun May 21 13:52:19 2023 +0200

    Add DebugTimer class and change some Logger::error calls to Logger::debug

commit 2e68c0b916
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat May 20 17:27:20 2023 +0200

    Fixed key > gpu_names check

commit 04ed16a9f6
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat May 20 17:15:45 2023 +0200

    Merged changes from main

commit 8c710a2b68
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat May 20 01:41:04 2023 +0200

    Makefile auto detection and initial logic for excluding gpu code when libs are missing

commit 8bae1ec092
Author: aristocratos <gnmjpl@gmail.com>
Date:   Sat May 20 00:13:00 2023 +0200

    Fixed debug timer for gpu

commit 01acfd603e
Author: romner-set <roman@skotnica.com>
Date:   Fri May 19 16:42:32 2023 +0200

    Bind GPU panel to 5,6,7,8,9,0 and fully implement multi-GPU support

commit 22a463976d
Author: romner <roman@skotnica.com>
Date:   Thu May 18 16:07:05 2023 +0200

    Add GPU info to CPU panel

commit c352bf2613
Author: romner-set <roman@skotnica.com>
Date:   Mon May 15 19:42:55 2023 +0200

    Add ROCm SMI backend for AMD GPU support

commit 917d568a77
Author: romner-set <roman@skotnica.com>
Date:   Mon May 15 13:58:54 2023 +0200

    Add multi-GPU support for NVML data collection

commit 2d27f2ff61
Author: romner <roman@skotnica.com>
Date:   Sun May 14 17:40:50 2023 +0200

    Fix crash when no nvidia GPU is detected

commit 0e0025a2c3
Author: romner <roman@skotnica.com>
Date:   Sun May 14 17:31:39 2023 +0200

    Update makefile text, fix typo and adhere to contibuting guidelines

commit bcffcdf19f
Author: romner <roman@skotnica.com>
Date:   Sun May 14 16:53:06 2023 +0200

    Make GPU window's size dynamic and integrate it with the rest of btop

commit 95b3228308
Author: romner <roman@skotnica.com>
Date:   Sat May 13 19:41:51 2023 +0200

    Improve GPU side panel

commit adcdc583b0
Author: romner <roman@skotnica.com>
Date:   Sat May 13 00:27:23 2023 +0200

    Add GPU side panel

commit d522a91ef4
Author: romner <roman@skotnica.com>
Date:   Fri May 12 19:34:47 2023 +0200

    Add rudimentary, fullscreen single-GPU NVML utilization graph
2024-01-02 15:29:53 +01:00

2241 lines
95 KiB
C++

/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
indent = tab
tab-size = 4
*/
#include <array>
#include <algorithm>
#include <cmath>
#include <ranges>
#include <stdexcept>
#include <string>
#include <utility>
#include "btop_draw.hpp"
#include "btop_config.hpp"
#include "btop_theme.hpp"
#include "btop_shared.hpp"
#include "btop_tools.hpp"
#include "btop_input.hpp"
#include "btop_menu.hpp"
using std::array;
using std::clamp;
using std::cmp_equal;
using std::cmp_greater;
using std::cmp_less;
using std::cmp_less_equal;
using std::floor;
using std::max;
using std::min;
using std::round;
using std::to_string;
using std::views::iota;
using namespace Tools;
using namespace std::literals; // for operator""s
namespace rng = std::ranges;
namespace Symbols {
const string meter = "";
const array<string, 10> superscript = { "", "¹", "²", "³", "", "", "", "", "", "" };
const std::unordered_map<string, vector<string>> graph_symbols = {
{ "braille_up", {
" ", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", ""
}},
{"braille_down", {
" ", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", ""
}},
{"block_up", {
" ", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", ""
}},
{"block_down", {
" ", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", ""
}},
{"tty_up", {
" ", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", ""
}},
{"tty_down", {
" ", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", ""
}}
};
}
namespace Draw {
string banner_gen(int y, int x, bool centered, bool redraw) {
static string banner;
static size_t width = 0;
if (redraw) banner.clear();
if (banner.empty()) {
string b_color, bg, fg, oc, letter;
auto lowcolor = Config::getB("lowcolor");
auto tty_mode = Config::getB("tty_mode");
for (size_t z = 0; const auto& line : Global::Banner_src) {
if (const auto w = ulen(line[1]); w > width) width = w;
if (tty_mode) {
fg = (z > 2) ? "\x1b[31m" : "\x1b[91m";
bg = (z > 2) ? "\x1b[90m" : "\x1b[37m";
}
else {
fg = Theme::hex_to_color(line[0], lowcolor);
int bg_i = 120 - z * 12;
bg = Theme::dec_to_color(bg_i, bg_i, bg_i, lowcolor);
}
for (size_t i = 0; i < line[1].size(); i += 3) {
if (line[1][i] == ' ') {
letter = Mv::r(1);
i -= 2;
}
else
letter = line[1].substr(i, 3);
b_color = (letter == "") ? fg : bg;
if (b_color != oc) banner += b_color;
banner += letter;
oc = b_color;
}
if (++z < Global::Banner_src.size()) banner += Mv::l(ulen(line[1])) + Mv::d(1);
}
banner += Mv::r(18 - Global::Version.size())
+ Theme::c("main_fg") + Fx::b + Fx::i + "v" + Global::Version + Fx::reset;
}
if (redraw) return "";
return (centered ? Mv::to(y, Term::width / 2 - width / 2) : Mv::to(y, x)) + banner;
}
TextEdit::TextEdit() {}
TextEdit::TextEdit(string text, bool numeric) : numeric(numeric), text(text) {
pos = this->text.size();
upos = ulen(this->text);
}
bool TextEdit::command(const string& key) {
if (key == "left" and upos > 0) {
upos--;
pos = uresize(text, upos).size();
}
else if (key == "right" and pos < text.size()) {
upos++;
pos = uresize(text, upos).size();
}
else if (key == "home" and not text.empty() and pos > 0) {
pos = upos = 0;
}
else if (key == "end" and not text.empty() and pos < text.size()) {
pos = text.size();
upos = ulen(text);
}
else if (key == "backspace" and pos > 0) {
if (pos == text.size()) {
text = uresize(text, --upos);
pos = text.size();
}
else {
const string first = uresize(text, --upos);
pos = first.size();
text = first + luresize(text.substr(pos), ulen(text) - upos - 1);
}
}
else if (key == "delete" and pos < text.size()) {
const string first = uresize(text, upos + 1);
text = uresize(first, ulen(first) - 1) + text.substr(first.size());
}
else if (key == "space" and not numeric) {
text.insert(pos++, 1, ' ');
upos++;
}
else if (ulen(key) == 1 and text.size() < text.max_size() - 20) {
if (numeric and not isint(key)) return false;
if (key.size() == 1) {
text.insert(pos++, 1, key.at(0));
upos++;
}
else {
const string first = uresize(text, upos) + key;
text = first + text.substr(pos);
upos++;
pos = first.size();
}
}
else
return false;
return true;
}
string TextEdit::operator()(const size_t limit) {
string out;
size_t c_upos = upos;
if (text.empty())
return Fx::ul + " " + Fx::uul;
if (limit > 0 and ulen(text) + 1 > limit) {
try {
const size_t half = (size_t)round((double)limit / 2);
string first;
if (upos + half > ulen(text))
first = luresize(text.substr(0, pos), limit - (ulen(text) - upos));
else if (upos - half < 1)
first = text.substr(0, pos);
else
first = luresize(text.substr(0, pos), half);
out = first + uresize(text.substr(pos), limit - ulen(first));
c_upos = ulen(first);
}
catch (const std::exception& e) {
Logger::error("In TextEdit::operator() : " + string{e.what()});
return "";
}
}
else
out = text;
if (c_upos == 0)
return Fx::ul + uresize(out, 1) + Fx::uul + luresize(out, ulen(out) - 1);
else if (c_upos == ulen(out))
return out + Fx::ul + " " + Fx::uul;
else
return uresize(out, c_upos) + Fx::ul + luresize(uresize(out, c_upos + 1), 1) + Fx::uul + luresize(out, ulen(out) - c_upos - 1);
}
void TextEdit::clear() {
this->text.clear();
}
string createBox(const int x, const int y, const int width,
const int height, string line_color, bool fill,
const string title, const string title2, const int num) {
string out;
if (line_color.empty())
line_color = Theme::c("div_line");
auto tty_mode = Config::getB("tty_mode");
auto rounded = Config::getB("rounded_corners");
const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript.at(clamp(num, 0, 9)));
const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
const auto& left_down = (tty_mode or not rounded ? Symbols::left_down : Symbols::round_left_down);
out = Fx::reset + line_color;
//? Draw horizontal lines
for (const int& hpos : {y, y + height - 1}) {
out += Mv::to(hpos, x) + Symbols::h_line * (width - 1);
}
//? Draw vertical lines and fill if enabled
for (const int& hpos : iota(y + 1, y + height - 1)) {
out += Mv::to(hpos, x) + Symbols::v_line
+ ((fill) ? string(width - 2, ' ') : Mv::r(width - 2))
+ Symbols::v_line;
}
//? Draw corners
out += Mv::to(y, x) + left_up
+ Mv::to(y, x + width - 1) + right_up
+ Mv::to(y + height - 1, x) +left_down
+ Mv::to(y + height - 1, x + width - 1) + right_down;
//? Draw titles if defined
if (not title.empty()) {
out += Mv::to(y, x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + title
+ Fx::ub + line_color + Symbols::title_right;
}
if (not title2.empty()) {
out += Mv::to(y + height - 1, x + 2) + Symbols::title_left_down + Fx::b + numbering + Theme::c("title") + title2
+ Fx::ub + line_color + Symbols::title_right_down;
}
return out + Fx::reset + Mv::to(y + 1, x + 1);
}
bool update_clock(bool force) {
const auto& clock_format = Config::getS("clock_format");
if (not Cpu::shown or clock_format.empty()) {
if (clock_format.empty() and not Global::clock.empty()) Global::clock.clear();
return false;
}
static const std::unordered_map<string, string> clock_custom_format = {
{"/user", Tools::username()},
{"/host", Tools::hostname()},
{"/uptime", ""}
};
static time_t c_time{}; // defaults to 0
static size_t clock_len{}; // defaults to 0
static string clock_str;
if (auto n_time = time(nullptr); not force and n_time == c_time)
return false;
else {
c_time = n_time;
const auto new_clock = Tools::strf_time(clock_format);
if (not force and new_clock == clock_str) return false;
clock_str = new_clock;
}
auto& out = Global::clock;
auto cpu_bottom = Config::getB("cpu_bottom");
const auto& x = Cpu::x;
const auto y = (cpu_bottom ? Cpu::y + Cpu::height - 1 : Cpu::y);
const auto& width = Cpu::width;
const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
const auto& title_right = (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
for (const auto& [c_format, replacement] : clock_custom_format) {
if (s_contains(clock_str, c_format)) {
if (c_format == "/uptime") {
string upstr = sec_to_dhms(system_uptime());
if (upstr.size() > 8) upstr.resize(upstr.size() - 3);
clock_str = s_replace(clock_str, c_format, upstr);
}
else {
clock_str = s_replace(clock_str, c_format, replacement);
}
}
}
clock_str = uresize(clock_str, std::max(10, width - 66 - (Term::width >= 100 and Config::getB("show_battery") and Cpu::has_battery ? 22 : 0)));
out.clear();
if (clock_str.size() != clock_len) {
if (not Global::resized and clock_len > 0)
out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
clock_len = clock_str.size();
}
out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left
+ Theme::c("title") + Fx::b + clock_str + Theme::c("cpu_box") + Fx::ub + title_right;
return true;
}
//* Meter class ------------------------------------------------------------------------------------------------------------>
Meter::Meter() {}
Meter::Meter(const int width, const string& color_gradient, bool invert)
: width(width), color_gradient(color_gradient), invert(invert) {}
string Meter::operator()(int value) {
if (width < 1) return "";
value = clamp(value, 0, 100);
if (not cache.at(value).empty()) return cache.at(value);
auto& out = cache.at(value);
for (const int& i : iota(1, width + 1)) {
int y = round((double)i * 100.0 / width);
if (value >= y)
out += Theme::g(color_gradient).at(invert ? 100 - y : y) + Symbols::meter;
else {
out += Theme::c("meter_bg") + Symbols::meter * (width + 1 - i);
break;
}
}
out += Fx::reset;
return out;
}
//* Graph class ------------------------------------------------------------------------------------------------------------>
void Graph::_create(const deque<long long>& data, int data_offset) {
bool mult = (data.size() - data_offset > 1);
const auto& graph_symbol = Symbols::graph_symbols.at(symbol + '_' + (invert ? "down" : "up"));
array<int, 2> result;
const float mod = (height == 1) ? 0.3 : 0.1;
long long data_value = 0;
if (mult and data_offset > 0) {
last = data.at(data_offset - 1);
if (max_value > 0) last = clamp((last + offset) * 100 / max_value, 0ll, 100ll);
}
//? Horizontal iteration over values in <data>
for (const int& i : iota(data_offset, (int)data.size())) {
// if (tty_mode and mult and i % 2 != 0) continue;
if (not tty_mode and mult) current = not current;
if (i < 0) {
data_value = 0;
last = 0;
}
else {
data_value = data.at(i);
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
}
//? Vertical iteration over height of graph
for (const int& horizon : iota(0, height)) {
const int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100;
const int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0;
//? Calculate previous + current value to fit two values in 1 braille character
for (int ai = 0; const auto& value : {last, data_value}) {
const int clamp_min = (no_zero and horizon == height - 1 and not (mult and i == data_offset and ai == 0)) ? 1 : 0;
if (value >= cur_high)
result[ai++] = 4;
else if (value <= cur_low)
result[ai++] = clamp_min;
else {
result[ai++] = clamp((int)round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod), clamp_min, 4);
}
}
//? Generate graph symbol from 5x5 2D vector
if (height == 1) {
if (result.at(0) + result.at(1) == 0) graphs.at(current).at(horizon) += Mv::r(1);
else {
if (not color_gradient.empty()) graphs.at(current).at(horizon) += Theme::g(color_gradient).at(clamp(max(last, data_value), 0ll, 100ll));
graphs.at(current).at(horizon) += graph_symbol.at((result.at(0) * 5 + result.at(1)));
}
}
else graphs.at(current).at(horizon) += graph_symbol.at((result.at(0) * 5 + result.at(1)));
}
if (mult and i >= 0) last = data_value;
}
last = data_value;
out.clear();
if (height == 1) {
//if (not color_gradient.empty())
// out += (last < 1 ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(clamp(last, 0ll, 100ll)));
out += graphs.at(current).at(0);
}
else {
for (const int& i : iota(1, height + 1)) {
if (i > 1) out += Mv::d(1) + Mv::l(width);
if (not color_gradient.empty())
out += (invert) ? Theme::g(color_gradient).at(i * 100 / height) : Theme::g(color_gradient).at(100 - ((i - 1) * 100 / height));
out += (invert) ? graphs.at(current).at(height - i) : graphs.at(current).at(i-1);
}
}
if (not color_gradient.empty()) out += Fx::reset;
}
Graph::Graph() {}
Graph::Graph(int width, int height, const string& color_gradient,
const deque<long long>& data, const string& symbol,
bool invert, bool no_zero, long long max_value, long long offset)
: width(width), height(height), color_gradient(color_gradient),
invert(invert), no_zero(no_zero), offset(offset) {
if (Config::getB("tty_mode") or symbol == "tty") this->symbol = "tty";
else if (symbol != "default") this->symbol = symbol;
else this->symbol = Config::getS("graph_symbol");
if (this->symbol == "tty") tty_mode = true;
if (max_value == 0 and offset > 0) max_value = 100;
this->max_value = max_value;
const int value_width = (tty_mode ? data.size() : ceil((double)data.size() / 2));
int data_offset = (value_width > width) ? data.size() - width * (tty_mode ? 1 : 2) : 0;
if (not tty_mode and (data.size() - data_offset) % 2 != 0) {
data_offset--;
}
//? Populate the two switching graph vectors and fill empty space if data size < width
for (const int& i : iota(0, height * 2)) {
if (tty_mode and i % 2 != current) continue;
graphs[(i % 2 != 0)].push_back((value_width < width) ? ((height == 1) ? Mv::r(1) : " "s) * (width - value_width) : "");
}
if (data.size() == 0) return;
this->_create(data, data_offset);
}
string& Graph::operator()(const deque<long long>& data, bool data_same) {
if (data_same) return out;
//? Make room for new characters on graph
if (not tty_mode) current = not current;
for (const int& i : iota(0, height)) {
if (height == 1 and graphs.at(current).at(i).at(1) == '[') {
if (graphs.at(current).at(i).at(3) == 'C') graphs.at(current).at(i).erase(0, 4);
else graphs.at(current).at(i).erase(0, graphs.at(current).at(i).find_first_of('m') + 4);
}
else if (graphs.at(current).at(i).at(0) == ' ') graphs.at(current).at(i).erase(0, 1);
else graphs.at(current).at(i).erase(0, 3);
}
this->_create(data, (int)data.size() - 1);
return out;
}
string& Graph::operator()() {
return out;
}
//*------------------------------------------------------------------------------------------------------------------------->
}
namespace Cpu {
int width_p = 100, height_p = 32;
int min_width = 60, min_height = 8;
int x = 1, y = 1, width = 20, height;
int b_columns, b_column_size;
int b_x, b_y, b_width, b_height;
long unsigned int lavg_str_len = 0;
int graph_up_height, graph_low_height;
int graph_up_width, graph_low_width;
int gpu_meter_width;
bool shown = true, redraw = true, mid_line = false;
string box;
vector<Draw::Graph> graphs_upper;
vector<Draw::Graph> graphs_lower;
Draw::Meter cpu_meter;
vector<Draw::Meter> gpu_meters;
vector<Draw::Graph> core_graphs;
vector<Draw::Graph> temp_graphs;
vector<Draw::Graph> gpu_temp_graphs;
vector<Draw::Graph> gpu_mem_graphs;
string draw(const cpu_info& cpu, const vector<Gpu::gpu_info>& gpus, bool force_redraw, bool data_same) {
if (Runner::stopping) return "";
if (force_redraw) redraw = true;
bool show_temps = (Config::getB("check_temp") and got_sensors);
auto single_graph = Config::getB("cpu_single_graph");
bool hide_cores = show_temps and (cpu_temp_only or not Config::getB("show_coretemp"));
const int extra_width = (hide_cores ? max(6, 6 * b_column_size) : 0);
#ifdef GPU_SUPPORT
const auto& show_gpu_info = Config::getS("show_gpu_info");
const bool gpu_always = show_gpu_info == "On";
bool show_gpu = (gpus.size() > 0 and (gpu_always or (show_gpu_info == "Auto" and Gpu::shown == 0)));
#else
(void)gpus;
#endif
auto graph_up_field = Config::getS("cpu_graph_upper");
if (graph_up_field == "Auto" or not v_contains(Cpu::available_fields, graph_up_field))
graph_up_field = "total";
auto graph_lo_field = Config::getS("cpu_graph_lower");
if (graph_lo_field == "Auto" or not v_contains(Cpu::available_fields, graph_lo_field)) {
#ifdef GPU_SUPPORT
graph_lo_field = show_gpu ? "gpu-totals" : graph_up_field;
#else
graph_lo_field = graph_up_field;
#endif
}
auto tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu"));
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6);
auto& temp_scale = Config::getS("temp_scale");
auto cpu_bottom = Config::getB("cpu_bottom");
const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
static int bat_pos = 0, bat_len = 0;
if (safeVal(cpu.cpu_percent, "total"s).empty()
or safeVal(cpu.core_percent, 0).empty()
or (show_temps and safeVal(cpu.temp, 0).empty())) return "";
if (safeVal(cpu.cpu_percent, "total"s).empty()
or safeVal(cpu.core_percent, 0).empty()
or (show_temps and safeVal(cpu.temp, 0).empty())) return "";
string out;
out.reserve(width * height);
//* Redraw elements not needed to be updated every cycle
if (redraw) {
mid_line = (not single_graph and graph_up_field != graph_lo_field);
graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0));
graph_low_height = height - 2 - graph_up_height - mid_line;
const int button_y = cpu_bottom ? y + height - 1 : y;
out += box;
//? Buttons on title
out += Mv::to(button_y, x + 10) + title_left + Theme::c("hi_fg") + Fx::b + 'm' + Theme::c("title") + "enu" + Fx::ub + title_right;
Input::mouse_mappings["m"] = {button_y, x + 11, 1, 4};
out += Mv::to(button_y, x + 16) + title_left + Theme::c("hi_fg") + Fx::b + 'p' + Theme::c("title") + "reset "
+ (Config::current_preset < 0 ? "*" : to_string(Config::current_preset)) + Fx::ub + title_right;
Input::mouse_mappings["p"] = {button_y, x + 17, 1, 8};
const string update = to_string(Config::getI("update_ms")) + "ms";
out += Mv::to(button_y, x + width - update.size() - 8) + title_left + Fx::b + Theme::c("hi_fg") + "- " + Theme::c("title") + update
+ Theme::c("hi_fg") + " +" + Fx::ub + title_right;
Input::mouse_mappings["-"] = {button_y, x + width - (int)update.size() - 7, 1, 2};
Input::mouse_mappings["+"] = {button_y, x + width - 5, 1, 2};
//? Graphs & meters
const int graph_default_width = x + width - b_width - 3;
auto init_graphs = [&](vector<Draw::Graph>& graphs, const int graph_height, int& graph_width, const string& graph_field, bool invert) {
#ifdef GPU_SUPPORT
if (graph_field.starts_with("gpu")) {
if (graph_field.find("totals") != string::npos) {
graphs.resize(gpus.size());
gpu_temp_graphs.resize(gpus.size());
gpu_mem_graphs.resize(gpus.size());
gpu_meters.resize(gpus.size());
graph_width = graph_default_width/(int)gpus.size() - (int)gpus.size() + 1 + graph_default_width%gpus.size();
for (unsigned long i = 0;;) {
auto& gpu = gpus[i]; auto& graph = graphs[i];
//? GPU graphs/meters
if (gpu.supported_functions.temp_info)
gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 };
if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total)
gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol };
if (gpu.supported_functions.gpu_utilization) {
gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9);
gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" };
}
bool utilization_support = gpu.supported_functions.gpu_utilization;
if (++i < gpus.size()) {
if (utilization_support)
graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true};
} else {
if (utilization_support)
graph = Draw::Graph{
graph_width + graph_default_width%graph_width - (int)gpus.size() + 1,
graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true
};
break;
}
}
} else {
graphs.resize(1);
graph_width = graph_default_width;
graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true };
gpu_temp_graphs.resize(gpus.size());
gpu_mem_graphs.resize(gpus.size());
gpu_meters.resize(gpus.size());
for (unsigned long i = 0; i < gpus.size(); ++i) {
if (gpus[i].supported_functions.temp_info)
gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 };
if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total)
gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol };
if (gpus[i].supported_functions.gpu_utilization) {
gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9);
gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" };
}
}
}
} else {
#endif
graphs.resize(1);
graph_width = graph_default_width;
graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true };
#ifdef GPU_SUPPORT
if (std::cmp_less(Gpu::shown, gpus.size())) {
gpu_temp_graphs.resize(gpus.size());
gpu_mem_graphs.resize(gpus.size());
gpu_meters.resize(gpus.size());
for (unsigned long i = 0; i < gpus.size(); ++i) {
if (gpus[i].supported_functions.temp_info)
gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 };
if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total)
gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol };
if (gpus[i].supported_functions.gpu_utilization) {
gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9);
gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" };
}
}
}
}
#endif
};
init_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field, false);
if (not single_graph)
init_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field, Config::getB("cpu_invert_lower"));
cpu_meter = Draw::Meter{b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11), "cpu"};
if (mid_line) {
out += Mv::to(y + graph_up_height + 1, x) + Fx::ub + Theme::c("cpu_box") + Symbols::div_left + Theme::c("div_line")
+ Symbols::h_line * (width - b_width - 2) + Symbols::div_right
+ Mv::to(y + graph_up_height + 1, x + ((width - b_width) / 2) - ((graph_up_field.size() + graph_lo_field.size()) / 2) - 4)
+ Theme::c("main_fg") + graph_up_field + Mv::r(1) + "▲▼" + Mv::r(1) + graph_lo_field;
}
if (b_column_size > 0 or extra_width > 0) {
core_graphs.clear();
for (const auto& core_data : cpu.core_percent) {
core_graphs.emplace_back(5 * b_column_size + extra_width, 1, "cpu", core_data, graph_symbol);
}
}
if (show_temps) {
temp_graphs.clear();
temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23);
if (not hide_cores and b_column_size > 1) {
for (const auto& i : iota((size_t)1, cpu.temp.size())) {
temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23);
}
}
}
}
//? Draw battery if enabled and present
if (Config::getB("show_battery") and has_battery) {
static int old_percent{}; // defaults to = 0
static long old_seconds{}; // defaults to = 0
static string old_status;
static Draw::Meter bat_meter {10, "cpu", true};
static const std::unordered_map<string, string> bat_symbols = {
{"charging", ""},
{"discharging", ""},
{"full", ""},
{"unknown", ""}
};
const auto& [percent, seconds, status] = current_bat;
if (redraw or percent != old_percent or seconds != old_seconds or status != old_status) {
old_percent = percent;
old_seconds = seconds;
old_status = status;
const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : "");
const string str_percent = to_string(percent) + '%';
const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown"));
const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + to_string(Config::getI("update_ms")).size();
const int current_pos = Term::width - current_len - 17;
if ((bat_pos != current_pos or bat_len != current_len) and bat_pos > 0 and not redraw)
out += Mv::to(y, bat_pos) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * (bat_len + 4);
bat_pos = current_pos;
bat_len = current_len;
out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent
+ (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "")
+ (not str_time.empty() ? ' ' + Theme::c("title") + str_time : " ") + Fx::ub + title_right;
}
}
else if (bat_pos > 0) {
out += Mv::to(y, bat_pos) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * (bat_len + 4);
bat_pos = bat_len = 0;
}
try {
//? Cpu/Gpu graphs
out += Fx::ub + Mv::to(y + 1, x + 1);
auto draw_graphs = [&](vector<Draw::Graph>& graphs, const int graph_height, const int graph_width, const string& graph_field) {
#ifdef GPU_SUPPORT
if (graph_field.starts_with("gpu"))
if (graph_field.find("totals") != string::npos)
for (unsigned long i = 0;;) {
out += graphs[i](safeVal(gpus[i].gpu_percent, graph_field), (data_same or redraw));
if (gpus.size() > 1) {
auto i_str = to_string(i);
out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str
+ Mv::d(graph_height/2) + Mv::r(graph_width - 1 - (graph_width > 5)*4 - i_str.size());
}
if (++i < graphs.size())
out += Theme::c("div_line") + (Symbols::v_line + Mv::l(1) + Mv::u(1))*graph_height + Mv::r(1) + Mv::d(1);
else break;
}
else
out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw));
else
#else
(void)graph_height;
(void)graph_width;
#endif
out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw));
};
draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field);
if (not single_graph) {
out += Mv::to(y + graph_up_height + 1 + mid_line, x + 1);
draw_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field);
}
//? Uptime
if (Config::getB("show_uptime")) {
string upstr = sec_to_dhms(system_uptime());
if (upstr.size() > 8) {
upstr.resize(upstr.size() - 3);
upstr = trans(upstr);
}
out += Mv::to(y + (single_graph or not Config::getB("cpu_invert_lower") ? 1 : height - 2), x + 2)
+ Theme::c("graph_text") + "up" + Mv::r(1) + upstr;
}
//? Cpu clock and cpu meter
if (Config::getB("show_cpu_freq") and not cpuHz.empty())
out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size())
+ Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right;
out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back())
+ Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%';
if (show_temps) {
const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale);
const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll));
if ((b_column_size > 1 or b_columns > 1) and temp_graphs.size() >= 1ll)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color
+ temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw);
out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
}
out += Theme::c("div_line") + Symbols::v_line;
} catch (const std::exception& e) { throw std::runtime_error("graphs, clock, meter : " + string{e.what()}); }
//? Core text and graphs
int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3);
if (Shared::coreCount >= 100) core_width++;
for (const auto& n : iota(0, Shared::coreCount)) {
if (cmp_less(core_graphs.size(), n+1)) break;
out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "")
+ ljust(to_string(n), core_width);
if (b_column_size > 0 or extra_width > 0)
out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width)
+ core_graphs.at(n)(safeVal(cpu.core_percent, n), data_same or redraw);
out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll));
out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%';
if (show_temps and not hide_cores and std::cmp_greater_equal(temp_graphs.size(), n)) {
const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale);
const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll));
if (b_column_size > 1)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5)
+ temp_graphs.at(n+1)(safeVal(cpu.temp, n+1), data_same or redraw);
out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
}
out += Theme::c("div_line") + Symbols::v_line;
if ((++cy > ceil((double)Shared::coreCount / b_columns) or cy == b_height - 2) and n != Shared::coreCount - 1) {
if (++cc >= b_columns) break;
cy = 1; cx = (b_width / b_columns) * cc;
}
}
//? Load average
if (cy < b_height - 1 and cc <= b_columns) {
string lavg_pre;
int sep = 1;
if (b_column_size == 2 and show_temps) { lavg_pre = "Load AVG:"; sep = 3; }
else if (b_column_size == 2 or (b_column_size == 1 and show_temps)) { lavg_pre = "LAV:"; }
else if (b_column_size == 1 or (b_column_size == 0 and show_temps)) { lavg_pre = "L"; }
string lavg;
for (const auto& val : cpu.load_avg) {
lavg += string(sep, ' ') + (lavg_pre.size() < 3 ? to_string((int)round(val)) : to_string(val).substr(0, 4));
}
string lavg_str = lavg_pre + lavg;
// if previous load average string is longer than current
// then right pad the current string with spaces until
// the current string is the same length as the previous
// otherwise set the lavg_str_len.
//
// When a shorter string gets written, the remaining characters
// from the previous string get left behind. This "erases" the
// leftover characters from the previous string.
if (lavg_str_len > lavg_str.length()) {
lavg_str += string(lavg_str_len - lavg_str.length(), ' ');
} else {
lavg_str_len = lavg_str.length();
}
#ifdef GPU_SUPPORT
cy = b_height - 2 - (show_gpu ? (gpus.size() - (gpu_always ? 0 : Gpu::shown)) : 0);
#else
cy = b_height - 2;
#endif
out += Mv::to(b_y + cy, b_x + cx + 1) + Theme::c("main_fg") + lavg_str;
}
#ifdef GPU_SUPPORT
//? Gpu brief info
if (show_gpu) {
for (unsigned long i = 0; i < gpus.size(); ++i) {
if (gpu_always or not v_contains(Gpu::shown_panels, i)) {
out += Mv::to(b_y + ++cy, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU";
if (show_temps and gpus[i].supported_functions.temp_info and b_width < 34) {
const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale);
if (temp < 100) out += " ";
}
if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9));
if (gpus[i].supported_functions.gpu_utilization) {
string meter = gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back());
out += (meter.size() > 1 ? " " : "") + meter
+ Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%';
} else out += Mv::r(gpu_meter_width);
if (gpus[i].supported_functions.mem_used) {
out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back())
+ gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw) + Theme::c("main_fg")
+ rjust(floating_humanizer(gpus[i].mem_used, true), 5);
if (gpus[i].supported_functions.mem_total)
out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true);
else out += Mv::r(5);
} else out += Mv::r(17);
if (show_temps and gpus[i].supported_functions.temp_info) {
const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale);
if (b_width > 38)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll))
+ gpu_temp_graphs[i](gpus[i].temp, data_same or redraw);
else out += Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll));
out += rjust(to_string(temp), 3 + (b_width >= 34 or temp > 99)) + Theme::c("main_fg") + unit;
}
}
if (cy < b_height - 1) break;
}
}
#endif
redraw = false;
return out + Fx::reset;
}
}
#ifdef GPU_SUPPORT
namespace Gpu {
int width_p = 100, height_p = 32;
int min_width = 41, min_height = 11;
int width = 41, height;
vector<int> x_vec = {}, y_vec = {}, b_height_vec = {};
int b_width;
vector<int> b_x_vec = {}, b_y_vec = {};
vector<bool> redraw = {};
int shown = 0;
vector<char> shown_panels = {};
int graph_up_height;
vector<Draw::Graph> graph_upper_vec = {}, graph_lower_vec = {};
vector<Draw::Graph> temp_graph_vec = {};
vector<Draw::Graph> mem_used_graph_vec = {}, mem_util_graph_vec = {};
vector<Draw::Meter> gpu_meter_vec = {};
vector<Draw::Meter> pwr_meter_vec = {};
vector<string> box = {};
string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same) {
if (Runner::stopping) return "";
auto& b_x = b_x_vec[index];
auto& b_y = b_y_vec[index];
auto& x = x_vec[index];
auto& y = y_vec[index];
auto& graph_upper = graph_upper_vec[index];
auto& graph_lower = graph_lower_vec[index];
auto& temp_graph = temp_graph_vec[index];
auto& mem_used_graph = mem_used_graph_vec[index];
auto& mem_util_graph = mem_util_graph_vec[index];
auto& gpu_meter = gpu_meter_vec[index];
auto& pwr_meter = pwr_meter_vec[index];
if (force_redraw) redraw[index] = true;
bool show_temps = gpu.supported_functions.temp_info and (Config::getB("check_temp"));
auto tty_mode = Config::getB("tty_mode");
auto& temp_scale = Config::getS("temp_scale");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_gpu"));
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6);
auto single_graph = !Config::getB("gpu_mirror_graph");
string out;
out.reserve(width * height);
//* Redraw elements not needed to be updated every cycle
if (redraw[index]) {
graph_up_height = single_graph ? height - 2 : ceil((double)(height - 2) / 2);
const int graph_low_height = height - 2 - graph_up_height;
out += box[index];
if (gpu.supported_functions.gpu_utilization) {
graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu
if (not single_graph) {
graph_lower = Draw::Graph{
x + width - b_width - 3,
graph_low_height, "cpu",
safeVal(gpu.gpu_percent, "gpu-totals"s),
graph_symbol,
Config::getB("cpu_invert_lower"), true
};
}
gpu_meter = Draw::Meter{b_width - (show_temps ? 24 : 11), "cpu"};
}
if (gpu.supported_functions.temp_info)
temp_graph = Draw::Graph{6, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23};
if (gpu.supported_functions.pwr_usage)
pwr_meter = Draw::Meter{b_width - 24, "cached"};
if (gpu.supported_functions.mem_utilization)
mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization
if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total)
mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol};
}
//* General GPU info
//? Gpu graph, meter & clock speed
if (gpu.supported_functions.gpu_utilization) {
out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index]));
if (not single_graph)
out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index]));
out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back())
+ Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%';
//? Temperature graph, I assume the device supports utilization if it supports temperature
if (show_temps) {
const auto [temp, unit] = celsius_to(gpu.temp.back(), temp_scale);
out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpu.temp.back() * 100 / gpu.temp_max, 0ll, 100ll))
+ temp_graph(gpu.temp, data_same or redraw[index]);
out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
}
out += Theme::c("div_line") + Symbols::v_line;
}
if (gpu.supported_functions.gpu_clock) {
string clock_speed_string = to_string(gpu.gpu_clock_speed);
out += Mv::to(b_y, b_x + b_width - 12) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size())
+ Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right;
}
//? Power usage meter, power state
if (gpu.supported_functions.pwr_usage) {
out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back())
+ Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W';
if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card
out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state);
}
if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) {
out += Mv::to(b_y + 3, b_x);
if (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) {
string used_memory_string = floating_humanizer(gpu.mem_used);
auto offset = (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used)
* (1 + 2*(gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) + 2*gpu.supported_functions.mem_utilization);
//? Used graph, memory section header, total vram
out += Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Fx::b + Theme::c("title") + "vram" + Theme::c("div_line") + Fx::ub + Symbols::title_right
+ Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up
+ Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line")
+ Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right
+ Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index]))
+ Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub
+ Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%';
//? Memory utilization
if (gpu.supported_functions.mem_utilization)
out += Mv::l(b_width/2+6) + Mv::d(1) + Theme::c("div_line") + Symbols::div_left+Symbols::h_line + Theme::c("title") + "Utilization:" + Theme::c("div_line") + Symbols::h_line*(b_width/2-14) + Symbols::div_right
+ Mv::l(b_width/2) + Mv::d(1) + mem_util_graph(gpu.mem_utilization_percent, (data_same or redraw[index]))
+ Mv::l(b_width/2-1) + Mv::u(1) + rjust(to_string(gpu.mem_utilization_percent.back()), 3) + '%';
//? Memory clock speed
if (gpu.supported_functions.mem_clock) {
string clock_speed_string = to_string(gpu.mem_clock_speed);
out += Mv::to(b_y + 3, b_x + b_width/2 - 11) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size())
+ Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right;
}
} else {
out += Theme::c("main_fg") + Mv::r(1);
if (gpu.supported_functions.mem_total)
out += "VRAM total:" + rjust(floating_humanizer(gpu.mem_total), b_width/(1 + gpu.supported_functions.mem_clock)-14);
else out += "VRAM usage:" + rjust(floating_humanizer(gpu.mem_used), b_width/(1 + gpu.supported_functions.mem_clock)-14);
if (gpu.supported_functions.mem_clock)
out += " VRAM clock:" + rjust(to_string(gpu.mem_clock_speed) + " Mhz", b_width/2-13);
}
}
//? Processes section header
//out += Mv::to(b_y+8, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Theme::c("main_fg") + Fx::b + "gpu-proc" + Fx::ub + Theme::c("div_line")
// + Symbols::title_right + Symbols::h_line*(b_width/2-12) + Symbols::div_down + Symbols::h_line*(b_width/2-2) + Symbols::div_right;
//? PCIe link throughput
if (gpu.supported_functions.pcie_txrx and Config::getB("nvml_measure_pcie_speeds")) {
string tx_string = floating_humanizer(gpu.pcie_tx, 0, 1, 0, 1);
string rx_string = floating_humanizer(gpu.pcie_rx, 0, 1, 0, 1);
out += Mv::to(b_y + b_height_vec[index] - 1, b_x+2) + Theme::c("div_line")
+ Symbols::title_left_down + Theme::c("title") + Fx::b + "TX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2-9-tx_string.size())
+ Symbols::title_left_down + Theme::c("title") + Fx::b + tx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used ? Symbols::div_down : Symbols::h_line)
+ Symbols::title_left_down + Theme::c("title") + Fx::b + "RX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2+b_width%2-9-rx_string.size())
+ Symbols::title_left_down + Theme::c("title") + Fx::b + rx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::round_right_down;
}
redraw[index] = false;
return out + Fx::reset;
}
}
#endif
namespace Mem {
int width_p = 45, height_p = 36;
int min_width = 36, min_height = 10;
int x = 1, y, width = 20, height;
int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter;
int disks_io_h = 0;
int disks_io_half = 0;
bool shown = true, redraw = true;
string box;
std::unordered_map<string, Draw::Meter> mem_meters;
std::unordered_map<string, Draw::Graph> mem_graphs;
std::unordered_map<string, Draw::Meter> disk_meters_used;
std::unordered_map<string, Draw::Meter> disk_meters_free;
std::unordered_map<string, Draw::Graph> io_graphs;
string draw(const mem_info& mem, bool force_redraw, bool data_same) {
if (Runner::stopping) return "";
if (force_redraw) redraw = true;
auto show_swap = Config::getB("show_swap");
auto swap_disk = Config::getB("swap_disk");
auto show_disks = Config::getB("show_disks");
auto show_io_stat = Config::getB("show_io_stat");
auto io_mode = Config::getB("io_mode");
auto io_graph_combined = Config::getB("io_graph_combined");
auto use_graphs = Config::getB("mem_graphs");
auto tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_mem"));
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6);
auto totalMem = Mem::get_totalMem();
string out;
out.reserve(height * width);
//* Redraw elements not needed to be updated every cycle
if (redraw) {
out += box;
mem_meters.clear();
mem_graphs.clear();
disk_meters_free.clear();
disk_meters_used.clear();
io_graphs.clear();
//? Mem graphs and meters
for (const auto& name : mem_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol};
else
mem_meters[name] = Draw::Meter{mem_meter, name};
}
if (show_swap and has_swap) {
for (const auto& name : swap_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol};
else
mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)};
}
}
//? Disk meters and io graphs
if (show_disks) {
if (show_io_stat or io_mode) {
std::unordered_map<string, int> custom_speeds;
int half_height = 0;
if (io_mode) {
disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2));
half_height = ceil((double)disks_io_h / 2);
if (not Config::getS("io_graph_speeds").empty()) {
auto split = ssplit(Config::getS("io_graph_speeds"));
for (const auto& entry : split) {
auto vals = ssplit(entry);
if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1)))
try {
custom_speeds[vals.at(0)] = std::stoi(vals.at(1));
}
catch (const std::out_of_range&) { continue; }
}
}
}
for (const auto& [name, disk] : mem.disks) {
if (disk.io_read.empty()) continue;
io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "available", disk.io_activity, graph_symbol};
if (io_mode) {
//? Create one combined graph for IO read/write if enabled
long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 100) << 20;
if (io_graph_combined) {
deque<long long> combined(disk.io_read.size(), 0);
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
io_graphs[name] = Draw::Graph{
disks_width - (io_mode ? 0 : 6),
disks_io_h, "available", combined,
graph_symbol, false, true, speed};
}
else {
io_graphs[name + "_read"] = Draw::Graph{
disks_width, half_height, "free",
disk.io_read, graph_symbol, false,
true, speed};
io_graphs[name + "_write"] = Draw::Graph{
disks_width, disks_io_h - half_height,
"used", disk.io_write, graph_symbol,
true, true, speed};
}
}
}
}
for (int i = 0; const auto& [name, ignored] : mem.disks) {
if (i * 2 > height - 2) break;
disk_meters_used[name] = Draw::Meter{disk_meter, "used"};
if (cmp_less_equal(mem.disks.size() * 3, height - 1))
disk_meters_free[name] = Draw::Meter{disk_meter, "free"};
}
out += Mv::to(y, x + width - 6) + Fx::ub + Theme::c("mem_box") + Symbols::title_left + (io_mode ? Fx::b : "") + Theme::c("hi_fg")
+ 'i' + Theme::c("title") + 'o' + Fx::ub + Theme::c("mem_box") + Symbols::title_right;
Input::mouse_mappings["i"] = {y, x + width - 5, 1, 2};
}
}
//? Mem and swap
int cx = 1, cy = 1;
string divider = (graph_height > 0 ? Mv::l(2) + Theme::c("mem_box") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * (mem_width - 1)
+ (show_disks ? "" : Theme::c("mem_box")) + Symbols::div_right + Mv::l(mem_width - 1) + Theme::c("main_fg") : "");
string up = (graph_height >= 2 ? Mv::l(mem_width - 2) + Mv::u(graph_height - 1) : "");
bool big_mem = mem_width > 21;
out += Mv::to(y + 1, x + 2) + Theme::c("title") + Fx::b + "Total:" + rjust(floating_humanizer(totalMem), mem_width - 9) + Fx::ub + Theme::c("main_fg");
vector<string> comb_names (mem_names.begin(), mem_names.end());
if (show_swap and has_swap and not swap_disk) comb_names.insert(comb_names.end(), swap_names.begin(), swap_names.end());
for (auto name : comb_names) {
if (cy > height - 4) break;
string title;
if (name == "swap_used") {
if (cy > height - 5) break;
if (height - cy > 6) {
if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider;
cy += 1;
}
out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8)
+ Theme::c("main_fg") + Fx::ub;
cy += 1;
title = "Used";
}
else if (name == "swap_free")
title = "Free";
if (title.empty()) title = capitalize(name);
const string humanized = floating_humanizer(safeVal(mem.stats, name));
const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0);
const string graphics = (
use_graphs and mem_graphs.contains(name) ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same)
: mem_meters.contains(name) ? mem_meters.at(name)(safeVal(mem.percent, name).back())
: "");
if (mem_size > 2) {
out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":"
+ Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized))
+ Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4);
cy += (graph_height == 0 ? 2 : graph_height + 1);
}
else {
out += Mv::to(y+1+cy, x+1+cx) + ljust(title, (mem_size > 1 ? 5 : 1)) + (graph_height >= 2 ? "" : " ")
+ graphics + Theme::c("title") + rjust(humanized, (mem_size > 1 ? 9 : 7));
cy += (graph_height == 0 ? 1 : graph_height);
}
}
if (graph_height > 0 and cy < height - 2)
out += Mv::to(y+1+cy, x+1+cx) + divider;
//? Disks
if (show_disks) {
const auto& disks = mem.disks;
cx = mem_width; cy = 0;
bool big_disk = disks_width >= 25;
divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Fx::ub + Symbols::div_right + Mv::l(disks_width);
const string hu_div = Theme::c("div_line") + Symbols::h_line + Theme::c("main_fg");
if (io_mode) {
for (const auto& mount : mem.disks_order) {
if (not disks.contains(mount)) continue;
if (cy > height - 3) break;
const auto& disk = safeVal(disks, mount);
if (disk.io_read.empty()) continue;
const string total = floating_humanizer(disk.total, not big_disk);
out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size())
+ trans(total) + Fx::ub;
if (big_disk) {
const string used_percent = to_string(disk.used_percent);
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div;
}
if (io_graphs.contains(mount + "_activity")) {
out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6)
+ Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg");
}
if (++cy > height - 3) break;
if (not io_graphs.contains(mount)) continue;
if (io_graph_combined) {
auto comb_val = disk.io_read.back() + disk.io_write.back();
const string humanized = (disk.io_write.back() > 0 ? ""s : ""s) + (disk.io_read.back() > 0 ? ""s : ""s)
+ (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW");
if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ');
out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same)
+ Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized;
cy += disks_io_h;
}
else {
const string human_read = (disk.io_read.back() > 0 ? "" + floating_humanizer(disk.io_read.back(), true) : "R");
const string human_write = (disk.io_write.back() > 0 ? "" + floating_humanizer(disk.io_write.back(), true) : "W");
if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' ');
out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width)
+ Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same)
+ Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write;
cy += disks_io_h;
}
}
}
else {
for (const auto& mount : mem.disks_order) {
if (not disks.contains(mount)) continue;
if (cy > height - 3) break;
const auto& disk = safeVal(disks, mount);
if (disk.name.empty() or not disk_meters_used.contains(mount)) continue;
auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll);
const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? ""s : ""s) + (disk.io_read.back() > 0 and big_disk ? ""s : ""s)
+ floating_humanizer(comb_val, true) : "");
const string human_total = floating_humanizer(disk.total, not big_disk);
const string human_used = floating_humanizer(disk.used, not big_disk);
const string human_free = floating_humanizer(disk.free, not big_disk);
out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - human_total.size())
+ trans(human_total) + Fx::ub + Theme::c("main_fg");
if (big_disk and not human_io.empty())
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)human_io.size() / 2) - 1) + hu_div + human_io + hu_div;
if (++cy > height - 3) break;
if (show_io_stat and io_graphs.contains(mount + "_activity")) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll))
+ Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg");
if (not big_disk) out += Mv::to(y+1+cy, x+cx+1) + Theme::c("main_fg") + human_io;
if (++cy > height - 3) break;
}
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' '
+ disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5));
if (++cy > height - 3) break;
if (disk_meters_free.contains(mount) and cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' '
+ disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5));
cy++;
if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++;
}
}
}
if (cy < height - 2) out += Mv::to(y+1+cy, x+1+cx) + divider;
}
redraw = false;
return out + Fx::reset;
}
}
namespace Net {
int width_p = 45, height_p = 32;
int min_width = 36, min_height = 6;
int x = 1, y, width = 20, height;
int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height;
bool shown = true, redraw = true;
string old_ip;
std::unordered_map<string, Draw::Graph> graphs;
string box;
string draw(const net_info& net, bool force_redraw, bool data_same) {
if (Runner::stopping) return "";
if (force_redraw) redraw = true;
auto net_sync = Config::getB("net_sync");
auto net_auto = Config::getB("net_auto");
auto tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_net"));
string ip_addr = (net.ipv4.empty() ? net.ipv6 : net.ipv4);
if (old_ip != ip_addr) {
old_ip = ip_addr;
redraw = true;
}
string out;
out.reserve(width * height);
const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left;
const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right;
const int i_size = min((int)selected_iface.size(), 10);
const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8);
const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8);
//* Redraw elements not needed to be updated every cycle
if (redraw) {
out = box;
//? Graphs
graphs.clear();
if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty())
return out + Fx::reset;
graphs["download"] = Draw::Graph{
width - b_width - 2, u_graph_height, "download",
net.bandwidth.at("download"), graph_symbol,
false, true, down_max};
graphs["upload"] = Draw::Graph{
width - b_width - 2, d_graph_height, "upload",
net.bandwidth.at("upload"), graph_symbol, true, true, up_max};
//? Interface selector and buttons
out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "<b " + Theme::c("title")
+ uresize(selected_iface, 10) + Theme::c("hi_fg") + " n>" + title_right
+ Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z'
+ Theme::c("title") + "ero" + title_right;
Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3};
Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3};
Input::mouse_mappings["z"] = {y, x+width - i_size - 14, 1, 4};
if (width - i_size - 20 > 6) {
out += Mv::to(y, x+width - i_size - 21) + title_left + Theme::c("hi_fg") + (net_auto ? Fx::b : "") + 'a' + Theme::c("title") + "uto" + title_right;
Input::mouse_mappings["a"] = {y, x+width - i_size - 20, 1, 4};
}
if (width - i_size - 20 > 13) {
out += Mv::to(y, x+width - i_size - 27) + title_left + Theme::c("title") + (net_sync ? Fx::b : "") + 's' + Theme::c("hi_fg")
+ 'y' + Theme::c("title") + "nc" + title_right;
Input::mouse_mappings["y"] = {y, x+width - i_size - 26, 1, 4};
}
}
//? IP or device address
if (not ip_addr.empty() and cmp_greater(width - i_size - 36, ip_addr.size())) {
out += Mv::to(y, x + 8) + title_left + Theme::c("title") + Fx::b + ip_addr + title_right;
}
//? Graphs and stats
int cy = 0;
for (const string dir : {"download", "upload"}) {
out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected)
+ Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text")
+ floating_humanizer((dir == "upload" ? up_max : down_max), true);
const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true);
const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : "");
const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true);
const string total = floating_humanizer(safeVal(net.stat, dir).total);
const string symbol = (dir == "upload" ? "" : "");
out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : "");
cy += (b_height == 5 ? 2 : 1);
if (b_height >= 8) {
out += Mv::to(b_y+1+cy, b_x+1) + symbol + ' ' + "Top: " + rjust('(' + top, (b_width >= 20 ? 17 : 9)) + ')';
cy++;
}
if (b_height >= 6) {
out += Mv::to(b_y+1+cy, b_x+1) + symbol + ' ' + "Total: " + rjust(total, (b_width >= 20 ? 16 : 8));
cy += (b_height > 6 and b_height % 2 ? 2 : 1);
}
}
redraw = false;
return out + Fx::reset;
}
}
namespace Proc {
int width_p = 55, height_p = 68;
int min_width = 44, min_height = 16;
int x, y, width = 20, height;
int start, selected, select_max;
bool shown = true, redraw = true;
int selected_pid = 0, selected_depth = 0;
string selected_name;
std::unordered_map<size_t, Draw::Graph> p_graphs;
std::unordered_map<size_t, bool> p_wide_cmd;
std::unordered_map<size_t, int> p_counters;
int counter = 0;
Draw::TextEdit filter;
Draw::Graph detailed_cpu_graph;
Draw::Graph detailed_mem_graph;
int user_size, thread_size, prog_size, cmd_size, tree_size;
int dgraph_x, dgraph_width, d_width, d_x, d_y;
string box;
int selection(const string& cmd_key) {
auto start = Config::getI("proc_start");
auto selected = Config::getI("proc_selected");
auto last_selected = Config::getI("proc_last_selected");
const int select_max = (Config::getB("show_detailed") ? Proc::select_max - 8 : Proc::select_max);
auto vim_keys = Config::getB("vim_keys");
int numpids = Proc::numpids;
if ((cmd_key == "up" or (vim_keys and cmd_key == "k")) and selected > 0) {
if (start > 0 and selected == 1) start--;
else selected--;
if (Config::getI("proc_last_selected") > 0) Config::set("proc_last_selected", 0);
}
else if (cmd_key == "mouse_scroll_up" and start > 0) {
start = max(0, start - 3);
}
else if (cmd_key == "mouse_scroll_down" and start < numpids - select_max) {
start = min(numpids - select_max, start + 3);
}
else if (cmd_key == "down" or (vim_keys and cmd_key == "j")) {
if (start < numpids - select_max and selected == select_max) start++;
else if (selected == 0 and last_selected > 0) {
selected = last_selected;
Config::set("proc_last_selected", 0);
}
else selected++;
}
else if (cmd_key == "page_up") {
if (selected > 0 and start == 0) selected = 0;
else start = max(0, start - select_max);
}
else if (cmd_key == "page_down") {
if (selected > 0 and start >= numpids - select_max) selected = select_max;
else start = clamp(start + select_max, 0, max(0, numpids - select_max));
}
else if (cmd_key == "home" or (vim_keys and cmd_key == "g")) {
start = 0;
if (selected > 0) selected = 1;
}
else if (cmd_key == "end" or (vim_keys and cmd_key == "G")) {
start = max(0, numpids - select_max);
if (selected > 0) selected = select_max;
}
else if (cmd_key.starts_with("mousey")) {
int mouse_y = std::stoi(cmd_key.substr(6));
start = clamp((int)round((double)mouse_y * (numpids - select_max - 2) / (select_max - 2)), 0, max(0, numpids - select_max));
}
bool changed = false;
if (start != Config::getI("proc_start")) {
Config::set("proc_start", start);
changed = true;
}
if (selected != Config::getI("proc_selected")) {
Config::set("proc_selected", selected);
changed = true;
}
return (not changed ? -1 : selected);
}
string draw(const vector<proc_info>& plist, bool force_redraw, bool data_same) {
if (Runner::stopping) return "";
auto proc_tree = Config::getB("proc_tree");
bool show_detailed = (Config::getB("show_detailed") and cmp_equal(Proc::detailed.last_pid, Config::getI("detailed_pid")));
bool proc_gradient = (Config::getB("proc_gradient") and not Config::getB("lowcolor") and Theme::gradients.contains("proc"));
auto proc_colors = Config::getB("proc_colors");
auto tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc"));
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6);
auto mem_bytes = Config::getB("proc_mem_bytes");
auto vim_keys = Config::getB("vim_keys");
auto show_graphs = Config::getB("proc_cpu_graphs");
start = Config::getI("proc_start");
selected = Config::getI("proc_selected");
const int y = show_detailed ? Proc::y + 8 : Proc::y;
const int height = show_detailed ? Proc::height - 8 : Proc::height;
const int select_max = show_detailed ? Proc::select_max - 8 : Proc::select_max;
auto totalMem = Mem::get_totalMem();
int numpids = Proc::numpids;
if (force_redraw) redraw = true;
string out;
out.reserve(width * height);
//* Redraw elements not needed to be updated every cycle
if (redraw) {
out = box;
const string title_left = Theme::c("proc_box") + Symbols::title_left;
const string title_right = Theme::c("proc_box") + Symbols::title_right;
const string title_left_down = Theme::c("proc_box") + Symbols::title_left_down;
const string title_right_down = Theme::c("proc_box") + Symbols::title_right_down;
for (const auto& key : {"T", "K", "S", "enter"})
if (Input::mouse_mappings.contains(key)) Input::mouse_mappings.erase(key);
//? Adapt sizes of text fields
user_size = (width < 75 ? 5 : 10);
thread_size = (width < 75 ? - 1 : 4);
prog_size = (width > 70 ? 16 : ( width > 55 ? 8 : width - user_size - thread_size - 33));
cmd_size = (width > 55 ? width - prog_size - user_size - thread_size - 33 : -1);
tree_size = width - user_size - thread_size - 23;
if (not show_graphs) {
cmd_size += 5;
tree_size += 5;
}
//? Detailed box
if (show_detailed) {
bool alive = detailed.status != "Dead";
dgraph_x = x;
dgraph_width = max(width / 3, width - 121);
d_width = width - dgraph_width - 1;
d_x = x + dgraph_width + 1;
d_y = Proc::y;
//? Create cpu and mem graphs if process is alive
if (alive) {
detailed_cpu_graph = Draw::Graph{dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol, false, true};
detailed_mem_graph = Draw::Graph{d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem};
}
//? Draw structure of details box
const string pid_str = to_string(detailed.entry.pid);
out += Mv::to(y, x) + Theme::c("proc_box") + Symbols::div_left + Symbols::h_line + title_left + Theme::c("hi_fg") + Fx::b
+ (tty_mode ? "4" : Symbols::superscript.at(4)) + Theme::c("title") + "proc"
+ Fx::ub + title_right + Symbols::h_line * (width - 10) + Symbols::div_right
+ Mv::to(d_y, dgraph_x + 2) + title_left + Fx::b + Theme::c("title") + pid_str + Fx::ub + title_right
+ title_left + Fx::b + Theme::c("title") + uresize(detailed.entry.name, dgraph_width - pid_str.size() - 7, true) + Fx::ub + title_right;
out += Mv::to(d_y, d_x - 1) + Theme::c("proc_box") + Symbols::div_up + Mv::to(y, d_x - 1) + Symbols::div_down + Theme::c("div_line");
for (const int& i : iota(1, 8)) out += Mv::to(d_y + i, d_x - 1) + Symbols::v_line;
const string& t_color = (not alive or selected > 0 ? Theme::c("inactive_fg") : Theme::c("title"));
const string& hi_color = (not alive or selected > 0 ? t_color : Theme::c("hi_fg"));
const string hide = (selected > 0 ? t_color + "hide " : Theme::c("title") + "hide " + Theme::c("hi_fg"));
int mouse_x = d_x + 2;
out += Mv::to(d_y, d_x + 1);
if (width > 55) {
out += Fx::ub + title_left + hi_color + Fx::b + 't' + t_color + "erminate" + Fx::ub + title_right;
if (alive and selected == 0) Input::mouse_mappings["t"] = {d_y, mouse_x, 1, 9};
mouse_x += 11;
}
out += title_left + hi_color + Fx::b + (vim_keys ? 'K' : 'k') + t_color + "ill" + Fx::ub + title_right
+ title_left + hi_color + Fx::b + 's' + t_color + "ignals" + Fx::ub + title_right
+ Mv::to(d_y, d_x + d_width - 10) + title_left + t_color + Fx::b + hide + Symbols::enter + Fx::ub + title_right;
if (alive and selected == 0) {
Input::mouse_mappings["k"] = {d_y, mouse_x, 1, 4};
mouse_x += 6;
Input::mouse_mappings["s"] = {d_y, mouse_x, 1, 7};
}
if (selected == 0) Input::mouse_mappings["enter"] = {d_y, d_x + d_width - 9, 1, 6};
//? Labels
const int item_fit = floor((double)(d_width - 2) / 10);
const int item_width = floor((double)(d_width - 2) / min(item_fit, 8));
out += Mv::to(d_y + 1, d_x + 1) + Fx::b + Theme::c("title")
+ cjust("Status:", item_width)
+ cjust("Elapsed:", item_width);
if (item_fit >= 3) out += cjust("IO/R:", item_width);
if (item_fit >= 4) out += cjust("IO/W:", item_width);
if (item_fit >= 5) out += cjust("Parent:", item_width);
if (item_fit >= 6) out += cjust("User:", item_width);
if (item_fit >= 7) out += cjust("Threads:", item_width);
if (item_fit >= 8) out += cjust("Nice:", item_width);
//? Command line
for (int i = 0; const auto& l : {'C', 'M', 'D'})
out += Mv::to(d_y + 5 + i++, d_x + 1) + l;
out += Theme::c("main_fg") + Fx::ub;
const int cmd_size = ulen(detailed.entry.cmd, true);
for (int num_lines = min(3, (int)ceil((double)cmd_size / (d_width - 5))), i = 0; i < num_lines; i++) {
out += Mv::to(d_y + 5 + (num_lines == 1 ? 1 : i), d_x + 3)
+ cjust(luresize(detailed.entry.cmd, cmd_size - (d_width - 5) * i, true), d_width - 5, true, true);
}
}
//? Filter
auto filtering = Config::getB("proc_filtering"); // ? filter(20) : Config::getS("proc_filter"))
const auto filter_text = (filtering) ? filter(max(6, width - 58)) : uresize(Config::getS("proc_filter"), max(6, width - 58));
out += Mv::to(y, x+9) + title_left + (not filter_text.empty() ? Fx::b : "") + Theme::c("hi_fg") + 'f'
+ Theme::c("title") + (not filter_text.empty() ? ' ' + filter_text : "ilter")
+ (not filtering and not filter_text.empty() ? Theme::c("hi_fg") + " del" : "")
+ (filtering ? Theme::c("hi_fg") + ' ' + Symbols::enter : "") + Fx::ub + title_right;
if (not filtering) {
int f_len = (filter_text.empty() ? 6 : ulen(filter_text) + 2);
Input::mouse_mappings["f"] = {y, x + 10, 1, f_len};
if (filter_text.empty() and Input::mouse_mappings.contains("delete"))
Input::mouse_mappings.erase("delete");
else if (not filter_text.empty())
Input::mouse_mappings["delete"] = {y, x + 11 + f_len, 1, 3};
}
//? per-core, reverse, tree and sorting
const auto& sorting = Config::getS("proc_sorting");
const int sort_len = sorting.size();
const int sort_pos = x + width - sort_len - 8;
if (width > 55 + sort_len) {
out += Mv::to(y, sort_pos - 25) + title_left + (Config::getB("proc_per_core") ? Fx::b : "") + Theme::c("title")
+ "per-" + Theme::c("hi_fg") + 'c' + Theme::c("title") + "ore" + Fx::ub + title_right;
Input::mouse_mappings["c"] = {y, sort_pos - 24, 1, 8};
}
if (width > 45 + sort_len) {
out += Mv::to(y, sort_pos - 15) + title_left + (Config::getB("proc_reversed") ? Fx::b : "") + Theme::c("hi_fg")
+ 'r' + Theme::c("title") + "everse" + Fx::ub + title_right;
Input::mouse_mappings["r"] = {y, sort_pos - 14, 1, 7};
}
if (width > 35 + sort_len) {
out += Mv::to(y, sort_pos - 6) + title_left + (Config::getB("proc_tree") ? Fx::b : "") + Theme::c("title") + "tre"
+ Theme::c("hi_fg") + 'e' + Fx::ub + title_right;
Input::mouse_mappings["e"] = {y, sort_pos - 5, 1, 4};
}
out += Mv::to(y, sort_pos) + title_left + Fx::b + Theme::c("hi_fg") + "< " + Theme::c("title") + sorting + Theme::c("hi_fg")
+ " >" + Fx::ub + title_right;
Input::mouse_mappings["left"] = {y, sort_pos + 1, 1, 2};
Input::mouse_mappings["right"] = {y, sort_pos + sort_len + 3, 1, 2};
//? select, info and signal buttons
const string down_button = (selected == select_max and start == numpids - select_max ? Theme::c("inactive_fg") : Theme::c("hi_fg")) + Symbols::down;
const string t_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("title"));
const string hi_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("hi_fg"));
int mouse_x = x + 14;
out += Mv::to(y + height - 1, x + 1) + title_left_down + Fx::b + hi_color + Symbols::up + Theme::c("title") + " select " + down_button + Fx::ub + title_right_down
+ title_left_down + Fx::b + t_color + "info " + hi_color + Symbols::enter + Fx::ub + title_right_down;
if (selected > 0) Input::mouse_mappings["enter"] = {y + height - 1, mouse_x, 1, 6};
mouse_x += 8;
if (width > 60) {
out += title_left_down + Fx::b + hi_color + 't' + t_color + "erminate" + Fx::ub + title_right_down;
if (selected > 0) Input::mouse_mappings["t"] = {y + height - 1, mouse_x, 1, 9};
mouse_x += 11;
}
if (width > 55) {
out += title_left_down + Fx::b + hi_color + (vim_keys ? 'K' : 'k') + t_color + "ill" + Fx::ub + title_right_down;
if (selected > 0) Input::mouse_mappings["k"] = {y + height - 1, mouse_x, 1, 4};
mouse_x += 6;
}
out += title_left_down + Fx::b + hi_color + 's' + t_color + "ignals" + Fx::ub + title_right_down;
if (selected > 0) Input::mouse_mappings["s"] = {y + height - 1, mouse_x, 1, 7};
//? Labels for fields in list
if (not proc_tree)
out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b
+ rjust("Pid:", 8) + ' '
+ ljust("Program:", prog_size) + ' '
+ (cmd_size > 0 ? ljust("Command:", cmd_size) : "") + ' ';
else
out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b
+ ljust("Tree:", tree_size) + ' ';
out += (thread_size > 0 ? Mv::l(4) + "Threads: " : "")
+ ljust("User:", user_size) + ' '
+ rjust((mem_bytes ? "MemB" : "Mem%"), 5) + ' '
+ rjust("Cpu%", (show_graphs ? 10 : 5)) + Fx::ub;
}
//* End of redraw block
//? Draw details box if shown
if (show_detailed) {
bool alive = detailed.status != "Dead";
const int item_fit = floor((double)(d_width - 2) / 10);
const int item_width = floor((double)(d_width - 2) / min(item_fit, 8));
//? Graph part of box
string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : "");
if (alive) {
cpu_str.resize((detailed.entry.cpu_p < 10 or detailed.entry.cpu_p >= 100 ? 3 : 4));
cpu_str += '%';
}
out += Mv::to(d_y + 1, dgraph_x + 1) + Fx::ub + detailed_cpu_graph(detailed.cpu_percent, (redraw or data_same or not alive))
+ Mv::to(d_y + 1, dgraph_x + 1) + Theme::c("title") + Fx::b + cpu_str;
for (int i = 0; const auto& l : {'C', 'P', 'U'})
out += Mv::to(d_y + 3 + i++, dgraph_x + 1) + l;
//? Info part of box
const string stat_color = (not alive ? Theme::c("inactive_fg") : (detailed.status == "Running" ? Theme::c("proc_misc") : Theme::c("main_fg")));
out += Mv::to(d_y + 2, d_x + 1) + stat_color + Fx::ub
+ cjust(detailed.status, item_width) + Theme::c("main_fg")
+ cjust(detailed.elapsed, item_width);
if (item_fit >= 3) out += cjust(detailed.io_read, item_width);
if (item_fit >= 4) out += cjust(detailed.io_write, item_width);
if (item_fit >= 5) out += cjust(detailed.parent, item_width, true);
if (item_fit >= 6) out += cjust(detailed.entry.user, item_width, true);
if (item_fit >= 7) out += cjust(to_string(detailed.entry.threads), item_width);
if (item_fit >= 8) out += cjust(to_string(detailed.entry.p_nice), item_width);
const double mem_p = (double)detailed.mem_bytes.back() * 100 / totalMem;
string mem_str = to_string(mem_p);
mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4));
out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + mem_str + "% ", (d_width / 3) - 2)
+ Theme::c("inactive_fg") + Fx::ub + graph_bg * (d_width / 3) + Mv::l(d_width / 3)
+ Theme::c("proc_misc") + detailed_mem_graph(detailed.mem_bytes, (redraw or data_same or not alive)) + ' '
+ Theme::c("title") + Fx::b + detailed.memory;
}
//? Check bounds of current selection and view
if (start > 0 and numpids <= select_max)
start = 0;
if (start > numpids - select_max)
start = max(0, numpids - select_max);
if (selected > select_max)
selected = select_max;
if (selected > numpids)
selected = numpids;
//* Iteration over processes
int lc = 0;
for (int n=0; auto& p : plist) {
if (p.filtered or (proc_tree and p.tree_index == plist.size()) or n++ < start) continue;
bool is_selected = (lc + 1 == selected);
if (is_selected) {
selected_pid = (int)p.pid;
selected_name = p.name;
selected_depth = p.depth;
}
//? Update graphs for processes with above 0.0% cpu usage, delete if below 0.1% 10x times
bool has_graph = show_graphs ? p_counters.contains(p.pid) : false;
if (show_graphs and ((p.cpu_p > 0 and not has_graph) or (not data_same and has_graph))) {
if (not has_graph) {
p_graphs[p.pid] = Draw::Graph{5, 1, "", {}, graph_symbol};
p_counters[p.pid] = 0;
}
else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) {
if (p_graphs.contains(p.pid)) p_graphs.erase(p.pid);
p_counters.erase(p.pid);
}
else
p_counters[p.pid] = 0;
}
out += Fx::reset;
//? Set correct gradient colors if enabled
string c_color, m_color, t_color, g_color, end;
if (is_selected) {
c_color = m_color = t_color = g_color = Fx::b;
end = Fx::ub;
out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b;
}
else {
int calc = (selected > lc) ? selected - lc : lc - selected;
if (proc_colors) {
end = Theme::c("main_fg") + Fx::ub;
array<string, 3> colors;
for (int i = 0; int v : {(int)round(p.cpu_p), (int)round(p.mem * 100 / totalMem), (int)p.threads / 3}) {
if (proc_gradient) {
int val = (min(v, 100) + 100) - calc * 100 / select_max;
if (val < 100) colors[i++] = Theme::g("proc_color").at(max(0, val));
else colors[i++] = Theme::g("process").at(clamp(val - 100, 0, 100));
}
else
colors[i++] = Theme::g("process").at(clamp(v, 0, 100));
}
c_color = colors.at(0); m_color = colors.at(1); t_color = colors.at(2);
}
else {
c_color = m_color = t_color = Fx::b;
end = Fx::ub;
}
if (proc_gradient) {
g_color = Theme::g("proc").at(clamp(calc * 100 / select_max, 0, 100));
}
}
if (not p_wide_cmd.contains(p.pid)) p_wide_cmd[p.pid] = ulen(p.cmd) != ulen(p.cmd, true);
//? Normal view line
if (not proc_tree) {
out += Mv::to(y+2+lc, x+1)
+ g_color + rjust(to_string(p.pid), 8) + ' '
+ c_color + ljust(p.name, prog_size, true) + ' ' + end
+ (cmd_size > 0 ? g_color + ljust(p.cmd, cmd_size, true, p_wide_cmd[p.pid]) + Mv::to(y+2+lc, x+11+prog_size+cmd_size) + ' ' : "");
}
//? Tree view line
else {
const string prefix_pid = p.prefix + to_string(p.pid);
int width_left = tree_size;
out += Mv::to(y+2+lc, x+1) + g_color + uresize(prefix_pid, width_left) + ' ';
width_left -= ulen(prefix_pid);
if (width_left > 0) {
out += c_color + uresize(p.name, width_left - 1) + end + ' ';
width_left -= (ulen(p.name) + 1);
}
if (width_left > 7) {
const string& cmd = width_left > 40 ? rtrim(p.cmd) : p.short_cmd;
if (not cmd.empty() and cmd != p.name) {
out += g_color + '(' + uresize(cmd, width_left - 3, p_wide_cmd[p.pid]) + ") ";
width_left -= (ulen(cmd, true) + 3);
}
}
out += string(max(0, width_left), ' ') + Mv::to(y+2+lc, x+2+tree_size);
}
//? Common end of line
string cpu_str = to_string(p.cpu_p);
if (p.cpu_p < 10 or (p.cpu_p >= 100 and p.cpu_p < 1000)) cpu_str.resize(3);
else if (p.cpu_p >= 10'000) {
cpu_str = to_string(p.cpu_p / 1000);
cpu_str.resize(3);
if (cpu_str.ends_with('.')) cpu_str.pop_back();
cpu_str += "k";
}
string mem_str = (mem_bytes ? floating_humanizer(p.mem, true) : "");
if (not mem_bytes) {
double mem_p = clamp((double)p.mem * 100 / totalMem, 0.0, 100.0);
mem_str = to_string(mem_p);
if (mem_str.size() < 4) mem_str = "0";
else mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4));
mem_str += '%';
}
// Shorten process thread representation when larger than 5 digits: 10000 -> 10K ...
const std::string proc_threads_string = [&] {
if (p.threads > 9999) {
return std::to_string(p.threads / 1000) + 'K';
} else {
return std::to_string(p.threads);
}
}();
out += (thread_size > 0 ? t_color + rjust(proc_threads_string, thread_size) + ' ' + end : "" )
+ g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' '
+ m_color + rjust(mem_str, 5) + end + ' '
+ (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "")
+ (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' '
+ c_color + rjust(cpu_str, 4) + " " + end;
if (lc++ > height - 5) break;
}
out += Fx::reset;
while (lc++ < height - 5) out += Mv::to(y+lc+1, x+1) + string(width - 2, ' ');
//? Draw scrollbar if needed
if (numpids > select_max) {
const int scroll_pos = clamp((int)round((double)start * select_max / (numpids - select_max)), 0, height - 5);
out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + Symbols::up
+ Mv::to(y + height - 2, x + width - 2) + Symbols::down;
for (int i = y + 2; i < y + height - 2; i++) {
out += Mv::to(i, x + width - 2) + ((i == y + 2 + scroll_pos) ? "" : " ");
}
}
//? Current selection and number of processes
string location = to_string(start + selected) + '/' + to_string(numpids);
string loc_clear = Symbols::h_line * max((size_t)0, 9 - location.size());
out += Mv::to(y + height - 1, x+width - 3 - max(9, (int)location.size())) + Fx::ub + Theme::c("proc_box") + loc_clear
+ Symbols::title_left_down + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right_down;
//? Clear out left over graphs from dead processes at a regular interval
if (not data_same and ++counter >= 100) {
counter = 0;
std::erase_if(p_graphs, [&](const auto& pair) {
return rng::find(plist, pair.first, &proc_info::pid) == plist.end();
});
std::erase_if(p_counters, [&](const auto& pair) {
return rng::find(plist, pair.first, &proc_info::pid) == plist.end();
});
std::erase_if(p_wide_cmd, [&](const auto& pair) {
return rng::find(plist, pair.first, &proc_info::pid) == plist.end();
});
}
if (selected == 0 and selected_pid != 0) {
selected_pid = 0;
selected_name.clear();
}
redraw = false;
return out + Fx::reset;
}
}
namespace Draw {
void calcSizes() {
atomic_wait(Runner::active);
Config::unlock();
auto boxes = Config::getS("shown_boxes");
auto cpu_bottom = Config::getB("cpu_bottom");
auto mem_below_net = Config::getB("mem_below_net");
auto proc_left = Config::getB("proc_left");
Cpu::box.clear();
Mem::box.clear();
Net::box.clear();
Proc::box.clear();
Global::clock.clear();
Global::overlay.clear();
Runner::pause_output = false;
Runner::redraw = true;
Proc::p_counters.clear();
Proc::p_graphs.clear();
if (Menu::active) Menu::redraw = true;
Input::mouse_mappings.clear();
Cpu::x = Mem::x = Net::x = Proc::x = 1;
Cpu::y = Mem::y = Net::y = Proc::y = 1;
Cpu::width = Mem::width = Net::width = Proc::width = 0;
Cpu::height = Mem::height = Net::height = Proc::height = 0;
Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true;
Cpu::shown = s_contains(boxes, "cpu");
#ifdef GPU_SUPPORT
Gpu::box.clear();
Gpu::width = 0;
Gpu::shown_panels.clear();
if (not Gpu::gpu_names.empty()) {
std::istringstream iss(boxes, std::istringstream::in);
string current;
while (iss >> current)
if ( current == "gpu0"
or current == "gpu1"
or current == "gpu2"
or current == "gpu3"
or current == "gpu4"
or current == "gpu5"
) Gpu::shown_panels.push_back(current.back()-'0');
}
Gpu::shown = Gpu::shown_panels.size();
#endif
Mem::shown = s_contains(boxes, "mem");
Net::shown = s_contains(boxes, "net");
Proc::shown = s_contains(boxes, "proc");
//* Calculate and draw cpu box outlines
if (Cpu::shown) {
using namespace Cpu;
#ifdef GPU_SUPPORT
const bool show_gpu_on = Config::getS("show_gpu_info") == "On";
const bool gpus_shown_in_cpu_panel = Gpu::gpu_names.size() > 0 and (
show_gpu_on or (Config::getS("cpu_graph_lower") == "Auto" and Gpu::shown == 0)
);
const int gpus_height_offset = (Gpu::gpu_names.size() - Gpu::shown)*gpus_shown_in_cpu_panel;
int gpus_extra_height = gpus_shown_in_cpu_panel ? Gpu::gpu_names.size() - (show_gpu_on ? 0 : Gpu::shown) : 0;
#endif
const bool show_temp = (Config::getB("check_temp") and got_sensors);
width = round((double)Term::width * width_p / 100);
#ifdef GPU_SUPPORT
if (Gpu::shown != 0 and not (Mem::shown or Net::shown or Proc::shown)) {
height = Term::height - Gpu::min_height*Gpu::shown - gpus_height_offset;
} else {
height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p/(Gpu::shown+1) + (Gpu::shown != 0)*5) / 100));
}
if (height <= Term::height-gpus_height_offset) height += gpus_height_offset;
if (height - gpus_extra_height < 7) gpus_extra_height = height - 7;
#else
height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100));
#endif
x = 1;
y = cpu_bottom ? Term::height - height + 1 : 1;
#ifdef GPU_SUPPORT
b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - gpus_extra_height - 5)));
#else
b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5)));
#endif
if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) {
b_column_size = 2;
b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1);
}
else if (b_columns * (15 + 6 * show_temp) < width - (width / 3)) {
b_column_size = 1;
b_width = (15 + 6 * show_temp) * b_columns - (b_columns - 1);
}
else if (b_columns * (8 + 6 * show_temp) < width - (width / 3)) {
b_column_size = 0;
}
else {
b_columns = (width - width / 3) / (8 + 6 * show_temp);
b_column_size = 0;
}
if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1;
#ifdef GPU_SUPPORT
b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4 + gpus_extra_height);
#else
b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4);
#endif
b_x = x + width - b_width - 1;
b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1;
box = createBox(x, y, width, height, Theme::c("cpu_box"), true, (cpu_bottom ? "" : "cpu"), (cpu_bottom ? "cpu" : ""), 1);
auto& custom = Config::getS("custom_cpu_name");
static const bool hasCpuHz = not Cpu::get_cpuHz().empty();
const string cpu_title = uresize(
(custom.empty() ? Cpu::cpuName : custom),
b_width - (Config::getB("show_cpu_freq") and hasCpuHz ? 14 : 4)
);
box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title);
}
#ifdef GPU_SUPPORT
//* Calculate and draw gpu box outlines
if (Gpu::shown != 0) {
using namespace Gpu;
x_vec.resize(shown); y_vec.resize(shown);
b_x_vec.resize(shown); b_y_vec.resize(shown);
b_height_vec.resize(shown);
box.resize(shown);
graph_upper_vec.resize(shown); graph_lower_vec.resize(shown);
temp_graph_vec.resize(shown);
mem_used_graph_vec.resize(shown); mem_util_graph_vec.resize(shown);
gpu_meter_vec.resize(shown);
pwr_meter_vec.resize(shown);
redraw.resize(shown);
for (auto i = 0; i < shown; ++i) {
redraw[i] = true;
width = Term::width;
if (Cpu::shown)
if (not (Mem::shown or Net::shown or Proc::shown))
height = min_height;
else height = Cpu::height;
else
if (not (Mem::shown or Net::shown or Proc::shown))
height = Term::height/Gpu::shown + (i == 0)*(Term::height%Gpu::shown);
else
height = max(min_height, (int)ceil((double)Term::height * height_p/Gpu::shown / 100));
height += (height+Cpu::height == Term::height-1);
x_vec[i] = 1; y_vec[i] = 1 + i*height + (not Config::getB("cpu_bottom"))*Cpu::shown*Cpu::height;
box[i] = createBox(x_vec[i], y_vec[i], width, height, Theme::c("cpu_box"), true, std::string("gpu") + (char)(shown_panels[i]+'0'), "", (shown_panels[i]+5)%10); // TODO gpu_box
b_height_vec[i] = 2 + gpu_b_height_offsets[shown_panels[i]];
b_width = clamp(width/2, min_width, 64);
//? Main statistics box
b_x_vec[i] = x_vec[i] + width - b_width - 1;
b_y_vec[i] = y_vec[i] + ceil((double)(height - 2) / 2) - ceil((double)(b_height_vec[i]) / 2) + 1;
string name = Config::getS(std::string("custom_gpu_name") + (char)(shown_panels[i]+'0'));
if (name.empty()) name = gpu_names[shown_panels[i]];
box[i] += createBox(b_x_vec[i], b_y_vec[i], b_width, b_height_vec[i], "", false, name.substr(0, b_width-5));
}
}
#endif
//* Calculate and draw mem box outlines
if (Mem::shown) {
using namespace Mem;
auto show_disks = Config::getB("show_disks");
auto swap_disk = Config::getB("swap_disk");
auto mem_graphs = Config::getB("mem_graphs");
width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100);
#ifdef GPU_SUPPORT
height = ceil((double)Term::height * (100 - Net::height_p * Net::shown*4 / ((Gpu::shown != 0 and Cpu::shown) + 4)) / 100) - Cpu::height - Gpu::height*Gpu::shown;
#else
height = ceil((double)Term::height * (100 - Cpu::height_p * Cpu::shown - Net::height_p * Net::shown) / 100) + 1;
#endif
x = (proc_left and Proc::shown) ? Term::width - width + 1: 1;
if (mem_below_net and Net::shown)
#ifdef GPU_SUPPORT
y = Term::height - height + 1 - (cpu_bottom ? Cpu::height + Gpu::height*Gpu::shown : 0);
else
y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1;
#else
y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0);
else
y = cpu_bottom ? 1 : Cpu::height + 1;
#endif
if (show_disks) {
mem_width = ceil((double)(width - 3) / 2);
mem_width += mem_width % 2;
disks_width = width - mem_width - 2;
divider = x + mem_width;
}
else
mem_width = width - 1;
item_height = has_swap and not swap_disk ? 6 : 4;
if (height - (has_swap and not swap_disk ? 3 : 2) > 2 * item_height)
mem_size = 3;
else if (mem_width > 25)
mem_size = 2;
else
mem_size = 1;
mem_meter = max(0, mem_width - (mem_size > 2 ? 7 : 17));
if (mem_size == 1) mem_meter += 6;
if (mem_graphs) {
graph_height = max(1, (int)round((double)((height - (has_swap and not swap_disk ? 2 : 1)) - (mem_size == 3 ? 2 : 1) * item_height) / item_height));
if (graph_height > 1) mem_meter += 6;
}
else
graph_height = 0;
if (show_disks) {
disk_meter = max(-14, width - mem_width - 23);
if (disks_width < 25) disk_meter += 14;
}
box = createBox(x, y, width, height, Theme::c("mem_box"), true, "mem", "", 2);
box += Mv::to(y, (show_disks ? divider + 2 : x + width - 9)) + Theme::c("mem_box") + Symbols::title_left + (show_disks ? Fx::b : "")
+ Theme::c("hi_fg") + 'd' + Theme::c("title") + "isks" + Fx::ub + Theme::c("mem_box") + Symbols::title_right;
Input::mouse_mappings["d"] = {y, (show_disks ? divider + 3 : x + width - 8), 1, 5};
if (show_disks) {
box += Mv::to(y, divider) + Symbols::div_up + Mv::to(y + height - 1, divider) + Symbols::div_down + Theme::c("div_line");
for (auto i : iota(1, height - 1))
box += Mv::to(y + i, divider) + Symbols::v_line;
}
}
//* Calculate and draw net box outlines
if (Net::shown) {
using namespace Net;
width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100);
#ifdef GPU_SUPPORT
height = Term::height - Cpu::height - Gpu::height*Gpu::shown - Mem::height;
#else
height = Term::height - Cpu::height - Mem::height;
#endif
x = (proc_left and Proc::shown) ? Term::width - width + 1 : 1;
if (mem_below_net and Mem::shown)
#ifdef GPU_SUPPORT
y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1;
#else
y = cpu_bottom ? 1 : Cpu::height + 1;
#endif
else
y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0);
b_width = (width > 45) ? 27 : 19;
b_height = (height > 10) ? 9 : height - 2;
b_x = x + width - b_width - 1;
b_y = y + ((height - 2) / 2) - b_height / 2 + 1;
d_graph_height = round((double)(height - 2) / 2);
u_graph_height = height - 2 - d_graph_height;
box = createBox(x, y, width, height, Theme::c("net_box"), true, "net", "", 3);
box += createBox(b_x, b_y, b_width, b_height, "", false, "download", "upload");
}
//* Calculate and draw proc box outlines
if (Proc::shown) {
using namespace Proc;
width = Term::width - (Mem::shown ? Mem::width : (Net::shown ? Net::width : 0));
#ifdef GPU_SUPPORT
height = Term::height - Cpu::height - Gpu::height*Gpu::shown;
#else
height = Term::height - Cpu::height;
#endif
x = proc_left ? 1 : Term::width - width + 1;
#ifdef GPU_SUPPORT
y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1;
#else
y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + 1;
#endif
select_max = height - 3;
box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4);
}
}
}